diff --git a/agent-node/.env.example b/agent-node/.env.example deleted file mode 100644 index 973122f..0000000 --- a/agent-node/.env.example +++ /dev/null @@ -1,45 +0,0 @@ -# ─── Agent Signing Key ──────────────────────────────────────────────────────── -# Private key of the wallet set as agentSigner in CronStreamRouter.sol -# Must match the address granted AGENT_MANAGER_ROLE on-chain -AGENT_SIGNER_PRIVATE_KEY=0x... - -# ─── Chain & Contract ───────────────────────────────────────────────────────── -# Chain ID — Robinhood Testnet: TBD | Arbitrum Sepolia: 421614 | Arbitrum One: 42161 -CHAIN_ID=421614 - -# Deployed CronStreamRouter contract address -CONTRACT_ADDRESS=0x... - -# RPC endpoint for the target network -RPC_URL=https://sepolia-rollup.arbitrum.io/rpc - -# ─── GitHub Integration ─────────────────────────────────────────────────────── -# Personal access token with repo read scope (for fetching PR diffs) -GITHUB_TOKEN=ghp_... - -# Webhook secret — must match the secret configured in your GitHub repo webhook settings -GITHUB_WEBHOOK_SECRET=your_webhook_secret_here - -# ─── Stream Defaults ────────────────────────────────────────────────────────── -# Default extension window granted when milestone passes (seconds) -# 86400 = 24 hours | 172800 = 48 hours -DEFAULT_EXTENSION_SECONDS=86400 - -# How long a signed voucher remains valid before it expires (seconds) -# Agent issues short-lived vouchers to limit replay window -VOUCHER_TTL_SECONDS=3600 - -# ─── Turso Database ─────────────────────────────────────────────────────────── -# Create a free DB at turso.tech — copy the URL and token from the dashboard -TURSO_DATABASE_URL=libsql://your-db-name.turso.io -TURSO_AUTH_TOKEN=your_turso_auth_token - -# ─── Server ─────────────────────────────────────────────────────────────────── -PORT=5000 - -# ─── Auth (SIWE + JWT) ──────────────────────────────────────────────────────── -# Required in production — generate with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" -JWT_SECRET=change_me_in_production -JWT_TTL_SECONDS=900 -# Lock frontend domain in production (prevents cross-origin SIWE attacks) -SIWE_DOMAIN=frontend-domain.vercel.app diff --git a/agent-node/.gitignore b/agent-node/.gitignore deleted file mode 100644 index 158004a..0000000 --- a/agent-node/.gitignore +++ /dev/null @@ -1,7 +0,0 @@ -/node_modules -/build -/dist -/coverage -/.env -/.env.local -/.env.development \ No newline at end of file diff --git a/agent-node/package-lock.json b/agent-node/package-lock.json deleted file mode 100644 index 9ab359f..0000000 --- a/agent-node/package-lock.json +++ /dev/null @@ -1,8330 +0,0 @@ -{ - "name": "cronstream-agent-node", - "version": "1.0.0", - "lockfileVersion": 3, - "requires": true, - "packages": { - "": { - "name": "cronstream-agent-node", - "version": "1.0.0", - "license": "ISC", - "dependencies": { - "@libsql/client": "^0.17.3", - "dotenv": "^16.4.5", - "ethers": "^6.13.4", - "express": "^5.2.1", - "express-rate-limit": "^8.5.2", - "helmet": "^8.2.0", - "jsonwebtoken": "^9.0.3", - "siwe": "^3.0.0", - "x402-express": "^1.2.0" - }, - "devDependencies": { - "nodemon": "^3.1.14" - } - }, - "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/@babel/runtime": { - "version": "7.29.7", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.29.7.tgz", - "integrity": "sha512-Nq8OhGWiZIZGV6hLHoyAKLLcJihP/xFeBMGJoUrxTX2psI8dCifzLhZISFb+VWS3wFMRDmCGw5R+dOySCqPLhw==", - "license": "MIT", - "engines": { - "node": ">=6.9.0" - } - }, - "node_modules/@base-org/account": { - "version": "2.4.0", - "resolved": "https://registry.npmjs.org/@base-org/account/-/account-2.4.0.tgz", - "integrity": "sha512-A4Umpi8B9/pqR78D1Yoze4xHyQaujioVRqqO3d6xuDFw9VRtjg6tK3bPlwE0aW+nVH/ntllCpPa2PbI8Rnjcug==", - "license": "Apache-2.0", - "dependencies": { - "@coinbase/cdp-sdk": "^1.0.0", - "@noble/hashes": "1.4.0", - "clsx": "1.2.1", - "eventemitter3": "5.0.1", - "idb-keyval": "6.2.1", - "ox": "0.6.9", - "preact": "10.24.2", - "viem": "^2.31.7", - "zustand": "5.0.3" - } - }, - "node_modules/@base-org/account/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/@base-org/account/node_modules/@noble/curves/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/@base-org/account/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@base-org/account/node_modules/ox": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", - "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.10.1", - "@noble/curves": "^1.6.0", - "@noble/hashes": "^1.5.0", - "@scure/bip32": "^1.5.0", - "@scure/bip39": "^1.4.0", - "abitype": "^1.0.6", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@base-org/account/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/@coinbase/cdp-sdk": { - "version": "1.50.0", - "resolved": "https://registry.npmjs.org/@coinbase/cdp-sdk/-/cdp-sdk-1.50.0.tgz", - "integrity": "sha512-lKK6aC2z8q8C3IA39unNuWc8lgM0hU9mSqkdd7Bncf5xvT28f8G6upexFtJweNwxkeAJwiLSgBkwOhqMK2/OGQ==", - "license": "MIT", - "dependencies": { - "@solana-program/system": "^0.10.0", - "@solana-program/token": "^0.9.0", - "@solana/kit": "^5.5.1", - "abitype": "1.0.6", - "axios": "1.16.0", - "axios-retry": "^4.5.0", - "bs58": "^6.0.0", - "jose": "^6.2.0", - "md5": "^2.3.0", - "uncrypto": "^0.1.3", - "viem": "^2.47.0", - "zod": "^3.25.76" - } - }, - "node_modules/@coinbase/wallet-sdk": { - "version": "4.3.6", - "resolved": "https://registry.npmjs.org/@coinbase/wallet-sdk/-/wallet-sdk-4.3.6.tgz", - "integrity": "sha512-4q8BNG1ViL4mSAAvPAtpwlOs1gpC+67eQtgIwNvT3xyeyFFd+guwkc8bcX5rTmQhXpqnhzC4f0obACbP9CqMSA==", - "license": "Apache-2.0", - "dependencies": { - "@noble/hashes": "1.4.0", - "clsx": "1.2.1", - "eventemitter3": "5.0.1", - "idb-keyval": "6.2.1", - "ox": "0.6.9", - "preact": "10.24.2", - "viem": "^2.27.2", - "zustand": "5.0.3" - } - }, - "node_modules/@coinbase/wallet-sdk/node_modules/@noble/curves": { - "version": "1.9.7", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", - "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.8.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@coinbase/wallet-sdk/node_modules/@noble/curves/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/@coinbase/wallet-sdk/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@coinbase/wallet-sdk/node_modules/ox": { - "version": "0.6.9", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.9.tgz", - "integrity": "sha512-wi5ShvzE4eOcTwQVsIPdFr+8ycyX+5le/96iAJutaZAvCes1J0+RvpEPg5QDPDiaR0XQQAvZVl7AwqQcINuUug==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.10.1", - "@noble/curves": "^1.6.0", - "@noble/hashes": "^1.5.0", - "@scure/bip32": "^1.5.0", - "@scure/bip39": "^1.4.0", - "abitype": "^1.0.6", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@coinbase/wallet-sdk/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/@ecies/ciphers": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/@ecies/ciphers/-/ciphers-0.2.6.tgz", - "integrity": "sha512-patgsRPKGkhhoBjETV4XxD0En4ui5fbX0hzayqI3M8tvNMGUoUvmyYAIWwlxBc1KX5cturfqByYdj5bYGRpN9g==", - "license": "MIT", - "engines": { - "bun": ">=1", - "deno": ">=2.7.10", - "node": ">=16" - }, - "peerDependencies": { - "@noble/ciphers": "^1.0.0" - } - }, - "node_modules/@ethereumjs/common": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-3.2.0.tgz", - "integrity": "sha512-pksvzI0VyLgmuEF2FA/JR/4/y6hcPq8OUail3/AvycBaW1d5VSauOZzqGvJ3RTmR4MU35lWE8KseKOsEhrFRBA==", - "license": "MIT", - "dependencies": { - "@ethereumjs/util": "^8.1.0", - "crc-32": "^1.2.0" - } - }, - "node_modules/@ethereumjs/rlp": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", - "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==", - "license": "MPL-2.0", - "bin": { - "rlp": "bin/rlp" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@ethereumjs/tx": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-4.2.0.tgz", - "integrity": "sha512-1nc6VO4jtFd172BbSnTnDQVr9IYBFl1y4xPzZdtkrkKIncBCkdbgfdRV+MiTkJYAtTxvV12GRZLqBFT1PNK6Yw==", - "license": "MPL-2.0", - "dependencies": { - "@ethereumjs/common": "^3.2.0", - "@ethereumjs/rlp": "^4.0.1", - "@ethereumjs/util": "^8.1.0", - "ethereum-cryptography": "^2.0.0" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@ethereumjs/util": { - "version": "8.1.0", - "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz", - "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==", - "license": "MPL-2.0", - "dependencies": { - "@ethereumjs/rlp": "^4.0.1", - "ethereum-cryptography": "^2.0.0", - "micro-ftch": "^0.3.1" - }, - "engines": { - "node": ">=14" - } - }, - "node_modules/@gemini-wallet/core": { - "version": "0.3.2", - "resolved": "https://registry.npmjs.org/@gemini-wallet/core/-/core-0.3.2.tgz", - "integrity": "sha512-Z4aHi3ECFf5oWYWM3F1rW83GJfB9OvhBYPTmb5q+VyK3uvzvS48lwo+jwh2eOoCRWEuT/crpb9Vwp2QaS5JqgQ==", - "license": "MIT", - "dependencies": { - "@metamask/rpc-errors": "7.0.2", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "viem": ">=2.0.0" - } - }, - "node_modules/@libsql/client": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@libsql/client/-/client-0.17.3.tgz", - "integrity": "sha512-HXk9wiAoJbKFbyBH4O+aEhN6ir5ERXuXvwE5OD2eR4/5RUa3Pw/8L9zrnVdU+iNJitRvisPWaIwmhkO3bH7giA==", - "license": "MIT", - "dependencies": { - "@libsql/core": "^0.17.3", - "@libsql/hrana-client": "^0.10.0", - "js-base64": "^3.7.5", - "libsql": "^0.5.28", - "promise-limit": "^2.7.0" - } - }, - "node_modules/@libsql/core": { - "version": "0.17.3", - "resolved": "https://registry.npmjs.org/@libsql/core/-/core-0.17.3.tgz", - "integrity": "sha512-2UjK1i7JBkMduJo4WdvvBxMMvVJ31pArBZNONyz/GCJJAH+1UHat2X6vn10S/WpY5fKzIT98WqYFl2vzWRLOfg==", - "license": "MIT", - "dependencies": { - "js-base64": "^3.7.5" - } - }, - "node_modules/@libsql/darwin-arm64": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/darwin-arm64/-/darwin-arm64-0.5.29.tgz", - "integrity": "sha512-K+2RIB1OGFPYQbfay48GakLhqf3ArcbHqPFu7EZiaUcRgFcdw8RoltsMyvbj5ix2fY0HV3Q3Ioa/ByvQdaSM0A==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@libsql/darwin-x64": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/darwin-x64/-/darwin-x64-0.5.29.tgz", - "integrity": "sha512-OtT+KFHsKFy1R5FVadr8FJ2Bb1mghtXTyJkxv0trocq7NuHntSki1eUbxpO5ezJesDvBlqFjnWaYYY516QNLhQ==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ] - }, - "node_modules/@libsql/hrana-client": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@libsql/hrana-client/-/hrana-client-0.10.0.tgz", - "integrity": "sha512-OoA4EMqRAC7kn7V2P6EQqRcpZf2W+AjsNIyCizBg339Tq/aMC7sRnzs3SklderhmQWAqEzvv8A2vhxVmWpkVvw==", - "license": "MIT", - "dependencies": { - "@libsql/isomorphic-ws": "^0.1.5", - "js-base64": "^3.7.5" - } - }, - "node_modules/@libsql/isomorphic-ws": { - "version": "0.1.5", - "resolved": "https://registry.npmjs.org/@libsql/isomorphic-ws/-/isomorphic-ws-0.1.5.tgz", - "integrity": "sha512-DtLWIH29onUYR00i0GlQ3UdcTRC6EP4u9w/h9LxpUZJWRMARk6dQwZ6Jkd+QdwVpuAOrdxt18v0K2uIYR3fwFg==", - "license": "MIT", - "dependencies": { - "@types/ws": "^8.5.4", - "ws": "^8.13.0" - } - }, - "node_modules/@libsql/linux-arm-gnueabihf": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm-gnueabihf/-/linux-arm-gnueabihf-0.5.29.tgz", - "integrity": "sha512-CD4n4zj7SJTHso4nf5cuMoWoMSS7asn5hHygsDuhRl8jjjCTT3yE+xdUvI4J7zsyb53VO5ISh4cwwOtf6k2UhQ==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-arm-musleabihf": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm-musleabihf/-/linux-arm-musleabihf-0.5.29.tgz", - "integrity": "sha512-2Z9qBVpEJV7OeflzIR3+l5yAd4uTOLxklScYTwpZnkm2vDSGlC1PRlueLaufc4EFITkLKXK2MWBpexuNJfMVcg==", - "cpu": [ - "arm" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-arm64-gnu": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-gnu/-/linux-arm64-gnu-0.5.29.tgz", - "integrity": "sha512-gURBqaiXIGGwFNEaUj8Ldk7Hps4STtG+31aEidCk5evMMdtsdfL3HPCpvys+ZF/tkOs2MWlRWoSq7SOuCE9k3w==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-arm64-musl": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/linux-arm64-musl/-/linux-arm64-musl-0.5.29.tgz", - "integrity": "sha512-fwgYZ0H8mUkyVqXZHF3mT/92iIh1N94Owi/f66cPVNsk9BdGKq5gVpoKO+7UxaNzuEH1roJp2QEwsCZMvBLpqg==", - "cpu": [ - "arm64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-x64-gnu": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/linux-x64-gnu/-/linux-x64-gnu-0.5.29.tgz", - "integrity": "sha512-y14V0vY0nmMC6G0pHeJcEarcnGU2H6cm21ZceRkacWHvQAEhAG0latQkCtoS2njFOXiYIg+JYPfAoWKbi82rkg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/linux-x64-musl": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/linux-x64-musl/-/linux-x64-musl-0.5.29.tgz", - "integrity": "sha512-gquqwA/39tH4pFl+J9n3SOMSymjX+6kZ3kWgY3b94nXFTwac9bnFNMffIomgvlFaC4ArVqMnOZD3nuJ3H3VO1w==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "linux" - ] - }, - "node_modules/@libsql/win32-x64-msvc": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/@libsql/win32-x64-msvc/-/win32-x64-msvc-0.5.29.tgz", - "integrity": "sha512-4/0CvEdhi6+KjMxMaVbFM2n2Z44escBRoEYpR+gZg64DdetzGnYm8mcNLcoySaDJZNaBd6wz5DNdgRmcI4hXcg==", - "cpu": [ - "x64" - ], - "license": "MIT", - "optional": true, - "os": [ - "win32" - ] - }, - "node_modules/@lit-labs/ssr-dom-shim": { - "version": "1.6.0", - "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.6.0.tgz", - "integrity": "sha512-VHb0ALPMTlgKjM6yIxxoQNnpKyUKLD04VzeQdsiXkMqkvYlAHxq9glGLmgbb889/1GsohSOAjvQYoiBppXFqrQ==", - "license": "BSD-3-Clause" - }, - "node_modules/@lit/reactive-element": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.2.tgz", - "integrity": "sha512-pbCDiVMnne1lYUIaYNN5wrwQXDtHaYtg7YEFPeW+hws6U47WeFvISGUWekPGKWOP1ygrs0ef0o1VJMk1exos5A==", - "license": "BSD-3-Clause", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.5.0" - } - }, - "node_modules/@metamask/eth-json-rpc-provider": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@metamask/eth-json-rpc-provider/-/eth-json-rpc-provider-1.0.1.tgz", - "integrity": "sha512-whiUMPlAOrVGmX8aKYVPvlKyG4CpQXiNNyt74vE1xb5sPvmx5oA7B/kOi/JdBvhGQq97U1/AVdXEdk2zkP8qyA==", - "dependencies": { - "@metamask/json-rpc-engine": "^7.0.0", - "@metamask/safe-event-emitter": "^3.0.0", - "@metamask/utils": "^5.0.1" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/json-rpc-engine": { - "version": "7.3.3", - "resolved": "https://registry.npmjs.org/@metamask/json-rpc-engine/-/json-rpc-engine-7.3.3.tgz", - "integrity": "sha512-dwZPq8wx9yV3IX2caLi9q9xZBw2XeIoYqdyihDDDpuHVCEiqadJLwqM3zy+uwf6F1QYQ65A8aOMQg1Uw7LMLNg==", - "license": "ISC", - "dependencies": { - "@metamask/rpc-errors": "^6.2.1", - "@metamask/safe-event-emitter": "^3.0.0", - "@metamask/utils": "^8.3.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/json-rpc-engine/node_modules/@metamask/utils": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", - "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", - "license": "ISC", - "dependencies": { - "@ethereumjs/tx": "^4.2.0", - "@metamask/superstruct": "^3.0.0", - "@noble/hashes": "^1.3.1", - "@scure/base": "^1.1.3", - "@types/debug": "^4.1.7", - "debug": "^4.3.4", - "pony-cause": "^2.1.10", - "semver": "^7.5.4", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/rpc-errors": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@metamask/rpc-errors/-/rpc-errors-6.4.0.tgz", - "integrity": "sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg==", - "license": "MIT", - "dependencies": { - "@metamask/utils": "^9.0.0", - "fast-safe-stringify": "^2.0.6" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/rpc-errors/node_modules/@metamask/utils": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-9.3.0.tgz", - "integrity": "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==", - "license": "ISC", - "dependencies": { - "@ethereumjs/tx": "^4.2.0", - "@metamask/superstruct": "^3.1.0", - "@noble/hashes": "^1.3.1", - "@scure/base": "^1.1.3", - "@types/debug": "^4.1.7", - "debug": "^4.3.4", - "pony-cause": "^2.1.10", - "semver": "^7.5.4", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/eth-json-rpc-provider/node_modules/@metamask/utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-5.0.2.tgz", - "integrity": "sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g==", - "license": "ISC", - "dependencies": { - "@ethereumjs/tx": "^4.1.2", - "@types/debug": "^4.1.7", - "debug": "^4.3.4", - "semver": "^7.3.8", - "superstruct": "^1.0.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/@metamask/json-rpc-engine": { - "version": "8.0.2", - "resolved": "https://registry.npmjs.org/@metamask/json-rpc-engine/-/json-rpc-engine-8.0.2.tgz", - "integrity": "sha512-IoQPmql8q7ABLruW7i4EYVHWUbF74yrp63bRuXV5Zf9BQwcn5H9Ww1eLtROYvI1bUXwOiHZ6qT5CWTrDc/t/AA==", - "license": "ISC", - "dependencies": { - "@metamask/rpc-errors": "^6.2.1", - "@metamask/safe-event-emitter": "^3.0.0", - "@metamask/utils": "^8.3.0" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/json-rpc-engine/node_modules/@metamask/rpc-errors": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@metamask/rpc-errors/-/rpc-errors-6.4.0.tgz", - "integrity": "sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg==", - "license": "MIT", - "dependencies": { - "@metamask/utils": "^9.0.0", - "fast-safe-stringify": "^2.0.6" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/json-rpc-engine/node_modules/@metamask/rpc-errors/node_modules/@metamask/utils": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-9.3.0.tgz", - "integrity": "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==", - "license": "ISC", - "dependencies": { - "@ethereumjs/tx": "^4.2.0", - "@metamask/superstruct": "^3.1.0", - "@noble/hashes": "^1.3.1", - "@scure/base": "^1.1.3", - "@types/debug": "^4.1.7", - "debug": "^4.3.4", - "pony-cause": "^2.1.10", - "semver": "^7.5.4", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/json-rpc-engine/node_modules/@metamask/utils": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", - "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", - "license": "ISC", - "dependencies": { - "@ethereumjs/tx": "^4.2.0", - "@metamask/superstruct": "^3.0.0", - "@noble/hashes": "^1.3.1", - "@scure/base": "^1.1.3", - "@types/debug": "^4.1.7", - "debug": "^4.3.4", - "pony-cause": "^2.1.10", - "semver": "^7.5.4", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/json-rpc-middleware-stream": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@metamask/json-rpc-middleware-stream/-/json-rpc-middleware-stream-7.0.2.tgz", - "integrity": "sha512-yUdzsJK04Ev98Ck4D7lmRNQ8FPioXYhEUZOMS01LXW8qTvPGiRVXmVltj2p4wrLkh0vW7u6nv0mNl5xzC5Qmfg==", - "license": "ISC", - "dependencies": { - "@metamask/json-rpc-engine": "^8.0.2", - "@metamask/safe-event-emitter": "^3.0.0", - "@metamask/utils": "^8.3.0", - "readable-stream": "^3.6.2" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/json-rpc-middleware-stream/node_modules/@metamask/utils": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", - "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", - "license": "ISC", - "dependencies": { - "@ethereumjs/tx": "^4.2.0", - "@metamask/superstruct": "^3.0.0", - "@noble/hashes": "^1.3.1", - "@scure/base": "^1.1.3", - "@types/debug": "^4.1.7", - "debug": "^4.3.4", - "pony-cause": "^2.1.10", - "semver": "^7.5.4", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/object-multiplex": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@metamask/object-multiplex/-/object-multiplex-2.1.0.tgz", - "integrity": "sha512-4vKIiv0DQxljcXwfpnbsXcfa5glMj5Zg9mqn4xpIWqkv6uJ2ma5/GtUfLFSxhlxnR8asRMv8dDmWya1Tc1sDFA==", - "license": "ISC", - "dependencies": { - "once": "^1.4.0", - "readable-stream": "^3.6.2" - }, - "engines": { - "node": "^16.20 || ^18.16 || >=20" - } - }, - "node_modules/@metamask/onboarding": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@metamask/onboarding/-/onboarding-1.0.1.tgz", - "integrity": "sha512-FqHhAsCI+Vacx2qa5mAFcWNSrTcVGMNjzxVgaX8ECSny/BJ9/vgXP9V7WF/8vb9DltPeQkxr+Fnfmm6GHfmdTQ==", - "license": "MIT", - "dependencies": { - "bowser": "^2.9.0" - } - }, - "node_modules/@metamask/providers": { - "version": "16.1.0", - "resolved": "https://registry.npmjs.org/@metamask/providers/-/providers-16.1.0.tgz", - "integrity": "sha512-znVCvux30+3SaUwcUGaSf+pUckzT5ukPRpcBmy+muBLC0yaWnBcvDqGfcsw6CBIenUdFrVoAFa8B6jsuCY/a+g==", - "license": "MIT", - "dependencies": { - "@metamask/json-rpc-engine": "^8.0.1", - "@metamask/json-rpc-middleware-stream": "^7.0.1", - "@metamask/object-multiplex": "^2.0.0", - "@metamask/rpc-errors": "^6.2.1", - "@metamask/safe-event-emitter": "^3.1.1", - "@metamask/utils": "^8.3.0", - "detect-browser": "^5.2.0", - "extension-port-stream": "^3.0.0", - "fast-deep-equal": "^3.1.3", - "is-stream": "^2.0.0", - "readable-stream": "^3.6.2", - "webextension-polyfill": "^0.10.0" - }, - "engines": { - "node": "^18.18 || >=20" - } - }, - "node_modules/@metamask/providers/node_modules/@metamask/rpc-errors": { - "version": "6.4.0", - "resolved": "https://registry.npmjs.org/@metamask/rpc-errors/-/rpc-errors-6.4.0.tgz", - "integrity": "sha512-1ugFO1UoirU2esS3juZanS/Fo8C8XYocCuBpfZI5N7ECtoG+zu0wF+uWZASik6CkO6w9n/Iebt4iI4pT0vptpg==", - "license": "MIT", - "dependencies": { - "@metamask/utils": "^9.0.0", - "fast-safe-stringify": "^2.0.6" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/providers/node_modules/@metamask/rpc-errors/node_modules/@metamask/utils": { - "version": "9.3.0", - "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-9.3.0.tgz", - "integrity": "sha512-w8CVbdkDrVXFJbfBSlDfafDR6BAkpDmv1bC1UJVCoVny5tW2RKAdn9i68Xf7asYT4TnUhl/hN4zfUiKQq9II4g==", - "license": "ISC", - "dependencies": { - "@ethereumjs/tx": "^4.2.0", - "@metamask/superstruct": "^3.1.0", - "@noble/hashes": "^1.3.1", - "@scure/base": "^1.1.3", - "@types/debug": "^4.1.7", - "debug": "^4.3.4", - "pony-cause": "^2.1.10", - "semver": "^7.5.4", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/providers/node_modules/@metamask/utils": { - "version": "8.5.0", - "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-8.5.0.tgz", - "integrity": "sha512-I6bkduevXb72TIM9q2LRO63JSsF9EXduh3sBr9oybNX2hNNpr/j1tEjXrsG0Uabm4MJ1xkGAQEMwifvKZIkyxQ==", - "license": "ISC", - "dependencies": { - "@ethereumjs/tx": "^4.2.0", - "@metamask/superstruct": "^3.0.0", - "@noble/hashes": "^1.3.1", - "@scure/base": "^1.1.3", - "@types/debug": "^4.1.7", - "debug": "^4.3.4", - "pony-cause": "^2.1.10", - "semver": "^7.5.4", - "uuid": "^9.0.1" - }, - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/rpc-errors": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@metamask/rpc-errors/-/rpc-errors-7.0.2.tgz", - "integrity": "sha512-YYYHsVYd46XwY2QZzpGeU4PSdRhHdxnzkB8piWGvJW2xbikZ3R+epAYEL4q/K8bh9JPTucsUdwRFnACor1aOYw==", - "license": "MIT", - "dependencies": { - "@metamask/utils": "^11.0.1", - "fast-safe-stringify": "^2.0.6" - }, - "engines": { - "node": "^18.20 || ^20.17 || >=22" - } - }, - "node_modules/@metamask/safe-event-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-3.1.2.tgz", - "integrity": "sha512-5yb2gMI1BDm0JybZezeoX/3XhPDOtTbcFvpTXM9kxsoZjPZFh4XciqRbpD6N86HYZqWDhEaKUDuOyR0sQHEjMA==", - "license": "ISC", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/@metamask/sdk": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@metamask/sdk/-/sdk-0.33.1.tgz", - "integrity": "sha512-1mcOQVGr9rSrVcbKPNVzbZ8eCl1K0FATsYH3WJ/MH4WcZDWGECWrXJPNMZoEAkLxWiMe8jOQBumg2pmcDa9zpQ==", - "deprecated": "No longer maintained, superseded by https://docs.metamask.io/metamask-connect", - "dependencies": { - "@babel/runtime": "^7.26.0", - "@metamask/onboarding": "^1.0.1", - "@metamask/providers": "16.1.0", - "@metamask/sdk-analytics": "0.0.5", - "@metamask/sdk-communication-layer": "0.33.1", - "@metamask/sdk-install-modal-web": "0.32.1", - "@paulmillr/qr": "^0.2.1", - "bowser": "^2.9.0", - "cross-fetch": "^4.0.0", - "debug": "4.3.4", - "eciesjs": "^0.4.11", - "eth-rpc-errors": "^4.0.3", - "eventemitter2": "^6.4.9", - "obj-multiplex": "^1.0.0", - "pump": "^3.0.0", - "readable-stream": "^3.6.2", - "socket.io-client": "^4.5.1", - "tslib": "^2.6.0", - "util": "^0.12.4", - "uuid": "^8.3.2" - } - }, - "node_modules/@metamask/sdk-analytics": { - "version": "0.0.5", - "resolved": "https://registry.npmjs.org/@metamask/sdk-analytics/-/sdk-analytics-0.0.5.tgz", - "integrity": "sha512-fDah+keS1RjSUlC8GmYXvx6Y26s3Ax1U9hGpWb6GSY5SAdmTSIqp2CvYy6yW0WgLhnYhW+6xERuD0eVqV63QIQ==", - "deprecated": "No longer maintained, superseded by @metamask/connect-analytics", - "license": "MIT", - "dependencies": { - "openapi-fetch": "^0.13.5" - } - }, - "node_modules/@metamask/sdk-communication-layer": { - "version": "0.33.1", - "resolved": "https://registry.npmjs.org/@metamask/sdk-communication-layer/-/sdk-communication-layer-0.33.1.tgz", - "integrity": "sha512-0bI9hkysxcfbZ/lk0T2+aKVo1j0ynQVTuB3sJ5ssPWlz+Z3VwveCkP1O7EVu1tsVVCb0YV5WxK9zmURu2FIiaA==", - "deprecated": "No longer maintained, superseded by https://docs.metamask.io/metamask-connect", - "dependencies": { - "@metamask/sdk-analytics": "0.0.5", - "bufferutil": "^4.0.8", - "date-fns": "^2.29.3", - "debug": "4.3.4", - "utf-8-validate": "^5.0.2", - "uuid": "^8.3.2" - }, - "peerDependencies": { - "cross-fetch": "^4.0.0", - "eciesjs": "*", - "eventemitter2": "^6.4.9", - "readable-stream": "^3.6.2", - "socket.io-client": "^4.5.1" - } - }, - "node_modules/@metamask/sdk-communication-layer/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@metamask/sdk-communication-layer/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, - "node_modules/@metamask/sdk-install-modal-web": { - "version": "0.32.1", - "resolved": "https://registry.npmjs.org/@metamask/sdk-install-modal-web/-/sdk-install-modal-web-0.32.1.tgz", - "integrity": "sha512-MGmAo6qSjf1tuYXhCu2EZLftq+DSt5Z7fsIKr2P+lDgdTPWgLfZB1tJKzNcwKKOdf6q9Qmmxn7lJuI/gq5LrKw==", - "deprecated": "No longer maintained, superseded by https://docs.metamask.io/metamask-connect", - "dependencies": { - "@paulmillr/qr": "^0.2.1" - } - }, - "node_modules/@metamask/sdk/node_modules/debug": { - "version": "4.3.4", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", - "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", - "license": "MIT", - "dependencies": { - "ms": "2.1.2" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/@metamask/sdk/node_modules/ms": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", - "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", - "license": "MIT" - }, - "node_modules/@metamask/superstruct": { - "version": "3.2.1", - "resolved": "https://registry.npmjs.org/@metamask/superstruct/-/superstruct-3.2.1.tgz", - "integrity": "sha512-fLgJnDOXFmuVlB38rUN5SmU7hAFQcCjrg3Vrxz67KTY7YHFnSNEKvX4avmEBdOI0yTCxZjwMCFEqsC8k2+Wd3g==", - "license": "MIT", - "engines": { - "node": ">=16.0.0" - } - }, - "node_modules/@metamask/utils": { - "version": "11.11.0", - "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-11.11.0.tgz", - "integrity": "sha512-0nF2CWjWQr/m0Y2t2lJnBTU1/CZPPTvKvcESLplyWe/tyeb8zFOi/FeneDmaFnML6LYRIGZU6f+xR0jKAIUZfw==", - "license": "ISC", - "dependencies": { - "@ethereumjs/tx": "^4.2.0", - "@metamask/superstruct": "^3.1.0", - "@noble/hashes": "^1.3.1", - "@scure/base": "^1.1.3", - "@types/debug": "^4.1.7", - "@types/lodash": "^4.17.20", - "debug": "^4.3.4", - "lodash": "^4.17.21", - "pony-cause": "^2.1.10", - "semver": "^7.5.4", - "uuid": "^9.0.1" - }, - "engines": { - "node": "^18.18 || ^20.14 || >=22" - } - }, - "node_modules/@neon-rs/load": { - "version": "0.0.4", - "resolved": "https://registry.npmjs.org/@neon-rs/load/-/load-0.0.4.tgz", - "integrity": "sha512-kTPhdZyTQxB+2wpiRcFWrDcejc4JI6tkPuS7UZCG4l6Zvc5kU/gGQ/ozvHTh1XR5tS+UlfAfGuPajjzQjCiHCw==", - "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/@paulmillr/qr": { - "version": "0.2.1", - "resolved": "https://registry.npmjs.org/@paulmillr/qr/-/qr-0.2.1.tgz", - "integrity": "sha512-IHnV6A+zxU7XwmKFinmYjUcwlyK9+xkG3/s9KcQhI9BjQKycrJ1JRO+FbNYPwZiPKW3je/DR0k7w8/gLa5eaxQ==", - "deprecated": "Switch to \"qr\" (new package name) for security updates: npm install qr", - "license": "(MIT OR Apache-2.0)", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@reown/appkit/-/appkit-1.7.8.tgz", - "integrity": "sha512-51kTleozhA618T1UvMghkhKfaPcc9JlKwLJ5uV+riHyvSoWPKPRIa5A6M1Wano5puNyW0s3fwywhyqTHSilkaA==", - "license": "Apache-2.0", - "dependencies": { - "@reown/appkit-common": "1.7.8", - "@reown/appkit-controllers": "1.7.8", - "@reown/appkit-pay": "1.7.8", - "@reown/appkit-polyfills": "1.7.8", - "@reown/appkit-scaffold-ui": "1.7.8", - "@reown/appkit-ui": "1.7.8", - "@reown/appkit-utils": "1.7.8", - "@reown/appkit-wallet": "1.7.8", - "@walletconnect/types": "2.21.0", - "@walletconnect/universal-provider": "2.21.0", - "bs58": "6.0.0", - "valtio": "1.13.2", - "viem": ">=2.29.0" - } - }, - "node_modules/@reown/appkit-common": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@reown/appkit-common/-/appkit-common-1.7.8.tgz", - "integrity": "sha512-ridIhc/x6JOp7KbDdwGKY4zwf8/iK8EYBl+HtWrruutSLwZyVi5P8WaZa+8iajL6LcDcDF7LoyLwMTym7SRuwQ==", - "license": "Apache-2.0", - "dependencies": { - "big.js": "6.2.2", - "dayjs": "1.11.13", - "viem": ">=2.29.0" - } - }, - "node_modules/@reown/appkit-controllers": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@reown/appkit-controllers/-/appkit-controllers-1.7.8.tgz", - "integrity": "sha512-IdXlJlivrlj6m63VsGLsjtPHHsTWvKGVzWIP1fXZHVqmK+rZCBDjCi9j267Rb9/nYRGHWBtlFQhO8dK35WfeDA==", - "license": "Apache-2.0", - "dependencies": { - "@reown/appkit-common": "1.7.8", - "@reown/appkit-wallet": "1.7.8", - "@walletconnect/universal-provider": "2.21.0", - "valtio": "1.13.2", - "viem": ">=2.29.0" - } - }, - "node_modules/@reown/appkit-controllers/node_modules/@noble/ciphers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", - "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit-controllers/node_modules/@noble/curves": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", - "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.7.1" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit-controllers/node_modules/@noble/hashes": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", - "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit-controllers/node_modules/@scure/bip32": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", - "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.8.1", - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit-controllers/node_modules/@scure/bip39": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", - "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.4" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/core": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.21.0.tgz", - "integrity": "sha512-o6R7Ua4myxR8aRUAJ1z3gT9nM+jd2B2mfamu6arzy1Cc6vi10fIwFWb6vg3bC8xJ6o9H3n/cN5TOW3aA9Y1XVw==", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-provider": "1.0.14", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/jsonrpc-ws-connection": "1.0.16", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.1.0", - "@walletconnect/safe-json": "1.0.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.21.0", - "@walletconnect/utils": "2.21.0", - "@walletconnect/window-getters": "1.0.1", - "es-toolkit": "1.33.0", - "events": "3.3.0", - "uint8arrays": "3.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/sign-client": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.21.0.tgz", - "integrity": "sha512-z7h+PeLa5Au2R591d/8ZlziE0stJvdzP9jNFzFolf2RG/OiXulgFKum8PrIyXy+Rg2q95U9nRVUF9fWcn78yBA==", - "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/core": "2.21.0", - "@walletconnect/events": "1.0.1", - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/logger": "2.1.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.21.0", - "@walletconnect/utils": "2.21.0", - "events": "3.3.0" - } - }, - "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/types": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.21.0.tgz", - "integrity": "sha512-ll+9upzqt95ZBWcfkOszXZkfnpbJJ2CmxMfGgE5GmhdxxxCcO5bGhXkI+x8OpiS555RJ/v/sXJYMSOLkmu4fFw==", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/events": "1.0.1", - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "events": "3.3.0" - } - }, - "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/universal-provider": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.21.0.tgz", - "integrity": "sha512-mtUQvewt+X0VBQay/xOJBvxsB3Xsm1lTwFjZ6WUwSOTR1X+FNb71hSApnV5kbsdDIpYPXeQUbGt2se1n5E5UBg==", - "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/events": "1.0.1", - "@walletconnect/jsonrpc-http-connection": "1.0.8", - "@walletconnect/jsonrpc-provider": "1.0.14", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.21.0", - "@walletconnect/types": "2.21.0", - "@walletconnect/utils": "2.21.0", - "es-toolkit": "1.33.0", - "events": "3.3.0" - } - }, - "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/utils": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.21.0.tgz", - "integrity": "sha512-zfHLiUoBrQ8rP57HTPXW7rQMnYxYI4gT9yTACxVW6LhIFROTF6/ytm5SKNoIvi4a5nX5dfXG4D9XwQUCu8Ilig==", - "license": "Apache-2.0", - "dependencies": { - "@noble/ciphers": "1.2.1", - "@noble/curves": "1.8.1", - "@noble/hashes": "1.7.1", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.1.0", - "@walletconnect/safe-json": "1.0.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.21.0", - "@walletconnect/window-getters": "1.0.1", - "@walletconnect/window-metadata": "1.0.1", - "bs58": "6.0.0", - "detect-browser": "5.3.0", - "query-string": "7.1.3", - "uint8arrays": "3.1.0", - "viem": "2.23.2" - } - }, - "node_modules/@reown/appkit-controllers/node_modules/@walletconnect/utils/node_modules/viem": { - "version": "2.23.2", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.2.tgz", - "integrity": "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@noble/curves": "1.8.1", - "@noble/hashes": "1.7.1", - "@scure/bip32": "1.6.2", - "@scure/bip39": "1.5.4", - "abitype": "1.0.8", - "isows": "1.0.6", - "ox": "0.6.7", - "ws": "8.18.0" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@reown/appkit-controllers/node_modules/abitype": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", - "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3 >=3.22.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/@reown/appkit-controllers/node_modules/isows": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", - "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/@reown/appkit-controllers/node_modules/ox": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.7.tgz", - "integrity": "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.10.1", - "@noble/curves": "^1.6.0", - "@noble/hashes": "^1.5.0", - "@scure/bip32": "^1.5.0", - "@scure/bip39": "^1.4.0", - "abitype": "^1.0.6", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@reown/appkit-pay": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@reown/appkit-pay/-/appkit-pay-1.7.8.tgz", - "integrity": "sha512-OSGQ+QJkXx0FEEjlpQqIhT8zGJKOoHzVnyy/0QFrl3WrQTjCzg0L6+i91Ad5Iy1zb6V5JjqtfIFpRVRWN4M3pw==", - "license": "Apache-2.0", - "dependencies": { - "@reown/appkit-common": "1.7.8", - "@reown/appkit-controllers": "1.7.8", - "@reown/appkit-ui": "1.7.8", - "@reown/appkit-utils": "1.7.8", - "lit": "3.3.0", - "valtio": "1.13.2" - } - }, - "node_modules/@reown/appkit-polyfills": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@reown/appkit-polyfills/-/appkit-polyfills-1.7.8.tgz", - "integrity": "sha512-W/kq786dcHHAuJ3IV2prRLEgD/2iOey4ueMHf1sIFjhhCGMynMkhsOhQMUH0tzodPqUgAC494z4bpIDYjwWXaA==", - "license": "Apache-2.0", - "dependencies": { - "buffer": "6.0.3" - } - }, - "node_modules/@reown/appkit-scaffold-ui": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@reown/appkit-scaffold-ui/-/appkit-scaffold-ui-1.7.8.tgz", - "integrity": "sha512-RCeHhAwOrIgcvHwYlNWMcIDibdI91waaoEYBGw71inE0kDB8uZbE7tE6DAXJmDkvl0qPh+DqlC4QbJLF1FVYdQ==", - "license": "Apache-2.0", - "dependencies": { - "@reown/appkit-common": "1.7.8", - "@reown/appkit-controllers": "1.7.8", - "@reown/appkit-ui": "1.7.8", - "@reown/appkit-utils": "1.7.8", - "@reown/appkit-wallet": "1.7.8", - "lit": "3.3.0" - } - }, - "node_modules/@reown/appkit-ui": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@reown/appkit-ui/-/appkit-ui-1.7.8.tgz", - "integrity": "sha512-1hjCKjf6FLMFzrulhl0Y9Vb9Fu4royE+SXCPSWh4VhZhWqlzUFc7kutnZKx8XZFVQH4pbBvY62SpRC93gqoHow==", - "license": "Apache-2.0", - "dependencies": { - "@reown/appkit-common": "1.7.8", - "@reown/appkit-controllers": "1.7.8", - "@reown/appkit-wallet": "1.7.8", - "lit": "3.3.0", - "qrcode": "1.5.3" - } - }, - "node_modules/@reown/appkit-utils": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@reown/appkit-utils/-/appkit-utils-1.7.8.tgz", - "integrity": "sha512-8X7UvmE8GiaoitCwNoB86pttHgQtzy4ryHZM9kQpvjQ0ULpiER44t1qpVLXNM4X35O0v18W0Dk60DnYRMH2WRw==", - "license": "Apache-2.0", - "dependencies": { - "@reown/appkit-common": "1.7.8", - "@reown/appkit-controllers": "1.7.8", - "@reown/appkit-polyfills": "1.7.8", - "@reown/appkit-wallet": "1.7.8", - "@walletconnect/logger": "2.1.2", - "@walletconnect/universal-provider": "2.21.0", - "valtio": "1.13.2", - "viem": ">=2.29.0" - }, - "peerDependencies": { - "valtio": "1.13.2" - } - }, - "node_modules/@reown/appkit-utils/node_modules/@noble/ciphers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", - "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit-utils/node_modules/@noble/curves": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", - "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.7.1" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit-utils/node_modules/@noble/hashes": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", - "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit-utils/node_modules/@scure/bip32": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", - "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.8.1", - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit-utils/node_modules/@scure/bip39": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", - "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.4" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit-utils/node_modules/@walletconnect/core": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.21.0.tgz", - "integrity": "sha512-o6R7Ua4myxR8aRUAJ1z3gT9nM+jd2B2mfamu6arzy1Cc6vi10fIwFWb6vg3bC8xJ6o9H3n/cN5TOW3aA9Y1XVw==", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-provider": "1.0.14", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/jsonrpc-ws-connection": "1.0.16", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.1.0", - "@walletconnect/safe-json": "1.0.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.21.0", - "@walletconnect/utils": "2.21.0", - "@walletconnect/window-getters": "1.0.1", - "es-toolkit": "1.33.0", - "events": "3.3.0", - "uint8arrays": "3.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@reown/appkit-utils/node_modules/@walletconnect/sign-client": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.21.0.tgz", - "integrity": "sha512-z7h+PeLa5Au2R591d/8ZlziE0stJvdzP9jNFzFolf2RG/OiXulgFKum8PrIyXy+Rg2q95U9nRVUF9fWcn78yBA==", - "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/core": "2.21.0", - "@walletconnect/events": "1.0.1", - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/logger": "2.1.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.21.0", - "@walletconnect/utils": "2.21.0", - "events": "3.3.0" - } - }, - "node_modules/@reown/appkit-utils/node_modules/@walletconnect/types": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.21.0.tgz", - "integrity": "sha512-ll+9upzqt95ZBWcfkOszXZkfnpbJJ2CmxMfGgE5GmhdxxxCcO5bGhXkI+x8OpiS555RJ/v/sXJYMSOLkmu4fFw==", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/events": "1.0.1", - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "events": "3.3.0" - } - }, - "node_modules/@reown/appkit-utils/node_modules/@walletconnect/universal-provider": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.21.0.tgz", - "integrity": "sha512-mtUQvewt+X0VBQay/xOJBvxsB3Xsm1lTwFjZ6WUwSOTR1X+FNb71hSApnV5kbsdDIpYPXeQUbGt2se1n5E5UBg==", - "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/events": "1.0.1", - "@walletconnect/jsonrpc-http-connection": "1.0.8", - "@walletconnect/jsonrpc-provider": "1.0.14", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.21.0", - "@walletconnect/types": "2.21.0", - "@walletconnect/utils": "2.21.0", - "es-toolkit": "1.33.0", - "events": "3.3.0" - } - }, - "node_modules/@reown/appkit-utils/node_modules/@walletconnect/utils": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.21.0.tgz", - "integrity": "sha512-zfHLiUoBrQ8rP57HTPXW7rQMnYxYI4gT9yTACxVW6LhIFROTF6/ytm5SKNoIvi4a5nX5dfXG4D9XwQUCu8Ilig==", - "license": "Apache-2.0", - "dependencies": { - "@noble/ciphers": "1.2.1", - "@noble/curves": "1.8.1", - "@noble/hashes": "1.7.1", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.1.0", - "@walletconnect/safe-json": "1.0.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.21.0", - "@walletconnect/window-getters": "1.0.1", - "@walletconnect/window-metadata": "1.0.1", - "bs58": "6.0.0", - "detect-browser": "5.3.0", - "query-string": "7.1.3", - "uint8arrays": "3.1.0", - "viem": "2.23.2" - } - }, - "node_modules/@reown/appkit-utils/node_modules/@walletconnect/utils/node_modules/viem": { - "version": "2.23.2", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.2.tgz", - "integrity": "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@noble/curves": "1.8.1", - "@noble/hashes": "1.7.1", - "@scure/bip32": "1.6.2", - "@scure/bip39": "1.5.4", - "abitype": "1.0.8", - "isows": "1.0.6", - "ox": "0.6.7", - "ws": "8.18.0" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@reown/appkit-utils/node_modules/abitype": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", - "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3 >=3.22.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/@reown/appkit-utils/node_modules/isows": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", - "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/@reown/appkit-utils/node_modules/ox": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.7.tgz", - "integrity": "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.10.1", - "@noble/curves": "^1.6.0", - "@noble/hashes": "^1.5.0", - "@scure/bip32": "^1.5.0", - "@scure/bip39": "^1.4.0", - "abitype": "^1.0.6", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@reown/appkit-wallet": { - "version": "1.7.8", - "resolved": "https://registry.npmjs.org/@reown/appkit-wallet/-/appkit-wallet-1.7.8.tgz", - "integrity": "sha512-kspz32EwHIOT/eg/ZQbFPxgXq0B/olDOj3YMu7gvLEFz4xyOFd/wgzxxAXkp5LbG4Cp++s/elh79rVNmVFdB9A==", - "license": "Apache-2.0", - "dependencies": { - "@reown/appkit-common": "1.7.8", - "@reown/appkit-polyfills": "1.7.8", - "@walletconnect/logger": "2.1.2", - "zod": "3.22.4" - } - }, - "node_modules/@reown/appkit-wallet/node_modules/zod": { - "version": "3.22.4", - "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", - "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/@reown/appkit/node_modules/@noble/ciphers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", - "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit/node_modules/@noble/curves": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", - "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.7.1" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit/node_modules/@noble/hashes": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", - "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit/node_modules/@scure/bip32": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", - "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.8.1", - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit/node_modules/@scure/bip39": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", - "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.4" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@reown/appkit/node_modules/@walletconnect/core": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.21.0.tgz", - "integrity": "sha512-o6R7Ua4myxR8aRUAJ1z3gT9nM+jd2B2mfamu6arzy1Cc6vi10fIwFWb6vg3bC8xJ6o9H3n/cN5TOW3aA9Y1XVw==", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-provider": "1.0.14", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/jsonrpc-ws-connection": "1.0.16", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.1.0", - "@walletconnect/safe-json": "1.0.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.21.0", - "@walletconnect/utils": "2.21.0", - "@walletconnect/window-getters": "1.0.1", - "es-toolkit": "1.33.0", - "events": "3.3.0", - "uint8arrays": "3.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@reown/appkit/node_modules/@walletconnect/sign-client": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.21.0.tgz", - "integrity": "sha512-z7h+PeLa5Au2R591d/8ZlziE0stJvdzP9jNFzFolf2RG/OiXulgFKum8PrIyXy+Rg2q95U9nRVUF9fWcn78yBA==", - "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/core": "2.21.0", - "@walletconnect/events": "1.0.1", - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/logger": "2.1.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.21.0", - "@walletconnect/utils": "2.21.0", - "events": "3.3.0" - } - }, - "node_modules/@reown/appkit/node_modules/@walletconnect/types": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.21.0.tgz", - "integrity": "sha512-ll+9upzqt95ZBWcfkOszXZkfnpbJJ2CmxMfGgE5GmhdxxxCcO5bGhXkI+x8OpiS555RJ/v/sXJYMSOLkmu4fFw==", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/events": "1.0.1", - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "events": "3.3.0" - } - }, - "node_modules/@reown/appkit/node_modules/@walletconnect/universal-provider": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.21.0.tgz", - "integrity": "sha512-mtUQvewt+X0VBQay/xOJBvxsB3Xsm1lTwFjZ6WUwSOTR1X+FNb71hSApnV5kbsdDIpYPXeQUbGt2se1n5E5UBg==", - "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/events": "1.0.1", - "@walletconnect/jsonrpc-http-connection": "1.0.8", - "@walletconnect/jsonrpc-provider": "1.0.14", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.21.0", - "@walletconnect/types": "2.21.0", - "@walletconnect/utils": "2.21.0", - "es-toolkit": "1.33.0", - "events": "3.3.0" - } - }, - "node_modules/@reown/appkit/node_modules/@walletconnect/utils": { - "version": "2.21.0", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.21.0.tgz", - "integrity": "sha512-zfHLiUoBrQ8rP57HTPXW7rQMnYxYI4gT9yTACxVW6LhIFROTF6/ytm5SKNoIvi4a5nX5dfXG4D9XwQUCu8Ilig==", - "license": "Apache-2.0", - "dependencies": { - "@noble/ciphers": "1.2.1", - "@noble/curves": "1.8.1", - "@noble/hashes": "1.7.1", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.1.0", - "@walletconnect/safe-json": "1.0.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.21.0", - "@walletconnect/window-getters": "1.0.1", - "@walletconnect/window-metadata": "1.0.1", - "bs58": "6.0.0", - "detect-browser": "5.3.0", - "query-string": "7.1.3", - "uint8arrays": "3.1.0", - "viem": "2.23.2" - } - }, - "node_modules/@reown/appkit/node_modules/@walletconnect/utils/node_modules/viem": { - "version": "2.23.2", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.2.tgz", - "integrity": "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@noble/curves": "1.8.1", - "@noble/hashes": "1.7.1", - "@scure/bip32": "1.6.2", - "@scure/bip39": "1.5.4", - "abitype": "1.0.8", - "isows": "1.0.6", - "ox": "0.6.7", - "ws": "8.18.0" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@reown/appkit/node_modules/abitype": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", - "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3 >=3.22.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/@reown/appkit/node_modules/isows": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", - "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/@reown/appkit/node_modules/ox": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.7.tgz", - "integrity": "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.10.1", - "@noble/curves": "^1.6.0", - "@noble/hashes": "^1.5.0", - "@scure/bip32": "^1.5.0", - "@scure/bip39": "^1.4.0", - "abitype": "^1.0.6", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@safe-global/safe-apps-provider": { - "version": "0.18.6", - "resolved": "https://registry.npmjs.org/@safe-global/safe-apps-provider/-/safe-apps-provider-0.18.6.tgz", - "integrity": "sha512-4LhMmjPWlIO8TTDC2AwLk44XKXaK6hfBTWyljDm0HQ6TWlOEijVWNrt2s3OCVMSxlXAcEzYfqyu1daHZooTC2Q==", - "license": "MIT", - "dependencies": { - "@safe-global/safe-apps-sdk": "^9.1.0", - "events": "^3.3.0" - } - }, - "node_modules/@safe-global/safe-apps-sdk": { - "version": "9.1.0", - "resolved": "https://registry.npmjs.org/@safe-global/safe-apps-sdk/-/safe-apps-sdk-9.1.0.tgz", - "integrity": "sha512-N5p/ulfnnA2Pi2M3YeWjULeWbjo7ei22JwU/IXnhoHzKq3pYCN6ynL9mJBOlvDVv892EgLPCWCOwQk/uBT2v0Q==", - "license": "MIT", - "dependencies": { - "@safe-global/safe-gateway-typescript-sdk": "^3.5.3", - "viem": "^2.1.1" - } - }, - "node_modules/@safe-global/safe-gateway-typescript-sdk": { - "version": "3.23.1", - "resolved": "https://registry.npmjs.org/@safe-global/safe-gateway-typescript-sdk/-/safe-gateway-typescript-sdk-3.23.1.tgz", - "integrity": "sha512-6ORQfwtEJYpalCeVO21L4XXGSdbEMfyp2hEv6cP82afKXSwvse6d3sdelgaPWUxHIsFRkWvHDdzh8IyyKHZKxw==", - "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", - "license": "MIT", - "engines": { - "node": ">=16" - } - }, - "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/@socket.io/component-emitter": { - "version": "3.1.2", - "resolved": "https://registry.npmjs.org/@socket.io/component-emitter/-/component-emitter-3.1.2.tgz", - "integrity": "sha512-9BCxFwvbGg/RsZK9tjXd8s4UcwR0MWeFQ1XEKIQVVvAGJyINdrqKMcTRyLoK8Rse1GjzLV9cwjWV1olXRWEXVA==", - "license": "MIT" - }, - "node_modules/@solana-program/compute-budget": { - "version": "0.11.0", - "resolved": "https://registry.npmjs.org/@solana-program/compute-budget/-/compute-budget-0.11.0.tgz", - "integrity": "sha512-7f1ePqB/eURkTwTOO9TNIdUXZcyrZoX3Uy2hNo7cXMfNhPFWp9AVgIyRNBc2jf15sdUa9gNpW+PfP2iV8AYAaw==", - "license": "Apache-2.0", - "peerDependencies": { - "@solana/kit": "^5.0" - } - }, - "node_modules/@solana-program/system": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/@solana-program/system/-/system-0.10.0.tgz", - "integrity": "sha512-Go+LOEZmqmNlfr+Gjy5ZWAdY5HbYzk2RBewD9QinEU/bBSzpFfzqDRT55JjFRBGJUvMgf3C2vfXEGT4i8DSI4g==", - "license": "Apache-2.0", - "peerDependencies": { - "@solana/kit": "^5.0" - } - }, - "node_modules/@solana-program/token": { - "version": "0.9.0", - "resolved": "https://registry.npmjs.org/@solana-program/token/-/token-0.9.0.tgz", - "integrity": "sha512-vnZxndd4ED4Fc56sw93cWZ2djEeeOFxtaPS8SPf5+a+JZjKA/EnKqzbE1y04FuMhIVrLERQ8uR8H2h72eZzlsA==", - "license": "Apache-2.0", - "peerDependencies": { - "@solana/kit": "^5.0" - } - }, - "node_modules/@solana-program/token-2022": { - "version": "0.6.1", - "resolved": "https://registry.npmjs.org/@solana-program/token-2022/-/token-2022-0.6.1.tgz", - "integrity": "sha512-Ex02cruDMGfBMvZZCrggVR45vdQQSI/unHVpt/7HPt/IwFYB4eTlXtO8otYZyqV/ce5GqZ8S6uwyRf0zy6fdbA==", - "license": "Apache-2.0", - "peerDependencies": { - "@solana/kit": "^5.0", - "@solana/sysvars": "^5.0" - } - }, - "node_modules/@solana/accounts": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/accounts/-/accounts-5.5.1.tgz", - "integrity": "sha512-TfOY9xixg5rizABuLVuZ9XI2x2tmWUC/OoN556xwfDlhBHBjKfszicYYOyD6nbFmwTGYarCmyGIdteXxTXIdhQ==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "5.5.1", - "@solana/codecs-core": "5.5.1", - "@solana/codecs-strings": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/rpc-spec": "5.5.1", - "@solana/rpc-types": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/addresses": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/addresses/-/addresses-5.5.1.tgz", - "integrity": "sha512-5xoah3Q9G30HQghu/9BiHLb5pzlPKRC3zydQDmE3O9H//WfayxTFppsUDCL6FjYUHqj/wzK6CWHySglc2RkpdA==", - "license": "MIT", - "dependencies": { - "@solana/assertions": "5.5.1", - "@solana/codecs-core": "5.5.1", - "@solana/codecs-strings": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/nominal-types": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/assertions": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/assertions/-/assertions-5.5.1.tgz", - "integrity": "sha512-YTCSWAlGwSlVPnWtWLm3ukz81wH4j2YaCveK+TjpvUU88hTy6fmUqxi0+hvAMAe4zKXpJyj3Az7BrLJRxbIm4Q==", - "license": "MIT", - "dependencies": { - "@solana/errors": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/codecs": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-5.5.1.tgz", - "integrity": "sha512-Vea29nJub/bXjfzEV7ZZQ/PWr1pYLZo3z0qW0LQL37uKKVzVFRQlwetd7INk3YtTD3xm9WUYr7bCvYUk3uKy2g==", - "license": "MIT", - "dependencies": { - "@solana/codecs-core": "5.5.1", - "@solana/codecs-data-structures": "5.5.1", - "@solana/codecs-numbers": "5.5.1", - "@solana/codecs-strings": "5.5.1", - "@solana/options": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/codecs-core": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-5.5.1.tgz", - "integrity": "sha512-TgBt//bbKBct0t6/MpA8ElaOA3sa8eYVvR7LGslCZ84WiAwwjCY0lW/lOYsFHJQzwREMdUyuEyy5YWBKtdh8Rw==", - "license": "MIT", - "dependencies": { - "@solana/errors": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/codecs-data-structures": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-5.5.1.tgz", - "integrity": "sha512-97bJWGyUY9WvBz3mX1UV3YPWGDTez6btCfD0ip3UVEXJbItVuUiOkzcO5iFDUtQT5riKT6xC+Mzl+0nO76gd0w==", - "license": "MIT", - "dependencies": { - "@solana/codecs-core": "5.5.1", - "@solana/codecs-numbers": "5.5.1", - "@solana/errors": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/codecs-numbers": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-5.5.1.tgz", - "integrity": "sha512-rllMIZAHqmtvC0HO/dc/21wDuWaD0B8Ryv8o+YtsICQBuiL/0U4AGwH7Pi5GNFySYk0/crSuwfIqQFtmxNSPFw==", - "license": "MIT", - "dependencies": { - "@solana/codecs-core": "5.5.1", - "@solana/errors": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/codecs-strings": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-5.5.1.tgz", - "integrity": "sha512-7klX4AhfHYA+uKKC/nxRGP2MntbYQCR3N6+v7bk1W/rSxYuhNmt+FN8aoThSZtWIKwN6BEyR1167ka8Co1+E7A==", - "license": "MIT", - "dependencies": { - "@solana/codecs-core": "5.5.1", - "@solana/codecs-numbers": "5.5.1", - "@solana/errors": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "fastestsmallesttextencoderdecoder": "^1.0.22", - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "fastestsmallesttextencoderdecoder": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/errors": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-5.5.1.tgz", - "integrity": "sha512-vFO3p+S7HoyyrcAectnXbdsMfwUzY2zYFUc2DEe5BwpiE9J1IAxPBGjOWO6hL1bbYdBrlmjNx8DXCslqS+Kcmg==", - "license": "MIT", - "dependencies": { - "chalk": "5.6.2", - "commander": "14.0.2" - }, - "bin": { - "errors": "bin/cli.mjs" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/fast-stable-stringify": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/fast-stable-stringify/-/fast-stable-stringify-5.5.1.tgz", - "integrity": "sha512-Ni7s2FN33zTzhTFgRjEbOVFO+UAmK8qi3Iu0/GRFYK4jN696OjKHnboSQH/EacQ+yGqS54bfxf409wU5dsLLCw==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/functional": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/functional/-/functional-5.5.1.tgz", - "integrity": "sha512-tTHoJcEQq3gQx5qsdsDJ0LEJeFzwNpXD80xApW9o/PPoCNimI3SALkZl+zNW8VnxRrV3l3yYvfHWBKe/X3WG3w==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/instruction-plans": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/instruction-plans/-/instruction-plans-5.5.1.tgz", - "integrity": "sha512-7z3CB7YMcFKuVvgcnNY8bY6IsZ8LG61Iytbz7HpNVGX2u1RthOs1tRW8luTzSG1MPL0Ox7afyAVMYeFqSPHnaQ==", - "license": "MIT", - "dependencies": { - "@solana/errors": "5.5.1", - "@solana/instructions": "5.5.1", - "@solana/keys": "5.5.1", - "@solana/promises": "5.5.1", - "@solana/transaction-messages": "5.5.1", - "@solana/transactions": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/instructions": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/instructions/-/instructions-5.5.1.tgz", - "integrity": "sha512-h0G1CG6S+gUUSt0eo6rOtsaXRBwCq1+Js2a+Ps9Bzk9q7YHNFA75/X0NWugWLgC92waRp66hrjMTiYYnLBoWOQ==", - "license": "MIT", - "dependencies": { - "@solana/codecs-core": "5.5.1", - "@solana/errors": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/keys": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/keys/-/keys-5.5.1.tgz", - "integrity": "sha512-KRD61cL7CRL+b4r/eB9dEoVxIf/2EJ1Pm1DmRYhtSUAJD2dJ5Xw8QFuehobOGm9URqQ7gaQl+Fkc1qvDlsWqKg==", - "license": "MIT", - "dependencies": { - "@solana/assertions": "5.5.1", - "@solana/codecs-core": "5.5.1", - "@solana/codecs-strings": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/nominal-types": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/kit": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-5.5.1.tgz", - "integrity": "sha512-irKUGiV2yRoyf+4eGQ/ZeCRxa43yjFEL1DUI5B0DkcfZw3cr0VJtVJnrG8OtVF01vT0OUfYOcUn6zJW5TROHvQ==", - "license": "MIT", - "dependencies": { - "@solana/accounts": "5.5.1", - "@solana/addresses": "5.5.1", - "@solana/codecs": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/functional": "5.5.1", - "@solana/instruction-plans": "5.5.1", - "@solana/instructions": "5.5.1", - "@solana/keys": "5.5.1", - "@solana/offchain-messages": "5.5.1", - "@solana/plugin-core": "5.5.1", - "@solana/programs": "5.5.1", - "@solana/rpc": "5.5.1", - "@solana/rpc-api": "5.5.1", - "@solana/rpc-parsed-types": "5.5.1", - "@solana/rpc-spec-types": "5.5.1", - "@solana/rpc-subscriptions": "5.5.1", - "@solana/rpc-types": "5.5.1", - "@solana/signers": "5.5.1", - "@solana/sysvars": "5.5.1", - "@solana/transaction-confirmation": "5.5.1", - "@solana/transaction-messages": "5.5.1", - "@solana/transactions": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/nominal-types": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/nominal-types/-/nominal-types-5.5.1.tgz", - "integrity": "sha512-I1ImR+kfrLFxN5z22UDiTWLdRZeKtU0J/pkWkO8qm/8WxveiwdIv4hooi8pb6JnlR4mSrWhq0pCIOxDYrL9GIQ==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/offchain-messages": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/offchain-messages/-/offchain-messages-5.5.1.tgz", - "integrity": "sha512-g+xHH95prTU+KujtbOzj8wn+C7ZNoiLhf3hj6nYq3MTyxOXtBEysguc97jJveUZG0K97aIKG6xVUlMutg5yxhw==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "5.5.1", - "@solana/codecs-core": "5.5.1", - "@solana/codecs-data-structures": "5.5.1", - "@solana/codecs-numbers": "5.5.1", - "@solana/codecs-strings": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/keys": "5.5.1", - "@solana/nominal-types": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/options": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/options/-/options-5.5.1.tgz", - "integrity": "sha512-eo971c9iLNLmk+yOFyo7yKIJzJ/zou6uKpy6mBuyb/thKtS/haiKIc3VLhyTXty3OH2PW8yOlORJnv4DexJB8A==", - "license": "MIT", - "dependencies": { - "@solana/codecs-core": "5.5.1", - "@solana/codecs-data-structures": "5.5.1", - "@solana/codecs-numbers": "5.5.1", - "@solana/codecs-strings": "5.5.1", - "@solana/errors": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/plugin-core": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/plugin-core/-/plugin-core-5.5.1.tgz", - "integrity": "sha512-VUZl30lDQFJeiSyNfzU1EjYt2QZvoBFKEwjn1lilUJw7KgqD5z7mbV7diJhT+dLFs36i0OsjXvq5kSygn8YJ3A==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/programs": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/programs/-/programs-5.5.1.tgz", - "integrity": "sha512-7U9kn0Jsx1NuBLn5HRTFYh78MV4XN145Yc3WP/q5BlqAVNlMoU9coG5IUTJIG847TUqC1lRto3Dnpwm6T4YRpA==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "5.5.1", - "@solana/errors": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/promises": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/promises/-/promises-5.5.1.tgz", - "integrity": "sha512-T9lfuUYkGykJmppEcssNiCf6yiYQxJkhiLPP+pyAc2z84/7r3UVIb2tNJk4A9sucS66pzJnVHZKcZVGUUp6wzA==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/rpc": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/rpc/-/rpc-5.5.1.tgz", - "integrity": "sha512-ku8zTUMrkCWci66PRIBC+1mXepEnZH/q1f3ck0kJZ95a06bOTl5KU7HeXWtskkyefzARJ5zvCs54AD5nxjQJ+A==", - "license": "MIT", - "dependencies": { - "@solana/errors": "5.5.1", - "@solana/fast-stable-stringify": "5.5.1", - "@solana/functional": "5.5.1", - "@solana/rpc-api": "5.5.1", - "@solana/rpc-spec": "5.5.1", - "@solana/rpc-spec-types": "5.5.1", - "@solana/rpc-transformers": "5.5.1", - "@solana/rpc-transport-http": "5.5.1", - "@solana/rpc-types": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/rpc-api": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/rpc-api/-/rpc-api-5.5.1.tgz", - "integrity": "sha512-XWOQQPhKl06Vj0xi3RYHAc6oEQd8B82okYJ04K7N0Vvy3J4PN2cxeK7klwkjgavdcN9EVkYCChm2ADAtnztKnA==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "5.5.1", - "@solana/codecs-core": "5.5.1", - "@solana/codecs-strings": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/keys": "5.5.1", - "@solana/rpc-parsed-types": "5.5.1", - "@solana/rpc-spec": "5.5.1", - "@solana/rpc-transformers": "5.5.1", - "@solana/rpc-types": "5.5.1", - "@solana/transaction-messages": "5.5.1", - "@solana/transactions": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/rpc-parsed-types": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/rpc-parsed-types/-/rpc-parsed-types-5.5.1.tgz", - "integrity": "sha512-HEi3G2nZqGEsa3vX6U0FrXLaqnUCg4SKIUrOe8CezD+cSFbRTOn3rCLrUmJrhVyXlHoQVaRO9mmeovk31jWxJg==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/rpc-spec": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/rpc-spec/-/rpc-spec-5.5.1.tgz", - "integrity": "sha512-m3LX2bChm3E3by4mQrH4YwCAFY57QBzuUSWqlUw7ChuZ+oLLOq7b2czi4i6L4Vna67j3eCmB3e+4tqy1j5wy7Q==", - "license": "MIT", - "dependencies": { - "@solana/errors": "5.5.1", - "@solana/rpc-spec-types": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/rpc-spec-types": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/rpc-spec-types/-/rpc-spec-types-5.5.1.tgz", - "integrity": "sha512-6OFKtRpIEJQs8Jb2C4OO8KyP2h2Hy1MFhatMAoXA+0Ik8S3H+CicIuMZvGZ91mIu/tXicuOOsNNLu3HAkrakrw==", - "license": "MIT", - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/rpc-subscriptions": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions/-/rpc-subscriptions-5.5.1.tgz", - "integrity": "sha512-CTMy5bt/6mDh4tc6vUJms9EcuZj3xvK0/xq8IQ90rhkpYvate91RjBP+egvjgSayUg9yucU9vNuUpEjz4spM7w==", - "license": "MIT", - "dependencies": { - "@solana/errors": "5.5.1", - "@solana/fast-stable-stringify": "5.5.1", - "@solana/functional": "5.5.1", - "@solana/promises": "5.5.1", - "@solana/rpc-spec-types": "5.5.1", - "@solana/rpc-subscriptions-api": "5.5.1", - "@solana/rpc-subscriptions-channel-websocket": "5.5.1", - "@solana/rpc-subscriptions-spec": "5.5.1", - "@solana/rpc-transformers": "5.5.1", - "@solana/rpc-types": "5.5.1", - "@solana/subscribable": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/rpc-subscriptions-api": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-api/-/rpc-subscriptions-api-5.5.1.tgz", - "integrity": "sha512-5Oi7k+GdeS8xR2ly1iuSFkAv6CZqwG0Z6b1QZKbEgxadE1XGSDrhM2cn59l+bqCozUWCqh4c/A2znU/qQjROlw==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "5.5.1", - "@solana/keys": "5.5.1", - "@solana/rpc-subscriptions-spec": "5.5.1", - "@solana/rpc-transformers": "5.5.1", - "@solana/rpc-types": "5.5.1", - "@solana/transaction-messages": "5.5.1", - "@solana/transactions": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/rpc-subscriptions-channel-websocket": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-channel-websocket/-/rpc-subscriptions-channel-websocket-5.5.1.tgz", - "integrity": "sha512-7tGfBBrYY8TrngOyxSHoCU5shy86iA9SRMRrPSyBhEaZRAk6dnbdpmUTez7gtdVo0BCvh9nzQtUycKWSS7PnFQ==", - "license": "MIT", - "dependencies": { - "@solana/errors": "5.5.1", - "@solana/functional": "5.5.1", - "@solana/rpc-subscriptions-spec": "5.5.1", - "@solana/subscribable": "5.5.1", - "ws": "^8.19.0" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/rpc-subscriptions-spec": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-spec/-/rpc-subscriptions-spec-5.5.1.tgz", - "integrity": "sha512-iq+rGq5fMKP3/mKHPNB6MC8IbVW41KGZg83Us/+LE3AWOTWV1WT20KT2iH1F1ik9roi42COv/TpoZZvhKj45XQ==", - "license": "MIT", - "dependencies": { - "@solana/errors": "5.5.1", - "@solana/promises": "5.5.1", - "@solana/rpc-spec-types": "5.5.1", - "@solana/subscribable": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/rpc-transformers": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/rpc-transformers/-/rpc-transformers-5.5.1.tgz", - "integrity": "sha512-OsWqLCQdcrRJKvHiMmwFhp9noNZ4FARuMkHT5us3ustDLXaxOjF0gfqZLnMkulSLcKt7TGXqMhBV+HCo7z5M8Q==", - "license": "MIT", - "dependencies": { - "@solana/errors": "5.5.1", - "@solana/functional": "5.5.1", - "@solana/nominal-types": "5.5.1", - "@solana/rpc-spec-types": "5.5.1", - "@solana/rpc-types": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/rpc-transport-http": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/rpc-transport-http/-/rpc-transport-http-5.5.1.tgz", - "integrity": "sha512-yv8GoVSHqEV0kUJEIhkdOVkR2SvJ6yoWC51cJn2rSV7plr6huLGe0JgujCmB7uZhhaLbcbP3zxXxu9sOjsi7Fg==", - "license": "MIT", - "dependencies": { - "@solana/errors": "5.5.1", - "@solana/rpc-spec": "5.5.1", - "@solana/rpc-spec-types": "5.5.1", - "undici-types": "^7.19.2" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/rpc-transport-http/node_modules/undici-types": { - "version": "7.26.0", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.26.0.tgz", - "integrity": "sha512-OY7qWYg4TsPpqg/kL2FfNnGA8cmAhPpLt45XQ2jd8p9UobYQ7Q09LeiCq5QwZhlKNLBj0iTUlBNhs4M2AVFmxA==", - "license": "MIT" - }, - "node_modules/@solana/rpc-types": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/rpc-types/-/rpc-types-5.5.1.tgz", - "integrity": "sha512-bibTFQ7PbHJJjGJPmfYC2I+/5CRFS4O2p9WwbFraX1Keeel+nRrt/NBXIy8veP5AEn2sVJIyJPpWBRpCx1oATA==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "5.5.1", - "@solana/codecs-core": "5.5.1", - "@solana/codecs-numbers": "5.5.1", - "@solana/codecs-strings": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/nominal-types": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/signers": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/signers/-/signers-5.5.1.tgz", - "integrity": "sha512-FY0IVaBT2kCAze55vEieR6hag4coqcuJ31Aw3hqRH7mv6sV8oqwuJmUrx+uFwOp1gwd5OEAzlv6N4hOOple4sQ==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "5.5.1", - "@solana/codecs-core": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/instructions": "5.5.1", - "@solana/keys": "5.5.1", - "@solana/nominal-types": "5.5.1", - "@solana/offchain-messages": "5.5.1", - "@solana/transaction-messages": "5.5.1", - "@solana/transactions": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/subscribable": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/subscribable/-/subscribable-5.5.1.tgz", - "integrity": "sha512-9K0PsynFq0CsmK1CDi5Y2vUIJpCqkgSS5yfDN0eKPgHqEptLEaia09Kaxc90cSZDZU5mKY/zv1NBmB6Aro9zQQ==", - "license": "MIT", - "dependencies": { - "@solana/errors": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/sysvars": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-5.5.1.tgz", - "integrity": "sha512-k3Quq87Mm+geGUu1GWv6knPk0ALsfY6EKSJGw9xUJDHzY/RkYSBnh0RiOrUhtFm2TDNjOailg8/m0VHmi3reFA==", - "license": "MIT", - "dependencies": { - "@solana/accounts": "5.5.1", - "@solana/codecs": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/rpc-types": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/transaction-confirmation": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/transaction-confirmation/-/transaction-confirmation-5.5.1.tgz", - "integrity": "sha512-j4mKlYPHEyu+OD7MBt3jRoX4ScFgkhZC6H65on4Fux6LMScgivPJlwnKoZMnsgxFgWds0pl+BYzSiALDsXlYtw==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "5.5.1", - "@solana/codecs-strings": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/keys": "5.5.1", - "@solana/promises": "5.5.1", - "@solana/rpc": "5.5.1", - "@solana/rpc-subscriptions": "5.5.1", - "@solana/rpc-types": "5.5.1", - "@solana/transaction-messages": "5.5.1", - "@solana/transactions": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/transaction-messages": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/transaction-messages/-/transaction-messages-5.5.1.tgz", - "integrity": "sha512-aXyhMCEaAp3M/4fP0akwBBQkFPr4pfwoC5CLDq999r/FUwDax2RE/h4Ic7h2Xk+JdcUwsb+rLq85Y52hq84XvQ==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "5.5.1", - "@solana/codecs-core": "5.5.1", - "@solana/codecs-data-structures": "5.5.1", - "@solana/codecs-numbers": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/functional": "5.5.1", - "@solana/instructions": "5.5.1", - "@solana/nominal-types": "5.5.1", - "@solana/rpc-types": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/transactions": { - "version": "5.5.1", - "resolved": "https://registry.npmjs.org/@solana/transactions/-/transactions-5.5.1.tgz", - "integrity": "sha512-8hHtDxtqalZ157pnx6p8k10D7J/KY/biLzfgh9R09VNLLY3Fqi7kJvJCr7M2ik3oRll56pxhraAGCC9yIT6eOA==", - "license": "MIT", - "dependencies": { - "@solana/addresses": "5.5.1", - "@solana/codecs-core": "5.5.1", - "@solana/codecs-data-structures": "5.5.1", - "@solana/codecs-numbers": "5.5.1", - "@solana/codecs-strings": "5.5.1", - "@solana/errors": "5.5.1", - "@solana/functional": "5.5.1", - "@solana/instructions": "5.5.1", - "@solana/keys": "5.5.1", - "@solana/nominal-types": "5.5.1", - "@solana/rpc-types": "5.5.1", - "@solana/transaction-messages": "5.5.1" - }, - "engines": { - "node": ">=20.18.0" - }, - "peerDependencies": { - "typescript": "^5.0.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@solana/wallet-standard-features": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@solana/wallet-standard-features/-/wallet-standard-features-1.3.0.tgz", - "integrity": "sha512-ZhpZtD+4VArf6RPitsVExvgkF+nGghd1rzPjd97GmBximpnt1rsUxMOEyoIEuH3XBxPyNB6Us7ha7RHWQR+abg==", - "license": "Apache-2.0", - "dependencies": { - "@wallet-standard/base": "^1.1.0", - "@wallet-standard/features": "^1.1.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@spruceid/siwe-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@spruceid/siwe-parser/-/siwe-parser-3.0.0.tgz", - "integrity": "sha512-Y92k63ilw/8jH9Ry4G2e7lQd0jZAvb0d/Q7ssSD0D9mp/Zt2aCXIc3g0ny9yhplpAx1QXHsMz/JJptHK/zDGdw==", - "license": "Apache-2.0", - "dependencies": { - "@noble/hashes": "^1.1.2", - "apg-js": "^4.4.0" - } - }, - "node_modules/@stablelib/binary": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/binary/-/binary-1.0.1.tgz", - "integrity": "sha512-ClJWvmL6UBM/wjkvv/7m5VP3GMr9t0osr4yVgLZsLCOz4hGN9gIAFEqnJ0TsSMAN+n840nf2cHZnA5/KFqHC7Q==", - "license": "MIT", - "dependencies": { - "@stablelib/int": "^1.0.1" - } - }, - "node_modules/@stablelib/int": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/int/-/int-1.0.1.tgz", - "integrity": "sha512-byr69X/sDtDiIjIV6m4roLVWnNNlRGzsvxw+agj8CIEazqWGOQp2dTYgQhtyVXV9wpO6WyXRQUzLV/JRNumT2w==", - "license": "MIT" - }, - "node_modules/@stablelib/random": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@stablelib/random/-/random-1.0.2.tgz", - "integrity": "sha512-rIsE83Xpb7clHPVRlBj8qNe5L8ISQOzjghYQm/dZ7VaM2KHYwMW5adjQjrzTZCchFnNCNhkwtnOBa9HTMJCI8w==", - "license": "MIT", - "dependencies": { - "@stablelib/binary": "^1.0.1", - "@stablelib/wipe": "^1.0.1" - } - }, - "node_modules/@stablelib/wipe": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@stablelib/wipe/-/wipe-1.0.1.tgz", - "integrity": "sha512-WfqfX/eXGiAd3RJe4VU2snh/ZPwtSjLG4ynQ/vYzvghTh7dHFcI1wl+nrkWG6lGhukOxOsUHfv8dUXr58D0ayg==", - "license": "MIT" - }, - "node_modules/@tanstack/query-core": { - "version": "5.100.14", - "resolved": "https://registry.npmjs.org/@tanstack/query-core/-/query-core-5.100.14.tgz", - "integrity": "sha512-5X41dGpxgeaHISCRW2oYwcSycZeULZzAunaudXT9ov1KOTj9xwt0CH6hbwqP1/z74ZWF7rYFnDpyYH07XFcZew==", - "license": "MIT", - "peer": true, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - } - }, - "node_modules/@tanstack/react-query": { - "version": "5.100.14", - "resolved": "https://registry.npmjs.org/@tanstack/react-query/-/react-query-5.100.14.tgz", - "integrity": "sha512-oOr6aRdSFEwWhzxEkD/9ZcItM3+LjBSkeVmadWKwUssAHTsqd/7bOjWrX4AbvEkoEhgAxzN0Xk6H/aYzXiYBAw==", - "license": "MIT", - "peer": true, - "dependencies": { - "@tanstack/query-core": "5.100.14" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/tannerlinsley" - }, - "peerDependencies": { - "react": "^18 || ^19" - } - }, - "node_modules/@types/debug": { - "version": "4.1.13", - "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.13.tgz", - "integrity": "sha512-KSVgmQmzMwPlmtljOomayoR89W4FynCAi3E8PPs7vmDVPe84hT+vGPKkJfThkmXs0x0jAaa9U8uW8bbfyS2fWw==", - "license": "MIT", - "dependencies": { - "@types/ms": "*" - } - }, - "node_modules/@types/lodash": { - "version": "4.17.24", - "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.17.24.tgz", - "integrity": "sha512-gIW7lQLZbue7lRSWEFql49QJJWThrTFFeIMJdp3eH4tKoxm1OvEPg02rm4wCCSHS0cL3/Fizimb35b7k8atwsQ==", - "license": "MIT" - }, - "node_modules/@types/ms": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/@types/ms/-/ms-2.1.0.tgz", - "integrity": "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA==", - "license": "MIT" - }, - "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/@types/trusted-types": { - "version": "2.0.7", - "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", - "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", - "license": "MIT" - }, - "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/@wagmi/connectors": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/@wagmi/connectors/-/connectors-6.2.0.tgz", - "integrity": "sha512-2NfkbqhNWdjfibb4abRMrn7u6rPjEGolMfApXss6HCDVt9AW2oVC6k8Q5FouzpJezElxLJSagWz9FW1zaRlanA==", - "license": "MIT", - "dependencies": { - "@base-org/account": "2.4.0", - "@coinbase/wallet-sdk": "4.3.6", - "@gemini-wallet/core": "0.3.2", - "@metamask/sdk": "0.33.1", - "@safe-global/safe-apps-provider": "0.18.6", - "@safe-global/safe-apps-sdk": "9.1.0", - "@walletconnect/ethereum-provider": "2.21.1", - "cbw-sdk": "npm:@coinbase/wallet-sdk@3.9.3", - "porto": "0.2.35" - }, - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "@wagmi/core": "2.22.1", - "typescript": ">=5.0.4", - "viem": "2.x" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@wagmi/core": { - "version": "2.22.1", - "resolved": "https://registry.npmjs.org/@wagmi/core/-/core-2.22.1.tgz", - "integrity": "sha512-cG/xwQWsBEcKgRTkQVhH29cbpbs/TdcUJVFXCyri3ZknxhMyGv0YEjTcrNpRgt2SaswL1KrvslSNYKKo+5YEAg==", - "license": "MIT", - "dependencies": { - "eventemitter3": "5.0.1", - "mipd": "0.0.7", - "zustand": "5.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "@tanstack/query-core": ">=5.0.0", - "typescript": ">=5.0.4", - "viem": "2.x" - }, - "peerDependenciesMeta": { - "@tanstack/query-core": { - "optional": true - }, - "typescript": { - "optional": true - } - } - }, - "node_modules/@wagmi/core/node_modules/zustand": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.0.tgz", - "integrity": "sha512-LE+VcmbartOPM+auOjCCLQOsQ05zUTp8RkgwRzefUk+2jISdMMFnxvyTjA4YNWr5ZGXYbVsEMZosttuxUBkojQ==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - }, - "node_modules/@wallet-standard/app": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@wallet-standard/app/-/app-1.1.0.tgz", - "integrity": "sha512-3CijvrO9utx598kjr45hTbbeeykQrQfKmSnxeWOgU25TOEpvcipD/bYDQWIqUv1Oc6KK4YStokSMu/FBNecGUQ==", - "license": "Apache-2.0", - "dependencies": { - "@wallet-standard/base": "^1.1.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@wallet-standard/base": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@wallet-standard/base/-/base-1.1.0.tgz", - "integrity": "sha512-DJDQhjKmSNVLKWItoKThJS+CsJQjR9AOBOirBVT1F9YpRyC9oYHE+ZnSf8y8bxUphtKqdQMPVQ2mHohYdRvDVQ==", - "license": "Apache-2.0", - "engines": { - "node": ">=16" - } - }, - "node_modules/@wallet-standard/features": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@wallet-standard/features/-/features-1.1.0.tgz", - "integrity": "sha512-hiEivWNztx73s+7iLxsuD1sOJ28xtRix58W7Xnz4XzzA/pF0+aicnWgjOdA10doVDEDZdUuZCIIqG96SFNlDUg==", - "license": "Apache-2.0", - "dependencies": { - "@wallet-standard/base": "^1.1.0" - }, - "engines": { - "node": ">=16" - } - }, - "node_modules/@walletconnect/core": { - "version": "2.21.1", - "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.21.1.tgz", - "integrity": "sha512-Tp4MHJYcdWD846PH//2r+Mu4wz1/ZU/fr9av1UWFiaYQ2t2TPLDiZxjLw54AAEpMqlEHemwCgiRiAmjR1NDdTQ==", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-provider": "1.0.14", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/jsonrpc-ws-connection": "1.0.16", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.1.0", - "@walletconnect/safe-json": "1.0.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.21.1", - "@walletconnect/utils": "2.21.1", - "@walletconnect/window-getters": "1.0.1", - "es-toolkit": "1.33.0", - "events": "3.3.0", - "uint8arrays": "3.1.0" - }, - "engines": { - "node": ">=18" - } - }, - "node_modules/@walletconnect/environment": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@walletconnect/environment/-/environment-1.0.1.tgz", - "integrity": "sha512-T426LLZtHj8e8rYnKfzsw1aG6+M0BT1ZxayMdv/p8yM0MU+eJDISqNY3/bccxRr4LrF9csq02Rhqt08Ibl0VRg==", - "license": "MIT", - "dependencies": { - "tslib": "1.14.1" - } - }, - "node_modules/@walletconnect/environment/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, - "node_modules/@walletconnect/ethereum-provider": { - "version": "2.21.1", - "resolved": "https://registry.npmjs.org/@walletconnect/ethereum-provider/-/ethereum-provider-2.21.1.tgz", - "integrity": "sha512-SSlIG6QEVxClgl1s0LMk4xr2wg4eT3Zn/Hb81IocyqNSGfXpjtawWxKxiC5/9Z95f1INyBD6MctJbL/R1oBwIw==", - "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", - "license": "Apache-2.0", - "dependencies": { - "@reown/appkit": "1.7.8", - "@walletconnect/jsonrpc-http-connection": "1.0.8", - "@walletconnect/jsonrpc-provider": "1.0.14", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/sign-client": "2.21.1", - "@walletconnect/types": "2.21.1", - "@walletconnect/universal-provider": "2.21.1", - "@walletconnect/utils": "2.21.1", - "events": "3.3.0" - } - }, - "node_modules/@walletconnect/events": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@walletconnect/events/-/events-1.0.1.tgz", - "integrity": "sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ==", - "license": "MIT", - "dependencies": { - "keyvaluestorage-interface": "^1.0.0", - "tslib": "1.14.1" - } - }, - "node_modules/@walletconnect/events/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, - "node_modules/@walletconnect/heartbeat": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/@walletconnect/heartbeat/-/heartbeat-1.2.2.tgz", - "integrity": "sha512-uASiRmC5MwhuRuf05vq4AT48Pq8RMi876zV8rr8cV969uTOzWdB/k+Lj5yI2PBtB1bGQisGen7MM1GcZlQTBXw==", - "license": "MIT", - "dependencies": { - "@walletconnect/events": "^1.0.1", - "@walletconnect/time": "^1.0.2", - "events": "^3.3.0" - } - }, - "node_modules/@walletconnect/jsonrpc-http-connection": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-http-connection/-/jsonrpc-http-connection-1.0.8.tgz", - "integrity": "sha512-+B7cRuaxijLeFDJUq5hAzNyef3e3tBDIxyaCNmFtjwnod5AGis3RToNqzFU33vpVcxFhofkpE7Cx+5MYejbMGw==", - "license": "MIT", - "dependencies": { - "@walletconnect/jsonrpc-utils": "^1.0.6", - "@walletconnect/safe-json": "^1.0.1", - "cross-fetch": "^3.1.4", - "events": "^3.3.0" - } - }, - "node_modules/@walletconnect/jsonrpc-http-connection/node_modules/cross-fetch": { - "version": "3.2.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", - "integrity": "sha512-Q+xVJLoGOeIMXZmbUK4HYk+69cQH6LudR0Vu/pRm2YlU/hDV9CiS0gKUMaWY5f2NeUH9C1nV3bsTlCo0FsTV1Q==", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, - "node_modules/@walletconnect/jsonrpc-provider": { - "version": "1.0.14", - "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.14.tgz", - "integrity": "sha512-rtsNY1XqHvWj0EtITNeuf8PHMvlCLiS3EjQL+WOkxEOA4KPxsohFnBDeyPYiNm4ZvkQdLnece36opYidmtbmow==", - "license": "MIT", - "dependencies": { - "@walletconnect/jsonrpc-utils": "^1.0.8", - "@walletconnect/safe-json": "^1.0.2", - "events": "^3.3.0" - } - }, - "node_modules/@walletconnect/jsonrpc-types": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.4.tgz", - "integrity": "sha512-P6679fG/M+wuWg9TY8mh6xFSdYnFyFjwFelxyISxMDrlbXokorEVXYOxiqEbrU3x1BmBoCAJJ+vtEaEoMlpCBQ==", - "license": "MIT", - "dependencies": { - "events": "^3.3.0", - "keyvaluestorage-interface": "^1.0.0" - } - }, - "node_modules/@walletconnect/jsonrpc-utils": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.8.tgz", - "integrity": "sha512-vdeb03bD8VzJUL6ZtzRYsFMq1eZQcM3EAzT0a3st59dyLfJ0wq+tKMpmGH7HlB7waD858UWgfIcudbPFsbzVdw==", - "license": "MIT", - "dependencies": { - "@walletconnect/environment": "^1.0.1", - "@walletconnect/jsonrpc-types": "^1.0.3", - "tslib": "1.14.1" - } - }, - "node_modules/@walletconnect/jsonrpc-utils/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, - "node_modules/@walletconnect/jsonrpc-ws-connection": { - "version": "1.0.16", - "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.16.tgz", - "integrity": "sha512-G81JmsMqh5nJheE1mPst1W0WfVv0SG3N7JggwLLGnI7iuDZJq8cRJvQwLGKHn5H1WTW7DEPCo00zz5w62AbL3Q==", - "license": "MIT", - "dependencies": { - "@walletconnect/jsonrpc-utils": "^1.0.6", - "@walletconnect/safe-json": "^1.0.2", - "events": "^3.3.0", - "ws": "^7.5.1" - } - }, - "node_modules/@walletconnect/keyvaluestorage": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", - "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", - "license": "MIT", - "dependencies": { - "@walletconnect/safe-json": "^1.0.1", - "idb-keyval": "^6.2.1", - "unstorage": "^1.9.0" - }, - "peerDependencies": { - "@react-native-async-storage/async-storage": "1.x" - }, - "peerDependenciesMeta": { - "@react-native-async-storage/async-storage": { - "optional": true - } - } - }, - "node_modules/@walletconnect/logger": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/@walletconnect/logger/-/logger-2.1.2.tgz", - "integrity": "sha512-aAb28I3S6pYXZHQm5ESB+V6rDqIYfsnHaQyzFbwUUBFY4H0OXx/YtTl8lvhUNhMMfb9UxbwEBS253TlXUYJWSw==", - "license": "MIT", - "dependencies": { - "@walletconnect/safe-json": "^1.0.2", - "pino": "7.11.0" - } - }, - "node_modules/@walletconnect/relay-api": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11.tgz", - "integrity": "sha512-tLPErkze/HmC9aCmdZOhtVmYZq1wKfWTJtygQHoWtgg722Jd4homo54Cs4ak2RUFUZIGO2RsOpIcWipaua5D5Q==", - "license": "MIT", - "dependencies": { - "@walletconnect/jsonrpc-types": "^1.0.2" - } - }, - "node_modules/@walletconnect/relay-auth": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@walletconnect/relay-auth/-/relay-auth-1.1.0.tgz", - "integrity": "sha512-qFw+a9uRz26jRCDgL7Q5TA9qYIgcNY8jpJzI1zAWNZ8i7mQjaijRnWFKsCHAU9CyGjvt6RKrRXyFtFOpWTVmCQ==", - "license": "MIT", - "dependencies": { - "@noble/curves": "1.8.0", - "@noble/hashes": "1.7.0", - "@walletconnect/safe-json": "^1.0.1", - "@walletconnect/time": "^1.0.2", - "uint8arrays": "^3.0.0" - } - }, - "node_modules/@walletconnect/relay-auth/node_modules/@noble/curves": { - "version": "1.8.0", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.0.tgz", - "integrity": "sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.7.0" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@walletconnect/relay-auth/node_modules/@noble/hashes": { - "version": "1.7.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.0.tgz", - "integrity": "sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@walletconnect/safe-json": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@walletconnect/safe-json/-/safe-json-1.0.2.tgz", - "integrity": "sha512-Ogb7I27kZ3LPC3ibn8ldyUr5544t3/STow9+lzz7Sfo808YD7SBWk7SAsdBFlYgP2zDRy2hS3sKRcuSRM0OTmA==", - "license": "MIT", - "dependencies": { - "tslib": "1.14.1" - } - }, - "node_modules/@walletconnect/safe-json/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, - "node_modules/@walletconnect/sign-client": { - "version": "2.21.1", - "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.21.1.tgz", - "integrity": "sha512-QaXzmPsMnKGV6tc4UcdnQVNOz4zyXgarvdIQibJ4L3EmLat73r5ZVl4c0cCOcoaV7rgM9Wbphgu5E/7jNcd3Zg==", - "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/core": "2.21.1", - "@walletconnect/events": "1.0.1", - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/logger": "2.1.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.21.1", - "@walletconnect/utils": "2.21.1", - "events": "3.3.0" - } - }, - "node_modules/@walletconnect/time": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/@walletconnect/time/-/time-1.0.2.tgz", - "integrity": "sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g==", - "license": "MIT", - "dependencies": { - "tslib": "1.14.1" - } - }, - "node_modules/@walletconnect/time/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, - "node_modules/@walletconnect/types": { - "version": "2.21.1", - "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.21.1.tgz", - "integrity": "sha512-UeefNadqP6IyfwWC1Yi7ux+ljbP2R66PLfDrDm8izmvlPmYlqRerJWJvYO4t0Vvr9wrG4Ko7E0c4M7FaPKT/sQ==", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/events": "1.0.1", - "@walletconnect/heartbeat": "1.2.2", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "events": "3.3.0" - } - }, - "node_modules/@walletconnect/universal-provider": { - "version": "2.21.1", - "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.21.1.tgz", - "integrity": "sha512-Wjx9G8gUHVMnYfxtasC9poGm8QMiPCpXpbbLFT+iPoQskDDly8BwueWnqKs4Mx2SdIAWAwuXeZ5ojk5qQOxJJg==", - "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", - "license": "Apache-2.0", - "dependencies": { - "@walletconnect/events": "1.0.1", - "@walletconnect/jsonrpc-http-connection": "1.0.8", - "@walletconnect/jsonrpc-provider": "1.0.14", - "@walletconnect/jsonrpc-types": "1.0.4", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/logger": "2.1.2", - "@walletconnect/sign-client": "2.21.1", - "@walletconnect/types": "2.21.1", - "@walletconnect/utils": "2.21.1", - "es-toolkit": "1.33.0", - "events": "3.3.0" - } - }, - "node_modules/@walletconnect/utils": { - "version": "2.21.1", - "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.21.1.tgz", - "integrity": "sha512-VPZvTcrNQCkbGOjFRbC24mm/pzbRMUq2DSQoiHlhh0X1U7ZhuIrzVtAoKsrzu6rqjz0EEtGxCr3K1TGRqDG4NA==", - "license": "Apache-2.0", - "dependencies": { - "@noble/ciphers": "1.2.1", - "@noble/curves": "1.8.1", - "@noble/hashes": "1.7.1", - "@walletconnect/jsonrpc-utils": "1.0.8", - "@walletconnect/keyvaluestorage": "1.1.1", - "@walletconnect/relay-api": "1.0.11", - "@walletconnect/relay-auth": "1.1.0", - "@walletconnect/safe-json": "1.0.2", - "@walletconnect/time": "1.0.2", - "@walletconnect/types": "2.21.1", - "@walletconnect/window-getters": "1.0.1", - "@walletconnect/window-metadata": "1.0.1", - "bs58": "6.0.0", - "detect-browser": "5.3.0", - "query-string": "7.1.3", - "uint8arrays": "3.1.0", - "viem": "2.23.2" - } - }, - "node_modules/@walletconnect/utils/node_modules/@noble/ciphers": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.2.1.tgz", - "integrity": "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@walletconnect/utils/node_modules/@noble/curves": { - "version": "1.8.1", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", - "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.7.1" - }, - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@walletconnect/utils/node_modules/@noble/hashes": { - "version": "1.7.1", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", - "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", - "license": "MIT", - "engines": { - "node": "^14.21.3 || >=16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@walletconnect/utils/node_modules/@scure/bip32": { - "version": "1.6.2", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.6.2.tgz", - "integrity": "sha512-t96EPDMbtGgtb7onKKqxRLfE5g05k7uHnHRM2xdE6BP/ZmxaLtPek4J4KfVn/90IQNrU1IOAqMgiDtUdtbe3nw==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.8.1", - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.2" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@walletconnect/utils/node_modules/@scure/bip39": { - "version": "1.5.4", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.5.4.tgz", - "integrity": "sha512-TFM4ni0vKvCfBpohoh+/lY05i9gRbSwXWngAsF4CABQxoaOHijxuaZ2R6cStDQ5CHtHO9aGJTr4ksVJASRRyMA==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.7.1", - "@scure/base": "~1.2.4" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/@walletconnect/utils/node_modules/abitype": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", - "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3 >=3.22.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/@walletconnect/utils/node_modules/isows": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.6.tgz", - "integrity": "sha512-lPHCayd40oW98/I0uvgaHKWCSvkzY27LjWLbtzOm64yQ+G3Q5npjjbdppU65iZXkK1Zt+kH9pfegli0AYfwYYw==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "peerDependencies": { - "ws": "*" - } - }, - "node_modules/@walletconnect/utils/node_modules/ox": { - "version": "0.6.7", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.6.7.tgz", - "integrity": "sha512-17Gk/eFsFRAZ80p5eKqv89a57uXjd3NgIf1CaXojATPBuujVc/fQSVhBeAU9JCRB+k7J50WQAyWTxK19T9GgbA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@adraffy/ens-normalize": "^1.10.1", - "@noble/curves": "^1.6.0", - "@noble/hashes": "^1.5.0", - "@scure/bip32": "^1.5.0", - "@scure/bip39": "^1.4.0", - "abitype": "^1.0.6", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@walletconnect/utils/node_modules/viem": { - "version": "2.23.2", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.23.2.tgz", - "integrity": "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wevm" - } - ], - "license": "MIT", - "dependencies": { - "@noble/curves": "1.8.1", - "@noble/hashes": "1.7.1", - "@scure/bip32": "1.6.2", - "@scure/bip39": "1.5.4", - "abitype": "1.0.8", - "isows": "1.0.6", - "ox": "0.6.7", - "ws": "8.18.0" - }, - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/@walletconnect/window-getters": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@walletconnect/window-getters/-/window-getters-1.0.1.tgz", - "integrity": "sha512-vHp+HqzGxORPAN8gY03qnbTMnhqIwjeRJNOMOAzePRg4xVEEE2WvYsI9G2NMjOknA8hnuYbU3/hwLcKbjhc8+Q==", - "license": "MIT", - "dependencies": { - "tslib": "1.14.1" - } - }, - "node_modules/@walletconnect/window-getters/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, - "node_modules/@walletconnect/window-metadata": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/@walletconnect/window-metadata/-/window-metadata-1.0.1.tgz", - "integrity": "sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA==", - "license": "MIT", - "dependencies": { - "@walletconnect/window-getters": "^1.0.1", - "tslib": "1.14.1" - } - }, - "node_modules/@walletconnect/window-metadata/node_modules/tslib": { - "version": "1.14.1", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", - "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", - "license": "0BSD" - }, - "node_modules/abitype": { - "version": "1.0.6", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.6.tgz", - "integrity": "sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "typescript": ">=5.0.4", - "zod": "^3 >=3.22.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - }, - "zod": { - "optional": true - } - } - }, - "node_modules/accepts": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/accepts/-/accepts-2.0.0.tgz", - "integrity": "sha512-5cvg6CtKwfgdmVqY1WIiXKc3Q1bkRqGLi+2W/6ao+6Y7gu/RCwRuAhGEzh5B4KlszSuTLgZYuqFqo5bImjNKng==", - "license": "MIT", - "dependencies": { - "mime-types": "^3.0.0", - "negotiator": "^1.0.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/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/ansi-regex": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", - "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/ansi-styles": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", - "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "license": "MIT", - "dependencies": { - "color-convert": "^2.0.1" - }, - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/chalk/ansi-styles?sponsor=1" - } - }, - "node_modules/anymatch": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", - "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "license": "ISC", - "dependencies": { - "normalize-path": "^3.0.0", - "picomatch": "^2.0.4" - }, - "engines": { - "node": ">= 8" - } - }, - "node_modules/apg-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/apg-js/-/apg-js-4.4.0.tgz", - "integrity": "sha512-fefmXFknJmtgtNEXfPwZKYkMFX4Fyeyz+fNF6JWp87biGOPslJbCBVU158zvKRZfHBKnJDy8CMM40oLFGkXT8Q==", - "license": "BSD-2-Clause" - }, - "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/async-mutex": { - "version": "0.2.6", - "resolved": "https://registry.npmjs.org/async-mutex/-/async-mutex-0.2.6.tgz", - "integrity": "sha512-Hs4R+4SPgamu6rSGW8C7cV9gaWUKEHykfzCCvIRuaVv636Ju10ZdeUbvb4TBEW0INuq2DHZqXbK4Nd3yG4RaRw==", - "license": "MIT", - "dependencies": { - "tslib": "^2.0.0" - } - }, - "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/atomic-sleep": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", - "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", - "license": "MIT", - "engines": { - "node": ">=8.0.0" - } - }, - "node_modules/available-typed-arrays": { - "version": "1.0.7", - "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", - "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "license": "MIT", - "dependencies": { - "possible-typed-array-names": "^1.0.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/axios": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.16.0.tgz", - "integrity": "sha512-6hp5CwvTPlN2A31g5dxnwAX0orzM7pmCRDLnZSX772mv8WDqICwFjowHuPs04Mc8deIld1+ejhtaMn5vp6b+1w==", - "license": "MIT", - "dependencies": { - "follow-redirects": "^1.16.0", - "form-data": "^4.0.5", - "proxy-from-env": "^2.1.0" - } - }, - "node_modules/axios-retry": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/axios-retry/-/axios-retry-4.5.0.tgz", - "integrity": "sha512-aR99oXhpEDGo0UuAlYcn2iGRds30k366Zfa05XWScR9QaQD4JYiP3/1Qt1u7YlefUOK+cn0CcwoL1oefavQUlQ==", - "license": "Apache-2.0", - "dependencies": { - "is-retry-allowed": "^2.2.0" - }, - "peerDependencies": { - "axios": "0.x || 1.x" - } - }, - "node_modules/balanced-match": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-4.0.4.tgz", - "integrity": "sha512-BLrgEcRTwX2o6gGxGOCNyMvGSp35YofuYzw9h1IMTRmKqttAZZVU67bdb9Pr2vUHA8+j3i2tJfjO6C6+4myGTA==", - "dev": true, - "license": "MIT", - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/base-x": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", - "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", - "license": "MIT" - }, - "node_modules/base64-js": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/big.js": { - "version": "6.2.2", - "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz", - "integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==", - "license": "MIT", - "engines": { - "node": "*" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/bigjs" - } - }, - "node_modules/binary-extensions": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", - "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/bn.js": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-5.2.3.tgz", - "integrity": "sha512-EAcmnPkxpntVL+DS7bO1zhcZNvCkxqtkd0ZY53h06GNQ3DEkkGZ/gKgmDv6DdZQGj9BgfSPKtJJ7Dp1GPP8f7w==", - "license": "MIT" - }, - "node_modules/body-parser": { - "version": "2.2.2", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-2.2.2.tgz", - "integrity": "sha512-oP5VkATKlNwcgvxi0vM0p/D3n2C3EReYVX+DNYs5TjZFn/oQt2j+4sVJtSMr18pdRr8wjTcBl6LoV+FUwzPmNA==", - "license": "MIT", - "dependencies": { - "bytes": "^3.1.2", - "content-type": "^1.0.5", - "debug": "^4.4.3", - "http-errors": "^2.0.0", - "iconv-lite": "^0.7.0", - "on-finished": "^2.4.1", - "qs": "^6.14.1", - "raw-body": "^3.0.1", - "type-is": "^2.0.1" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/bowser": { - "version": "2.14.1", - "resolved": "https://registry.npmjs.org/bowser/-/bowser-2.14.1.tgz", - "integrity": "sha512-tzPjzCxygAKWFOJP011oxFHs57HzIhOEracIgAePE4pqB3LikALKnSzUyU4MGs9/iCEUuHlAJTjTc5M+u7YEGg==", - "license": "MIT" - }, - "node_modules/brace-expansion": { - "version": "5.0.6", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-5.0.6.tgz", - "integrity": "sha512-kLpxurY4Z4r9sgMsyG0Z9uzsBlgiU/EFKhj/h91/8yHu0edo7XuixOIH3VcJ8kkxs6/jPzoI6U9Vj3WqbMQ94g==", - "dev": true, - "license": "MIT", - "dependencies": { - "balanced-match": "^4.0.2" - }, - "engines": { - "node": "18 || 20 || >=22" - } - }, - "node_modules/braces": { - "version": "3.0.3", - "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", - "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", - "dev": true, - "license": "MIT", - "dependencies": { - "fill-range": "^7.1.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/bs58": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", - "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", - "license": "MIT", - "dependencies": { - "base-x": "^5.0.0" - } - }, - "node_modules/buffer": { - "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT", - "dependencies": { - "base64-js": "^1.3.1", - "ieee754": "^1.2.1" - } - }, - "node_modules/buffer-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/bufferutil": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", - "integrity": "sha512-ZMANVnAixE6AWWnPzlW2KpUrxhm9woycYvPOo67jWHyFowASTEd9s+QN1EIMsSDtwhIxN4sWE1jotpuDUIgyIw==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "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": { - "version": "1.0.9", - "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", - "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "get-intrinsic": "^1.3.0", - "set-function-length": "^1.2.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/call-bind-apply-helpers": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", - "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/call-bound": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", - "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "get-intrinsic": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/camelcase": { - "version": "5.3.1", - "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", - "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/cbw-sdk": { - "name": "@coinbase/wallet-sdk", - "version": "3.9.3", - "resolved": "https://registry.npmjs.org/@coinbase/wallet-sdk/-/wallet-sdk-3.9.3.tgz", - "integrity": "sha512-N/A2DRIf0Y3PHc1XAMvbBUu4zisna6qAdqABMZwBMNEfWrXpAwx16pZGkYCLGE+Rvv1edbcB2LYDRnACNcmCiw==", - "license": "Apache-2.0", - "dependencies": { - "bn.js": "^5.2.1", - "buffer": "^6.0.3", - "clsx": "^1.2.1", - "eth-block-tracker": "^7.1.0", - "eth-json-rpc-filters": "^6.0.0", - "eventemitter3": "^5.0.1", - "keccak": "^3.0.3", - "preact": "^10.16.0", - "sha.js": "^2.4.11" - } - }, - "node_modules/chalk": { - "version": "5.6.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", - "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", - "license": "MIT", - "engines": { - "node": "^12.17.0 || ^14.13 || >=16.0.0" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/charenc": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", - "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/chokidar": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", - "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", - "dev": true, - "license": "MIT", - "dependencies": { - "anymatch": "~3.1.2", - "braces": "~3.0.2", - "glob-parent": "~5.1.2", - "is-binary-path": "~2.1.0", - "is-glob": "~4.0.1", - "normalize-path": "~3.0.0", - "readdirp": "~3.6.0" - }, - "engines": { - "node": ">= 8.10.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - }, - "optionalDependencies": { - "fsevents": "~2.3.2" - } - }, - "node_modules/cliui": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", - "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", - "license": "ISC", - "dependencies": { - "string-width": "^4.2.0", - "strip-ansi": "^6.0.0", - "wrap-ansi": "^6.2.0" - } - }, - "node_modules/clsx": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/clsx/-/clsx-1.2.1.tgz", - "integrity": "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/color-convert": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", - "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "license": "MIT", - "dependencies": { - "color-name": "~1.1.4" - }, - "engines": { - "node": ">=7.0.0" - } - }, - "node_modules/color-name": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "license": "MIT" - }, - "node_modules/combined-stream": { - "version": "1.0.8", - "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", - "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", - "license": "MIT", - "dependencies": { - "delayed-stream": "~1.0.0" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/commander": { - "version": "14.0.2", - "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.2.tgz", - "integrity": "sha512-TywoWNNRbhoD0BXs1P3ZEScW8W5iKrnbithIl0YH+uCmBd0QpPOA8yc82DS3BIE5Ma6FnBVUsJ7wVUDz4dvOWQ==", - "license": "MIT", - "engines": { - "node": ">=20" - } - }, - "node_modules/content-disposition": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", - "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/content-type": { - "version": "1.0.5", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", - "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/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-es": { - "version": "1.2.3", - "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.3.tgz", - "integrity": "sha512-lXVyvUvrNXblMqzIRrxHb57UUVmqsSWlxqt3XIjCkUP0wDAf6uicO6KMbEgYrMNtEvWgWHwe42CKxPu9MYAnWw==", - "license": "MIT" - }, - "node_modules/cookie-signature": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.2.2.tgz", - "integrity": "sha512-D76uU73ulSXrD1UXF4KE2TMxVVwhsnCgfAyTg9k8P6KGZjlXKrOLe4dJQKI3Bxi5wjesZoFXJWElNWBjPZMbhg==", - "license": "MIT", - "engines": { - "node": ">=6.6.0" - } - }, - "node_modules/core-util-is": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", - "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", - "license": "MIT" - }, - "node_modules/crc-32": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/crc-32/-/crc-32-1.2.2.tgz", - "integrity": "sha512-ROmzCKrTnOwybPcJApAA6WBWij23HVfGVNKqqrZpuyZOHqK2CwHSvpGuyt/UNNvaIjEd8X5IFGp4Mh+Ie1IHJQ==", - "license": "Apache-2.0", - "bin": { - "crc32": "bin/crc32.njs" - }, - "engines": { - "node": ">=0.8" - } - }, - "node_modules/cross-fetch": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", - "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", - "license": "MIT", - "dependencies": { - "node-fetch": "^2.7.0" - } - }, - "node_modules/crossws": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", - "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", - "license": "MIT", - "dependencies": { - "uncrypto": "^0.1.3" - } - }, - "node_modules/crypt": { - "version": "0.0.2", - "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", - "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", - "license": "BSD-3-Clause", - "engines": { - "node": "*" - } - }, - "node_modules/date-fns": { - "version": "2.30.0", - "resolved": "https://registry.npmjs.org/date-fns/-/date-fns-2.30.0.tgz", - "integrity": "sha512-fnULvOpxnC5/Vg3NCiWelDsLiUc9bRwAPs/+LfTLNvetFCtCTN+yQz15C/fs4AwX1R9K5GLtLfn8QW+dWisaAw==", - "license": "MIT", - "dependencies": { - "@babel/runtime": "^7.21.0" - }, - "engines": { - "node": ">=0.11" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/date-fns" - } - }, - "node_modules/dayjs": { - "version": "1.11.13", - "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", - "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", - "license": "MIT" - }, - "node_modules/debug": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", - "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", - "license": "MIT", - "dependencies": { - "ms": "^2.1.3" - }, - "engines": { - "node": ">=6.0" - }, - "peerDependenciesMeta": { - "supports-color": { - "optional": true - } - } - }, - "node_modules/decamelize": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", - "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/decode-uri-component": { - "version": "0.2.2", - "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", - "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", - "license": "MIT", - "engines": { - "node": ">=0.10" - } - }, - "node_modules/define-data-property": { - "version": "1.1.4", - "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", - "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0", - "es-errors": "^1.3.0", - "gopd": "^1.0.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/defu": { - "version": "6.1.7", - "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.7.tgz", - "integrity": "sha512-7z22QmUWiQ/2d0KkdYmANbRUVABpZ9SNYyH5vx6PZ+nE5bcC0l7uFvEfHlyld/HcGBFTL536ClDt3DEcSlEJAQ==", - "license": "MIT" - }, - "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/derive-valtio": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/derive-valtio/-/derive-valtio-0.1.0.tgz", - "integrity": "sha512-OCg2UsLbXK7GmmpzMXhYkdO64vhJ1ROUUGaTFyHjVwEdMEcTTRj7W1TxLbSBxdY8QLBPCcp66MTyaSy0RpO17A==", - "license": "MIT", - "peerDependencies": { - "valtio": "*" - } - }, - "node_modules/destr": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", - "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", - "license": "MIT" - }, - "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/detect-browser": { - "version": "5.3.0", - "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", - "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==", - "license": "MIT" - }, - "node_modules/detect-libc": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.2.tgz", - "integrity": "sha512-UX6sGumvvqSaXgdKGUsgZWqcUyIXZ/vZTrlRT/iobiKhGL0zL4d3osHj3uqllWJK+i+sixDS/3COVEOFbupFyw==", - "license": "Apache-2.0", - "engines": { - "node": ">=8" - } - }, - "node_modules/dijkstrajs": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", - "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", - "license": "MIT" - }, - "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/duplexify": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", - "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.4.1", - "inherits": "^2.0.3", - "readable-stream": "^3.1.1", - "stream-shift": "^1.0.2" - } - }, - "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/eciesjs": { - "version": "0.4.18", - "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.18.tgz", - "integrity": "sha512-wG99Zcfcys9fZux7Cft8BAX/YrOJLJSZ3jyYPfhZHqN2E+Ffx+QXBDsv3gubEgPtV6dTzJMSQUwk1H98/t/0wQ==", - "license": "MIT", - "dependencies": { - "@ecies/ciphers": "^0.2.5", - "@noble/ciphers": "^1.3.0", - "@noble/curves": "^1.9.7", - "@noble/hashes": "^1.8.0" - }, - "engines": { - "bun": ">=1", - "deno": ">=2", - "node": ">=16" - } - }, - "node_modules/eciesjs/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/eciesjs/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/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/emoji-regex": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", - "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", - "license": "MIT" - }, - "node_modules/encode-utf8": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", - "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==", - "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/end-of-stream": { - "version": "1.4.5", - "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", - "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", - "license": "MIT", - "dependencies": { - "once": "^1.4.0" - } - }, - "node_modules/engine.io-client": { - "version": "6.6.5", - "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.5.tgz", - "integrity": "sha512-QCwxUDULPlXv8F6tqMMKx5dNkTe6OaBYRMPYeXKBlyOoKvAmE0ac6pW7fFhSscJ/5SI7666/U/B+MElbsrJlIg==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-parser": "~5.2.1", - "ws": "~8.20.1", - "xmlhttprequest-ssl": "~2.1.1" - } - }, - "node_modules/engine.io-parser": { - "version": "5.2.3", - "resolved": "https://registry.npmjs.org/engine.io-parser/-/engine.io-parser-5.2.3.tgz", - "integrity": "sha512-HqD3yTBfnBxIrbnM1DoD6Pcq8NECnh8d4As1Qgh0z5Gg3jRRIqijury0CL3ghu/edArpUYiYqQiDUQBIs4np3Q==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" - } - }, - "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.2", - "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.2.tgz", - "integrity": "sha512-HWcBoN6NileqtSydK2FqHbS/LoDd2pqrnQHLyJzBj4kOp/ky2MWMN694xOfkK8/SnUsW2DH7EfyVlydKCsm1Zw==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-set-tostringtag": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", - "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "get-intrinsic": "^1.2.6", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/es-toolkit": { - "version": "1.33.0", - "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.33.0.tgz", - "integrity": "sha512-X13Q/ZSc+vsO1q600bvNK4bxgXMkHcf//RxCmYDaRY5DAcT+eoXjY5hoAPGMdRnWQjvyLEcyauG3b6hz76LNqg==", - "license": "MIT", - "workspaces": [ - "docs", - "benchmarks" - ] - }, - "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/eth-block-tracker": { - "version": "7.1.0", - "resolved": "https://registry.npmjs.org/eth-block-tracker/-/eth-block-tracker-7.1.0.tgz", - "integrity": "sha512-8YdplnuE1IK4xfqpf4iU7oBxnOYAc35934o083G8ao+8WM8QQtt/mVlAY6yIAdY1eMeLqg4Z//PZjJGmWGPMRg==", - "license": "MIT", - "dependencies": { - "@metamask/eth-json-rpc-provider": "^1.0.0", - "@metamask/safe-event-emitter": "^3.0.0", - "@metamask/utils": "^5.0.1", - "json-rpc-random-id": "^1.0.1", - "pify": "^3.0.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/eth-block-tracker/node_modules/@metamask/utils": { - "version": "5.0.2", - "resolved": "https://registry.npmjs.org/@metamask/utils/-/utils-5.0.2.tgz", - "integrity": "sha512-yfmE79bRQtnMzarnKfX7AEJBwFTxvTyw3nBQlu/5rmGXrjAeAMltoGxO62TFurxrQAFMNa/fEjIHNvungZp0+g==", - "license": "ISC", - "dependencies": { - "@ethereumjs/tx": "^4.1.2", - "@types/debug": "^4.1.7", - "debug": "^4.3.4", - "semver": "^7.3.8", - "superstruct": "^1.0.3" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/eth-json-rpc-filters": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/eth-json-rpc-filters/-/eth-json-rpc-filters-6.0.1.tgz", - "integrity": "sha512-ITJTvqoCw6OVMLs7pI8f4gG92n/St6x80ACtHodeS+IXmO0w+t1T5OOzfSt7KLSMLRkVUoexV7tztLgDxg+iig==", - "license": "ISC", - "dependencies": { - "@metamask/safe-event-emitter": "^3.0.0", - "async-mutex": "^0.2.6", - "eth-query": "^2.1.2", - "json-rpc-engine": "^6.1.0", - "pify": "^5.0.0" - }, - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/eth-json-rpc-filters/node_modules/pify": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-5.0.0.tgz", - "integrity": "sha512-eW/gHNMlxdSP6dmG6uJip6FXN0EQBwm2clYYd8Wul42Cwu/DK8HEftzsapcNdYe2MfLiIwZqsDk2RDEsTE79hA==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/eth-query": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/eth-query/-/eth-query-2.1.2.tgz", - "integrity": "sha512-srES0ZcvwkR/wd5OQBRA1bIJMww1skfGS0s8wlwK3/oNP4+wnds60krvu5R1QbpRQjMmpG5OMIWro5s7gvDPsA==", - "license": "ISC", - "dependencies": { - "json-rpc-random-id": "^1.0.0", - "xtend": "^4.0.1" - } - }, - "node_modules/eth-rpc-errors": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/eth-rpc-errors/-/eth-rpc-errors-4.0.3.tgz", - "integrity": "sha512-Z3ymjopaoft7JDoxZcEb3pwdGh7yiYMhOwm2doUt6ASXlMavpNlK6Cre0+IMl2VSGyEU9rkiperQhp5iRxn5Pg==", - "license": "MIT", - "dependencies": { - "fast-safe-stringify": "^2.0.6" - } - }, - "node_modules/ethereum-cryptography": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", - "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", - "license": "MIT", - "dependencies": { - "@noble/curves": "1.4.2", - "@noble/hashes": "1.4.0", - "@scure/bip32": "1.4.0", - "@scure/bip39": "1.3.0" - } - }, - "node_modules/ethereum-cryptography/node_modules/@noble/curves": { - "version": "1.4.2", - "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", - "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "1.4.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethereum-cryptography/node_modules/@noble/hashes": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", - "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", - "license": "MIT", - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethereum-cryptography/node_modules/@scure/base": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", - "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", - "license": "MIT", - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethereum-cryptography/node_modules/@scure/bip32": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.4.0.tgz", - "integrity": "sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg==", - "license": "MIT", - "dependencies": { - "@noble/curves": "~1.4.0", - "@noble/hashes": "~1.4.0", - "@scure/base": "~1.1.6" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/ethereum-cryptography/node_modules/@scure/bip39": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.3.0.tgz", - "integrity": "sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ==", - "license": "MIT", - "dependencies": { - "@noble/hashes": "~1.4.0", - "@scure/base": "~1.1.6" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "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/eventemitter2": { - "version": "6.4.9", - "resolved": "https://registry.npmjs.org/eventemitter2/-/eventemitter2-6.4.9.tgz", - "integrity": "sha512-JEPTiaOt9f04oa6NOkc4aH+nVp5I3wEjpHbIPqfgCdD5v5bUzy7xQqwcVO2aDQgOWhI28da57HksMrzK9HlRxg==", - "license": "MIT" - }, - "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/events": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", - "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", - "license": "MIT", - "engines": { - "node": ">=0.8.x" - } - }, - "node_modules/express": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", - "integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==", - "license": "MIT", - "dependencies": { - "accepts": "^2.0.0", - "body-parser": "^2.2.1", - "content-disposition": "^1.0.0", - "content-type": "^1.0.5", - "cookie": "^0.7.1", - "cookie-signature": "^1.2.1", - "debug": "^4.4.0", - "depd": "^2.0.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "finalhandler": "^2.1.0", - "fresh": "^2.0.0", - "http-errors": "^2.0.0", - "merge-descriptors": "^2.0.0", - "mime-types": "^3.0.0", - "on-finished": "^2.4.1", - "once": "^1.4.0", - "parseurl": "^1.3.3", - "proxy-addr": "^2.0.7", - "qs": "^6.14.0", - "range-parser": "^1.2.1", - "router": "^2.2.0", - "send": "^1.1.0", - "serve-static": "^2.2.0", - "statuses": "^2.0.1", - "type-is": "^2.0.1", - "vary": "^1.1.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/express-rate-limit": { - "version": "8.5.2", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.5.2.tgz", - "integrity": "sha512-5Kb34ipNX694DH48vN9irak1Qx30nb0PLYHXfJgw4YEjiC3ZEmZJhwOp+VfiCYwFzvFTdB9QkArYS5kXa2cx2A==", - "license": "MIT", - "dependencies": { - "ip-address": "^10.2.0" - }, - "engines": { - "node": ">= 16" - }, - "funding": { - "url": "https://github.com/sponsors/express-rate-limit" - }, - "peerDependencies": { - "express": ">= 4.11" - } - }, - "node_modules/extension-port-stream": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/extension-port-stream/-/extension-port-stream-3.0.0.tgz", - "integrity": "sha512-an2S5quJMiy5bnZKEf6AkfH/7r8CzHvhchU40gxN+OM6HPhe7Z9T1FUychcf2M9PpPOO0Hf7BAEfJkw2TDIBDw==", - "license": "ISC", - "dependencies": { - "readable-stream": "^3.6.2 || ^4.4.2", - "webextension-polyfill": ">=0.10.0 <1.0" - }, - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/fast-deep-equal": { - "version": "3.1.3", - "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", - "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", - "license": "MIT" - }, - "node_modules/fast-redact": { - "version": "3.5.0", - "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", - "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/fast-safe-stringify": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", - "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", - "license": "MIT" - }, - "node_modules/fill-range": { - "version": "7.1.1", - "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", - "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", - "dev": true, - "license": "MIT", - "dependencies": { - "to-regex-range": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/filter-obj": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", - "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/finalhandler": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-2.1.1.tgz", - "integrity": "sha512-S8KoZgRZN+a5rNwqTxlZZePjT/4cnm0ROV70LedRHZ0p8u9fRID0hJUZQpkKLzro8LfmC8sx23bY6tVNxv8pQA==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "on-finished": "^2.4.1", - "parseurl": "^1.3.3", - "statuses": "^2.0.1" - }, - "engines": { - "node": ">= 18.0.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/find-up": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", - "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", - "license": "MIT", - "dependencies": { - "locate-path": "^5.0.0", - "path-exists": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/follow-redirects": { - "version": "1.16.0", - "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.16.0.tgz", - "integrity": "sha512-y5rN/uOsadFT/JfYwhxRS5R7Qce+g3zG97+JrtFZlC9klX/W5hD7iiLzScI4nZqUS7DNUdhPgw4xI8W2LuXlUw==", - "funding": [ - { - "type": "individual", - "url": "https://github.com/sponsors/RubenVerborgh" - } - ], - "license": "MIT", - "engines": { - "node": ">=4.0" - }, - "peerDependenciesMeta": { - "debug": { - "optional": true - } - } - }, - "node_modules/for-each": { - "version": "0.3.5", - "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", - "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "license": "MIT", - "dependencies": { - "is-callable": "^1.2.7" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/form-data": { - "version": "4.0.5", - "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", - "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", - "license": "MIT", - "dependencies": { - "asynckit": "^0.4.0", - "combined-stream": "^1.0.8", - "es-set-tostringtag": "^2.1.0", - "hasown": "^2.0.2", - "mime-types": "^2.1.12" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/form-data/node_modules/mime-db": { - "version": "1.52.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", - "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/form-data/node_modules/mime-types": { - "version": "2.1.35", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", - "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", - "license": "MIT", - "dependencies": { - "mime-db": "1.52.0" - }, - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/forwarded": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", - "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/fresh": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/fresh/-/fresh-2.0.0.tgz", - "integrity": "sha512-Rx/WycZ60HOaqLKAi6cHRKKI7zxWbJ31MhntmtwMoaTeF7XFH9hhBp8vITaMidfljRQ6eYWCKkaTK+ykVJHP2A==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/fsevents": { - "version": "2.3.3", - "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", - "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", - "dev": true, - "hasInstallScript": true, - "license": "MIT", - "optional": true, - "os": [ - "darwin" - ], - "engines": { - "node": "^8.16.0 || ^10.6.0 || >=11.0.0" - } - }, - "node_modules/function-bind": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", - "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/generator-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", - "integrity": "sha512-SFdFmIJi+ybC0vjlHN0ZGVGHc3lgE0DxPAT0djjVg+kjOnSqclqmj0KQ7ykTOLP6YxoqOvuAODGdcHJn+43q3g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/get-caller-file": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", - "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", - "license": "ISC", - "engines": { - "node": "6.* || 8.* || >= 10.*" - } - }, - "node_modules/get-intrinsic": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", - "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", - "license": "MIT", - "dependencies": { - "call-bind-apply-helpers": "^1.0.2", - "es-define-property": "^1.0.1", - "es-errors": "^1.3.0", - "es-object-atoms": "^1.1.1", - "function-bind": "^1.1.2", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-symbols": "^1.1.0", - "hasown": "^2.0.2", - "math-intrinsics": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/get-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/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, - "license": "ISC", - "dependencies": { - "is-glob": "^4.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/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/h3": { - "version": "1.15.11", - "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.11.tgz", - "integrity": "sha512-L3THSe2MPeBwgIZVSH5zLdBBU90TOxarvhK9d04IDY2AmVS8j2Jz2LIWtwsGOU3lu2I5jCN7FNvVfY2+XyF+mg==", - "license": "MIT", - "dependencies": { - "cookie-es": "^1.2.3", - "crossws": "^0.3.5", - "defu": "^6.1.6", - "destr": "^2.0.5", - "iron-webcrypto": "^1.2.1", - "node-mock-http": "^1.0.4", - "radix3": "^1.1.2", - "ufo": "^1.6.3", - "uncrypto": "^0.1.3" - } - }, - "node_modules/has-flag": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", - "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/has-property-descriptors": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", - "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "license": "MIT", - "dependencies": { - "es-define-property": "^1.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/has-symbols": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", - "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "license": "MIT", - "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.3", - "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.3.tgz", - "integrity": "sha512-ej4AhfhfL2Q2zpMmLo7U1Uv9+PyhIZpgQLGT1F9miIGmiCJIoCgSmczFdrc97mWT4kVY72KA+WnnhJ5pghSvSg==", - "license": "MIT", - "dependencies": { - "function-bind": "^1.1.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/helmet": { - "version": "8.2.0", - "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.2.0.tgz", - "integrity": "sha512-DRgTIUgnWcJ62KyarxxziuqYxKGnR6Rgg19BlbucN/dpmJbl1XOit6qvoOX0ZT+HhWe5OUVhU/a1zpGyc1xA0Q==", - "license": "MIT", - "engines": { - "node": ">=18.0.0" - }, - "funding": { - "url": "https://github.com/sponsors/EvanHahn" - } - }, - "node_modules/hono": { - "version": "4.12.23", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.23.tgz", - "integrity": "sha512-eIaZ9qDgu7XV0pxOCrg7/WhnQ6Ivm22UcxhXx/A3dcbqbbYgBEkc6e/J/s7j2tS96zoB0S9VBdLwQNCWwUo4LA==", - "license": "MIT", - "engines": { - "node": ">=16.9.0" - } - }, - "node_modules/http-errors": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", - "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", - "license": "MIT", - "dependencies": { - "depd": "~2.0.0", - "inherits": "~2.0.4", - "setprototypeof": "~1.2.0", - "statuses": "~2.0.2", - "toidentifier": "~1.0.1" - }, - "engines": { - "node": ">= 0.8" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/iconv-lite": { - "version": "0.7.2", - "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", - "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", - "license": "MIT", - "dependencies": { - "safer-buffer": ">= 2.1.2 < 3.0.0" - }, - "engines": { - "node": ">=0.10.0" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/idb-keyval": { - "version": "6.2.1", - "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.1.tgz", - "integrity": "sha512-8Sb3veuYCyrZL+VBt9LJfZjLUPWVvqn8tG28VqYNFCo43KHcKuq+b4EiXGeuaLAQWL2YmyDgMp2aSpH9JHsEQg==", - "license": "Apache-2.0" - }, - "node_modules/ieee754": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "BSD-3-Clause" - }, - "node_modules/ignore-by-default": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", - "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", - "dev": true, - "license": "ISC" - }, - "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/ip-address": { - "version": "10.2.0", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.2.0.tgz", - "integrity": "sha512-/+S6j4E9AHvW9SWMSEY9Xfy66O5PWvVEJ08O0y5JGyEKQpojb0K0GKpz/v5HJ/G0vi3D2sjGK78119oXZeE0qA==", - "license": "MIT", - "engines": { - "node": ">= 12" - } - }, - "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/iron-webcrypto": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", - "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/brc-dd" - } - }, - "node_modules/is-arguments": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", - "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-binary-path": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", - "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", - "dev": true, - "license": "MIT", - "dependencies": { - "binary-extensions": "^2.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/is-buffer": { - "version": "1.1.6", - "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-1.1.6.tgz", - "integrity": "sha512-NcdALwpXkTm5Zvvbk7owOUSvVvBKDgKP5/ewfXEznmQFfs4ZRmanOeKBTjRVjka3QFoN6XJ+9F3USqfHqTaU5w==", - "license": "MIT" - }, - "node_modules/is-callable": { - "version": "1.2.7", - "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", - "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-extglob": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", - "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-fullwidth-code-point": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", - "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/is-generator-function": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/is-generator-function/-/is-generator-function-1.1.2.tgz", - "integrity": "sha512-upqt1SkGkODW9tsGNG5mtXTXtECizwtS2kA161M+gJPc1xdb/Ax629af6YrTwcOeQHbewrPNlE5Dx7kzvXTizA==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.4", - "generator-function": "^2.0.0", - "get-proto": "^1.0.1", - "has-tostringtag": "^1.0.2", - "safe-regex-test": "^1.1.0" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-glob": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", - "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-extglob": "^2.1.1" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/is-number": { - "version": "7.0.0", - "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", - "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", - "dev": true, - "license": "MIT", - "engines": { - "node": ">=0.12.0" - } - }, - "node_modules/is-promise": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/is-promise/-/is-promise-4.0.0.tgz", - "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", - "license": "MIT" - }, - "node_modules/is-regex": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", - "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2", - "hasown": "^2.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/is-retry-allowed": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/is-retry-allowed/-/is-retry-allowed-2.2.0.tgz", - "integrity": "sha512-XVm7LOeLpTW4jV19QSH38vkswxoLud8sQ57YwJVTPWdiaI9I8keEhGFpBlslyVsgdQy4Opg8QOLb8YRgsyZiQg==", - "license": "MIT", - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-stream": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.1.tgz", - "integrity": "sha512-hFoiJiTl63nn+kstHGBtewWSKnQLpyb155KHheA1l39uvtO9nWIop1p3udqPcUd/xbF1VLMO4n7OI6p7RbngDg==", - "license": "MIT", - "engines": { - "node": ">=8" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/is-typed-array": { - "version": "1.1.15", - "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", - "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "license": "MIT", - "dependencies": { - "which-typed-array": "^1.1.16" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/isarray": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", - "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "license": "MIT" - }, - "node_modules/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/jose": { - "version": "6.2.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.3.tgz", - "integrity": "sha512-YYVDInQKFJfR/xa3ojUTl8c2KoTwiL1R5Wg9YCydwH0x0B9grbzlg5HC7mMjCtUJjbQ/YnGEZIhI5tCgfTb4Hw==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/panva" - } - }, - "node_modules/js-base64": { - "version": "3.7.8", - "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-3.7.8.tgz", - "integrity": "sha512-hNngCeKxIUQiEUN3GPJOkz4wF/YvdUdbNL9hsBcMQTkKzboD7T/q3OYOuuPZLUE6dBxSGpwhk5mwuDud7JVAow==", - "license": "BSD-3-Clause" - }, - "node_modules/js-tokens": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", - "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", - "license": "MIT", - "peer": true - }, - "node_modules/json-rpc-engine": { - "version": "6.1.0", - "resolved": "https://registry.npmjs.org/json-rpc-engine/-/json-rpc-engine-6.1.0.tgz", - "integrity": "sha512-NEdLrtrq1jUZyfjkr9OCz9EzCNhnRyWtt1PAnvnhwy6e8XETS0Dtc+ZNCO2gvuAoKsIn2+vCSowXTYE4CkgnAQ==", - "license": "ISC", - "dependencies": { - "@metamask/safe-event-emitter": "^2.0.0", - "eth-rpc-errors": "^4.0.2" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/json-rpc-engine/node_modules/@metamask/safe-event-emitter": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@metamask/safe-event-emitter/-/safe-event-emitter-2.0.0.tgz", - "integrity": "sha512-/kSXhY692qiV1MXu6EeOZvg5nECLclxNXcKCxJ3cXQgYuRymRHpdx/t7JXfsK+JLjwA1e1c1/SBrlQYpusC29Q==", - "license": "ISC" - }, - "node_modules/json-rpc-random-id": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/json-rpc-random-id/-/json-rpc-random-id-1.0.1.tgz", - "integrity": "sha512-RJ9YYNCkhVDBuP4zN5BBtYAzEl03yq/jIIsyif0JY9qyJuQQZNeDK7anAPKKlyEtLSj2s8h6hNh2F8zO5q7ScA==", - "license": "ISC" - }, - "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/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/keccak": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/keccak/-/keccak-3.0.4.tgz", - "integrity": "sha512-3vKuW0jV8J3XNTzvfyicFR5qvxrSAGl7KIhvgOu5cmWwM7tZRj3fMbj/pfIf4be7aznbc+prBWGjywox/g2Y6Q==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-addon-api": "^2.0.0", - "node-gyp-build": "^4.2.0", - "readable-stream": "^3.6.0" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/keyvaluestorage-interface": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz", - "integrity": "sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==", - "license": "MIT" - }, - "node_modules/libsql": { - "version": "0.5.29", - "resolved": "https://registry.npmjs.org/libsql/-/libsql-0.5.29.tgz", - "integrity": "sha512-8lMP8iMgiBzzoNbAPQ59qdVcj6UaE/Vnm+fiwX4doX4Narook0a4GPKWBEv+CR8a1OwbfkgL18uBfBjWdF0Fzg==", - "cpu": [ - "x64", - "arm64", - "wasm32", - "arm" - ], - "license": "MIT", - "os": [ - "darwin", - "linux", - "win32" - ], - "dependencies": { - "@neon-rs/load": "^0.0.4", - "detect-libc": "2.0.2" - }, - "optionalDependencies": { - "@libsql/darwin-arm64": "0.5.29", - "@libsql/darwin-x64": "0.5.29", - "@libsql/linux-arm-gnueabihf": "0.5.29", - "@libsql/linux-arm-musleabihf": "0.5.29", - "@libsql/linux-arm64-gnu": "0.5.29", - "@libsql/linux-arm64-musl": "0.5.29", - "@libsql/linux-x64-gnu": "0.5.29", - "@libsql/linux-x64-musl": "0.5.29", - "@libsql/win32-x64-msvc": "0.5.29" - } - }, - "node_modules/lit": { - "version": "3.3.0", - "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.0.tgz", - "integrity": "sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw==", - "license": "BSD-3-Clause", - "dependencies": { - "@lit/reactive-element": "^2.1.0", - "lit-element": "^4.2.0", - "lit-html": "^3.3.0" - } - }, - "node_modules/lit-element": { - "version": "4.2.2", - "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.2.tgz", - "integrity": "sha512-aFKhNToWxoyhkNDmWZwEva2SlQia+jfG0fjIWV//YeTaWrVnOxD89dPKfigCUspXFmjzOEUQpOkejH5Ly6sG0w==", - "license": "BSD-3-Clause", - "dependencies": { - "@lit-labs/ssr-dom-shim": "^1.5.0", - "@lit/reactive-element": "^2.1.0", - "lit-html": "^3.3.0" - } - }, - "node_modules/lit-html": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.3.tgz", - "integrity": "sha512-el8M6jK2o3RXBnrSHX3ZKrsN8zEV63pSExTO1wYJz7QndGYZ8353e2a5PPX+qHe2aGayfnchQmkAojaWAREOIA==", - "license": "BSD-3-Clause", - "dependencies": { - "@types/trusted-types": "^2.0.2" - } - }, - "node_modules/locate-path": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", - "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", - "license": "MIT", - "dependencies": { - "p-locate": "^4.1.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/lodash": { - "version": "4.18.1", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", - "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", - "license": "MIT" - }, - "node_modules/lodash.includes": { - "version": "4.3.0", - "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", - "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", - "license": "MIT" - }, - "node_modules/lodash.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/loose-envify": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", - "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==", - "license": "MIT", - "peer": true, - "dependencies": { - "js-tokens": "^3.0.0 || ^4.0.0" - }, - "bin": { - "loose-envify": "cli.js" - } - }, - "node_modules/lru-cache": { - "version": "11.5.0", - "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.0.tgz", - "integrity": "sha512-5YgH9UJd7wVb9hIouI2adWpgqrrICkt070Dnj8EUY1+B4B2P9eRLPAkAAo6NICA7CEhOIeBHl46u9zSNpNu7zA==", - "license": "BlueOak-1.0.0", - "engines": { - "node": "20 || >=22" - } - }, - "node_modules/math-intrinsics": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", - "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/md5": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/md5/-/md5-2.3.0.tgz", - "integrity": "sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==", - "license": "BSD-3-Clause", - "dependencies": { - "charenc": "0.0.2", - "crypt": "0.0.2", - "is-buffer": "~1.1.6" - } - }, - "node_modules/media-typer": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", - "integrity": "sha512-aisnrDP4GNe06UcKFnV5bfMNPBUw4jsLGaWwWfnH3v02GnBuXX2MCVn5RbrWo0j3pczUilYblq7fQ7Nw2t5XKw==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/merge-descriptors": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-2.0.0.tgz", - "integrity": "sha512-Snk314V5ayFLhp3fkUREub6WtjBfPdCPY1Ln8/8munuLuiYhsABgBVWsozAG+MWMbVEvcdcpbi9R7ww22l9Q3g==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/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/micro-ftch": { - "version": "0.3.1", - "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", - "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", - "license": "MIT" - }, - "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.54.0", - "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", - "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/mime-types": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-3.0.2.tgz", - "integrity": "sha512-Lbgzdk0h4juoQ9fCKXW4by0UJqj+nOOrI9MJ1sSj4nI8aI2eo1qmvQEie4VD1glsS250n15LsWsYtCugiStS5A==", - "license": "MIT", - "dependencies": { - "mime-db": "^1.54.0" - }, - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/minimatch": { - "version": "10.2.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-10.2.5.tgz", - "integrity": "sha512-MULkVLfKGYDFYejP07QOurDLLQpcjk7Fw+7jXS2R2czRQzR56yHRveU5NDJEOviH+hETZKSkIk5c+T23GjFUMg==", - "dev": true, - "license": "BlueOak-1.0.0", - "dependencies": { - "brace-expansion": "^5.0.5" - }, - "engines": { - "node": "18 || 20 || >=22" - }, - "funding": { - "url": "https://github.com/sponsors/isaacs" - } - }, - "node_modules/mipd": { - "version": "0.0.7", - "resolved": "https://registry.npmjs.org/mipd/-/mipd-0.0.7.tgz", - "integrity": "sha512-aAPZPNDQ3uMTdKbuO2YmAw2TxLHO0moa4YKAyETM/DTj5FloZo+a+8tU+iv4GmW+sOxKLSRwcSFuczk+Cpt6fg==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/wagmi-dev" - } - ], - "license": "MIT", - "peerDependencies": { - "typescript": ">=5.0.4" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "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/multiformats": { - "version": "9.9.0", - "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", - "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", - "license": "(Apache-2.0 AND MIT)" - }, - "node_modules/negotiator": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", - "integrity": "sha512-8Ofs/AUQh8MaEcrlq5xOX0CQ9ypTF5dl78mjlMNfOK08fzpgTHQRQPBxcPlEtIw0yRpws+Zo/3r+5WRby7u3Gg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/node-addon-api": { - "version": "2.0.2", - "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-2.0.2.tgz", - "integrity": "sha512-Ntyt4AIXyaLIuMHF6IOoTakB3K+RWxwtsHNRxllEoA6vPwP9o4866g6YWDLUdnucilZhmkxiHwHr11gAENw+QA==", - "license": "MIT" - }, - "node_modules/node-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/node-fetch-native": { - "version": "1.6.7", - "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", - "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", - "license": "MIT" - }, - "node_modules/node-gyp-build": { - "version": "4.8.4", - "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", - "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", - "license": "MIT", - "bin": { - "node-gyp-build": "bin.js", - "node-gyp-build-optional": "optional.js", - "node-gyp-build-test": "build-test.js" - } - }, - "node_modules/node-mock-http": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.4.tgz", - "integrity": "sha512-8DY+kFsDkNXy1sJglUfuODx1/opAGJGyrTuFqEoN90oRc2Vk0ZbD4K2qmKXBBEhZQzdKHIVfEJpDU8Ak2NJEvQ==", - "license": "MIT" - }, - "node_modules/nodemon": { - "version": "3.1.14", - "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.14.tgz", - "integrity": "sha512-jakjZi93UtB3jHMWsXL68FXSAosbLfY0In5gtKq3niLSkrWznrVBzXFNOEMJUfc9+Ke7SHWoAZsiMkNP3vq6Jw==", - "dev": true, - "license": "MIT", - "dependencies": { - "chokidar": "^3.5.2", - "debug": "^4", - "ignore-by-default": "^1.0.1", - "minimatch": "^10.2.1", - "pstree.remy": "^1.1.8", - "semver": "^7.5.3", - "simple-update-notifier": "^2.0.0", - "supports-color": "^5.5.0", - "touch": "^3.1.0", - "undefsafe": "^2.0.5" - }, - "bin": { - "nodemon": "bin/nodemon.js" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/nodemon" - } - }, - "node_modules/normalize-path": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", - "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/obj-multiplex": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/obj-multiplex/-/obj-multiplex-1.0.0.tgz", - "integrity": "sha512-0GNJAOsHoBHeNTvl5Vt6IWnpUEcc3uSRxzBri7EDyIcMgYvnY2JL2qdeV5zTMjWQX5OHcD5amcW2HFfDh0gjIA==", - "license": "ISC", - "dependencies": { - "end-of-stream": "^1.4.0", - "once": "^1.4.0", - "readable-stream": "^2.3.3" - } - }, - "node_modules/obj-multiplex/node_modules/isarray": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", - "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", - "license": "MIT" - }, - "node_modules/obj-multiplex/node_modules/readable-stream": { - "version": "2.3.8", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", - "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", - "license": "MIT", - "dependencies": { - "core-util-is": "~1.0.0", - "inherits": "~2.0.3", - "isarray": "~1.0.0", - "process-nextick-args": "~2.0.0", - "safe-buffer": "~5.1.1", - "string_decoder": "~1.1.1", - "util-deprecate": "~1.0.1" - } - }, - "node_modules/obj-multiplex/node_modules/safe-buffer": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", - "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", - "license": "MIT" - }, - "node_modules/obj-multiplex/node_modules/string_decoder": { - "version": "1.1.1", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", - "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.1.0" - } - }, - "node_modules/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/ofetch": { - "version": "1.5.1", - "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.5.1.tgz", - "integrity": "sha512-2W4oUZlVaqAPAil6FUg/difl6YhqhUR7x2eZY4bQCko22UXg3hptq9KLQdqFClV+Wu85UX7hNtdGTngi/1BxcA==", - "license": "MIT", - "dependencies": { - "destr": "^2.0.5", - "node-fetch-native": "^1.6.7", - "ufo": "^1.6.1" - } - }, - "node_modules/on-exit-leak-free": { - "version": "0.2.0", - "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz", - "integrity": "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==", - "license": "MIT" - }, - "node_modules/on-finished": { - "version": "2.4.1", - "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", - "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", - "license": "MIT", - "dependencies": { - "ee-first": "1.1.1" - }, - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/once": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", - "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "license": "ISC", - "dependencies": { - "wrappy": "1" - } - }, - "node_modules/openapi-fetch": { - "version": "0.13.8", - "resolved": "https://registry.npmjs.org/openapi-fetch/-/openapi-fetch-0.13.8.tgz", - "integrity": "sha512-yJ4QKRyNxE44baQ9mY5+r/kAzZ8yXMemtNAOFwOzRXJscdjSxxzWSNlyBAr+o5JjkUw9Lc3W7OIoca0cY3PYnQ==", - "license": "MIT", - "dependencies": { - "openapi-typescript-helpers": "^0.0.15" - } - }, - "node_modules/openapi-typescript-helpers": { - "version": "0.0.15", - "resolved": "https://registry.npmjs.org/openapi-typescript-helpers/-/openapi-typescript-helpers-0.0.15.tgz", - "integrity": "sha512-opyTPaunsklCBpTK8JGef6mfPhLSnyy5a0IN9vKtx3+4aExf+KxEqYwIy3hqkedXIB97u357uLMJsOnm3GVjsw==", - "license": "MIT" - }, - "node_modules/ox": { - "version": "0.14.26-386a343.0", - "resolved": "https://pkg.pr.new/ox@386a3439fe1ce76d237930f8c6e6bb493746069a", - "integrity": "sha512-OHHm9re1yVjiMN66GZ2JSGuqmvJPrk40zh3PIS/3I6prZLbt6U/zKlgW18eIIkO0Y/ZyySKr6D/4mUXjBmky1g==", - "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/ox/node_modules/abitype": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.4.tgz", - "integrity": "sha512-dpKH+N27vRjarMVTFFkeY445VTKftzGWpL0FiT7xmVmzQRKazZexzC5uHG0f6XKsVLAuUlndnbGau6lRejClxg==", - "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/p-limit": { - "version": "2.3.0", - "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", - "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", - "license": "MIT", - "dependencies": { - "p-try": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/p-locate": { - "version": "4.1.0", - "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", - "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", - "license": "MIT", - "dependencies": { - "p-limit": "^2.2.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/p-try": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", - "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/parseurl": { - "version": "1.3.3", - "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", - "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", - "license": "MIT", - "engines": { - "node": ">= 0.8" - } - }, - "node_modules/path-exists": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", - "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "license": "MIT", - "engines": { - "node": ">=8" - } - }, - "node_modules/path-to-regexp": { - "version": "8.4.2", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", - "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/picomatch": { - "version": "2.3.2", - "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.2.tgz", - "integrity": "sha512-V7+vQEJ06Z+c5tSye8S+nHUfI51xoXIXjHQ99cQtKUkQqqO1kO/KCJUfZXuB47h/YBlDhah2H3hdUGXn8ie0oA==", - "license": "MIT", - "engines": { - "node": ">=8.6" - }, - "funding": { - "url": "https://github.com/sponsors/jonschlinkert" - } - }, - "node_modules/pify": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/pify/-/pify-3.0.0.tgz", - "integrity": "sha512-C3FsVNH1udSEX48gGX1xfvwTWfsYWj5U+8/uK15BGzIGrKoUpghX8hWZwa/OFnakBiiVNmBvemTJR5mcy7iPcg==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/pino": { - "version": "7.11.0", - "resolved": "https://registry.npmjs.org/pino/-/pino-7.11.0.tgz", - "integrity": "sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0", - "fast-redact": "^3.0.0", - "on-exit-leak-free": "^0.2.0", - "pino-abstract-transport": "v0.5.0", - "pino-std-serializers": "^4.0.0", - "process-warning": "^1.0.0", - "quick-format-unescaped": "^4.0.3", - "real-require": "^0.1.0", - "safe-stable-stringify": "^2.1.0", - "sonic-boom": "^2.2.1", - "thread-stream": "^0.15.1" - }, - "bin": { - "pino": "bin.js" - } - }, - "node_modules/pino-abstract-transport": { - "version": "0.5.0", - "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz", - "integrity": "sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==", - "license": "MIT", - "dependencies": { - "duplexify": "^4.1.2", - "split2": "^4.0.0" - } - }, - "node_modules/pino-std-serializers": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", - "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==", - "license": "MIT" - }, - "node_modules/pngjs": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", - "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", - "license": "MIT", - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/pony-cause": { - "version": "2.1.11", - "resolved": "https://registry.npmjs.org/pony-cause/-/pony-cause-2.1.11.tgz", - "integrity": "sha512-M7LhCsdNbNgiLYiP4WjsfLUuFmCfnjdF6jKe2R9NKl4WFN+HZPGHJZ9lnLP7f9ZnKe3U9nuWD0szirmj+migUg==", - "license": "0BSD", - "engines": { - "node": ">=12.0.0" - } - }, - "node_modules/porto": { - "version": "0.2.35", - "resolved": "https://registry.npmjs.org/porto/-/porto-0.2.35.tgz", - "integrity": "sha512-gu9FfjjvvYBgQXUHWTp6n3wkTxVtEcqFotM7i3GEZeoQbvLGbssAicCz6hFZ8+xggrJWwi/RLmbwNra50SMmUQ==", - "license": "MIT", - "dependencies": { - "hono": "^4.10.3", - "idb-keyval": "^6.2.1", - "mipd": "^0.0.7", - "ox": "^0.9.6", - "zod": "^4.1.5", - "zustand": "^5.0.1" - }, - "bin": { - "porto": "dist/cli/bin/index.js" - }, - "peerDependencies": { - "@tanstack/react-query": ">=5.59.0", - "@wagmi/core": ">=2.16.3", - "expo-auth-session": ">=7.0.8", - "expo-crypto": ">=15.0.7", - "expo-web-browser": ">=15.0.8", - "react": ">=18", - "react-native": ">=0.81.4", - "typescript": ">=5.4.0", - "viem": ">=2.37.0", - "wagmi": ">=2.0.0" - }, - "peerDependenciesMeta": { - "@tanstack/react-query": { - "optional": true - }, - "expo-auth-session": { - "optional": true - }, - "expo-crypto": { - "optional": true - }, - "expo-web-browser": { - "optional": true - }, - "react": { - "optional": true - }, - "react-native": { - "optional": true - }, - "typescript": { - "optional": true - }, - "wagmi": { - "optional": true - } - } - }, - "node_modules/porto/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/porto/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/porto/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/porto/node_modules/abitype": { - "version": "1.2.4", - "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.2.4.tgz", - "integrity": "sha512-dpKH+N27vRjarMVTFFkeY445VTKftzGWpL0FiT7xmVmzQRKazZexzC5uHG0f6XKsVLAuUlndnbGau6lRejClxg==", - "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/porto/node_modules/ox": { - "version": "0.9.17", - "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.17.tgz", - "integrity": "sha512-rKAnhzhRU3Xh3hiko+i1ZxywZ55eWQzeS/Q4HRKLx2PqfHOolisZHErSsJVipGlmQKHW5qwOED/GighEw9dbLg==", - "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.0.9", - "eventemitter3": "5.0.1" - }, - "peerDependencies": { - "typescript": ">=5.4.0" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/porto/node_modules/zod": { - "version": "4.4.3", - "resolved": "https://registry.npmjs.org/zod/-/zod-4.4.3.tgz", - "integrity": "sha512-ytENFjIJFl2UwYglde2jchW2Hwm4GJFLDiSXWdTrJQBIN9Fcyp7n4DhxJEiWNAJMV1/BqWfW/kkg71UDcHJyTQ==", - "license": "MIT", - "funding": { - "url": "https://github.com/sponsors/colinhacks" - } - }, - "node_modules/possible-typed-array-names": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", - "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "license": "MIT", - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/preact": { - "version": "10.24.2", - "resolved": "https://registry.npmjs.org/preact/-/preact-10.24.2.tgz", - "integrity": "sha512-1cSoF0aCC8uaARATfrlz4VCBqE8LwZwRfLgkxJOQwAlQt6ayTmi0D9OF7nXid1POI5SZidFuG9CnlXbDfLqY/Q==", - "license": "MIT", - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/preact" - } - }, - "node_modules/process-nextick-args": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", - "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", - "license": "MIT" - }, - "node_modules/process-warning": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", - "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==", - "license": "MIT" - }, - "node_modules/promise-limit": { - "version": "2.7.0", - "resolved": "https://registry.npmjs.org/promise-limit/-/promise-limit-2.7.0.tgz", - "integrity": "sha512-7nJ6v5lnJsXwGprnGXga4wx6d1POjvi5Qmf1ivTRxTjH4Z/9Czja/UCMLVmB9N93GeWOU93XaFaEt6jbuoagNw==", - "license": "ISC" - }, - "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-compare": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.6.0.tgz", - "integrity": "sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw==", - "license": "MIT" - }, - "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/pstree.remy": { - "version": "1.1.8", - "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", - "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", - "dev": true, - "license": "MIT" - }, - "node_modules/pump": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.4.tgz", - "integrity": "sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==", - "license": "MIT", - "dependencies": { - "end-of-stream": "^1.1.0", - "once": "^1.3.1" - } - }, - "node_modules/qrcode": { - "version": "1.5.3", - "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", - "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==", - "license": "MIT", - "dependencies": { - "dijkstrajs": "^1.0.1", - "encode-utf8": "^1.0.3", - "pngjs": "^5.0.0", - "yargs": "^15.3.1" - }, - "bin": { - "qrcode": "bin/qrcode" - }, - "engines": { - "node": ">=10.13.0" - } - }, - "node_modules/qs": { - "version": "6.15.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.2.tgz", - "integrity": "sha512-Rzq0KEyX/w/tEybncDgdkZrJgVUsUMk3xjh3t5bv3S1HTAtg+uOYt72+ZfwiQwKdysThkTBdL/rTi6HDmX9Ddw==", - "license": "BSD-3-Clause", - "dependencies": { - "side-channel": "^1.1.0" - }, - "engines": { - "node": ">=0.6" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/query-string": { - "version": "7.1.3", - "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", - "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", - "license": "MIT", - "dependencies": { - "decode-uri-component": "^0.2.2", - "filter-obj": "^1.1.0", - "split-on-first": "^1.0.0", - "strict-uri-encode": "^2.0.0" - }, - "engines": { - "node": ">=6" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" - } - }, - "node_modules/quick-format-unescaped": { - "version": "4.0.4", - "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", - "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", - "license": "MIT" - }, - "node_modules/radix3": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", - "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", - "license": "MIT" - }, - "node_modules/range-parser": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", - "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", - "license": "MIT", - "engines": { - "node": ">= 0.6" - } - }, - "node_modules/raw-body": { - "version": "3.0.2", - "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-3.0.2.tgz", - "integrity": "sha512-K5zQjDllxWkf7Z5xJdV0/B0WTNqx6vxG70zJE4N0kBs4LovmEYWJzQGxC9bS9RAKu3bgM40lrd5zoLJ12MQ5BA==", - "license": "MIT", - "dependencies": { - "bytes": "~3.1.2", - "http-errors": "~2.0.1", - "iconv-lite": "~0.7.0", - "unpipe": "~1.0.0" - }, - "engines": { - "node": ">= 0.10" - } - }, - "node_modules/react": { - "version": "18.3.1", - "resolved": "https://registry.npmjs.org/react/-/react-18.3.1.tgz", - "integrity": "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ==", - "license": "MIT", - "peer": true, - "dependencies": { - "loose-envify": "^1.1.0" - }, - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/readable-stream": { - "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "string_decoder": "^1.1.1", - "util-deprecate": "^1.0.1" - }, - "engines": { - "node": ">= 6" - } - }, - "node_modules/readdirp": { - "version": "3.6.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", - "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", - "dev": true, - "license": "MIT", - "dependencies": { - "picomatch": "^2.2.1" - }, - "engines": { - "node": ">=8.10.0" - } - }, - "node_modules/real-require": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.1.0.tgz", - "integrity": "sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==", - "license": "MIT", - "engines": { - "node": ">= 12.13.0" - } - }, - "node_modules/require-directory": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", - "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", - "license": "MIT", - "engines": { - "node": ">=0.10.0" - } - }, - "node_modules/require-main-filename": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", - "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", - "license": "ISC" - }, - "node_modules/router": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/router/-/router-2.2.0.tgz", - "integrity": "sha512-nLTrUKm2UyiL7rlhapu/Zl45FwNgkZGaCpZbIHajDYgwlJCOzLSk+cIPAnsEqV955GjILJnKbdQC1nVPz+gAYQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.0", - "depd": "^2.0.0", - "is-promise": "^4.0.0", - "parseurl": "^1.3.3", - "path-to-regexp": "^8.0.0" - }, - "engines": { - "node": ">= 18" - } - }, - "node_modules/safe-buffer": { - "version": "5.2.1", - "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", - "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", - "funding": [ - { - "type": "github", - "url": "https://github.com/sponsors/feross" - }, - { - "type": "patreon", - "url": "https://www.patreon.com/feross" - }, - { - "type": "consulting", - "url": "https://feross.org/support" - } - ], - "license": "MIT" - }, - "node_modules/safe-regex-test": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", - "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.2", - "es-errors": "^1.3.0", - "is-regex": "^1.2.1" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/safe-stable-stringify": { - "version": "2.5.0", - "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", - "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", - "license": "MIT", - "engines": { - "node": ">=10" - } - }, - "node_modules/safer-buffer": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", - "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", - "license": "MIT" - }, - "node_modules/semver": { - "version": "7.8.1", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.8.1.tgz", - "integrity": "sha512-rkVq3IXh+4FDGch+KwzX3aV9W3kO54GyEgpvBzSyctDA6Xtd7RJQV1xmXbeQp5v7+VzLOfVqiutSE6GICgPFvg==", - "license": "ISC", - "bin": { - "semver": "bin/semver.js" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/send": { - "version": "1.2.1", - "resolved": "https://registry.npmjs.org/send/-/send-1.2.1.tgz", - "integrity": "sha512-1gnZf7DFcoIcajTjTwjwuDjzuz4PPcY2StKPlsGAQ1+YH20IRVrBaXSWmdjowTJ6u8Rc01PoYOGHXfP1mYcZNQ==", - "license": "MIT", - "dependencies": { - "debug": "^4.4.3", - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "etag": "^1.8.1", - "fresh": "^2.0.0", - "http-errors": "^2.0.1", - "mime-types": "^3.0.2", - "ms": "^2.1.3", - "on-finished": "^2.4.1", - "range-parser": "^1.2.1", - "statuses": "^2.0.2" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/serve-static": { - "version": "2.2.1", - "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-2.2.1.tgz", - "integrity": "sha512-xRXBn0pPqQTVQiC8wyQrKs2MOlX24zQ0POGaj0kultvoOCstBQM5yvOhAVSUwOMjQtTvsPWoNCHfPGwaaQJhTw==", - "license": "MIT", - "dependencies": { - "encodeurl": "^2.0.0", - "escape-html": "^1.0.3", - "parseurl": "^1.3.3", - "send": "^1.2.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/set-blocking": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", - "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", - "license": "ISC" - }, - "node_modules/set-function-length": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", - "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "license": "MIT", - "dependencies": { - "define-data-property": "^1.1.4", - "es-errors": "^1.3.0", - "function-bind": "^1.1.2", - "get-intrinsic": "^1.2.4", - "gopd": "^1.0.1", - "has-property-descriptors": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/setprototypeof": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", - "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", - "license": "ISC" - }, - "node_modules/sha.js": { - "version": "2.4.12", - "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", - "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", - "license": "(MIT AND BSD-3-Clause)", - "dependencies": { - "inherits": "^2.0.4", - "safe-buffer": "^5.2.1", - "to-buffer": "^1.2.0" - }, - "bin": { - "sha.js": "bin.js" - }, - "engines": { - "node": ">= 0.10" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/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.1", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", - "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", - "license": "MIT", - "dependencies": { - "es-errors": "^1.3.0", - "object-inspect": "^1.13.4" - }, - "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/simple-update-notifier": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", - "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", - "dev": true, - "license": "MIT", - "dependencies": { - "semver": "^7.5.3" - }, - "engines": { - "node": ">=10" - } - }, - "node_modules/siwe": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/siwe/-/siwe-3.0.0.tgz", - "integrity": "sha512-P2/ry7dHYJA6JJ5+veS//Gn2XDwNb3JMvuD6xiXX8L/PJ1SNVD4a3a8xqEbmANx+7kNQcD8YAh1B9bNKKvRy/g==", - "license": "Apache-2.0", - "dependencies": { - "@spruceid/siwe-parser": "^3.0.0", - "@stablelib/random": "^1.0.1" - }, - "peerDependencies": { - "ethers": "^5.6.8 || ^6.0.8" - } - }, - "node_modules/socket.io-client": { - "version": "4.8.3", - "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", - "integrity": "sha512-uP0bpjWrjQmUt5DTHq9RuoCBdFJF10cdX9X+a368j/Ft0wmaVgxlrjvK3kjvgCODOMMOz9lcaRzxmso0bTWZ/g==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1", - "engine.io-client": "~6.6.1", - "socket.io-parser": "~4.2.4" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/socket.io-parser": { - "version": "4.2.6", - "resolved": "https://registry.npmjs.org/socket.io-parser/-/socket.io-parser-4.2.6.tgz", - "integrity": "sha512-asJqbVBDsBCJx0pTqw3WfesSY0iRX+2xzWEWzrpcH7L6fLzrhyF8WPI8UaeM4YCuDfpwA/cgsdugMsmtz8EJeg==", - "license": "MIT", - "dependencies": { - "@socket.io/component-emitter": "~3.1.0", - "debug": "~4.4.1" - }, - "engines": { - "node": ">=10.0.0" - } - }, - "node_modules/sonic-boom": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz", - "integrity": "sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==", - "license": "MIT", - "dependencies": { - "atomic-sleep": "^1.0.0" - } - }, - "node_modules/split-on-first": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", - "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", - "license": "MIT", - "engines": { - "node": ">=6" - } - }, - "node_modules/split2": { - "version": "4.2.0", - "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", - "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", - "license": "ISC", - "engines": { - "node": ">= 10.x" - } - }, - "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/stream-shift": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", - "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", - "license": "MIT" - }, - "node_modules/strict-uri-encode": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", - "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", - "license": "MIT", - "engines": { - "node": ">=4" - } - }, - "node_modules/string_decoder": { - "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", - "license": "MIT", - "dependencies": { - "safe-buffer": "~5.2.0" - } - }, - "node_modules/string-width": { - "version": "4.2.3", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", - "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", - "license": "MIT", - "dependencies": { - "emoji-regex": "^8.0.0", - "is-fullwidth-code-point": "^3.0.0", - "strip-ansi": "^6.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/strip-ansi": { - "version": "6.0.1", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", - "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", - "license": "MIT", - "dependencies": { - "ansi-regex": "^5.0.1" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/superstruct": { - "version": "1.0.4", - "resolved": "https://registry.npmjs.org/superstruct/-/superstruct-1.0.4.tgz", - "integrity": "sha512-7JpaAoX2NGyoFlI9NBh66BQXGONc+uE+MRS5i2iOBKuS4e+ccgMDjATgZldkah+33DakBxDHiss9kvUcGAO8UQ==", - "license": "MIT", - "engines": { - "node": ">=14.0.0" - } - }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "license": "MIT", - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, - "node_modules/thread-stream": { - "version": "0.15.2", - "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-0.15.2.tgz", - "integrity": "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==", - "license": "MIT", - "dependencies": { - "real-require": "^0.1.0" - } - }, - "node_modules/to-buffer": { - "version": "1.2.2", - "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", - "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", - "license": "MIT", - "dependencies": { - "isarray": "^2.0.5", - "safe-buffer": "^5.2.1", - "typed-array-buffer": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/to-regex-range": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", - "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "is-number": "^7.0.0" - }, - "engines": { - "node": ">=8.0" - } - }, - "node_modules/toidentifier": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", - "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", - "license": "MIT", - "engines": { - "node": ">=0.6" - } - }, - "node_modules/touch": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", - "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", - "dev": true, - "license": "ISC", - "bin": { - "nodetouch": "bin/nodetouch.js" - } - }, - "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.7.0", - "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", - "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", - "license": "0BSD" - }, - "node_modules/type-is": { - "version": "2.1.0", - "resolved": "https://registry.npmjs.org/type-is/-/type-is-2.1.0.tgz", - "integrity": "sha512-faYHw0anBbc/kWF3zFTEnxSFOAGUX9GFbOBthvDdLsIlEoWOFOtS0zgCiQYwIskL9iGXZL3kAXD8OoZ4GmMATA==", - "license": "MIT", - "dependencies": { - "content-type": "^2.0.0", - "media-typer": "^1.1.0", - "mime-types": "^3.0.0" - }, - "engines": { - "node": ">= 18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/type-is/node_modules/content-type": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/content-type/-/content-type-2.0.0.tgz", - "integrity": "sha512-j/O/d7GcZCyNl7/hwZAb606rzqkyvaDctLmckbxLzHvFBzTJHuGEdodATcP3yIRoDrLHkIATJuvzbFlp/ki2cQ==", - "license": "MIT", - "engines": { - "node": ">=18" - }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/express" - } - }, - "node_modules/typed-array-buffer": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", - "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3", - "es-errors": "^1.3.0", - "is-typed-array": "^1.1.14" - }, - "engines": { - "node": ">= 0.4" - } - }, - "node_modules/ufo": { - "version": "1.6.4", - "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", - "integrity": "sha512-JFNbkD1Svwe0KvGi8GOeLcP4kAWQ609twvCdcHxq1oSL8svv39ZuSvajcD8B+5D0eL4+s1Is2D/O6KN3qcTeRA==", - "license": "MIT" - }, - "node_modules/uint8arrays": { - "version": "3.1.0", - "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.0.tgz", - "integrity": "sha512-ei5rfKtoRO8OyOIor2Rz5fhzjThwIHJZ3uyDPnDHTXbP0aMQ1RN/6AI5B5d9dBxJOU+BvOAk7ZQ1xphsX8Lrog==", - "license": "MIT", - "dependencies": { - "multiformats": "^9.4.2" - } - }, - "node_modules/uncrypto": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", - "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", - "license": "MIT" - }, - "node_modules/undefsafe": { - "version": "2.0.5", - "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", - "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", - "dev": true, - "license": "MIT" - }, - "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/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/unstorage": { - "version": "1.17.5", - "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.5.tgz", - "integrity": "sha512-0i3iqvRfx29hkNntHyQvJTpf5W9dQ9ZadSoRU8+xVlhVtT7jAX57fazYO9EHvcRCfBCyi5YRya7XCDOsbTgkPg==", - "license": "MIT", - "dependencies": { - "anymatch": "^3.1.3", - "chokidar": "^5.0.0", - "destr": "^2.0.5", - "h3": "^1.15.10", - "lru-cache": "^11.2.7", - "node-fetch-native": "^1.6.7", - "ofetch": "^1.5.1", - "ufo": "^1.6.3" - }, - "peerDependencies": { - "@azure/app-configuration": "^1.8.0", - "@azure/cosmos": "^4.2.0", - "@azure/data-tables": "^13.3.0", - "@azure/identity": "^4.6.0", - "@azure/keyvault-secrets": "^4.9.0", - "@azure/storage-blob": "^12.26.0", - "@capacitor/preferences": "^6 || ^7 || ^8", - "@deno/kv": ">=0.9.0", - "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", - "@planetscale/database": "^1.19.0", - "@upstash/redis": "^1.34.3", - "@vercel/blob": ">=0.27.1", - "@vercel/functions": "^2.2.12 || ^3.0.0", - "@vercel/kv": "^1 || ^2 || ^3", - "aws4fetch": "^1.0.20", - "db0": ">=0.2.1", - "idb-keyval": "^6.2.1", - "ioredis": "^5.4.2", - "uploadthing": "^7.4.4" - }, - "peerDependenciesMeta": { - "@azure/app-configuration": { - "optional": true - }, - "@azure/cosmos": { - "optional": true - }, - "@azure/data-tables": { - "optional": true - }, - "@azure/identity": { - "optional": true - }, - "@azure/keyvault-secrets": { - "optional": true - }, - "@azure/storage-blob": { - "optional": true - }, - "@capacitor/preferences": { - "optional": true - }, - "@deno/kv": { - "optional": true - }, - "@netlify/blobs": { - "optional": true - }, - "@planetscale/database": { - "optional": true - }, - "@upstash/redis": { - "optional": true - }, - "@vercel/blob": { - "optional": true - }, - "@vercel/functions": { - "optional": true - }, - "@vercel/kv": { - "optional": true - }, - "aws4fetch": { - "optional": true - }, - "db0": { - "optional": true - }, - "idb-keyval": { - "optional": true - }, - "ioredis": { - "optional": true - }, - "uploadthing": { - "optional": true - } - } - }, - "node_modules/unstorage/node_modules/chokidar": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-5.0.0.tgz", - "integrity": "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw==", - "license": "MIT", - "dependencies": { - "readdirp": "^5.0.0" - }, - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/unstorage/node_modules/readdirp": { - "version": "5.0.0", - "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-5.0.0.tgz", - "integrity": "sha512-9u/XQ1pvrQtYyMpZe7DXKv2p5CNvyVwzUB6uhLAnQwHMSgKMBR62lc7AHljaeteeHXn11XTAaLLUVZYVZyuRBQ==", - "license": "MIT", - "engines": { - "node": ">= 20.19.0" - }, - "funding": { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - }, - "node_modules/use-sync-external-store": { - "version": "1.4.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.4.0.tgz", - "integrity": "sha512-9WXSPC5fMv61vaupRkCKCxsPxBocVnwakBEkMIHHpkTTg6icbJtg6jzgtLDm4bl3cSHAca52rYWih0k4K3PfHw==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" - } - }, - "node_modules/utf-8-validate": { - "version": "5.0.10", - "resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz", - "integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==", - "hasInstallScript": true, - "license": "MIT", - "dependencies": { - "node-gyp-build": "^4.3.0" - }, - "engines": { - "node": ">=6.14.2" - } - }, - "node_modules/util": { - "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", - "license": "MIT", - "dependencies": { - "inherits": "^2.0.3", - "is-arguments": "^1.0.4", - "is-generator-function": "^1.0.7", - "is-typed-array": "^1.1.3", - "which-typed-array": "^1.1.2" - } - }, - "node_modules/util-deprecate": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "license": "MIT" - }, - "node_modules/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": "11.1.1", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-11.1.1.tgz", - "integrity": "sha512-vIYxrBCC/N/K+Js3qSN88go7kIfNPssr/hHCesKCQNAjmgvYS2oqr69kIufEG+O4+PfezOH4EbIeHCfFov8ZgQ==", - "funding": [ - "https://github.com/sponsors/broofa", - "https://github.com/sponsors/ctavan" - ], - "license": "MIT", - "bin": { - "uuid": "dist/esm/bin/uuid" - } - }, - "node_modules/valtio": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz", - "integrity": "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==", - "license": "MIT", - "dependencies": { - "derive-valtio": "0.1.0", - "proxy-compare": "2.6.0", - "use-sync-external-store": "1.2.0" - }, - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=16.8", - "react": ">=16.8" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "react": { - "optional": true - } - } - }, - "node_modules/valtio/node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, - "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.51.0", - "resolved": "https://registry.npmjs.org/viem/-/viem-2.51.0.tgz", - "integrity": "sha512-8C0Ca+eEapXE29vHMUW59NqKENl1X4s9P6xSNC9Nvw6EvAeAhn/LNUlgztk6TOw7KN1Gzz5a/n9Wv4okUfmY9g==", - "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": "https://pkg.pr.new/ox@386a3439fe1ce76d237930f8c6e6bb493746069a", - "ws": "8.20.1" - }, - "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/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/wagmi": { - "version": "2.19.5", - "resolved": "https://registry.npmjs.org/wagmi/-/wagmi-2.19.5.tgz", - "integrity": "sha512-RQUfKMv6U+EcSNNGiPbdkDtJwtuFxZWLmvDiQmjjBgkuPulUwDJsKhi7gjynzJdsx2yDqhHCXkKsbbfbIsHfcQ==", - "license": "MIT", - "dependencies": { - "@wagmi/connectors": "6.2.0", - "@wagmi/core": "2.22.1", - "use-sync-external-store": "1.4.0" - }, - "funding": { - "url": "https://github.com/sponsors/wevm" - }, - "peerDependencies": { - "@tanstack/react-query": ">=5.0.0", - "react": ">=18", - "typescript": ">=5.0.4", - "viem": "2.x" - }, - "peerDependenciesMeta": { - "typescript": { - "optional": true - } - } - }, - "node_modules/webextension-polyfill": { - "version": "0.10.0", - "resolved": "https://registry.npmjs.org/webextension-polyfill/-/webextension-polyfill-0.10.0.tgz", - "integrity": "sha512-c5s35LgVa5tFaHhrZDnr3FpQpjj1BB+RXhLTYUxGqBVN460HkbM8TBtEqdXWbpTKfzwCcjAZVF7zXCYSKtcp9g==", - "license": "MPL-2.0" - }, - "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/which-module": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", - "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", - "license": "ISC" - }, - "node_modules/which-typed-array": { - "version": "1.1.20", - "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", - "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "license": "MIT", - "dependencies": { - "available-typed-arrays": "^1.0.7", - "call-bind": "^1.0.8", - "call-bound": "^1.0.4", - "for-each": "^0.3.5", - "get-proto": "^1.0.1", - "gopd": "^1.2.0", - "has-tostringtag": "^1.0.2" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } - }, - "node_modules/wrap-ansi": { - "version": "6.2.0", - "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", - "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", - "license": "MIT", - "dependencies": { - "ansi-styles": "^4.0.0", - "string-width": "^4.1.0", - "strip-ansi": "^6.0.0" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/wrappy": { - "version": "1.0.2", - "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "license": "ISC" - }, - "node_modules/ws": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.0.tgz", - "integrity": "sha512-Vsp28b7DRcimFQvrqu2Wek3z1iYxDCWqHYB8Qsnk/S4RfaCQzPGPyBNuVjJV3cd6UiKtUtp6sNM77gWvzcCH+g==", - "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/x402": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/x402/-/x402-1.2.0.tgz", - "integrity": "sha512-cqcB9LNw1e1Kv6wkKyyKvn7wcYBnJ9vd8336M9jZRgLKDcIDt2n3liiSXyzx4HJTv07f9M2OAk5uKhh/LcbKQQ==", - "license": "Apache-2.0", - "dependencies": { - "@scure/base": "^1.2.6", - "@solana-program/compute-budget": "^0.11.0", - "@solana-program/token": "^0.9.0", - "@solana-program/token-2022": "^0.6.1", - "@solana/kit": "^5.0.0", - "@solana/transaction-confirmation": "^5.0.0", - "@solana/wallet-standard-features": "^1.3.0", - "@wallet-standard/app": "^1.1.0", - "@wallet-standard/base": "^1.1.0", - "@wallet-standard/features": "^1.1.0", - "viem": "^2.21.26", - "wagmi": "^2.15.6", - "zod": "^3.24.2" - } - }, - "node_modules/x402-express": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/x402-express/-/x402-express-1.2.0.tgz", - "integrity": "sha512-0AyJIv5vhz4qYbNeEy79J4/uOy44Ze4vI6PjkSMZRA9YuSL8RShKF4N2o+dl/5RNtdz7RkDtXpFNnsYo4Grd/g==", - "license": "Apache-2.0", - "dependencies": { - "@coinbase/cdp-sdk": "^1.22.0", - "@solana/kit": "^5.0.0", - "express": "^4.18.2", - "viem": "^2.21.26", - "x402": "^1.2.0", - "zod": "^3.24.2" - } - }, - "node_modules/x402-express/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/x402-express/node_modules/body-parser": { - "version": "1.20.5", - "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.5.tgz", - "integrity": "sha512-3grm+/2tUOvu2cjJkvsIxrv/wVpfXQW4PsQHYm7yk4vfpu7Ekl6nEsYBoJUL6qDwZUx8wUhQ8tR2qz+ad9c9OA==", - "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.15.1", - "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/x402-express/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/x402-express/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/x402-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/x402-express/node_modules/debug/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/x402-express/node_modules/express": { - "version": "4.22.2", - "resolved": "https://registry.npmjs.org/express/-/express-4.22.2.tgz", - "integrity": "sha512-IuL+Elrou2ZvCFHs18/CIzy2Nzvo25nZ1/D2eIZlz7c+QUayAcYoiM2BthCjs+EBHVpjYjcuLDAiCWgeIX3X1Q==", - "license": "MIT", - "dependencies": { - "accepts": "~1.3.8", - "array-flatten": "1.1.1", - "body-parser": "~1.20.5", - "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.15.1", - "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/x402-express/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/x402-express/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/x402-express/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/x402-express/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/x402-express/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/x402-express/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/x402-express/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/x402-express/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/x402-express/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/x402-express/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/x402-express/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/x402-express/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/x402-express/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/xmlhttprequest-ssl": { - "version": "2.1.2", - "resolved": "https://registry.npmjs.org/xmlhttprequest-ssl/-/xmlhttprequest-ssl-2.1.2.tgz", - "integrity": "sha512-TEU+nJVUUnA4CYJFLvK5X9AOeH4KvDvhIfm0vV1GaQRtchnG0hgK5p8hw/xjv8cunWYCsiPCSDzObPyhEwq3KQ==", - "engines": { - "node": ">=0.4.0" - } - }, - "node_modules/xtend": { - "version": "4.0.2", - "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", - "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", - "license": "MIT", - "engines": { - "node": ">=0.4" - } - }, - "node_modules/y18n": { - "version": "4.0.3", - "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", - "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", - "license": "ISC" - }, - "node_modules/yargs": { - "version": "15.4.1", - "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", - "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", - "license": "MIT", - "dependencies": { - "cliui": "^6.0.0", - "decamelize": "^1.2.0", - "find-up": "^4.1.0", - "get-caller-file": "^2.0.1", - "require-directory": "^2.1.1", - "require-main-filename": "^2.0.0", - "set-blocking": "^2.0.0", - "string-width": "^4.2.0", - "which-module": "^2.0.0", - "y18n": "^4.0.0", - "yargs-parser": "^18.1.2" - }, - "engines": { - "node": ">=8" - } - }, - "node_modules/yargs-parser": { - "version": "18.1.3", - "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", - "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", - "license": "ISC", - "dependencies": { - "camelcase": "^5.0.0", - "decamelize": "^1.2.0" - }, - "engines": { - "node": ">=6" - } - }, - "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" - } - }, - "node_modules/zustand": { - "version": "5.0.3", - "resolved": "https://registry.npmjs.org/zustand/-/zustand-5.0.3.tgz", - "integrity": "sha512-14fwWQtU3pH4dE0dOpdMiWjddcH+QzKIgk1cl8epwSE7yag43k/AD/m4L6+K7DytAOr9gGBe3/EXj9g7cdostg==", - "license": "MIT", - "engines": { - "node": ">=12.20.0" - }, - "peerDependencies": { - "@types/react": ">=18.0.0", - "immer": ">=9.0.6", - "react": ">=18.0.0", - "use-sync-external-store": ">=1.2.0" - }, - "peerDependenciesMeta": { - "@types/react": { - "optional": true - }, - "immer": { - "optional": true - }, - "react": { - "optional": true - }, - "use-sync-external-store": { - "optional": true - } - } - } - } -} diff --git a/agent-node/package.json b/agent-node/package.json deleted file mode 100644 index 73e6462..0000000 --- a/agent-node/package.json +++ /dev/null @@ -1,33 +0,0 @@ -{ - "name": "cronstream-agent-node", - "version": "1.0.0", - "description": "CronStream autonomous off-chain validation & EIP-712 signing agent", - "main": "src/server.js", - "type": "module", - "scripts": { - "start": "node src/server.js", - "dev": "nodemon src/server.js", - "demo": "node scripts/demo.js" - }, - "keywords": [], - "author": "", - "license": "ISC", - "dependencies": { - "@libsql/client": "^0.17.3", - "dotenv": "^16.4.5", - "ethers": "^6.13.4", - "express": "^5.2.1", - "express-rate-limit": "^8.5.2", - "helmet": "^8.2.0", - "jsonwebtoken": "^9.0.3", - "siwe": "^3.0.0", - "x402-express": "^1.2.0" - }, - "devDependencies": { - "nodemon": "^3.1.14" - }, - "overrides": { - "ws": "^8.20.1", - "uuid": "^11.1.1" - } -} diff --git a/agent-node/scripts/demo.js b/agent-node/scripts/demo.js deleted file mode 100644 index 0124012..0000000 --- a/agent-node/scripts/demo.js +++ /dev/null @@ -1,344 +0,0 @@ -#!/usr/bin/env node -/** - * CronStream — Full Autonomous Payroll Demo - * ========================================== - * - * Demonstrates the complete end-to-end flow: - * - * 1. Fund check — verify agent wallet has gas - * 2. Create stream — company wallet creates an on-chain stream - * 3. Register stream — tell the agent which repo to watch - * 4. Simulate webhook — fire a GitHub "PR merged" event at the agent - * 5. Extension — agent verifies + signs + submits on-chain extension - * 6. Balance check — read contractor's withdrawable balance - * 7. Withdraw — contractor withdraws earned USDC - * - * Usage: - * node scripts/demo.js - * - * Required env vars (copy from .env): - * ARBITRUM_RPC_URL - * CONTRACT_ADDRESS_ARB_SEPOLIA (or CONTRACT_ADDRESS) - * DEMO_COMPANY_PRIVATE_KEY — wallet that funds the stream - * DEMO_CONTRACTOR_ADDRESS — recipient wallet address - * DEMO_USDC_ADDRESS — ERC-20 token to stream - * DEMO_GITHUB_REPO — "owner/repo" that the agent watches - * AGENT_URL — https://your-agent.onrender.com - * AGENT_API_KEY — cs_live_ for the company wallet - */ - -import 'dotenv/config'; -import { ethers } from 'ethers'; - -// ─── Config ─────────────────────────────────────────────────────────────────── - -const RPC_URL = process.env.ARBITRUM_RPC_URL; -const CONTRACT_ADDRESS = process.env.CONTRACT_ADDRESS_ARB_SEPOLIA ?? process.env.CONTRACT_ADDRESS; -const COMPANY_KEY = process.env.DEMO_COMPANY_PRIVATE_KEY; -const CONTRACTOR_ADDR = process.env.DEMO_CONTRACTOR_ADDRESS; -const USDC_ADDRESS = process.env.DEMO_USDC_ADDRESS ?? '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d'; -const GITHUB_REPO = process.env.DEMO_GITHUB_REPO ?? 'acme/api-service'; -const AGENT_URL = process.env.AGENT_URL ?? 'http://localhost:3000'; -const AGENT_API_KEY = process.env.AGENT_API_KEY; - -// Stream params — adjust for demo -const RATE_PER_SECOND = ethers.parseUnits('0.001', 6); // 0.001 USDC/s = 86.4 USDC/day -const DURATION_SECONDS = 30000; // ~8.3 hours = 30 USDC deposit -const DEMO_PR_NUMBER = 2; -const DEMO_PR_TITLE = 'Feat/payment module'; - -// ─── ABIs ───────────────────────────────────────────────────────────────────── - -const ROUTER_ABI = [ - 'function createStream(address recipient, address token, uint256 ratePerSecond, uint256 initialDurationSeconds) external returns (bytes32)', - 'function withdrawFromStream(bytes32 streamId, uint256 amount) external', - 'function balanceOf(bytes32 streamId) external view returns (uint256)', - 'function streams(bytes32) external view returns (address sender, address recipient, address token, uint256 ratePerSecond, uint256 startTime, uint256 streamValidUntil, uint256 totalDeposited, uint256 totalWithdrawn, uint256 nonce)', -]; - -const ERC20_ABI = [ - 'function approve(address spender, uint256 amount) external returns (bool)', - 'function balanceOf(address) external view returns (uint256)', - 'function decimals() external view returns (uint8)', -]; - -// ─── Helpers ────────────────────────────────────────────────────────────────── - -function log(emoji, msg) { - console.log(`\n${emoji} ${msg}`); -} - -function sep() { - console.log('\n' + '─'.repeat(60)); -} - -async function agentPost(path, body, auth = true) { - const headers = { 'Content-Type': 'application/json' }; - if (auth && AGENT_API_KEY) headers['Authorization'] = `Bearer ${AGENT_API_KEY}`; - const res = await fetch(`${AGENT_URL}${path}`, { - method: 'POST', - headers, - body: JSON.stringify(body), - }); - const data = await res.json(); - if (!res.ok) throw new Error(`Agent ${path} failed (${res.status}): ${JSON.stringify(data)}`); - return data; -} - -async function sleep(ms) { - return new Promise(r => setTimeout(r, ms)); -} - -// ─── Main ───────────────────────────────────────────────────────────────────── - -async function main() { - // ── Validate env ──────────────────────────────────────────────────────────── - const missing = ['RPC_URL', 'CONTRACT_ADDRESS', 'COMPANY_KEY', 'CONTRACTOR_ADDR', 'AGENT_API_KEY'] - .filter(k => !eval(k)); - if (missing.length) { - console.error('Missing env vars:', missing.join(', ')); - process.exit(1); - } - - console.log('\n╔══════════════════════════════════════════════════════════╗'); - console.log('║ CronStream — Autonomous Payroll Demo ║'); - console.log('╚══════════════════════════════════════════════════════════╝'); - - // ── Setup provider + wallets ───────────────────────────────────────────── - const provider = new ethers.JsonRpcProvider(RPC_URL); - const company = new ethers.Wallet(COMPANY_KEY, provider); - const router = new ethers.Contract(CONTRACT_ADDRESS, ROUTER_ABI, company); - const usdc = new ethers.Contract(USDC_ADDRESS, ERC20_ABI, company); - - sep(); - - // ── Step 1: Fund check ─────────────────────────────────────────────────── - log('🔍', 'Step 1 — Pre-flight checks'); - const [companyEth, companyUsdc, { chainId }] = await Promise.all([ - provider.getBalance(company.address), - usdc.balanceOf(company.address), - provider.getNetwork(), - ]); - - console.log(` Chain: ${chainId} (Arbitrum Sepolia)`); - console.log(` Company: ${company.address}`); - console.log(` Contractor: ${CONTRACTOR_ADDR}`); - console.log(` ETH: ${ethers.formatEther(companyEth)} ETH`); - console.log(` USDC: ${ethers.formatUnits(companyUsdc, 6)} USDC`); - console.log(` Agent: ${AGENT_URL}`); - - const deposit = RATE_PER_SECOND * BigInt(DURATION_SECONDS); - if (companyUsdc < deposit) { - console.error(`\n Insufficient USDC. Need ${ethers.formatUnits(deposit, 6)}, have ${ethers.formatUnits(companyUsdc, 6)}`); - process.exit(1); - } - log('✅', 'Pre-flight passed'); - - // ── Step 2: Approve USDC ───────────────────────────────────────────────── - sep(); - log('🔐', 'Step 2 — Approve USDC spend'); - const approveTx = await usdc.approve(CONTRACT_ADDRESS, deposit * 2n); // headroom for retries - console.log(` Tx: ${approveTx.hash}`); - await approveTx.wait(1); - log('✅', `Approved ${ethers.formatUnits(deposit, 6)} USDC`); - - // ── Step 3: Create stream ──────────────────────────────────────────────── - sep(); - log('🚀', 'Step 3 — Create payment stream'); - console.log(` Rate: ${ethers.formatUnits(RATE_PER_SECOND, 6)} USDC/sec`); - console.log(` Duration: ${DURATION_SECONDS}s (24h)`); - console.log(` Deposit: ${ethers.formatUnits(deposit, 6)} USDC`); - - const createTx = await router.createStream( - CONTRACTOR_ADDR, USDC_ADDRESS, RATE_PER_SECOND, DURATION_SECONDS - ); - console.log(` Tx: ${createTx.hash}`); - const createReceipt = await createTx.wait(1); - - // Parse StreamCreated event to get streamId - const streamCreatedTopic = ethers.id('StreamCreated(bytes32,address,address,uint256)'); - const streamLog = createReceipt.logs.find(l => - l.topics[0] === streamCreatedTopic && - l.address.toLowerCase() === CONTRACT_ADDRESS.toLowerCase() - ); - if (!streamLog) throw new Error('StreamCreated event not found in receipt'); - const streamId = streamLog.topics[1]; - - log('✅', `Stream created`); - console.log(` Stream ID: ${streamId}`); - console.log(` Block: ${createReceipt.blockNumber}`); - - // ── Step 4: Register with agent ────────────────────────────────────────── - sep(); - log('📡', 'Step 4 — Register stream with agent node'); - const regRes = await agentPost('/api/v1/register-stream', { - streamId, - verificationSource: 'github', - verificationTarget: GITHUB_REPO, - recipient: CONTRACTOR_ADDR, - chainId: Number(chainId), - }); - log('✅', `Registered: ${GITHUB_REPO}`); - - // ── Step 5: Simulate contractor doing work (time warp simulation) ──────── - sep(); - log('⏳', 'Step 5 — Contractor delivers work (simulating 1 hour of stream time)'); - console.log(' Waiting 3s to let some USDC accrue...'); - await sleep(3000); - - const accrued = await router.balanceOf(streamId); - log('💰', `Contractor has earned: ${ethers.formatUnits(accrued, 6)} USDC`); - - // ── Step 6: Simulate GitHub webhook (PR merged) ────────────────────────── - sep(); - log('🐙', 'Step 6 — Simulate GitHub webhook: PR merged with passing CI'); - - const prBody = `## Summary\nPayment processing module implemented.\n\n` + - `CronStream-Stream-Id: ${streamId}\nCronStream-Nonce: 0`; - - const webhookPayload = { - action: 'closed', - pull_request: { - number: DEMO_PR_NUMBER, - title: DEMO_PR_TITLE, - merged: true, - body: prBody, - user: { login: 'contractor-dev' }, - base: { ref: 'main' }, - head: { ref: 'feat/payment-module' }, - }, - repository: { - full_name: GITHUB_REPO, - name: GITHUB_REPO.split('/')[1], - owner: { login: GITHUB_REPO.split('/')[0] }, - }, - }; - - let webhookRes; - try { - const res = await fetch(`${AGENT_URL}/api/v1/webhook/github`, { - method: 'POST', - headers: { - 'Content-Type': 'application/json', - 'X-GitHub-Event': 'pull_request', - // Skip HMAC if GITHUB_WEBHOOK_SECRET not set on agent - }, - body: JSON.stringify(webhookPayload), - }); - webhookRes = await res.json(); - if (!res.ok) { - console.warn(' Agent webhook response:', webhookRes); - } - } catch (err) { - console.warn(' Webhook error (may be expected if agent needs real GitHub verification):', err.message); - } - - // ── Step 7: Manual verify + sign path (always works in demo) ───────────── - sep(); - log('🤖', 'Step 7 — Agent: verify milestone + sign extension voucher'); - - const stream = await router.streams(streamId); - const nonce = Number(stream[8]); // nonce field - - let voucher; - try { - const verifyRes = await agentPost('/api/v1/verify-milestone', { - streamId, - contractorAddress: CONTRACTOR_ADDR, - nonce, - verificationSource: 'github', - verificationTarget: GITHUB_REPO, - githubPayload: { - repository: webhookPayload.repository, - pull_request: { ...webhookPayload.pull_request, merged: true }, - workflow_run: { conclusion: 'success' }, - }, - }); - voucher = verifyRes.voucher; - log('✅', 'Milestone verified. Voucher signed by agent.'); - console.log(` Extension: +${voucher.extensionDurationSeconds}s`); - console.log(` Expiry: ${new Date(voucher.expiry * 1000).toISOString()}`); - console.log(` Sig: ${voucher.signature.slice(0, 20)}…`); - } catch (err) { - log('⚠️', `Verify-milestone failed: ${err.message}`); - log('ℹ️', 'This is expected if GITHUB_WEBHOOK_SECRET is set and repo is not real.'); - console.log(' Skipping on-chain extension step.'); - } - - // ── Step 8: Submit extension on-chain (if voucher obtained) ───────────── - if (voucher) { - sep(); - log('⛓', 'Step 8 — Submit extension voucher on-chain'); - - const contractorProvider = new ethers.JsonRpcProvider(RPC_URL); - const routerRead = new ethers.Contract(CONTRACT_ADDRESS, ROUTER_ABI, contractorProvider); - - try { - const extTx = await router.connect(company).extendStreamWindowWithSignature( - voucher.streamId, - voucher.extensionDurationSeconds, - voucher.expiry, - voucher.signature, - ); - console.log(` Tx: ${extTx.hash}`); - const extReceipt = await extTx.wait(1); - log('✅', `Stream extended by ${voucher.extensionDurationSeconds}s`); - console.log(` Block: ${extReceipt.blockNumber}`); - } catch (err) { - log('⚠️', `Extension tx failed: ${err.message}`); - } - } - - // ── Step 9: Contractor withdraws ───────────────────────────────────────── - sep(); - log('💸', 'Step 9 — Contractor withdraws earned USDC'); - - const withdrawable = await router.balanceOf(streamId); - console.log(` Withdrawable: ${ethers.formatUnits(withdrawable, 6)} USDC`); - - if (withdrawable === 0n) { - log('ℹ️', 'Nothing to withdraw yet (stream just started).'); - } else { - const contractorKey = process.env.DEMO_CONTRACTOR_PRIVATE_KEY; - const contractorSigner = contractorKey - ? new ethers.Wallet(contractorKey, provider) - : null; - - if (!contractorSigner) { - log('⚠️', 'DEMO_CONTRACTOR_PRIVATE_KEY not set — skipping withdrawal.'); - log('ℹ️', `Contractor (${CONTRACTOR_ADDR}) can withdraw ${ethers.formatUnits(withdrawable, 6)} USDC manually.`); - } else { - const routerContractor = router.connect(contractorSigner); - - const withdrawTx = await routerContractor.withdrawFromStream(streamId, withdrawable); - console.log(` Tx: ${withdrawTx.hash}`); - await withdrawTx.wait(1); - log('✅', `Contractor received ${ethers.formatUnits(withdrawable, 6)} USDC`); - } - } - - // ── Summary ────────────────────────────────────────────────────────────── - sep(); - console.log('\n╔══════════════════════════════════════════════════════════╗'); - console.log('║ Demo Complete ║'); - console.log('╚══════════════════════════════════════════════════════════╝'); - console.log(` - Stream ID: ${streamId} - Chain: Arbitrum Sepolia (${chainId}) - Contract: ${CONTRACT_ADDRESS} - Agent: ${AGENT_URL} - Repo: ${GITHUB_REPO} - - Flow completed: - ✅ Stream created on-chain - ✅ Stream registered with agent - ✅ Milestone verified by agent - ${voucher ? '✅' : '⏭'} Extension voucher signed + submitted - ${withdrawable > 0n ? '✅' : '⏭'} Contractor withdrawal processed - `); -} - -main().catch(err => { - console.error('\n❌ Demo failed:', err.message); - process.exit(1); -}); diff --git a/agent-node/src/agentSigner.js b/agent-node/src/agentSigner.js deleted file mode 100644 index cd195aa..0000000 --- a/agent-node/src/agentSigner.js +++ /dev/null @@ -1,110 +0,0 @@ -/** - * agentSigner.js - * EIP-712 cryptographic signature engine for the CronStream agent node. - * - * Signs ExtensionVoucher structs using the AGENT_SIGNER_PRIVATE_KEY. - * The recovered signer must match the agentSigner address registered - * in CronStreamRouter.sol for the on-chain verification to pass. - * - * Supports both Arbitrum Sepolia (421614) and Robinhood Chain (46630). - * The EIP-712 domain must be built per-chain — chainId and verifyingContract - * are both part of the domain separator hashed into the signature. - */ - -import { ethers } from 'ethers'; - -// ─── EIP-712 Type Definitions ──────────────────────────────────────────────── - -const EIP712_DOMAIN_BASE = { - name: 'CronStream', - version: '1', -}; - -/** Must exactly match the ExtensionVoucher struct in CronStreamRouter.sol */ -const EXTENSION_VOUCHER_TYPES = { - ExtensionVoucher: [ - { name: 'streamId', type: 'bytes32' }, - { name: 'extensionDurationSeconds', type: 'uint256' }, - { name: 'nonce', type: 'uint256' }, - { name: 'expiry', type: 'uint256' }, - ], -}; - -// ─── Chain → contract address map ──────────────────────────────────────────── - -const CONTRACT_BY_CHAIN = { - 421614: () => process.env.CONTRACT_ADDRESS_ARB_SEPOLIA || process.env.CONTRACT_ADDRESS, - 46630: () => process.env.CONTRACT_ADDRESS_ROBINHOOD || process.env.CONTRACT_ADDRESS, -}; - -// ─── Internal Helpers ──────────────────────────────────────────────────────── - -function getWallet() { - const privateKey = process.env.AGENT_SIGNER_PRIVATE_KEY; - if (!privateKey) throw new Error('[agentSigner] AGENT_SIGNER_PRIVATE_KEY is not set'); - return new ethers.Wallet(privateKey); -} - -/** - * Build the EIP-712 domain for a specific chain. - * chainId and verifyingContract are both included in the domain separator — - * a voucher signed for chain A will be rejected by the contract on chain B. - * - * @param {number} chainId - 421614 or 46630 - */ -function getDomain(chainId) { - const resolver = CONTRACT_BY_CHAIN[chainId]; - if (!resolver) throw new Error(`[agentSigner] Unsupported chainId: ${chainId}`); - - const verifyingContract = resolver(); - if (!verifyingContract) { - throw new Error( - `[agentSigner] No contract address configured for chainId ${chainId}. ` + - `Set CONTRACT_ADDRESS_ARB_SEPOLIA or CONTRACT_ADDRESS_ROBINHOOD in env.` - ); - } - - return { ...EIP712_DOMAIN_BASE, chainId, verifyingContract }; -} - -// ─── Public API ────────────────────────────────────────────────────────────── - -/** - * Sign an ExtensionVoucher using EIP-712 structured data signing. - * - * @param {object} params - * @param {string} params.streamId - bytes32 stream identifier (0x hex) - * @param {number|bigint} params.extensionDurationSeconds - seconds to extend the stream window - * @param {number|bigint} params.nonce - current stream nonce (from on-chain) - * @param {number} params.expiry - unix timestamp after which voucher is invalid - * @param {number} params.chainId - 421614 (Arb Sepolia) or 46630 (Robinhood) - * @returns {Promise} 65-byte ECDSA signature (0x hex) - */ -export async function signExtensionVoucher({ streamId, extensionDurationSeconds, nonce, expiry, chainId }) { - const wallet = getWallet(); - const domain = getDomain(chainId ?? Number(process.env.CHAIN_ID ?? 421614)); - - const value = { - streamId, - extensionDurationSeconds: BigInt(extensionDurationSeconds), - nonce: BigInt(nonce), - expiry: BigInt(expiry), - }; - - const signature = await wallet.signTypedData(domain, EXTENSION_VOUCHER_TYPES, value); - - console.log( - `[agentSigner] Signed voucher | chain=${domain.chainId} stream=${streamId.slice(0, 10)}… nonce=${nonce} expiry=${expiry}` - ); - return signature; -} - -/** - * Return the public address that corresponds to AGENT_SIGNER_PRIVATE_KEY. - * Confirm this matches the agentSigner registered on-chain in CronStreamRouter. - * - * @returns {string} checksummed Ethereum address - */ -export function getSignerAddress() { - return getWallet().address; -} diff --git a/agent-node/src/auth.js b/agent-node/src/auth.js deleted file mode 100644 index f242ef1..0000000 --- a/agent-node/src/auth.js +++ /dev/null @@ -1,213 +0,0 @@ -/** - * auth.js - * SIWE (Sign-In with Ethereum) + JWT auth for the CronStream agent node. - * - * Flow: - * 1. GET /api/v1/auth/nonce → client gets a one-time nonce - * 2. Client builds + signs a SIWE message with their wallet - * 3. POST /api/v1/auth/siwe → agent verifies signature, issues JWT - * 4. Client sends JWT as `Authorization: Bearer ` on protected routes - * 5. verifyJwt middleware extracts address and attaches to req.callerAddress - */ - -import { SiweMessage } from 'siwe'; -import jwt from 'jsonwebtoken'; -import crypto from 'crypto'; -import { paymentMiddleware } from 'x402-express'; - -// ─── Config ─────────────────────────────────────────────────────────────────── - -const JWT_SECRET = process.env.JWT_SECRET ?? (() => { - if (process.env.NODE_ENV === 'production') { - throw new Error('JWT_SECRET must be set in production'); - } - // Dev only — regenerates on each server restart, all sessions invalidated - return crypto.randomBytes(32).toString('hex'); -})(); - -const JWT_TTL_SECONDS = Number(process.env.JWT_TTL_SECONDS ?? 900); // 15 min default -const NONCE_TTL_MS = 5 * 60 * 1000; // 5 min -const ALLOWED_DOMAIN = process.env.SIWE_DOMAIN ?? null; // set in production - -// ─── Nonce store (in-memory, intentionally simple) ──────────────────────────── -// Keys: nonce string → { expiresAt: number } -// Nonces are single-use and short-lived (5 min). -const _nonceStore = new Map(); - -// Cleanup expired nonces every 10 min -setInterval(() => { - const now = Date.now(); - for (const [k, v] of _nonceStore) { - if (now > v.expiresAt) _nonceStore.delete(k); - } -}, 10 * 60 * 1000); - -export function generateNonce() { - const nonce = crypto.randomBytes(16).toString('hex'); - _nonceStore.set(nonce, { expiresAt: Date.now() + NONCE_TTL_MS }); - return nonce; -} - -function consumeNonce(nonce) { - const entry = _nonceStore.get(nonce); - if (!entry) return false; - if (Date.now() > entry.expiresAt) { _nonceStore.delete(nonce); return false; } - _nonceStore.delete(nonce); // single-use - return true; -} - -// ─── SIWE verification ──────────────────────────────────────────────────────── - -export async function verifySiwe({ message, signature }) { - if (!message || !signature) throw new Error('message and signature are required'); - - const siwe = new SiweMessage(message); - - // Verify signature + expiration time built into the SIWE message itself - const { data: fields, success, error } = await siwe.verify({ signature }); - - if (!success) throw new Error(error?.type ?? 'SIWE signature invalid'); - - // Verify nonce was issued by us and hasn't been replayed - if (!consumeNonce(fields.nonce)) { - throw new Error('Nonce invalid or expired — request a fresh nonce and sign again'); - } - - // Optionally lock to our domain in production - if (ALLOWED_DOMAIN && fields.domain !== ALLOWED_DOMAIN) { - throw new Error(`Domain mismatch: expected ${ALLOWED_DOMAIN}, got ${fields.domain}`); - } - - return fields.address; // checksum address -} - -// ─── JWT issuance + verification ────────────────────────────────────────────── - -export function issueJwt(address) { - return jwt.sign( - { sub: address.toLowerCase(), address }, - JWT_SECRET, - { expiresIn: JWT_TTL_SECONDS, algorithm: 'HS256' }, - ); -} - -export function verifyJwtToken(token) { - return jwt.verify(token, JWT_SECRET, { algorithms: ['HS256'] }); -} - -// ─── Express middleware ─────────────────────────────────────────────────────── - -/** - * verifyJwt — require a valid SIWE-issued JWT. - * Sets req.callerAddress to the wallet address encoded in the token. - */ -export function verifyJwt(req, res, next) { - const auth = (req.headers['authorization'] ?? '').trim(); - if (!auth.startsWith('Bearer ')) { - return res.status(401).json({ error: 'Authorization header required (Bearer )' }); - } - - const token = auth.slice('Bearer '.length); - try { - const payload = verifyJwtToken(token); - req.callerAddress = payload.address; - return next(); - } catch (err) { - if (err.name === 'TokenExpiredError') { - return res.status(401).json({ error: 'Session expired — sign in again', expired: true }); - } - return res.status(401).json({ error: 'Invalid session token' }); - } -} - -/** - * verifyJwtOrApiKey — accepts either a valid JWT (SIWE) or an API key (cs_live_...). - * Used on endpoints that need to support both the frontend (JWT) and server-to-server (API key). - */ -export function verifyJwtOrApiKey(getProfileByApiKeyFn) { - return async (req, res, next) => { - const auth = (req.headers['authorization'] ?? '').trim(); - - // API key path - if (auth.startsWith('Bearer cs_live_')) { - const key = auth.slice('Bearer '.length); - try { - const profile = await getProfileByApiKeyFn(key); - if (profile) { - req.callerAddress = profile.address; - req.authMethod = 'apikey'; - return next(); - } - } catch { /* DB unavailable */ } - return res.status(401).json({ error: 'Invalid API key' }); - } - - // JWT path - if (auth.startsWith('Bearer ')) { - const token = auth.slice('Bearer '.length); - try { - const payload = verifyJwtToken(token); - req.callerAddress = payload.address; - req.authMethod = 'jwt'; - return next(); - } catch (err) { - if (err.name === 'TokenExpiredError') { - return res.status(401).json({ error: 'Session expired — sign in again', expired: true }); - } - return res.status(401).json({ error: 'Invalid session token' }); - } - } - - return res.status(401).json({ error: 'Authorization required' }); - }; -} - -/** - * verifyJwtOrApiKeyOrX402 — extends verifyJwtOrApiKey with x402 as a third path. - * - * Priority: - * 1. Bearer cs_live_* → API key auth - * 2. Bearer → SIWE JWT auth - * 3. X-PAYMENT header → x402 pay-per-call (no account needed) - * 4. Otherwise → 401 - * - * When x402 payment is accepted, req.callerAddress is set to 'x402' and - * req.authMethod is 'x402'. Downstream handlers should treat this as an - * anonymous but paid caller with no profile. - */ -export function verifyJwtOrApiKeyOrX402(getProfileByApiKeyFn, { payTo, network, price } = {}) { - const jwtOrKey = verifyJwtOrApiKey(getProfileByApiKeyFn); - - // Build x402 middleware lazily so missing AGENT_PRIVATE_KEY in dev doesn't crash - const x402 = payTo - ? paymentMiddleware(payTo, { - 'POST /api/v1/register-stream': { price: price ?? '$0.05', network: network ?? 'base-sepolia', config: { description: 'Register a CronStream payment stream with the agent' } }, - 'POST /api/v1/verify-milestone': { price: price ?? '$0.10', network: network ?? 'base-sepolia', config: { description: 'Verify a work milestone and get a signed extension voucher' } }, - }) - : null; - - return async (req, res, next) => { - const auth = (req.headers['authorization'] ?? '').trim(); - - // Has Bearer token → try JWT / API key - if (auth.startsWith('Bearer ')) { - return jwtOrKey(req, res, next); - } - - // Has X-PAYMENT header → x402 path - if (req.headers['x-payment']) { - if (!x402) { - return res.status(402).json({ error: 'x402 payments not configured on this agent' }); - } - return x402(req, res, () => { - req.callerAddress = 'x402'; - req.authMethod = 'x402'; - next(); - }); - } - - return res.status(401).json({ - error: 'Authorization required — provide a JWT, API key, or x402 payment', - }); - }; -} diff --git a/agent-node/src/chainSubmitter.js b/agent-node/src/chainSubmitter.js deleted file mode 100644 index 61234ca..0000000 --- a/agent-node/src/chainSubmitter.js +++ /dev/null @@ -1,235 +0,0 @@ -/** - * chainSubmitter.js - * Submits signed ExtensionVouchers on-chain by calling - * CronStreamRouter.extendStreamWindowWithSignature(). - * - * Supports both Arbitrum Sepolia and Robinhood Chain Testnet from a single - * agent instance. Chain is selected per-request via chainId parameter. - */ - -import { ethers } from 'ethers'; - -// ─── ABIs ───────────────────────────────────────────────────────────────────── - -const ROUTER_ABI = [ - 'function extendStreamWindowWithSignature(bytes32 streamId, uint256 extensionDurationSeconds, uint256 expiry, bytes calldata signature) external', - 'event StreamExtended(bytes32 indexed streamId, uint256 newValidUntil, uint256 newNonce)', -]; - -// Read-only ABI for stream data queries -const STREAM_READ_ABI = [ - 'function streams(bytes32) external view returns (address sender, address recipient, address token, uint256 ratePerSecond, uint256 startTime, uint256 streamValidUntil, uint256 totalDeposited, uint256 totalWithdrawn, uint256 nonce, uint256 earnedSnapshot, uint256 lastWindowStart)', - 'function balanceOf(bytes32 streamId) external view returns (uint256)', -]; - -// ─── Chain Config ───────────────────────────────────────────────────────────── -// Each chain entry resolves its RPC URL and contract address from env at runtime. -// Chain-specific vars take priority; CONTRACT_ADDRESS is the legacy fallback. - -const CHAIN_CONFIG = { - // Arbitrum Sepolia - 421614: { - name: 'Arbitrum Sepolia', - rpcUrl: () => process.env.ARBITRUM_RPC_URL || process.env.ARBTRIUM_RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc', - contractAddress: () => process.env.CONTRACT_ADDRESS_ARB_SEPOLIA || process.env.CONTRACT_ADDRESS, - }, - // Robinhood Chain - 46630: { - name: 'Robinhood Chain', - rpcUrl: () => process.env.ROBINHOOD_RPC_URL, - contractAddress: () => process.env.CONTRACT_ADDRESS_ROBINHOOD || process.env.CONTRACT_ADDRESS, - }, -}; - -// ─── Internal Helpers ──────────────────────────────────────────────────────── - -/** - * Build an ethers Wallet connected to the correct chain. - * - * @param {number} chainId - 421614 (Arb Sepolia) or 46630 (Robinhood) - */ -function getConnectedWallet(chainId) { - const privateKey = process.env.AGENT_SIGNER_PRIVATE_KEY; - if (!privateKey) throw new Error('[chainSubmitter] AGENT_SIGNER_PRIVATE_KEY is not set'); - - const chain = CHAIN_CONFIG[chainId]; - if (!chain) throw new Error(`[chainSubmitter] Unsupported chainId: ${chainId}`); - - const rpcUrl = chain.rpcUrl(); - const contractAddress = chain.contractAddress(); - - if (!rpcUrl) throw new Error(`[chainSubmitter] No RPC URL configured for ${chain.name} — set ROBINHOOD_RPC_URL or ARBITRUM_RPC_URL`); - if (!contractAddress) throw new Error(`[chainSubmitter] No contract address for ${chain.name} — set CONTRACT_ADDRESS_ARB_SEPOLIA / CONTRACT_ADDRESS_ROBINHOOD`); - - const provider = new ethers.JsonRpcProvider(rpcUrl); - const wallet = new ethers.Wallet(privateKey, provider); - - return { wallet, contractAddress, chainName: chain.name }; -} - -// ─── Public API ────────────────────────────────────────────────────────────── - -// Serializes all on-chain submissions from the agent signer. Concurrent -// extensions (e.g. one PR fanning out to multiple streams on the same repo) -// otherwise grab the same account nonce and one tx fails with "nonce too low". -// Submissions are infrequent, so a single global queue is simplest and safe. -let _submitChain = Promise.resolve(); - -/** - * Submit an ExtensionVoucher on-chain. - * - * @param {object} params - * @param {string} params.streamId - bytes32 stream ID (0x hex) - * @param {number} params.extensionDurationSeconds - seconds to extend - * @param {number} params.nonce - current on-chain stream nonce - * @param {number} params.expiry - unix timestamp voucher expires - * @param {string} params.signature - 65-byte EIP-712 signature (0x hex) - * @param {number} params.chainId - 421614 or 46630 (defaults to env CHAIN_ID) - * - * @returns {Promise<{ txHash: string, blockNumber: number, gasUsed: string, chainId: number }>} - */ -export async function submitExtension(params) { - // Queue behind any in-flight submission so account nonces never collide. - const run = _submitChain.then(() => _submitExtension(params)); - // Keep the queue alive whether this one succeeds or fails. - _submitChain = run.then(() => {}, () => {}); - return run; -} - -async function _submitExtension({ - streamId, - extensionDurationSeconds, - nonce, - expiry, - signature, - chainId, -}) { - const targetChainId = chainId ?? Number(process.env.CHAIN_ID ?? 421614); - const { wallet, contractAddress, chainName } = getConnectedWallet(targetChainId); - const router = new ethers.Contract(contractAddress, ROUTER_ABI, wallet); - - console.log( - `[chainSubmitter] Submitting on ${chainName} | stream=${streamId} nonce=${nonce} expiry=${expiry}`, - ); - - let gasEstimate; - try { - gasEstimate = await router.extendStreamWindowWithSignature.estimateGas( - streamId, extensionDurationSeconds, expiry, signature, - ); - } catch (err) { - throw new Error(`[chainSubmitter] Gas estimation failed on ${chainName}: ${err.message}`); - } - - const gasLimit = (gasEstimate * 120n) / 100n; - - const tx = await router.extendStreamWindowWithSignature( - streamId, extensionDurationSeconds, expiry, signature, { gasLimit }, - ); - - console.log(`[chainSubmitter] Tx submitted on ${chainName} — hash: ${tx.hash}`); - - const receipt = await tx.wait(1); - - const result = { - txHash: receipt.hash, - blockNumber: receipt.blockNumber, - gasUsed: receipt.gasUsed.toString(), - chainId: targetChainId, - chainName, - }; - - console.log(`[chainSubmitter] ✓ Confirmed on ${chainName} | block=${result.blockNumber} gasUsed=${result.gasUsed}`); - - return result; -} - -/** - * Get agent wallet ETH balance on a specific chain. - * - * @param {number} chainId - 421614 or 46630 - * @returns {Promise<{ balance: string, chainName: string }>} - */ -export async function getAgentBalance(chainId) { - const targetChainId = chainId ?? Number(process.env.CHAIN_ID ?? 421614); - const { wallet, chainName } = getConnectedWallet(targetChainId); - const balance = await wallet.provider.getBalance(wallet.address); - return { - balance: ethers.formatEther(balance), - chainName, - }; -} - -/** - * Get balances on all supported chains. - * - * @returns {Promise} { arbitrumSepolia: "0.05", robinhoodTestnet: "0.01" } - */ -export async function getAllBalances() { - const results = {}; - for (const [chainId, config] of Object.entries(CHAIN_CONFIG)) { - try { - const { balance } = await getAgentBalance(Number(chainId)); - results[config.name] = balance; - } catch { - results[config.name] = 'unavailable'; - } - } - return results; -} - -/** - * Batch-read on-chain data for a list of stream IDs on a given chain. - * Returns one enriched object per stream (null if that stream's read failed). - * - * Result shape (all numeric fields as decimal strings so JSON transport is lossless): - * { - * sender, recipient, token, - * ratePerSecond, startTime, streamValidUntil, - * totalDeposited, totalWithdrawn, nonce, - * balance ← balanceOf result - * } - * - * @param {string[]} streamIds — 0x-prefixed bytes32 IDs - * @param {number} chainId — 421614 (Arb Sepolia) | 46630 (Robinhood) - * @returns {Promise<(object|null)[]>} - */ -export async function readStreamBatch(streamIds, chainId = 421614) { - if (!streamIds.length) return []; - - const chain = CHAIN_CONFIG[chainId]; - if (!chain) return streamIds.map(() => null); - - const rpcUrl = chain.rpcUrl(); - const contractAddress = chain.contractAddress(); - if (!rpcUrl || !contractAddress) return streamIds.map(() => null); - - const provider = new ethers.JsonRpcProvider(rpcUrl); - const contract = new ethers.Contract(contractAddress, STREAM_READ_ABI, provider); - - return Promise.all(streamIds.map(async (id) => { - try { - const [meta, bal] = await Promise.all([ - contract.streams(id), - contract.balanceOf(id), - ]); - return { - sender: meta.sender, - recipient: meta.recipient, - token: meta.token, - ratePerSecond: meta.ratePerSecond.toString(), - startTime: meta.startTime.toString(), - streamValidUntil: meta.streamValidUntil.toString(), - totalDeposited: meta.totalDeposited.toString(), - totalWithdrawn: meta.totalWithdrawn.toString(), - nonce: meta.nonce.toString(), - earnedSnapshot: meta.earnedSnapshot.toString(), - lastWindowStart: meta.lastWindowStart.toString(), - balance: bal.toString(), - }; - } catch (err) { - console.warn(`[readStreamBatch] chain=${chainId} stream=${id}: ${err.message}`); - return null; - } - })); -} diff --git a/agent-node/src/codeDiff.js b/agent-node/src/codeDiff.js deleted file mode 100644 index 6be120b..0000000 --- a/agent-node/src/codeDiff.js +++ /dev/null @@ -1,60 +0,0 @@ -/** - * codeDiff.js - * Shared "is this a real code change?" check for milestone verification. - * - * A merged PR counts as a deliverable if it adds real code — regardless of where - * the repo keeps that code. We use a DENYLIST (not a `src/`+`contracts/` - * allowlist) so it works for every project layout and every developer's - * workflow: Go in cmd/, Python at root, JS in lib/ or packages/, etc. - * - * A file is NOT counted when it is documentation, lockfiles, CI config, build - * config, or binary assets — changes to those alone shouldn't trigger payment. - */ - -const DOC_EXTS = new Set([ - '.md', '.txt', '.mdx', '.rst', '.adoc', -]); - -const ASSET_EXTS = new Set([ - '.png', '.jpg', '.jpeg', '.gif', '.svg', '.ico', '.webp', '.bmp', '.avif', - '.woff', '.woff2', '.ttf', '.eot', '.otf', '.mp4', '.mov', '.webm', '.pdf', -]); - -// Build/CI/config formats — a PR touching only these isn't a code deliverable. -const CONFIG_EXTS = new Set([ - '.json', '.yml', '.yaml', '.toml', '.ini', '.cfg', '.conf', - '.lock', '.env', '.editorconfig', '.log', '.map', '.snap', -]); - -// Non-code metadata files that have no extension. -const IGNORE_BASENAMES = new Set([ - 'license', 'copying', 'notice', 'authors', 'codeowners', 'changelog', -]); - -/** - * @param {string} filename - path as reported by the provider (e.g. "agent-node/src/db.js") - * @returns {boolean} true if the change should count toward a milestone - */ -export function isQualifyingCodeFile(filename) { - if (!filename) return false; - const path = filename.toLowerCase(); - - // CI / workflow definitions never count on their own. - if (path.startsWith('.github/') || path.includes('/.github/')) return false; - - const base = path.split('/').pop(); - if (!base) return false; - - // Dotfiles (.gitignore, .env, .prettierrc, .babelrc, …) are config by convention. - if (base.startsWith('.')) return false; - if (IGNORE_BASENAMES.has(base)) return false; - if (base.endsWith('.lock')) return false; - - const lastDot = base.lastIndexOf('.'); - const ext = lastDot >= 0 ? base.slice(lastDot) : ''; - if (DOC_EXTS.has(ext) || ASSET_EXTS.has(ext) || CONFIG_EXTS.has(ext)) return false; - - // Everything else (.js .ts .tsx .sol .py .go .rs .java .rb .php .c .cpp .sh, - // Dockerfile, Makefile, …) is real code. - return true; -} diff --git a/agent-node/src/db.js b/agent-node/src/db.js deleted file mode 100644 index 2bbdd21..0000000 --- a/agent-node/src/db.js +++ /dev/null @@ -1,855 +0,0 @@ -/** - * db.js - * Turso (libSQL) database client for the CronStream agent node. - * - * Tables: - * processed_extensions — replay guard + full extension history - * stream_registry — streams being monitored with their GitHub repo - * - * No migration tool needed — schema is applied idempotently on startup - * via CREATE TABLE IF NOT EXISTS. Add new columns with ALTER TABLE. - */ - -import { createClient } from '@libsql/client'; -import { encrypt, decrypt, hmacApiKey, isHmacKey, decryptProfile } from './encryption.js'; - -// ─── Client ────────────────────────────────────────────────────────────────── - -let _client = null; - -let _dbUnavailable = false; - -export function getDb() { - if (_client) return _client; - if (_dbUnavailable) return null; - - const url = process.env.TURSO_DATABASE_URL; - const token = process.env.TURSO_AUTH_TOKEN; - - if (!url || !token) { - _dbUnavailable = true; - return null; - } - - _client = createClient({ url, authToken: token }); - return _client; -} - -// ─── Schema ─────────────────────────────────────────────────────────────────── - -const SCHEMA = ` - CREATE TABLE IF NOT EXISTS processed_extensions ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - stream_id TEXT NOT NULL, - repository TEXT NOT NULL, - pr_number INTEGER, - event_ref TEXT, - chain_id INTEGER NOT NULL, - chain_name TEXT NOT NULL, - tx_hash TEXT, - block_number INTEGER, - gas_used TEXT, - voucher_expiry INTEGER, - extension_seconds INTEGER, - created_at INTEGER NOT NULL DEFAULT (unixepoch()) - ); - - CREATE TABLE IF NOT EXISTS stream_registry ( - stream_id TEXT PRIMARY KEY, - chain_id INTEGER NOT NULL, - github_repo TEXT, - verification_source TEXT NOT NULL DEFAULT 'github', - verification_target TEXT, - sender TEXT, - recipient TEXT, - token TEXT, - rate_per_second TEXT, - created_at INTEGER NOT NULL DEFAULT (unixepoch()) - ); - - CREATE TABLE IF NOT EXISTS profiles ( - address TEXT PRIMARY KEY, - username TEXT UNIQUE, - role TEXT NOT NULL CHECK (role IN ('company', 'contractor')), - name TEXT, - github TEXT, - twitter TEXT, - linkedin TEXT, - farcaster TEXT, - website TEXT, - avatar_url TEXT, - api_key TEXT UNIQUE, - created_at INTEGER NOT NULL DEFAULT (unixepoch()), - updated_at INTEGER NOT NULL DEFAULT (unixepoch()) - ); - - CREATE TABLE IF NOT EXISTS waitlist ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - email TEXT NOT NULL UNIQUE, - role TEXT, - company_name TEXT, - created_at INTEGER NOT NULL DEFAULT (unixepoch()) - ); - - CREATE TABLE IF NOT EXISTS repo_installations ( - repo TEXT PRIMARY KEY, - installation_id TEXT NOT NULL, - account TEXT, - created_at INTEGER NOT NULL DEFAULT (unixepoch()) - ); - - -- Verified work earned but not yet applied on-chain. Each verified deliverable - -- is banked here first, then drawn down as the stream's runway + weekly cap - -- allow. Overflow (e.g. PRs beyond the weekly cap) carries into later weeks - -- instead of being lost. - CREATE TABLE IF NOT EXISTS banked_work ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - stream_id TEXT NOT NULL, - source TEXT, - repository TEXT, - event_ref TEXT NOT NULL, - extension_seconds INTEGER NOT NULL, - created_at INTEGER NOT NULL DEFAULT (unixepoch()), - UNIQUE (stream_id, event_ref) - ); -`; - -/** - * Apply schema on startup — safe to call multiple times (idempotent). - * Add new columns here with ALTER TABLE IF NOT EXISTS (SQLite 3.37+). - */ -export async function initDb() { - const db = getDb(); - if (!db) { - console.warn('[db] ⚠ No database configured — TURSO_DATABASE_URL / TURSO_AUTH_TOKEN missing. Profile and lookup features disabled.'); - return; - } - - const statements = SCHEMA - .split(';') - .map(s => s.trim()) - .filter(Boolean); - - for (const sql of statements) { - await db.execute(sql); - } - - // Migrations — idempotent, safe to run on existing DBs - const migrations = [ - 'ALTER TABLE profiles ADD COLUMN api_key TEXT UNIQUE', - 'ALTER TABLE profiles ADD COLUMN twitter TEXT', - 'ALTER TABLE profiles ADD COLUMN linkedin TEXT', - 'ALTER TABLE profiles ADD COLUMN farcaster TEXT', - 'ALTER TABLE profiles ADD COLUMN jira_url TEXT', - 'ALTER TABLE profiles ADD COLUMN jira_email TEXT', - 'ALTER TABLE profiles ADD COLUMN jira_token TEXT', - 'ALTER TABLE profiles ADD COLUMN bitbucket_workspace TEXT', - 'ALTER TABLE profiles ADD COLUMN bitbucket_user TEXT', - 'ALTER TABLE profiles ADD COLUMN bitbucket_password TEXT', - 'ALTER TABLE profiles ADD COLUMN figma_token TEXT', - // stream_registry — verification source support - "ALTER TABLE stream_registry ADD COLUMN verification_source TEXT NOT NULL DEFAULT 'github'", - 'ALTER TABLE stream_registry ADD COLUMN verification_target TEXT', - "ALTER TABLE profiles ADD COLUMN display_currency TEXT NOT NULL DEFAULT 'USD'", - 'ALTER TABLE stream_registry ADD COLUMN rate_per_second TEXT', - // contract_address — tracks which deployed contract owns this stream so - // re-deploys don't orphan old stream records - 'ALTER TABLE stream_registry ADD COLUMN contract_address TEXT', - // event_ref — stores PR#N or commit SHA; replaces pr_number as the replay-guard key - 'ALTER TABLE processed_extensions ADD COLUMN event_ref TEXT', - // OAuth tokens — companies connect their platforms via OAuth instead of pasting credentials - 'ALTER TABLE profiles ADD COLUMN github_installation_id TEXT', - 'ALTER TABLE profiles ADD COLUMN github_oauth_token TEXT', - 'ALTER TABLE profiles ADD COLUMN atlassian_access_token TEXT', - 'ALTER TABLE profiles ADD COLUMN atlassian_refresh_token TEXT', - 'ALTER TABLE profiles ADD COLUMN atlassian_cloud_id TEXT', - 'ALTER TABLE profiles ADD COLUMN atlassian_expires_at INTEGER', - 'ALTER TABLE profiles ADD COLUMN bitbucket_oauth_token TEXT', - 'ALTER TABLE profiles ADD COLUMN bitbucket_refresh_token TEXT', - 'ALTER TABLE profiles ADD COLUMN figma_oauth_token TEXT', - 'ALTER TABLE profiles ADD COLUMN figma_refresh_token TEXT', - // period_seconds — each stream's configured period length, so the agent - // knows when a full period has elapsed (pay-in-arrears verification model) - 'ALTER TABLE stream_registry ADD COLUMN period_seconds INTEGER', - // hours_per_week — used to calculate per-event extension (one day of work) - // instead of extending by the full period on every verified event - 'ALTER TABLE stream_registry ADD COLUMN hours_per_week REAL', - // extension_seconds — records how long each verified extension actually was, - // so the agent can enforce the weekly hours cap across multiple events - 'ALTER TABLE processed_extensions ADD COLUMN extension_seconds INTEGER', - // jira_webhook_ids — comma-separated dynamic webhook IDs registered for this profile. - // Used to refresh expiring webhooks when Jira sends webhook_expiry_warning. - 'ALTER TABLE profiles ADD COLUMN jira_webhook_ids TEXT', - ]; - for (const sql of migrations) { - try { await db.execute(sql); } catch { /* column already exists */ } - } - - // ── processed_extensions rebuild ─────────────────────────────────────────── - // Older DBs created pr_number as INTEGER NOT NULL with UNIQUE(stream_id, - // repository, pr_number). Webhook extensions pass prNumber=null, so the row - // violated NOT NULL and was silently dropped by INSERT OR IGNORE — extensions - // never reached the activity feed. Rebuild with pr_number nullable; event_ref - // is the real dedup key. Atomic via batch — rolls back if anything fails. - try { - const info = await db.execute('PRAGMA table_info(processed_extensions)'); - const prCol = info.rows.find(r => r.name === 'pr_number'); - if (prCol && Number(prCol.notnull) === 1) { - console.log('[db] Rebuilding processed_extensions (pr_number → nullable, event_ref key)…'); - await db.batch([ - `CREATE TABLE processed_extensions_new ( - id INTEGER PRIMARY KEY AUTOINCREMENT, - stream_id TEXT NOT NULL, repository TEXT NOT NULL, - pr_number INTEGER, event_ref TEXT, - chain_id INTEGER NOT NULL, chain_name TEXT NOT NULL, - tx_hash TEXT, block_number INTEGER, gas_used TEXT, - voucher_expiry INTEGER, extension_seconds INTEGER, - created_at INTEGER NOT NULL DEFAULT (unixepoch()) - )`, - `INSERT INTO processed_extensions_new - (id, stream_id, repository, pr_number, event_ref, chain_id, chain_name, - tx_hash, block_number, gas_used, voucher_expiry, extension_seconds, created_at) - SELECT id, stream_id, repository, pr_number, event_ref, chain_id, chain_name, - tx_hash, block_number, gas_used, voucher_expiry, extension_seconds, created_at - FROM processed_extensions`, - 'DROP TABLE processed_extensions', - 'ALTER TABLE processed_extensions_new RENAME TO processed_extensions', - ], 'write'); - console.log('[db] ✓ processed_extensions rebuilt'); - } - } catch (err) { - console.warn('[db] processed_extensions rebuild skipped:', err.message); - } - // Dedup on the real key. SQLite allows multiple NULL event_refs (legacy rows). - try { - await db.execute('CREATE UNIQUE INDEX IF NOT EXISTS idx_proc_ext_event ON processed_extensions (stream_id, repository, event_ref)'); - } catch { /* duplicate legacy data — replay guard still dedups */ } - - console.log('[db] ✓ Schema initialized'); -} - -// ─── Replay Guard ───────────────────────────────────────────────────────────── - -/** - * Check if a (streamId, repository, prNumber) has already been processed. - */ -export async function isAlreadyProcessed(streamId, repository, eventRef) { - const db = getDb(); - if (!db) return false; - const result = await db.execute({ - sql: `SELECT 1 FROM processed_extensions - WHERE stream_id = ? AND repository = ? - AND (event_ref = ? OR (event_ref IS NULL AND pr_number = CAST(? AS INTEGER))) - LIMIT 1`, - args: [streamId, repository, String(eventRef), String(eventRef)], - }); - return result.rows.length > 0; -} - -/** - * Record a successfully processed extension. - * - * @param {object} params - * @param {string} params.streamId - * @param {string} params.repository - "owner/repo" - * @param {number} params.prNumber - * @param {number} params.chainId - * @param {string} params.chainName - * @param {string} [params.txHash] - * @param {number} [params.blockNumber] - * @param {string} [params.gasUsed] - * @param {number} [params.voucherExpiry] - */ -export async function recordExtension({ - streamId, repository, prNumber, eventRef, - chainId, chainName, - txHash, blockNumber, gasUsed, voucherExpiry, extensionSeconds, -}) { - const db = getDb(); - if (!db) return; - const ref = eventRef ?? (prNumber != null ? `PR#${prNumber}` : null); - // pr_number is a legacy NOT NULL column; event_ref is now the real identity. - // Webhook sources (Jira/Bitbucket/Figma and the GitHub extendFromEvent path) - // pass prNumber=null, which would violate NOT NULL and get silently dropped by - // INSERT OR IGNORE. Derive a non-null, distinct value: parse the number out of - // the event ref (e.g. GH#PR#5 -> 5), else fall back to a timestamp. - let prNum = prNumber; - if (prNum == null) { - const m = (ref ?? '').match(/(\d+)/); - prNum = m ? Number(m[1]) : Math.floor(Date.now() / 1000); - } - await db.execute({ - sql: `INSERT OR IGNORE INTO processed_extensions - (stream_id, repository, pr_number, event_ref, chain_id, chain_name, - tx_hash, block_number, gas_used, voucher_expiry, extension_seconds) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)`, - args: [ - streamId, repository, prNum, ref, - chainId, chainName, - txHash ?? null, blockNumber ?? null, gasUsed ?? null, voucherExpiry ?? null, - extensionSeconds ?? null, - ], - }); -} - -/** - * Sum of extension_seconds granted to a stream since weekStartTime. - * Used to enforce the weekly hours cap — contractor cannot earn more than - * hours_per_week × 3600 seconds of streaming per rolling 7-day window. - */ -export async function getWeeklyExtendedSeconds(streamId, weekStartTime) { - const db = getDb(); - if (!db) return 0; - const result = await db.execute({ - sql: `SELECT COALESCE(SUM(extension_seconds), 0) AS total - FROM processed_extensions - WHERE stream_id = ? AND created_at >= ?`, - args: [streamId, weekStartTime], - }); - return Number(result.rows[0]?.total ?? 0); -} - -// ─── Banked Work ────────────────────────────────────────────────────────────── -// Verified deliverables earned but not yet applied on-chain. The agent drains -// these as the stream's runway and weekly cap allow, so bursty work (or work -// beyond the weekly cap) is carried forward rather than lost. - -/** Queue a verified deliverable. Idempotent on (stream_id, event_ref). */ -export async function bankWork({ streamId, source, repository, eventRef, extensionSeconds }) { - const db = getDb(); - if (!db) return; - await db.execute({ - sql: `INSERT OR IGNORE INTO banked_work (stream_id, source, repository, event_ref, extension_seconds) - VALUES (?, ?, ?, ?, ?)`, - args: [streamId, source ?? null, repository ?? null, String(eventRef), Number(extensionSeconds)], - }); -} - -/** Has this event already been banked (still waiting to apply)? */ -export async function isWorkBanked(streamId, eventRef) { - const db = getDb(); - if (!db) return false; - const r = await db.execute({ - sql: 'SELECT 1 FROM banked_work WHERE stream_id = ? AND event_ref = ? LIMIT 1', - args: [streamId, String(eventRef)], - }); - return r.rows.length > 0; -} - -/** Banked entries for a stream, oldest first (FIFO drain order). */ -export async function getBankedWork(streamId) { - const db = getDb(); - if (!db) return []; - const r = await db.execute({ - sql: 'SELECT * FROM banked_work WHERE stream_id = ? ORDER BY created_at ASC, id ASC', - args: [streamId], - }); - return r.rows; -} - -/** Remove a banked entry once it has been applied on-chain. */ -export async function deleteBankedWork(id) { - const db = getDb(); - if (!db) return; - await db.execute({ sql: 'DELETE FROM banked_work WHERE id = ?', args: [id] }); -} - -/** Registry rows for every stream that has at least one banked entry waiting. */ -export async function getStreamsWithBankedWork() { - const db = getDb(); - if (!db) return []; - const r = await db.execute( - `SELECT s.* FROM stream_registry s - WHERE s.stream_id IN (SELECT DISTINCT stream_id FROM banked_work)`, - ); - return r.rows; -} - -// ─── Stream Repo Lookup ─────────────────────────────────────────────────────── - -/** - * Find all streams registered for a given GitHub repo. - * Used by the webhook to auto-identify streams from push events - * without requiring metadata in the commit message. - */ -export async function getStreamsByRepo(repo) { - const db = getDb(); - if (!db) return []; - const result = await db.execute({ - sql: `SELECT * FROM stream_registry - WHERE verification_target = ? OR github_repo = ?`, - args: [repo, repo], - }); - return result.rows; -} - -/** - * Fetch streams registered for a given verification source + target. - * Used by Jira/Bitbucket/Figma webhook handlers to route events. - * - * @param {string} source - 'jira' | 'bitbucket' | 'figma' - * @param {string} target - project key, repo slug, or file ID - */ -export async function getStreamsBySource(source, target) { - const db = getDb(); - if (!db) return []; - const result = await db.execute({ - sql: `SELECT * FROM stream_registry - WHERE verification_source = ? AND verification_target = ?`, - args: [source, target], - }); - return result.rows; -} - -// ─── Extension History ──────────────────────────────────────────────────────── - -/** - * Fetch recent extensions — for the /health endpoint or a dashboard. - * - * @param {number} limit - max rows to return (default 20) - */ -export async function getRecentExtensions(limit = 20) { - const db = getDb(); - if (!db) return []; - const result = await db.execute({ - sql: `SELECT * FROM processed_extensions - ORDER BY created_at DESC LIMIT ?`, - args: [limit], - }); - return result.rows; -} - -/** - * Total number of extensions ever recorded. - */ -export async function getExtensionCount() { - const db = getDb(); - if (!db) return 0; - const result = await db.execute( - 'SELECT COUNT(*) AS count FROM processed_extensions', - ); - return Number(result.rows[0].count); -} - -// ─── Stream Registry ────────────────────────────────────────────────────────── - -/** - * Register a stream so the agent knows which source + target to verify. - * - * @param {object} params - * @param {string} params.streamId - * @param {number} params.chainId - * @param {string} [params.githubRepo] — kept for backwards compatibility - * @param {string} [params.verificationSource] — 'github' | 'jira' | 'bitbucket' | 'figma' - * @param {string} [params.verificationTarget] — repo path, ticket key, Figma URL, etc. - * @param {string} [params.sender] — company wallet address - * @param {string} [params.recipient] — contractor wallet address - * @param {string} [params.token] — ERC-20 token address - * @param {string} [params.ratePerSecond] — immutable rate (BigInt as string) - * @param {number} [params.hoursPerWeek] — hrs/week from the create stream form - */ -export async function registerStream({ - streamId, chainId, githubRepo, - verificationSource, verificationTarget, - sender, recipient, token, ratePerSecond, - contractAddress, periodSeconds, hoursPerWeek, -}) { - const db = getDb(); - if (!db) return; - - // verificationTarget falls back to githubRepo for legacy callers - const finalTarget = verificationTarget ?? githubRepo ?? null; - const finalSource = verificationSource ?? 'github'; - - await db.execute({ - sql: `INSERT INTO stream_registry - (stream_id, chain_id, github_repo, verification_source, verification_target, sender, recipient, token, rate_per_second, contract_address, period_seconds, hours_per_week) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT (stream_id) DO UPDATE SET - verification_source = COALESCE(excluded.verification_source, stream_registry.verification_source), - verification_target = COALESCE(excluded.verification_target, stream_registry.verification_target), - github_repo = COALESCE(excluded.github_repo, stream_registry.github_repo), - sender = COALESCE(excluded.sender, stream_registry.sender), - recipient = COALESCE(excluded.recipient, stream_registry.recipient), - token = COALESCE(excluded.token, stream_registry.token), - rate_per_second = COALESCE(excluded.rate_per_second, stream_registry.rate_per_second), - contract_address = COALESCE(excluded.contract_address, stream_registry.contract_address), - period_seconds = COALESCE(excluded.period_seconds, stream_registry.period_seconds), - hours_per_week = COALESCE(excluded.hours_per_week, stream_registry.hours_per_week)`, - args: [ - streamId, chainId, - githubRepo ?? finalTarget, - finalSource, - finalTarget, - sender ?? null, recipient ?? null, token ?? null, - ratePerSecond != null ? String(ratePerSecond) : null, - contractAddress ?? null, - periodSeconds != null ? Number(periodSeconds) : null, - hoursPerWeek != null ? Number(hoursPerWeek) : null, - ], - }); -} - -/** - * Return all streams where address is sender OR recipient. - * Used by the frontend instead of scanning blockchain events. - */ -export async function getStreamsForAddress(address) { - const db = getDb(); - if (!db) return []; - const result = await db.execute({ - sql: `SELECT * FROM stream_registry - WHERE LOWER(sender) = ? OR LOWER(recipient) = ? - ORDER BY created_at DESC`, - args: [address.toLowerCase(), address.toLowerCase()], - }); - return result.rows; -} - -/** - * Look up a stream's registered metadata. - */ -export async function getStream(streamId) { - const db = getDb(); - if (!db) return null; - const result = await db.execute({ - sql: 'SELECT * FROM stream_registry WHERE stream_id = ? LIMIT 1', - args: [streamId], - }); - return result.rows[0] ?? null; -} - -// ─── Profile Registry ───────────────────────────────────────────────────────── - -/** - * Upsert a user profile keyed by wallet address. - */ -export async function upsertProfile({ address, username, role, name, github, twitter, linkedin, farcaster, website, avatarUrl, apiKey, - jiraUrl, jiraEmail, jiraToken, bitbucketWorkspace, bitbucketUser, bitbucketPassword, figmaToken, displayCurrency }) { - const db = getDb(); - if (!db) return; - - // ── Encrypt sensitive fields before writing ───────────────────────────────── - const encJiraToken = jiraToken ? encrypt(jiraToken) : null; - const encBitbucketPassword = bitbucketPassword ? encrypt(bitbucketPassword) : null; - const encFigmaToken = figmaToken ? encrypt(figmaToken) : null; - - // API key: store as HMAC so it is never written to disk in plaintext. - // NULL signals "clear the key"; undefined means "don't touch it". - const hashedApiKey = apiKey ? hmacApiKey(apiKey) : null; - const apiKeySentinel = apiKey === null ? 'clear' : 'keep'; - - await db.execute({ - sql: `INSERT INTO profiles - (address, username, role, name, github, twitter, linkedin, farcaster, website, avatar_url, api_key, - jira_url, jira_email, jira_token, bitbucket_workspace, bitbucket_user, bitbucket_password, figma_token, display_currency) - VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?) - ON CONFLICT(address) DO UPDATE SET - username = COALESCE(excluded.username, profiles.username), - name = excluded.name, - github = excluded.github, - twitter = excluded.twitter, - linkedin = excluded.linkedin, - farcaster = excluded.farcaster, - website = excluded.website, - avatar_url = COALESCE(excluded.avatar_url, profiles.avatar_url), - api_key = CASE WHEN excluded.api_key IS NULL AND ? = 'clear' - THEN NULL - ELSE COALESCE(excluded.api_key, profiles.api_key) END, - jira_url = COALESCE(excluded.jira_url, profiles.jira_url), - jira_email = COALESCE(excluded.jira_email, profiles.jira_email), - jira_token = COALESCE(excluded.jira_token, profiles.jira_token), - bitbucket_workspace = COALESCE(excluded.bitbucket_workspace, profiles.bitbucket_workspace), - bitbucket_user = COALESCE(excluded.bitbucket_user, profiles.bitbucket_user), - bitbucket_password = COALESCE(excluded.bitbucket_password, profiles.bitbucket_password), - figma_token = COALESCE(excluded.figma_token, profiles.figma_token), - display_currency = COALESCE(excluded.display_currency, profiles.display_currency), - updated_at = unixepoch()`, - args: [ - address.toLowerCase(), - username ? username.toLowerCase().trim() : null, - role, - name ?? null, - github ?? null, - twitter ?? null, - linkedin ?? null, - farcaster ?? null, - website ?? null, - avatarUrl ?? null, - hashedApiKey, // HMAC of api_key, or null - jiraUrl ?? null, - jiraEmail ?? null, - encJiraToken, // AES-256-GCM encrypted - bitbucketWorkspace ?? null, - bitbucketUser ?? null, - encBitbucketPassword, // AES-256-GCM encrypted - encFigmaToken, // AES-256-GCM encrypted - displayCurrency ?? 'USD', - apiKeySentinel, - ], - }); -} - -/** - * Look up a profile by API key. - * The key is HMAC'd before the DB lookup — the plaintext is never stored. - * Also handles legacy plaintext keys stored before encryption was enabled. - */ -export async function getProfileByApiKey(apiKey) { - const db = getDb(); - if (!db) return null; - - // Try HMAC lookup first (new keys) - try { - const hashed = hmacApiKey(apiKey); - const result = await db.execute({ - sql: 'SELECT * FROM profiles WHERE api_key = ? LIMIT 1', - args: [hashed], - }); - if (result.rows.length > 0) return decryptProfile(result.rows[0]); - } catch { - // ENCRYPTION_KEY missing — fall through to legacy plaintext lookup - } - - // Legacy fallback: plaintext api_key (pre-encryption rows) - // Only matches if the stored value is NOT an HMAC digest - const result = await db.execute({ - sql: "SELECT * FROM profiles WHERE api_key = ? AND api_key NOT LIKE 'hmac:v1:%' LIMIT 1", - args: [apiKey], - }); - return result.rows.length > 0 ? decryptProfile(result.rows[0]) : null; -} - -/** - * Fetch a profile by wallet address (case-insensitive). - * Returns with sensitive fields decrypted — ready for agent use. - */ -export async function getProfile(address) { - const db = getDb(); - if (!db) return null; - const result = await db.execute({ - sql: 'SELECT * FROM profiles WHERE address = ? LIMIT 1', - args: [address.toLowerCase()], - }); - return result.rows.length > 0 ? decryptProfile(result.rows[0]) : null; -} - -/** - * Fetch a single profile by username (case-insensitive). - * Used for public contractor profile pages (/p/:username). - */ -export async function getProfileByUsername(username) { - const db = getDb(); - if (!db) return null; - const result = await db.execute({ - sql: 'SELECT * FROM profiles WHERE username = ? LIMIT 1', - args: [username.toLowerCase()], - }); - return result.rows.length > 0 ? decryptProfile(result.rows[0]) : null; -} - -/** - * Save Jira dynamic webhook IDs to a profile so they can be refreshed on expiry warning. - * Merges with any existing IDs (deduplicates). - */ -export async function saveJiraWebhookIds(address, newIds = []) { - const db = getDb(); - if (!db || !newIds.length) return; - const profile = await getProfile(address); - const existing = (profile?.jira_webhook_ids ?? '').split(',').filter(Boolean); - const merged = [...new Set([...existing, ...newIds.map(String)])].join(','); - await db.execute({ - sql: 'UPDATE profiles SET jira_webhook_ids = ? WHERE address = ?', - args: [merged, address.toLowerCase()], - }); -} - -/** - * Find a profile whose jira_webhook_ids contains the given webhook ID. - * Used to locate the right access token when a webhook_expiry_warning arrives. - */ -export async function getProfileByJiraWebhookId(webhookId) { - const db = getDb(); - if (!db) return null; - const result = await db.execute({ - sql: `SELECT * FROM profiles WHERE jira_webhook_ids LIKE ? OR jira_webhook_ids LIKE ? OR jira_webhook_ids LIKE ? OR jira_webhook_ids = ? LIMIT 1`, - args: [`${webhookId},%`, `%,${webhookId},%`, `%,${webhookId}`, String(webhookId)], - }); - return result.rows.length > 0 ? decryptProfile(result.rows[0]) : null; -} - -/** - * Search profiles by GitHub handle (partial) or name (partial). - * Used for contractor lookup. - */ -export async function searchProfiles({ github, username, name, role } = {}) { - const db = getDb(); - if (!db) return []; - const conditions = []; - const args = []; - - if (username) { conditions.push('username = ?'); args.push(username.toLowerCase()); } - if (github) { conditions.push('github LIKE ?'); args.push(`%${github.toLowerCase()}%`); } - if (name) { conditions.push('name LIKE ?'); args.push(`%${name}%`); } - if (role) { conditions.push('role = ?'); args.push(role); } - - const where = conditions.length ? `WHERE ${conditions.join(' AND ')}` : ''; - const result = await db.execute({ - sql: `SELECT * FROM profiles ${where} ORDER BY updated_at DESC LIMIT 20`, - args, - }); - // Decrypt each row — searchProfiles is used internally, not for HTTP responses - return result.rows.map(row => decryptProfile(row)); -} - -/** - * Check if a username is already taken. - */ -export async function isUsernameTaken(username, excludeAddress = null) { - const db = getDb(); - if (!db) return false; - const result = await db.execute({ - sql: excludeAddress - ? 'SELECT 1 FROM profiles WHERE username = ? AND address != ? LIMIT 1' - : 'SELECT 1 FROM profiles WHERE username = ? LIMIT 1', - args: excludeAddress - ? [username.toLowerCase(), excludeAddress.toLowerCase()] - : [username.toLowerCase()], - }); - return result.rows.length > 0; -} - -// ─── Waitlist ───────────────────────────────────────────────────────────────── - -/** - * Add an email to the waitlist. - * Returns { inserted: true } on success, { inserted: false, reason: 'duplicate' } if already signed up. - */ -export async function addToWaitlist({ email, role, companyName }) { - const db = getDb(); - if (!db) throw new Error('Database unavailable'); - try { - await db.execute({ - sql: 'INSERT INTO waitlist (email, role, company_name) VALUES (?, ?, ?)', - args: [email.toLowerCase().trim(), role ?? null, companyName ?? null], - }); - return { inserted: true }; - } catch (err) { - if (err.message?.includes('UNIQUE') || err.message?.includes('unique')) { - return { inserted: false, reason: 'duplicate' }; - } - throw err; - } -} - -/** - * Get all streams that have a verification target — used by the milestone poller. - */ -export async function getAllMonitoredStreams() { - const db = getDb(); - if (!db) return []; - const result = await db.execute( - `SELECT * FROM stream_registry - WHERE sender IS NOT NULL - ORDER BY created_at DESC`, - ); - return result.rows; -} - -/** - * Get the most recent extension timestamp for a stream. - * Returns unix seconds or null if never extended. - */ -export async function getLastExtensionTime(streamId) { - const db = getDb(); - if (!db) return null; - const result = await db.execute({ - sql: `SELECT created_at FROM processed_extensions - WHERE stream_id = ? ORDER BY created_at DESC LIMIT 1`, - args: [streamId], - }); - return result.rows[0]?.created_at ?? null; -} - -// ─── OAuth Token Storage ────────────────────────────────────────────────────── - -const OAUTH_COLS = { - github: { access: 'github_oauth_token', installationId: 'github_installation_id' }, - atlassian: { access: 'atlassian_access_token', refresh: 'atlassian_refresh_token', cloudId: 'atlassian_cloud_id', expiresAt: 'atlassian_expires_at' }, - bitbucket: { access: 'bitbucket_oauth_token', refresh: 'bitbucket_refresh_token' }, - figma: { access: 'figma_oauth_token', refresh: 'figma_refresh_token' }, -}; - -export async function saveOAuthTokens(address, provider, { accessToken, refreshToken, cloudId, expiresAt } = {}) { - const db = getDb(); - if (!db) return; - const cols = OAUTH_COLS[provider]; - if (!cols) throw new Error(`Unknown OAuth provider: ${provider}`); - - const sets = [`${cols.access} = ?`]; - const args = [encrypt(accessToken)]; - - if (cols.refresh && refreshToken != null) { sets.push(`${cols.refresh} = ?`); args.push(encrypt(refreshToken)); } - if (cols.cloudId && cloudId != null) { sets.push(`${cols.cloudId} = ?`); args.push(cloudId); } - if (cols.expiresAt && expiresAt != null) { sets.push(`${cols.expiresAt} = ?`); args.push(expiresAt); } - sets.push('updated_at = unixepoch()'); - args.push(address.toLowerCase()); - - await db.execute({ sql: `UPDATE profiles SET ${sets.join(', ')} WHERE address = ?`, args }); -} - -// ─── GitHub App installation → repo mapping ─────────────────────────────────── -// A GitHub App installation can belong to EITHER the company or the contractor -// (whoever owns the repo). We map repos to their installation so the agent can -// always mint a token for any repo it's installed on, regardless of which -// CronStream account triggered the install. - -export async function saveRepoInstallation(repo, installationId, account) { - const db = getDb(); - if (!db) return; - await db.execute({ - sql: `INSERT INTO repo_installations (repo, installation_id, account) - VALUES (?, ?, ?) - ON CONFLICT (repo) DO UPDATE SET - installation_id = excluded.installation_id, - account = excluded.account`, - args: [repo.toLowerCase(), String(installationId), account ?? null], - }); -} - -export async function removeRepoInstallation(repo) { - const db = getDb(); - if (!db) return; - await db.execute({ sql: 'DELETE FROM repo_installations WHERE repo = ?', args: [repo.toLowerCase()] }); -} - -export async function getInstallationIdForRepo(repo) { - const db = getDb(); - if (!db || !repo) return null; - const result = await db.execute({ - sql: 'SELECT installation_id FROM repo_installations WHERE repo = ? LIMIT 1', - args: [repo.toLowerCase()], - }); - return result.rows[0]?.installation_id ?? null; -} - -export async function disconnectOAuth(address, provider) { - const db = getDb(); - if (!db) return; - const cols = OAUTH_COLS[provider]; - if (!cols) throw new Error(`Unknown OAuth provider: ${provider}`); - - const nullCols = Object.values(cols); - const sets = nullCols.map(c => `${c} = NULL`).join(', '); - await db.execute({ sql: `UPDATE profiles SET ${sets}, updated_at = unixepoch() WHERE address = ?`, args: [address.toLowerCase()] }); -} - -/** - * Count total waitlist signups. - */ -export async function getWaitlistCount() { - const db = getDb(); - if (!db) return 0; - const result = await db.execute('SELECT COUNT(*) as count FROM waitlist'); - return Number(result.rows[0]?.count ?? 0); -} diff --git a/agent-node/src/encryption.js b/agent-node/src/encryption.js deleted file mode 100644 index 92a3669..0000000 --- a/agent-node/src/encryption.js +++ /dev/null @@ -1,205 +0,0 @@ -/** - * encryption.js - * AES-256-GCM field-level encryption for sensitive profile credentials. - * - * Environment variable required: - * ENCRYPTION_KEY — 64 hex chars (32 bytes) or 44 base64 chars (32 bytes) - * Generate one with: node -e "console.log(require('crypto').randomBytes(32).toString('hex'))" - * - * Storage format: - * enc:v1::: - * ^— self-describing and versioned so future key rotations can be detected - * - * API keys use HMAC-SHA256 (one-way) so the DB value can be compared without - * ever storing or decrypting the plaintext key. - * hmac:v1: - * - * Behaviour when ENCRYPTION_KEY is missing: - * • encrypt() — throws. New credentials cannot be saved without the key. - * • decrypt() — throws if the stored value is already encrypted (enc:v1: prefix). - * Returns plaintext as-is for legacy un-encrypted values (migration path). - * • hmacApiKey() — throws. API keys cannot be verified without the key. - * - * If the key is lost all encrypted values become unreadable — users must - * re-enter their credentials. This is intentional. - */ - -import crypto from 'node:crypto'; - -const ALGORITHM = 'aes-256-gcm'; -const ENC_PREFIX = 'enc:v1:'; -const HMAC_PREFIX = 'hmac:v1:'; - -// ─── Key loading ────────────────────────────────────────────────────────────── - -function getEncryptionKey() { - const raw = process.env.ENCRYPTION_KEY; - if (!raw) { - throw new Error( - '[encryption] ENCRYPTION_KEY is not set in environment. ' + - 'Generate one with: node -e "console.log(require(\'crypto\').randomBytes(32).toString(\'hex\'))"', - ); - } - - // Accept 64-char hex (32 bytes) or 44-char base64 (32 bytes) - let buf; - if (raw.length === 64 && /^[0-9a-fA-F]+$/.test(raw)) { - buf = Buffer.from(raw, 'hex'); - } else { - buf = Buffer.from(raw, 'base64'); - } - - if (buf.length !== 32) { - throw new Error('[encryption] ENCRYPTION_KEY must be 32 bytes — use the hex generator above'); - } - - return buf; -} - -// ─── AES-256-GCM encrypt / decrypt ─────────────────────────────────────────── - -/** - * Encrypt a plaintext string. - * @param {string|null} plaintext - * @returns {string|null} "enc:v1:::" or null - * @throws if ENCRYPTION_KEY is not set - */ -export function encrypt(plaintext) { - if (plaintext === null || plaintext === undefined || plaintext === '') return null; - - const key = getEncryptionKey(); - const iv = crypto.randomBytes(12); // 96-bit IV — GCM recommended size - const cipher = crypto.createCipheriv(ALGORITHM, key, iv); - - const encrypted = Buffer.concat([ - cipher.update(String(plaintext), 'utf8'), - cipher.final(), - ]); - const tag = cipher.getAuthTag(); // 128-bit auth tag - - return ( - ENC_PREFIX + - iv.toString('hex') + ':' + - tag.toString('hex') + ':' + - encrypted.toString('hex') - ); -} - -/** - * Decrypt a stored value. - * • If the value starts with "enc:v1:" it is decrypted (requires the key). - * • If the value is plaintext (no prefix) it is returned as-is — migration path - * for values stored before encryption was enabled. - * • Returns null for null/undefined/empty input. - * @throws if the key is missing and the value is encrypted - * @throws if GCM auth tag validation fails (tampered or wrong key) - */ -export function decrypt(ciphertext) { - if (ciphertext === null || ciphertext === undefined || ciphertext === '') return null; - - // Legacy plaintext — return unchanged - if (!String(ciphertext).startsWith(ENC_PREFIX)) return String(ciphertext); - - const key = getEncryptionKey(); // throws if key missing - const rest = String(ciphertext).slice(ENC_PREFIX.length); - const parts = rest.split(':'); - - if (parts.length !== 3) { - throw new Error('[encryption] Malformed ciphertext — expected enc:v1:::'); - } - - const [ivHex, tagHex, dataHex] = parts; - const iv = Buffer.from(ivHex, 'hex'); - const tag = Buffer.from(tagHex, 'hex'); - const data = Buffer.from(dataHex,'hex'); - - const decipher = crypto.createDecipheriv(ALGORITHM, key, iv); - decipher.setAuthTag(tag); - - return Buffer.concat([decipher.update(data), decipher.final()]).toString('utf8'); -} - -// ─── HMAC — for API key lookup ──────────────────────────────────────────────── - -/** - * Produce a deterministic HMAC-SHA256 of an API key. - * Stored in the DB so the original key is never written to disk. - * Lookup: hash the incoming key and compare against the stored hash. - * - * @param {string} apiKey - * @returns {string} "hmac:v1:" - * @throws if ENCRYPTION_KEY is not set - */ -export function hmacApiKey(apiKey) { - const key = getEncryptionKey(); - const hash = crypto.createHmac('sha256', key).update(String(apiKey)).digest('hex'); - return HMAC_PREFIX + hash; -} - -/** - * Check whether a stored api_key value looks like an HMAC digest. - * Used to distinguish hashed keys from legacy plaintext keys during migration. - */ -export function isHmacKey(stored) { - return typeof stored === 'string' && stored.startsWith(HMAC_PREFIX); -} - -// ─── Convenience: decrypt a whole profile row ───────────────────────────────── - -/** - * Decrypt all sensitive fields in a profile row returned from the DB. - * Returns a new object — does not mutate the input. - * - * Sensitive fields (encrypted): jira_token, bitbucket_password, figma_token - * One-way field (not returned): api_key (HMAC digest — never expose to clients) - */ -export function decryptProfile(row) { - if (!row) return null; - - const decrypted = { ...row }; - - for (const field of [ - 'jira_token', 'bitbucket_password', 'figma_token', - 'github_oauth_token', 'atlassian_access_token', 'atlassian_refresh_token', - 'bitbucket_oauth_token', 'bitbucket_refresh_token', 'figma_oauth_token', 'figma_refresh_token', - ]) { - try { - decrypted[field] = decrypt(row[field]); - } catch (err) { - // Key missing or tampered — null out the field so the agent can report a config error - decrypted[field] = null; - console.warn(`[encryption] Could not decrypt ${field}: ${err.message}`); - } - } - - // Record whether a key exists BEFORE deleting the digest — callers use has_api_key for UI - decrypted.has_api_key = !!decrypted.api_key; - // Never return the HMAC digest to callers — it's useless outside of DB lookups - delete decrypted.api_key; - - return decrypted; -} - -/** - * Strip credentials from a profile before sending it to the frontend. - * Call this in any HTTP response that returns a profile object. - */ -export function publicProfile(row) { - if (!row) return null; - const { - // eslint-disable-next-line no-unused-vars - api_key, jira_token, jira_email, bitbucket_password, figma_token, - github_oauth_token, github_installation_id, atlassian_access_token, atlassian_refresh_token, - bitbucket_oauth_token, bitbucket_refresh_token, figma_oauth_token, figma_refresh_token, - ...pub - } = row; - return { - ...pub, - has_api_key: row.has_api_key ?? false, - github_connected: !!row.github_installation_id || !!row.github_oauth_token, - atlassian_connected: !!row.atlassian_access_token, - bitbucket_connected: !!row.bitbucket_oauth_token || (!!row.bitbucket_workspace && !!row.bitbucket_user && !!row.bitbucket_password), - figma_connected: !!row.figma_oauth_token || !!row.figma_token, - jira_connected: !!row.atlassian_access_token || (!!row.jira_url && !!row.jira_email && !!row.jira_token), - }; -} diff --git a/agent-node/src/githubApp.js b/agent-node/src/githubApp.js deleted file mode 100644 index 3874b08..0000000 --- a/agent-node/src/githubApp.js +++ /dev/null @@ -1,107 +0,0 @@ -/** - * githubApp.js - * GitHub App authentication — mints short-lived installation access tokens. - * - * A GitHub App authenticates in two steps: - * 1. Sign a JWT with the App's private key (RS256) — proves we are the App. - * 2. Exchange that JWT for an installation access token scoped to one - * installation (one company's selected repos). This token is what we - * use to call the GitHub API (read PR files, check CI, etc.). - * - * Installation tokens expire after 1 hour — we cache them per-installation - * and refresh a minute before expiry. - * - * Env: - * GITHUB_APP_ID — numeric App ID - * GITHUB_APP_PRIVATE_KEY — PEM private key (with literal \n or real newlines) - */ - -import jwt from 'jsonwebtoken'; - -const _tokenCache = new Map(); // installationId → { token, expiresAt } - -function getPrivateKey() { - let raw = process.env.GITHUB_APP_PRIVATE_KEY; - if (!raw) throw new Error('GITHUB_APP_PRIVATE_KEY not set'); - - // Strip accidental surrounding quotes (Render sometimes keeps them) - raw = raw.trim().replace(/^["']|["']$/g, ''); - - // Render / .env store multi-line secrets with literal \n — normalise to real newlines - if (raw.includes('\\n')) raw = raw.replace(/\\n/g, '\n'); - - // Base64-encoded PEM fallback — if no PEM header, try decoding - if (!raw.includes('-----BEGIN')) { - try { - const decoded = Buffer.from(raw, 'base64').toString('utf8'); - if (decoded.includes('-----BEGIN')) raw = decoded; - } catch { /* not base64 */ } - } - - return raw; -} - -/** - * Sign a short-lived App JWT (valid 10 min) used to request installation tokens. - */ -function mintAppJwt() { - const appId = process.env.GITHUB_APP_ID; - if (!appId) throw new Error('GITHUB_APP_ID not set'); - - const now = Math.floor(Date.now() / 1000); - return jwt.sign( - { - iat: now - 60, // backdate 60s to tolerate clock drift - exp: now + 9 * 60, // GitHub max is 10 min - iss: appId, - }, - getPrivateKey(), - { algorithm: 'RS256' }, - ); -} - -/** - * Get a cached or fresh installation access token for a given installation ID. - * Returns null if the App is not configured (so callers fall back gracefully). - */ -export async function getInstallationToken(installationId) { - if (!installationId) return null; - if (!process.env.GITHUB_APP_ID || !process.env.GITHUB_APP_PRIVATE_KEY) return null; - - const cached = _tokenCache.get(String(installationId)); - if (cached && cached.expiresAt - 60_000 > Date.now()) { - return cached.token; - } - - try { - const appJwt = mintAppJwt(); - const res = await fetch( - `https://api.github.com/app/installations/${installationId}/access_tokens`, - { - method: 'POST', - headers: { - Authorization: `Bearer ${appJwt}`, - Accept: 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - }, - signal: AbortSignal.timeout(8000), - }, - ); - - if (!res.ok) { - const body = await res.text(); - console.warn(`[githubApp] Could not mint installation token: ${res.status} ${body.slice(0, 200)}`); - return null; - } - - const data = await res.json(); - _tokenCache.set(String(installationId), { - token: data.token, - expiresAt: new Date(data.expires_at).getTime(), - }); - return data.token; - } catch (err) { - console.warn(`[githubApp] Error minting installation token: ${err.message}`); - return null; - } -} diff --git a/agent-node/src/publicApi.js b/agent-node/src/publicApi.js deleted file mode 100644 index 863e08b..0000000 --- a/agent-node/src/publicApi.js +++ /dev/null @@ -1,436 +0,0 @@ -/** - * publicApi.js - * ───────────────────────────────────────────────────────────────────────────── - * CronStream Public API — pay-per-call via the x402 Payment Protocol. - * - * External callers (AI agents, scripts, other dApps) pay a small USDC amount - * on Base for each request. No API key needed — any wallet can pay and call. - * Payments go directly to the agent's signing wallet. - * - * Streams are registered automatically when created on-chain (see streamListener.js). - * This API lets external parties query and interact with those streams. - * - * Endpoints: - * GET /api/public/info — free — pricing + usage instructions - * POST /api/public/verify-milestone — $0.10 — verify work + signed voucher - * GET /api/public/stream/:id — $0.01 — read stream registry entry - * - * Env vars: - * AGENT_PRIVATE_KEY — already required for signing; payment address is derived from it - * X402_NETWORK — 'base-sepolia' (default) | 'base' for mainnet - * - * x402 spec: https://x402.org - */ - -import { Router } from 'express'; -import { paymentMiddleware } from 'x402-express'; - -// ─── Per-wallet rate limiter ────────────────────────────────────────────────── -// Sliding window: max LIMIT calls per WINDOW_MS per payer wallet. -// Falls back to IP when no X-PAYMENT header is present (e.g. probe requests). -const RATE_LIMIT = parseInt(process.env.X402_RATE_LIMIT ?? '60', 10); // calls per window -const WINDOW_MS = parseInt(process.env.X402_WINDOW_MS ?? '60000', 10); // 1 minute - -const _rateBuckets = new Map(); // key → [timestamp, ...] - -function extractPayerKey(req) { - const payment = req.headers['x-payment']; - if (payment) { - try { - const decoded = JSON.parse(Buffer.from(payment, 'base64').toString('utf8')); - const from = decoded?.payload?.authorization?.from ?? decoded?.payload?.from; - if (from) return from.toLowerCase(); - } catch { /* fall through to IP */ } - } - return req.ip ?? 'unknown'; -} - -function x402RateLimit(req, res, next) { - const key = extractPayerKey(req); - const now = Date.now(); - const timestamps = (_rateBuckets.get(key) ?? []).filter(t => now - t < WINDOW_MS); - if (timestamps.length >= RATE_LIMIT) { - return res.status(429).json({ - error: 'Rate limit exceeded', - limit: RATE_LIMIT, - windowMs: WINDOW_MS, - retryAfter: Math.ceil((timestamps[0] + WINDOW_MS - now) / 1000), - }); - } - timestamps.push(now); - _rateBuckets.set(key, timestamps); - // Prune old entries every ~500 requests to avoid memory growth - if (_rateBuckets.size > 500) { - for (const [k, ts] of _rateBuckets) { - if (ts.every(t => now - t >= WINDOW_MS)) _rateBuckets.delete(k); - } - } - next(); -} -import { verifyMilestone, VerificationError } from './verifyMilestone.js'; -import { signExtensionVoucher, - getSignerAddress } from './agentSigner.js'; -import { getStream, getStreamsForAddress, getProfile, getInstallationIdForRepo } from './db.js'; -import { readStreamBatch } from './chainSubmitter.js'; -import { getInstallationToken } from './githubApp.js'; - -const router = Router(); - -// ─── Payment address + network ──────────────────────────────────────────────── -// Payments go to the same wallet the agent uses for EIP-712 signing. -// No extra env var needed — derived from AGENT_PRIVATE_KEY at startup. - -let PAY_TO; -try { - PAY_TO = getSignerAddress(); -} catch { - console.warn('[publicApi] ⚠ AGENT_PRIVATE_KEY not set — x402 payments disabled (dev mode)'); -} - -const NETWORK = process.env.X402_NETWORK ?? 'base-sepolia'; - -// ─── x402 middleware ────────────────────────────────────────────────────────── -// Returns HTTP 402 with full payment instructions when no valid X-PAYMENT header -// is present. The client pays on-chain and retries with the proof header. - -router.use( - PAY_TO - ? paymentMiddleware(PAY_TO, { - 'POST /verify-milestone': { - price: '$0.10', - network: NETWORK, - config: { description: 'Verify a work milestone and get a signed stream-extension voucher' }, - }, - 'GET /stream/*': { - price: '$0.01', - network: NETWORK, - config: { description: 'Read a stream entry from the CronStream registry' }, - }, - 'GET /balance/*': { - price: '$0.01', - network: NETWORK, - config: { description: 'Read live on-chain withdrawable balance for a stream' }, - }, - 'GET /streams/company/*': { - price: '$0.05', - network: NETWORK, - config: { description: 'List all streams a company has opened' }, - }, - 'GET /streams/contractor/*': { - price: '$0.05', - network: NETWORK, - config: { description: 'List all streams a contractor is receiving' }, - }, - }) - : (_req, _res, next) => next(), -); - -// Rate limiter runs after payment verification — only paying wallets are counted -router.use(x402RateLimit); - -// ─── GET /api/public/info ───────────────────────────────────────────────────── -// Free — no payment required. Describes the API so callers know what to expect. - -router.get('/info', (_req, res) => { - res.json({ - name: 'CronStream Public API', - version: '2.0.0', - protocol: 'x402', - network: NETWORK, - payTo: PAY_TO ?? 'not configured', - pricing: { - 'POST /api/public/verify-milestone': '$0.10 USDC per call', - 'GET /api/public/stream/:id': '$0.01 USDC per call', - 'GET /api/public/balance/:id': '$0.01 USDC per call', - 'GET /api/public/streams/company/:address': '$0.05 USDC per call', - 'GET /api/public/streams/contractor/:address': '$0.05 USDC per call', - }, - rateLimit: { - limit: RATE_LIMIT, - windowMs: WINDOW_MS, - key: 'per payer wallet address', - }, - usage: - 'Include a valid X-PAYMENT header with each paid request. ' + - 'Hit any paid endpoint without one to receive a 402 with full payment instructions. ' + - 'Streams are registered automatically when created on-chain.', - spec: 'https://x402.org', - }); -}); - -// ─── POST /api/public/verify-milestone ─────────────────────────────────────── -// Verify that a contractor completed a milestone and return a signed EIP-712 -// extension voucher the stream owner can submit on-chain to extend the stream. -// -// Body: -// streamId string — 0x-prefixed bytes32 -// contractorAddress string — 0x-prefixed 20-byte wallet -// nonce number — current on-chain stream nonce -// verificationSource string — 'github' | 'jira' | 'bitbucket' | 'figma' -// verificationTarget string — repo path, Jira key, Figma URL, etc. -// githubPayload object — (optional) raw GitHub PR/workflow event body -// -// Returns: -// { success: true, voucher: { streamId, extensionDurationSeconds, expiry, signature } } - -router.post('/verify-milestone', async (req, res) => { - const { - streamId, contractorAddress, - verificationSource, verificationTarget, githubPayload, - } = req.body; - - if (!streamId || !/^0x[a-fA-F0-9]{64}$/.test(streamId)) { - return res.status(400).json({ error: 'Invalid streamId — must be 0x-prefixed 32-byte hex' }); - } - if (!contractorAddress || !/^0x[a-fA-F0-9]{40}$/.test(contractorAddress)) { - return res.status(400).json({ error: 'Invalid contractorAddress' }); - } - - try { - const streamRecord = await getStream(streamId); - const chainId = Number(streamRecord?.chain_id ?? 421614); - - // Always read nonce from chain — never trust the caller to supply it. - // A stale nonce would produce a voucher that reverts on-chain. - const [onChain] = await readStreamBatch([streamId], chainId); - if (!onChain || onChain.sender === '0x0000000000000000000000000000000000000000') { - return res.status(404).json({ error: 'Stream not found on-chain' }); - } - const nonce = Number(onChain.nonce); - - const source = verificationSource ?? streamRecord?.verification_source ?? 'github'; - const target = verificationTarget ?? streamRecord?.verification_target ?? streamRecord?.github_repo; - - // Load the company's credentials (Jira/Bitbucket/Figma tokens) and resolve a - // GitHub App installation token by repo so private repos verify correctly. - let companyCredentials = null; - if (streamRecord?.sender) { - try { companyCredentials = await getProfile(streamRecord.sender); } catch { /* none */ } - } - let githubToken = null; - if (source === 'github' && target) { - const installationId = (await getInstallationIdForRepo(target)) - ?? companyCredentials?.github_installation_id - ?? null; - if (installationId) githubToken = await getInstallationToken(installationId); - } - - // verifyMilestone THROWS a VerificationError on failure; returns a summary on success. - let verifyResult; - try { - verifyResult = await verifyMilestone({ - streamId, contractorAddress, - verificationSource: source, - verificationTarget: target, - githubPayload, - companyCredentials, - githubToken, - }); - } catch (err) { - if (err instanceof VerificationError) { - return res.status(422).json({ success: false, error: err.message, failedLayer: err.layer }); - } - throw err; - } - - // Window length = the stream's configured period; expiry gives 1 hour to submit on-chain - const extensionDurationSeconds = Number(streamRecord?.period_seconds ?? 604800); - const expiry = Math.floor(Date.now() / 1000) + 3600; - - const signature = await signExtensionVoucher({ - streamId, nonce, chainId, - extensionDurationSeconds, - expiry, - }); - - const voucher = { streamId, extensionDurationSeconds, expiry, signature }; - return res.json({ success: true, verification: verifyResult, voucher, nonce }); - } catch (err) { - console.error('[publicApi:verify-milestone]', err); - return res.status(500).json({ error: 'Verification error', detail: err.message }); - } -}); - -// ─── GET /api/public/stream/:id ─────────────────────────────────────────────── -// Registry entry + live on-chain state. Never returns integration credentials. - -router.get('/stream/:id', async (req, res) => { - const { id } = req.params; - if (!/^0x[a-fA-F0-9]{64}$/.test(id)) { - return res.status(400).json({ error: 'Invalid stream ID' }); - } - try { - const [stream, onChain] = await Promise.all([ - getStream(id), - (async () => { - const rec = await getStream(id); - const chainId = Number(rec?.chain_id ?? 421614); - const [r] = await readStreamBatch([id], chainId); - return r; - })(), - ]); - if (!stream) return res.status(404).json({ error: 'Stream not found' }); - - const { stream_id, chain_id, verification_source, verification_target, - sender, recipient, token, created_at } = stream; - - return res.json({ - streamId: stream_id, - chainId: chain_id, - verificationSource: verification_source, - verificationTarget: verification_target, - sender: onChain?.sender ?? sender, - recipient: onChain?.recipient ?? recipient, - token: onChain?.token ?? token, - ratePerSecond: onChain?.ratePerSecond ?? null, - streamValidUntil: onChain?.streamValidUntil ?? null, - totalDeposited: onChain?.totalDeposited ?? null, - totalWithdrawn: onChain?.totalWithdrawn ?? null, - nonce: onChain?.nonce ?? null, - balance: onChain?.balance ?? null, - createdAt: created_at, - }); - } catch (err) { - console.error('[publicApi:stream]', err); - return res.status(500).json({ error: 'Failed to fetch stream' }); - } -}); - -// ─── GET /api/public/balance/:id ───────────────────────────────────────────── -// Live withdrawable balance for a stream. AI agents use this to check how much -// a contractor can claim right now before deciding to trigger a payment action. - -router.get('/balance/:id', async (req, res) => { - const { id } = req.params; - if (!/^0x[a-fA-F0-9]{64}$/.test(id)) { - return res.status(400).json({ error: 'Invalid stream ID' }); - } - try { - const record = await getStream(id); - const chainId = Number(record?.chain_id ?? 421614); - const [onChain] = await readStreamBatch([id], chainId); - - if (!onChain || onChain.sender === '0x0000000000000000000000000000000000000000') { - return res.status(404).json({ error: 'Stream not found on-chain' }); - } - - const now = Math.floor(Date.now() / 1000); - const isActive = now < Number(onChain.streamValidUntil); - - return res.json({ - streamId: id, - chainId, - balance: onChain.balance, - ratePerSecond: onChain.ratePerSecond, - streamValidUntil: onChain.streamValidUntil, - totalDeposited: onChain.totalDeposited, - totalWithdrawn: onChain.totalWithdrawn, - isActive, - }); - } catch (err) { - console.error('[publicApi:balance]', err); - return res.status(500).json({ error: 'Failed to read balance' }); - } -}); - -// ─── GET /api/public/streams/company/:address ──────────────────────────────── -// All streams a company has opened, enriched with live on-chain state. -// Useful for treasury AI agents auditing payroll obligations. - -router.get('/streams/company/:address', async (req, res) => { - const { address } = req.params; - if (!/^0x[a-fA-F0-9]{40}$/i.test(address)) { - return res.status(400).json({ error: 'Invalid wallet address' }); - } - try { - const dbStreams = await getStreamsForAddress(address); - const sent = dbStreams.filter(s => s.sender?.toLowerCase() === address.toLowerCase()); - - const byChain = {}; - for (const s of sent) { - const cid = s.chain_id ?? 421614; - (byChain[cid] ??= []).push(s); - } - - const enriched = []; - for (const [chainId, group] of Object.entries(byChain)) { - const onChain = await readStreamBatch(group.map(s => s.stream_id), Number(chainId)); - for (let i = 0; i < group.length; i++) { - const oc = onChain[i] ?? {}; - const now = Math.floor(Date.now() / 1000); - enriched.push({ - streamId: group[i].stream_id, - chainId: Number(chainId), - recipient: oc.recipient ?? group[i].recipient, - token: oc.token ?? group[i].token, - ratePerSecond: oc.ratePerSecond ?? null, - streamValidUntil: oc.streamValidUntil ?? null, - totalDeposited: oc.totalDeposited ?? null, - totalWithdrawn: oc.totalWithdrawn ?? null, - balance: oc.balance ?? null, - isActive: oc.streamValidUntil ? now < Number(oc.streamValidUntil) : false, - verificationSource: group[i].verification_source, - verificationTarget: group[i].verification_target, - }); - } - } - - return res.json({ address, count: enriched.length, streams: enriched }); - } catch (err) { - console.error('[publicApi:streams/company]', err); - return res.status(500).json({ error: 'Failed to fetch streams' }); - } -}); - -// ─── GET /api/public/streams/contractor/:address ───────────────────────────── -// All streams a contractor is receiving, with live balance and earning rate. -// Useful for AI agents checking earnings before advising on withdrawal timing. - -router.get('/streams/contractor/:address', async (req, res) => { - const { address } = req.params; - if (!/^0x[a-fA-F0-9]{40}$/i.test(address)) { - return res.status(400).json({ error: 'Invalid wallet address' }); - } - try { - const dbStreams = await getStreamsForAddress(address); - const received = dbStreams.filter(s => s.recipient?.toLowerCase() === address.toLowerCase()); - - const byChain = {}; - for (const s of received) { - const cid = s.chain_id ?? 421614; - (byChain[cid] ??= []).push(s); - } - - const enriched = []; - for (const [chainId, group] of Object.entries(byChain)) { - const onChain = await readStreamBatch(group.map(s => s.stream_id), Number(chainId)); - for (let i = 0; i < group.length; i++) { - const oc = onChain[i] ?? {}; - const now = Math.floor(Date.now() / 1000); - enriched.push({ - streamId: group[i].stream_id, - chainId: Number(chainId), - sender: oc.sender ?? group[i].sender, - token: oc.token ?? group[i].token, - ratePerSecond: oc.ratePerSecond ?? null, - streamValidUntil: oc.streamValidUntil ?? null, - totalDeposited: oc.totalDeposited ?? null, - totalWithdrawn: oc.totalWithdrawn ?? null, - balance: oc.balance ?? null, - isActive: oc.streamValidUntil ? now < Number(oc.streamValidUntil) : false, - verificationSource: group[i].verification_source, - verificationTarget: group[i].verification_target, - }); - } - } - - const totalClaimable = enriched.reduce((acc, s) => acc + BigInt(s.balance ?? 0), 0n).toString(); - - return res.json({ address, count: enriched.length, totalClaimable, streams: enriched }); - } catch (err) { - console.error('[publicApi:streams/contractor]', err); - return res.status(500).json({ error: 'Failed to fetch streams' }); - } -}); - -export default router; diff --git a/agent-node/src/replayGuard.js b/agent-node/src/replayGuard.js deleted file mode 100644 index 5ec1910..0000000 --- a/agent-node/src/replayGuard.js +++ /dev/null @@ -1,55 +0,0 @@ -/** - * replayGuard.js - * Lightweight in-memory replay protection for the CronStream agent node. - * - * Prevents the same (streamId, prNumber, repository) tuple from triggering - * more than one extension. The Set is cleared on process restart — acceptable - * because: (a) the on-chain nonce also prevents replay on the contract side, - * and (b) the agent restarts infrequently in production. - * - * For a persistent guard across restarts, swap the Set for a SQLite / Redis store. - */ - -/** @type {Set} Stores "::" keys */ -const _seen = new Set(); - -/** - * Build the dedup key for a given (streamId, repository, prNumber) tuple. - * - * @param {string} streamId - bytes32 stream ID (0x hex) - * @param {string} repository - "owner/repo" - * @param {number} prNumber - pull request number - * @returns {string} - */ -function _key(streamId, repository, prNumber) { - return `${streamId}:${repository}:${prNumber}`; -} - -/** - * Returns true if this (streamId, repo, prNumber) has already been processed. - * - * @param {string} streamId - * @param {string} repository - * @param {number} prNumber - * @returns {boolean} - */ -export function alreadyProcessed(streamId, repository, prNumber) { - return _seen.has(_key(streamId, repository, prNumber)); -} - -/** - * Mark this (streamId, repo, prNumber) as processed so future calls to - * alreadyProcessed() return true. - * - * @param {string} streamId - * @param {string} repository - * @param {number} prNumber - */ -export function markProcessed(streamId, repository, prNumber) { - _seen.add(_key(streamId, repository, prNumber)); -} - -/** How many unique extension events have been recorded since startup. */ -export function processedCount() { - return _seen.size; -} diff --git a/agent-node/src/scannerBlock.js b/agent-node/src/scannerBlock.js deleted file mode 100644 index 8a56896..0000000 --- a/agent-node/src/scannerBlock.js +++ /dev/null @@ -1,76 +0,0 @@ -/** - * scannerBlock.js - * Drops automated vulnerability scanners and bans their IP. - * - * Bots constantly probe public servers for leaked secrets and known exploits - * (/.env, /.aws/credentials, /wp-login.php, …). The first time an IP requests a - * path that no legitimate client would ever hit, we: - * 1. ban that IP for BAN_MS (default 24h), and - * 2. respond 404 (reveals nothing about what exists). - * Every subsequent request from a banned IP is dropped immediately with 403, - * before routing, logging, or any DB/chain work. - * - * Mounted FIRST in the middleware chain so banned traffic costs almost nothing. - */ - -const BAN_MS = parseInt(process.env.SCANNER_BAN_MS ?? String(24 * 60 * 60 * 1000), 10); -const MAX_TRACKED = 50_000; // hard cap so the ban map can't grow unbounded - -// ip -> unix ms when the ban expires -const _banned = new Map(); - -// Paths that are part of OUR surface and must never be flagged. -function isOwnSurface(path) { - return ( - path === '/' || - path === '/health' || - path === '/favicon.ico' || - path.startsWith('/api/') || - path.startsWith('/.well-known/') - ); -} - -// Probe signatures no real client of this API would ever request. -const SCANNER_PATTERNS = [ - /aws\.env|credentials|id_rsa|\.pem(\b|$)|\.pfx(\b|$)|\.p12(\b|$)/i, - /\.(env|sql|bak|backup|old|swp|ini|key)(\b|$|\.)/i, - /wp-admin|wp-login|wp-content|xmlrpc\.php|phpmyadmin|adminer|administrator/i, - /\/vendor\/|\/actuator|server-status|cgi-bin|\/telescope|_profiler|\/console(\b|\/)/i, - /config\.(php|json|ya?ml)|settings\.py|web\.config|\/\.git\//i, -]; - -function isScannerPath(rawPath) { - const path = (rawPath || '').toLowerCase(); - if (isOwnSurface(path)) return false; - // Any hidden dotfile at the root is a probe: /.env, /.git, /.aws/credentials … - if (/^\/\.[a-z]/.test(path)) return true; - return SCANNER_PATTERNS.some(re => re.test(path)); -} - -function sweepExpired(now) { - for (const [ip, expiry] of _banned) { - if (expiry <= now) _banned.delete(ip); - } -} - -export function scannerBlock(req, res, next) { - const now = Date.now(); - const ip = req.ip || req.socket?.remoteAddress || 'unknown'; - - // Already banned? Drop immediately (no logging, no routing). - const expiry = _banned.get(ip); - if (expiry !== undefined) { - if (expiry > now) return res.status(403).json({ error: 'Forbidden' }); - _banned.delete(ip); // ban lapsed - } - - if (isScannerPath(req.path)) { - // Avoid unbounded memory if we're being carpet-scanned from many IPs. - if (_banned.size >= MAX_TRACKED) sweepExpired(now); - _banned.set(ip, now + BAN_MS); - console.warn(`[scanner] Banned ${ip} for ${Math.round(BAN_MS / 3600000)}h — probed ${req.method} ${req.path}`); - return res.status(404).json({ error: 'Not found' }); - } - - next(); -} diff --git a/agent-node/src/server.js b/agent-node/src/server.js deleted file mode 100644 index 6f54b08..0000000 --- a/agent-node/src/server.js +++ /dev/null @@ -1,1908 +0,0 @@ -/** - * server.js - * CronStream Agent Node — Express REST API - * - * Endpoints: - * GET /health — liveness + signer address + chain balance - * POST /api/v1/verify-milestone — manual: 3-layer verify → sign → return voucher - * POST /api/v1/webhook/github — autonomous: GitHub event → verify → sign → submit on-chain - */ - -import 'dotenv/config'; -import express from 'express'; -import crypto from 'crypto'; -import helmet from 'helmet'; -import rateLimit from 'express-rate-limit'; - -import { verifyMilestone, VerificationError } from './verifyMilestone.js'; -import { verifyGitHubWebhook, verifyJiraWebhook, verifyBitbucketWebhook, verifyFigmaWebhook, extendFromEvent, checkStream, drainAllBankedWork } from './verificationEngine.js'; -import { signExtensionVoucher, getSignerAddress } from './agentSigner.js'; -import { submitExtension, getAllBalances, readStreamBatch } from './chainSubmitter.js'; -import { initDb, isAlreadyProcessed, recordExtension, getExtensionCount, registerStream, getStream, getStreamsByRepo, getStreamsBySource, getStreamsForAddress, getDb, upsertProfile, getProfile, getProfileByUsername, getProfileByApiKey, searchProfiles, isUsernameTaken, addToWaitlist, getWaitlistCount, saveOAuthTokens, disconnectOAuth, saveRepoInstallation, removeRepoInstallation, saveJiraWebhookIds, getProfileByJiraWebhookId, getInstallationIdForRepo, getBankedWork } from './db.js'; -import { publicProfile } from './encryption.js'; -import publicApiRouter from './publicApi.js'; -import { startStreamListeners } from './streamListener.js'; -import { generateNonce, verifySiwe, issueJwt, verifyJwt, verifyJwtOrApiKey, verifyJwtOrApiKeyOrX402 } from './auth.js'; -import { getInstallationToken } from './githubApp.js'; -import { scannerBlock } from './scannerBlock.js'; - -const app = express(); - -// Render (and most cloud hosts) sit behind a reverse proxy that sets X-Forwarded-For. -// Tell Express to trust the first proxy hop so rate-limit reads the real client IP. -app.set('trust proxy', 1); - -// Drop and ban automated vulnerability scanners before anything else runs. -app.use(scannerBlock); - -// ─── Security headers (Helmet) ──────────────────────────────────────────────── -// This is a public API server — disable policies that block cross-origin reads. -app.use(helmet({ - contentSecurityPolicy: false, // no HTML served - crossOriginEmbedderPolicy: false, // not an embedder - crossOriginResourcePolicy: false, // must allow cross-origin fetch from frontend - crossOriginOpenerPolicy: false, -})); - -// ─── CORS ───────────────────────────────────────────────────────────────────── -// Tighten with CORS_ORIGIN in production — defaults to * in dev only. -// In production set CORS_ORIGIN=https://your-frontend.vercel.app -// CORS_ORIGIN can be a single origin or comma-separated list. -// Trailing slashes are stripped — browsers never include them in the Origin header. -// Example: CORS_ORIGIN=https://cronstream.vercel.app,http://localhost:5173 -const ALLOWED_ORIGINS = (process.env.CORS_ORIGIN ?? '*') - .split(',') - .map(o => o.trim().replace(/\/+$/, '')) - .filter(Boolean); - -app.use((req, res, next) => { - const requestOrigin = req.headers.origin ?? ''; - const isWildcard = ALLOWED_ORIGINS.includes('*'); - const isAllowed = isWildcard || ALLOWED_ORIGINS.includes(requestOrigin); - - if (isAllowed) { - res.setHeader('Access-Control-Allow-Origin', isWildcard ? '*' : requestOrigin); - res.setHeader('Vary', 'Origin'); - } - res.setHeader('Access-Control-Allow-Methods', 'GET, POST, DELETE, OPTIONS'); - res.setHeader('Access-Control-Allow-Headers', 'Content-Type, Authorization, X-PAYMENT, X-Payment-Response'); - res.setHeader('Access-Control-Expose-Headers', 'X-Payment-Response, X-Payment-Requirements'); - if (req.method === 'OPTIONS') return res.sendStatus(204); - next(); -}); - -// ─── Rate limiting ──────────────────────────────────────────────────────────── -// Loose global limit — protects against basic flooding. -const globalLimiter = rateLimit({ - windowMs: 60_000, // 1 minute - max: 120, // 120 req/min per IP across all routes - standardHeaders: true, - legacyHeaders: false, - message: { error: 'Too many requests — slow down' }, -}); - -// Tight limit on expensive endpoints that hit external APIs or sign on-chain. -const sensitiveLimit = rateLimit({ - windowMs: 60_000, - max: 10, // 10 req/min per IP - standardHeaders: true, - legacyHeaders: false, - message: { error: 'Rate limit exceeded for this endpoint' }, -}); - -app.use(globalLimiter); - -// ─── Request logging ────────────────────────────────────────────────────────── -app.use((req, _res, next) => { - console.log(`[${new Date().toISOString()}] ${req.method} ${req.path}`); - next(); -}); - -// ─── Body Parsing ───────────────────────────────────────────────────────────── -// Webhook path gets express.raw() first so the handler receives the exact bytes -// for HMAC verification. Must be before express.json() so the stream isn't consumed. -app.use('/api/v1/webhook/github', express.raw({ type: '*/*', limit: '5mb' })); -// All other routes get standard JSON parsing. -app.use(express.json({ limit: '5mb' })); - -// ─── API Key Auth ───────────────────────────────────────────────────────────── -// Keys are generated per-profile and stored as HMAC-SHA256 in the DB. -// The plaintext key (cs_live_<32 chars>) is shown once at generation time. - -async function verifyApiKey(req, res, next) { - const auth = (req.headers['authorization'] ?? '').trim(); - if (!auth.startsWith('Bearer cs_live_')) { - return res.status(401).json({ error: 'Unauthorized — provide your API key as: Authorization: Bearer ' }); - } - - const key = auth.slice('Bearer '.length); - - try { - const profile = await getProfileByApiKey(key); - if (profile) { - req.callerAddress = profile.address; - return next(); - } - } catch { - // DB unavailable - } - - return res.status(401).json({ error: 'Unauthorized — invalid API key' }); -} - -// ─── OAuth State Store ──────────────────────────────────────────────────────── -// Short-lived state map prevents CSRF — state ties the OAuth callback to a -// specific wallet address. Expires after 10 minutes. - -const _oauthStates = new Map(); // state → { address, expiry } - -function createOAuthState(address, returnTo = '/app/profile') { - const state = crypto.randomBytes(16).toString('hex'); - _oauthStates.set(state, { address, returnTo, expiry: Date.now() + 600_000 }); - return state; -} - -function consumeOAuthState(state) { - const entry = _oauthStates.get(state); - _oauthStates.delete(state); - if (!entry || Date.now() > entry.expiry) return null; - return entry; -} - -const FRONTEND_URL = (process.env.FRONTEND_URL ?? 'http://localhost:5173').replace(/\/$/, ''); -const AGENT_EXT_URL = (process.env.AGENT_EXTERNAL_URL ?? `http://localhost:${process.env.PORT ?? 5000}`).replace(/\/$/, ''); - -// ─── POST /api/v1/auth/:provider/initiate ──────────────────────────────────── -// JWT-authenticated. Returns the OAuth redirect URL so the frontend can navigate. - -app.post('/api/v1/auth/:provider/initiate', verifyJwt, (req, res) => { - const { provider } = req.params; - const address = req.callerAddress; - const returnTo = req.body?.returnTo ?? '/app/profile'; - const state = createOAuthState(address, returnTo); - const cb = `${AGENT_EXT_URL}/api/v1/auth/${provider}/callback`; - - let redirectUrl; - switch (provider) { - case 'github': { - const appSlug = process.env.GITHUB_APP_SLUG; - if (!appSlug) return res.status(503).json({ error: 'GitHub App not configured — set GITHUB_APP_SLUG' }); - // GitHub App installation — company selects which repos to grant access to. - // State param is passed through so we know which wallet to associate the installation with. - redirectUrl = `https://github.com/apps/${appSlug}/installations/new?state=${state}`; - break; - } - case 'atlassian': - if (!process.env.ATLASSIAN_CLIENT_ID) return res.status(503).json({ error: 'Atlassian OAuth not configured on this server' }); - redirectUrl = `https://auth.atlassian.com/authorize?audience=api.atlassian.com&client_id=${process.env.ATLASSIAN_CLIENT_ID}&scope=${encodeURIComponent('read:jira-work manage:jira-webhook offline_access')}&redirect_uri=${encodeURIComponent(cb)}&state=${state}&response_type=code&prompt=consent`; - break; - case 'bitbucket': - if (!process.env.BITBUCKET_CLIENT_ID) return res.status(503).json({ error: 'Bitbucket OAuth not configured on this server' }); - redirectUrl = `https://bitbucket.org/site/oauth2/authorize?client_id=${process.env.BITBUCKET_CLIENT_ID}&response_type=code&scope=${encodeURIComponent('repository pullrequest webhook project')}&state=${state}&redirect_uri=${encodeURIComponent(cb)}`; - break; - case 'figma': - if (!process.env.FIGMA_CLIENT_ID) return res.status(503).json({ error: 'Figma OAuth not configured on this server' }); - redirectUrl = `https://www.figma.com/oauth?client_id=${process.env.FIGMA_CLIENT_ID}&redirect_uri=${encodeURIComponent(cb)}&scope=file_content:read,file_comments:read,webhooks:read&state=${state}&response_type=code`; - break; - default: - return res.status(400).json({ error: `Unknown provider: ${provider}` }); - } - - res.json({ redirectUrl }); -}); - -// ─── GET /api/v1/auth/:provider/callback ───────────────────────────────────── -// GitHub/Atlassian/Bitbucket/Figma redirect here after authorization. -// Exchanges the code for a token, saves encrypted to profile, redirects to frontend. - -app.get('/api/v1/auth/:provider/callback', async (req, res) => { - const { provider } = req.params; - const { code, state, error } = req.query; - if (error) { - const errBase = `${FRONTEND_URL}/app/profile`; - return res.redirect(`${errBase}?oauth=${provider}&status=error&message=${encodeURIComponent(error)}`); - } - - const session = consumeOAuthState(state); - if (!session) { - const errBase = `${FRONTEND_URL}/app/profile`; - return res.redirect(`${errBase}?oauth=${provider}&status=error&message=Invalid+or+expired+state`); - } - - const { address, returnTo = '/app/profile' } = session; - const cb = `${AGENT_EXT_URL}/api/v1/auth/${provider}/callback`; - const frontendReturn = `${FRONTEND_URL}${returnTo}`; - - try { - switch (provider) { - - case 'github': { - // GitHub App installation callback — receives installation_id, not a code. - const installationId = req.query.installation_id; - if (!installationId) throw new Error('No installation_id received from GitHub'); - const db = getDb(); - if (db) { - // Clear old repo installations for this address before saving the new one - // so the repo picker never shows repos from a previously linked account. - const existing = await getProfile(address); - const oldInstallId = existing?.github_installation_id; - if (oldInstallId && oldInstallId !== String(installationId)) { - await db.execute({ - sql: `DELETE FROM repo_installations WHERE installation_id = ?`, - args: [oldInstallId], - }); - console.log(`[oauth:github] Cleared old repo_installations for installationId=${oldInstallId}`); - } - await db.execute({ - sql: `UPDATE profiles SET github_installation_id = ?, updated_at = unixepoch() WHERE address = ?`, - args: [String(installationId), address.toLowerCase()], - }); - } - console.log(`[oauth:github] ✓ App installed for ${address.slice(0, 8)}…`); - break; - } - - case 'atlassian': { - const r = await fetch('https://auth.atlassian.com/oauth/token', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ grant_type: 'authorization_code', client_id: process.env.ATLASSIAN_CLIENT_ID, client_secret: process.env.ATLASSIAN_CLIENT_SECRET, code, redirect_uri: cb }), - }); - const d = await r.json(); - if (!d.access_token) throw new Error('No access_token from Atlassian'); - const rr = await fetch('https://api.atlassian.com/oauth/token/accessible-resources', { headers: { Authorization: `Bearer ${d.access_token}`, Accept: 'application/json' } }); - const sites = await rr.json(); - const cloudId = Array.isArray(sites) ? (sites[0]?.id ?? null) : null; - const expiresAt = d.expires_in ? Math.floor(Date.now() / 1000) + d.expires_in : null; - await saveOAuthTokens(address, 'atlassian', { accessToken: d.access_token, refreshToken: d.refresh_token, cloudId, expiresAt }); - - // Auto-register CronStream webhook on this Jira workspace so issue updates - // route to the agent without the user touching webhook settings. - if (cloudId && process.env.JIRA_WEBHOOK_SECRET) { - registerJiraWebhook(cloudId, d.access_token, address).catch(err => - console.warn(`[oauth:atlassian] Webhook auto-register failed (non-fatal): ${err.message}`), - ); - } - break; - } - - case 'bitbucket': { - const creds = Buffer.from(`${process.env.BITBUCKET_CLIENT_ID}:${process.env.BITBUCKET_CLIENT_SECRET}`).toString('base64'); - const r = await fetch('https://bitbucket.org/site/oauth2/access_token', { - method: 'POST', - headers: { Authorization: `Basic ${creds}`, 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ grant_type: 'authorization_code', code, redirect_uri: cb }), - }); - const d = await r.json(); - if (!d.access_token) throw new Error('No access_token from Bitbucket'); - await saveOAuthTokens(address, 'bitbucket', { accessToken: d.access_token, refreshToken: d.refresh_token }); - // Bitbucket webhooks are per-repo — registered at stream creation time - // when we know the exact repo from verificationTarget. - break; - } - - case 'figma': { - const figmaCreds = Buffer.from(`${process.env.FIGMA_CLIENT_ID}:${process.env.FIGMA_CLIENT_SECRET}`).toString('base64'); - const r = await fetch('https://api.figma.com/v1/oauth/token', { - method: 'POST', - headers: { 'Content-Type': 'application/x-www-form-urlencoded', Authorization: `Basic ${figmaCreds}` }, - body: new URLSearchParams({ redirect_uri: cb, code, grant_type: 'authorization_code' }), - }); - const d = await r.json(); - if (!d.access_token) throw new Error('No access_token from Figma'); - await saveOAuthTokens(address, 'figma', { accessToken: d.access_token, refreshToken: d.refresh_token }); - // Auto-register Figma webhook (requires Org/Enterprise plan — non-fatal if not supported) - if (process.env.FIGMA_WEBHOOK_SECRET) { - registerFigmaWebhook(d.access_token).catch(err => - console.warn(`[oauth:figma] Webhook auto-register skipped (plan may not support it): ${err.message}`), - ); - } - break; - } - - default: - return res.redirect(`${frontendReturn}?oauth=${provider}&status=error&message=Unknown+provider`); - } - - res.redirect(`${frontendReturn}?oauth=${provider}&status=success`); - } catch (err) { - console.error(`[oauth:${provider}/callback] Error:`, err.message); - res.redirect(`${frontendReturn}?oauth=${provider}&status=error&message=${encodeURIComponent(err.message)}`); - } -}); - -// ─── DELETE /api/v1/auth/:provider ─────────────────────────────────────────── -// Disconnect a provider — clears their OAuth tokens from the profile. - -app.delete('/api/v1/auth/:provider', verifyJwt, async (req, res) => { - const { provider } = req.params; - try { - if (provider === 'github') { - const profile = await getProfile(req.callerAddress); - const installId = profile?.github_installation_id; - if (installId) { - const db = getDb(); - await db.execute({ sql: 'DELETE FROM repo_installations WHERE installation_id = ?', args: [installId] }); - } - } - await disconnectOAuth(req.callerAddress, provider); - res.json({ success: true, provider }); - } catch (err) { - res.status(400).json({ error: err.message }); - } -}); - -// ─── Shared Helpers ────────────────────────────────────────────────────────── - -function getExtensionDuration(clientValue) { - // Last-resort guard only — the manual verify-milestone endpoint passes an - // explicit duration, and the poller uses each stream's stored period_seconds. - return clientValue ?? 604800; -} - -function getVoucherExpiry() { - const ttl = Number(process.env.VOUCHER_TTL_SECONDS ?? 3600); - return Math.floor(Date.now() / 1000) + ttl; -} - -// ─── Health Check ───────────────────────────────────────────────────────────── - -// ─── SIWE Auth ─────────────────────────────────────────────────────────────── - -// GET /api/v1/auth/nonce — returns a one-time nonce for the client to embed in its SIWE message. -// Nonces expire after 5 minutes and are single-use. -app.get('/api/v1/auth/nonce', (_req, res) => { - res.json({ nonce: generateNonce() }); -}); - -// POST /api/v1/auth/siwe — verify SIWE message + signature, issue JWT. -// Body: { message: string, signature: string } -app.post('/api/v1/auth/siwe', async (req, res) => { - const { message, signature } = req.body ?? {}; - try { - const address = await verifySiwe({ message, signature }); - const token = issueJwt(address); - return res.json({ token, address, expiresIn: Number(process.env.JWT_TTL_SECONDS ?? 900) }); - } catch (err) { - return res.status(401).json({ error: err.message ?? 'SIWE verification failed' }); - } -}); - -// ─── POST /api/v1/atlassian/personal-data-reporting ────────────────────────── -// Required by Atlassian for OAuth 2.0 apps that store personal data (GDPR). -// Atlassian calls this endpoint to ask what data we hold for a given accountId. -// Register this URL in the Atlassian developer console under app distribution. -// -// Spec: https://developer.atlassian.com/platform/marketplace/personal-data-reporting-api/ -// -// CronStream stores: jira_email, jira_url, atlassian_cloud_id, atlassian_expires_at -// (OAuth tokens are stored encrypted and are not personal data themselves) - -app.post('/api/v1/atlassian/personal-data-reporting', async (req, res) => { - const { accountId } = req.body ?? {}; - if (!accountId) return res.status(400).json({ message: 'accountId is required' }); - - try { - const db = getDb(); - if (!db) return res.json({ userData: { data: [] } }); - - // Find any profile whose jira_email matches the accountId lookup. - // Atlassian passes the accountId — we map it via jira_email since that's - // what we collect at profile setup. If we ever store accountId directly, - // add that column to this query. - const result = await db.execute({ - sql: `SELECT address, jira_email, jira_url, atlassian_cloud_id, atlassian_expires_at - FROM profiles - WHERE jira_email IS NOT NULL - AND atlassian_cloud_id IS NOT NULL - LIMIT 100`, - args: [], - }); - - // Filter for rows that could correspond to this accountId. - // Since we don't store the raw accountId, we return all Jira-connected - // profiles so Atlassian can cross-reference on their side. - const rows = result.rows ?? []; - const data = rows.flatMap(row => [ - row.jira_email ? { fieldName: 'jira_email', fieldValue: row.jira_email } : null, - row.jira_url ? { fieldName: 'jira_url', fieldValue: row.jira_url } : null, - row.atlassian_cloud_id ? { fieldName: 'atlassian_cloud_id', fieldValue: row.atlassian_cloud_id } : null, - ].filter(Boolean)); - - return res.json({ userData: { data } }); - } catch (err) { - console.error('[personal-data] Error:', err.message); - return res.status(500).json({ message: 'Internal error' }); - } -}); - -// ─── Root ───────────────────────────────────────────────────────────────────── -app.get('/', (_req, res) => { - res.json({ - name: 'CronStream Agent Node', - version: '1.0.0', - status: 'ok', - docs: '/api/public/info', - health: '/health', - }); -}); - -// ─── Public API (x402 pay-per-call) ────────────────────────────────────────── -app.use('/api/public', publicApiRouter); - -// ─── x402 config for developer endpoints ───────────────────────────────────── -// Used by verifyJwtOrApiKeyOrX402 on register-stream and verify-milestone. -let _x402PayTo; -try { _x402PayTo = getSignerAddress(); } catch { /* no key in dev */ } -const _x402Opts = { - payTo: _x402PayTo, - network: process.env.X402_NETWORK ?? 'base-sepolia', -}; - -function devAuth(fn) { - return verifyJwtOrApiKeyOrX402(fn, _x402Opts); -} - -app.get('/health', async (req, res) => { - // Detailed health info (signer address, balances) only for internal/authenticated callers. - // Public callers get a minimal status — enough for uptime monitors, nothing for attackers. - const isInternal = req.headers['x-internal-token'] === process.env.INTERNAL_HEALTH_TOKEN - && process.env.INTERNAL_HEALTH_TOKEN; - - let signerOk = false, balances, anyLowBalance = false; - - try { getSignerAddress(); signerOk = true; } catch { /* no key */ } - try { - balances = await getAllBalances(); - anyLowBalance = Object.values(balances).some(b => b !== 'unavailable' && parseFloat(b) < 0.005); - } catch { /* rpc unavailable */ } - - const status = (!signerOk || anyLowBalance) ? 'degraded' : 'ok'; - - if (isInternal) { - return res.json({ - status, - signerAddress: getSignerAddress(), - balances, - contracts: { - arbitrumSepolia: { chainId: 421614, address: process.env.CONTRACT_ADDRESS_ARB_SEPOLIA || process.env.CONTRACT_ADDRESS || null }, - robinhoodChain: { chainId: 46630, address: process.env.CONTRACT_ADDRESS_ROBINHOOD || process.env.CONTRACT_ADDRESS || null }, - }, - extensionsServed: await getExtensionCount(), - timestamp: new Date().toISOString(), - }); - } - - // Public response — status only - res.json({ status, timestamp: new Date().toISOString() }); -}); - -// ─── POST /api/v1/verify-milestone ──────────────────────────────────────────── -// -// Manual endpoint — caller supplies streamId + nonce + githubPayload. -// The agent verifies and signs; the CALLER submits the tx on-chain. -// Use this for B2B integrations where the company/contractor controls gas. -// -// Request body: -// { -// streamId: "0x<64 hex>", -// contractorAddress: "0x<40 hex>", -// nonce: , -// extensionDurationSeconds: , -// githubPayload: { -// repository: { owner: { login: "acme" }, name: "api-service" }, -// pull_request: { number: 42, merged: true }, -// workflow_run: { conclusion: "success" } -// } -// } -// -// Success 200: -// { -// success: true, -// verification: { passed, qualifyingFiles, prNumber, repository }, -// voucher: { streamId, extensionDurationSeconds, nonce, expiry, signature } -// } - -app.post('/api/v1/verify-milestone', sensitiveLimit, devAuth(getProfileByApiKey), async (req, res) => { - const { - streamId, - contractorAddress, - githubPayload, - verificationSource: bodySource, - verificationTarget: bodyTarget, - nonce, - extensionDurationSeconds: clientDuration, - } = req.body; - - // ── Input validation ────────────────────────────────────────────────────── - const missing = []; - if (!streamId) missing.push('streamId'); - if (!contractorAddress) missing.push('contractorAddress'); - if (nonce === undefined) missing.push('nonce'); - - // githubPayload required only for github source - const explicitSource = bodySource ?? 'github'; - if (explicitSource === 'github' && !githubPayload) missing.push('githubPayload'); - - if (missing.length > 0) { - return res.status(400).json({ - success: false, - error: `Missing required fields: ${missing.join(', ')}`, - }); - } - - if (!/^0x[a-fA-F0-9]{64}$/.test(streamId)) { - return res.status(400).json({ success: false, error: 'Invalid streamId — must be 0x-prefixed 32-byte hex' }); - } - if (!/^0x[a-fA-F0-9]{40}$/.test(contractorAddress)) { - return res.status(400).json({ success: false, error: 'Invalid contractorAddress — must be 0x-prefixed 20-byte hex' }); - } - if (typeof nonce !== 'number' || !Number.isInteger(nonce) || nonce < 0) { - return res.status(400).json({ success: false, error: 'nonce must be a non-negative integer' }); - } - - // ── Resolve verification source + target ────────────────────────────────── - // Priority: request body → stream registry → default 'github' - let verificationSource = explicitSource; - let verificationTarget = bodyTarget ?? null; - - if (!bodySource || !bodyTarget) { - try { - const stream = await getStream(streamId); - if (stream) { - verificationSource = bodySource ?? stream.verification_source ?? 'github'; - verificationTarget = bodyTarget ?? stream.verification_target ?? stream.github_repo ?? null; - } - } catch { /* DB unavailable — use body values */ } - } - - // ── Load company credentials from caller's profile ──────────────────────── - let companyCredentials = null; - if (verificationSource !== 'github') { - try { - companyCredentials = await getProfile(req.callerAddress); - } catch { /* DB unavailable — verifyMilestone will throw a config error */ } - } - - // ── Verification ────────────────────────────────────────────────────────── - let verificationResult; - try { - verificationResult = await verifyMilestone({ - streamId, - contractorAddress, - verificationSource, - verificationTarget, - githubPayload, - companyCredentials, - }); - } catch (err) { - if (err instanceof VerificationError) { - return res.status(422).json({ success: false, error: err.message, failedLayer: err.layer }); - } - console.error('[server] Unexpected verification error:', err); - return res.status(500).json({ success: false, error: 'Internal verification error' }); - } - - // ── Sign extension voucher ──────────────────────────────────────────────── - const extensionDurationSeconds = getExtensionDuration(clientDuration); - const expiry = getVoucherExpiry(); - - let signature; - try { - signature = await signExtensionVoucher({ streamId, extensionDurationSeconds, nonce, expiry }); - } catch (err) { - console.error('[server] Signing error:', err); - return res.status(500).json({ success: false, error: 'Failed to sign extension voucher' }); - } - - return res.json({ - success: true, - verification: verificationResult, - voucher: { streamId, extensionDurationSeconds, nonce, expiry, signature }, - }); -}); - -// ─── POST /api/v1/webhook/github ───────────────────────────────────────────── -// -// AUTONOMOUS endpoint — the GitHub App delivers events here directly. -// - installation / installation_repositories: map repos → installation_id so -// the agent can mint installation tokens for the API. -// - pull_request (merged) / push (default branch): look up every stream -// registered to the repo and hand each to the verification engine, which -// re-verifies the work and extends a pending / expiring / frozen stream. -// -// No manual webhook setup or commit-message metadata is needed — installing the -// GitHub App wires delivery automatically and streams are resolved by repo. - -app.post('/api/v1/webhook/github', async (req, res, next) => { try { - // req.body is a raw Buffer when express.raw() ran (application/json content-type). - // Fall back to empty buffer for malformed requests — HMAC check will reject them. - const rawBody = Buffer.isBuffer(req.body) - ? req.body - : req.body != null - ? Buffer.from(JSON.stringify(req.body)) - : Buffer.alloc(0); - - // ── HMAC signature verification ─────────────────────────────────────────── - const secret = process.env.GITHUB_WEBHOOK_SECRET; - - if (secret) { - const githubSig = req.headers['x-hub-signature-256']; - - if (!githubSig) { - return res.status(401).json({ error: 'Missing X-Hub-Signature-256 header' }); - } - - if (!rawBody.length) { - console.warn('[webhook] Empty request body — cannot verify signature'); - return res.status(400).json({ error: 'Empty request body' }); - } - - const expectedSig = `sha256=${crypto - .createHmac('sha256', secret) - .update(rawBody) - .digest('hex')}`; - - const sigBuffer = Buffer.from(githubSig); - const expectedBuffer = Buffer.from(expectedSig); - - if ( - sigBuffer.length !== expectedBuffer.length || - !crypto.timingSafeEqual(sigBuffer, expectedBuffer) - ) { - console.warn('[webhook] Invalid signature — request rejected'); - return res.status(401).json({ error: 'Invalid webhook signature' }); - } - } else { - if (process.env.NODE_ENV === 'production') { - console.error('[webhook] GITHUB_WEBHOOK_SECRET not set — rejecting request. Add it to Render env vars and set the same value in GitHub webhook settings.'); - return res.status(401).json({ error: 'Webhook secret not configured — set GITHUB_WEBHOOK_SECRET' }); - } - console.warn('[webhook] GITHUB_WEBHOOK_SECRET not set — signature check skipped (dev only)'); - } - - const event = req.headers['x-github-event']; - let payload; - try { - payload = JSON.parse(rawBody.toString('utf8')); - } catch { - return res.status(400).json({ error: 'Invalid JSON payload' }); - } - - console.log(`[webhook] Received event: ${event} | action: ${payload.action}`); - - // ── Installation lifecycle: map repos → installation so the agent can mint a - // token for any repo it's installed on (company- OR contractor-owned). ── - if (event === 'installation' || event === 'installation_repositories') { - const installationId = payload.installation?.id; - const account = payload.installation?.account?.login ?? null; - if (installationId) { - // Repos newly granted access (covers both event types) - const added = payload.repositories ?? payload.repositories_added ?? []; - for (const r of added) { - if (r.full_name) await saveRepoInstallation(r.full_name, installationId, account); - } - // Repos removed, or whole installation deleted - const removed = payload.repositories_removed ?? []; - for (const r of removed) { - if (r.full_name) await removeRepoInstallation(r.full_name); - } - if (payload.action === 'deleted' && payload.repositories) { - for (const r of payload.repositories) { - if (r.full_name) await removeRepoInstallation(r.full_name); - } - } - console.log(`[webhook] installation ${payload.action} — ${(added.length || 0)} repo(s) mapped for ${account ?? 'unknown'}`); - } - return res.json({ received: true, event, status: 'installation_synced' }); - } - - // ── Only act on merged PRs — direct commits are excluded (no company approval gate) ── - const repo = payload.repository?.full_name ?? 'unknown'; - - if (event !== 'pull_request') { - return res.json({ received: true, event, status: 'ignored' }); - } - if (payload.action !== 'closed' || payload.pull_request?.merged !== true) { - return res.json({ received: true, event, status: 'ignored', action: payload.action }); - } - - const prNumber = payload.pull_request.number; - console.log(`[webhook:github] PR #${prNumber} merged into ${repo}`); - - // ── Optional stream ID hints in PR title / body ─────────────────────────── - // Including "CronStream-Stream-Id: 0x" routes directly to that stream. - // Useful when multiple contractors share a repo. - const prMeta = `${payload.pull_request?.title ?? ''}\n${payload.pull_request?.body ?? ''}`; - const hintedStreamIds = [...new Set( - [...prMeta.matchAll(/CronStream-Stream-Id:\s*(0x[a-fA-F0-9]{64})/gi)].map(m => m[1].toLowerCase()), - )]; - - let toVerify = []; - if (hintedStreamIds.length) { - const rows = await Promise.all(hintedStreamIds.map(id => getStream(id))); - const found = rows.filter(Boolean); - const missing = hintedStreamIds.filter((_, i) => !rows[i]); - if (found.length) { - console.log(`[webhook:github] Routing to hinted stream(s): ${found.map(r => r.stream_id.slice(0, 10) + '…').join(', ')}`); - toVerify = found; - } - if (missing.length) { - console.warn(`[webhook:github] Hinted stream(s) not in registry: ${missing.map(id => id.slice(0, 10) + '…').join(', ')}`); - } - } - if (!toVerify.length) toVerify = await getStreamsByRepo(repo); - - if (!toVerify.length) { - console.log(`[webhook:github] No streams registered for ${repo} — skipping`); - return res.json({ received: true, event, status: 'skipped', reason: 'No streams registered for this repo' }); - } - - // ── Verify + extend each stream — fire-and-forget so GitHub gets a fast 2xx ── - res.json({ received: true, success: true, event, status: 'verifying', streams: toVerify.map(r => r.stream_id) }); - - for (const row of toVerify) { - (async () => { - try { - // Resolve installation token for this repo - const installationId = (await getInstallationIdForRepo(repo)) - ?? (await getProfile(row.sender).catch(() => null))?.github_installation_id - ?? null; - const token = installationId ? await getInstallationToken(installationId) : null; - - // Load contractor profile for author verification - const contractorProfile = row.recipient - ? await getProfile(row.recipient).catch(() => null) - : null; - - const result = await verifyGitHubWebhook(payload, contractorProfile, token); - if (!result.ok) { - console.log(`[webhook:github] PR #${prNumber} stream ${row.stream_id.slice(0, 10)}… — ${result.reason}`); - return; - } - - await extendFromEvent(row, result.eventRef, 'github'); - } catch (err) { - console.error(`[webhook:github] Error for stream ${row.stream_id?.slice(0, 10)}…: ${err.message}`); - } - })(); - } -} catch (err) { - console.error('[webhook] Unhandled error:', err); - return next(err); -}}); - -// ─── Webhook auto-registration helpers ─────────────────────────────────────── - -const AGENT_PUBLIC_URL = process.env.AGENT_EXTERNAL_URL ?? 'https://api.cronstream.xyz'; - -/** - * Register CronStream's Jira webhook on an Atlassian Cloud workspace. - * Called once per user at Atlassian OAuth callback completion. - * Idempotent — if the webhook already exists Jira returns a 200 with the - * existing record rather than creating a duplicate. - */ -async function registerJiraWebhook(cloudId, accessToken, callerAddress = null) { - // /rest/api/3/webhook is the OAuth 2.0 (3LO) dynamic webhook endpoint. - // /rest/webhooks/1.0/webhook is Connect-only and rejects OAuth tokens. - // Dynamic webhooks expire after 30 days — refreshed via webhook_expiry_warning event. - const url = `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/webhook`; - const body = { - url: `${AGENT_PUBLIC_URL}/api/v1/webhook/jira`, - webhooks: [ - { - events: ['jira:issue_updated'], - jqlFilter: 'project != "CRONSTREAM_PLACEHOLDER_XYZ"', - fieldIdsFilter: ['status'], - }, - ], - }; - const res = await fetch(url, { - method: 'POST', - headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', Accept: 'application/json' }, - body: JSON.stringify(body), - signal: AbortSignal.timeout(8000), - }); - if (!res.ok) { - const text = await res.text().catch(() => ''); - throw new Error(`Jira webhook registration failed (${res.status}): ${text}`); - } - const data = await res.json(); - const ids = (data.webhookRegistrationResult ?? []).map(r => r.createdWebhookId).filter(Boolean); - const maskedCloud = `${cloudId.slice(0, 8)}…`; - if (!ids.length) { - console.warn(`[oauth:atlassian] Webhook registered but no IDs returned — raw:`, JSON.stringify(data)); - } else { - console.log(`[oauth:atlassian] ✓ Jira webhook registered — ids=${ids.join(',')} cloudId=${maskedCloud}`); - } - if (callerAddress && ids.length) { - await saveJiraWebhookIds(callerAddress, ids).catch(() => {}); - } -} - -async function refreshJiraWebhooks(cloudId, accessToken, webhookIds) { - const url = `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/webhook/refresh`; - const res = await fetch(url, { - method: 'PUT', - headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', Accept: 'application/json' }, - body: JSON.stringify({ webhookIds }), - signal: AbortSignal.timeout(8000), - }); - if (!res.ok) { - const text = await res.text().catch(() => ''); - throw new Error(`Jira webhook refresh failed (${res.status}): ${text}`); - } - console.log(`[webhook:jira] ✓ Refreshed ${webhookIds.length} webhook(s) — cloudId=${cloudId.slice(0, 8)}…`); -} - -/** - * Register CronStream's Bitbucket webhook on a specific repository. - * Called when a stream with verificationSource=bitbucket is registered. - * Uses the company's stored OAuth token to make the API call on their behalf. - */ -async function registerBitbucketWebhook(workspace, repoSlug, accessToken) { - const url = `https://api.bitbucket.org/2.0/repositories/${workspace}/${repoSlug}/hooks`; - const body = { - description: 'CronStream milestone gate', - url: `${AGENT_PUBLIC_URL}/api/v1/webhook/bitbucket`, - active: true, - secret: process.env.BITBUCKET_WEBHOOK_SECRET ?? '', - events: ['pullrequest:fulfilled'], - }; - const res = await fetch(url, { - method: 'POST', - headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json', Accept: 'application/json' }, - body: JSON.stringify(body), - signal: AbortSignal.timeout(8000), - }); - if (!res.ok) { - const text = await res.text().catch(() => ''); - throw new Error(`Bitbucket webhook registration failed (${res.status}): ${text}`); - } - const data = await res.json(); - console.log(`[register-stream] ✓ Bitbucket webhook registered — uuid=${data.uuid} repo=${workspace}/${repoSlug}`); -} - -// ─── POST /api/v1/webhook/jira ──────────────────────────────────────────────── -// Receives jira:issue_updated events. Verifies signature, runs 3-layer gate, -// then calls extendFromEvent for each matching stream. - -app.use('/api/v1/webhook/jira', express.raw({ type: '*/*', limit: '2mb' })); - -app.post('/api/v1/webhook/jira', async (req, res, next) => { try { - const rawBody = Buffer.isBuffer(req.body) ? req.body : Buffer.from(req.body ?? ''); - const secret = process.env.JIRA_WEBHOOK_SECRET; - const sigHeader = req.headers['x-hub-signature'] ?? req.headers['x-jira-webhook-signature'] ?? ''; - - if (secret && sigHeader) { - const expected = `sha256=${crypto.createHmac('sha256', secret).update(rawBody).digest('hex')}`; - const valid = sigHeader.length === expected.length && - crypto.timingSafeEqual(Buffer.from(sigHeader), Buffer.from(expected)); - if (!valid) return res.status(401).json({ error: 'Invalid Jira webhook signature' }); - } - - let payload; - try { payload = JSON.parse(rawBody.toString()); } - catch { return res.status(400).json({ error: 'Invalid JSON' }); } - - const event = payload.webhookEvent; - - // Auto-refresh expiring webhooks — Jira sends this 7 days before the 30-day expiry - if (event === 'webhook_expiry_warning') { - const webhookId = payload.webhookId; - console.log(`[webhook:jira] Expiry warning for webhook ${webhookId} — refreshing…`); - try { - const profile = webhookId ? await getProfileByJiraWebhookId(webhookId) : null; - if (profile?.atlassian_access_token && profile?.atlassian_cloud_id) { - await refreshJiraWebhooks(profile.atlassian_cloud_id, profile.atlassian_access_token, [webhookId]); - } else { - console.warn(`[webhook:jira] No profile found for webhook ${webhookId} — cannot auto-refresh`); - } - } catch (err) { - console.error(`[webhook:jira] Refresh failed: ${err.message}`); - } - return res.json({ received: true, status: 'refreshed' }); - } - - if (event !== 'jira:issue_updated') return res.json({ received: true, status: 'ignored', event }); - - const hasStatusChange = payload.changelog?.items?.some(i => i.field === 'status'); - if (!hasStatusChange) return res.json({ received: true, status: 'ignored', reason: 'no status change' }); - - const projectKey = payload.issue?.fields?.project?.key; - if (!projectKey) return res.json({ received: true, status: 'ignored', reason: 'no project key' }); - - console.log(`[webhook:jira] Issue ${payload.issue?.key} updated in project ${projectKey}`); - - const streams = await getStreamsBySource('jira', projectKey); - if (!streams.length) return res.json({ received: true, status: 'skipped', reason: 'no streams for this project' }); - - for (const row of streams) { - const contractorProfile = await getProfile(row.recipient).catch(() => null); - const { ok, eventRef, reason } = verifyJiraWebhook(payload, contractorProfile); - if (!ok) { - console.log(`[webhook:jira] Stream ${row.stream_id?.slice(0, 10)}… not verified: ${reason}`); - continue; - } - extendFromEvent(row, eventRef, 'jira').catch(err => - console.error(`[webhook:jira] extendFromEvent failed for ${row.stream_id?.slice(0, 10)}…: ${err.message}`), - ); - } - - return res.json({ received: true, status: 'verifying', streams: streams.map(r => r.stream_id) }); -} catch (err) { - console.error('[webhook:jira] Unhandled error:', err); - return next(err); -}}); - -// ─── POST /api/v1/webhook/bitbucket ────────────────────────────────────────── -// Receives pullrequest:fulfilled events. Runs 3-layer gate (author + diff + pipeline), -// then calls extendFromEvent for each matching stream. - -app.use('/api/v1/webhook/bitbucket', express.raw({ type: '*/*', limit: '2mb' })); - -app.post('/api/v1/webhook/bitbucket', async (req, res, next) => { try { - const rawBody = Buffer.isBuffer(req.body) ? req.body : Buffer.from(req.body ?? ''); - const secret = process.env.BITBUCKET_WEBHOOK_SECRET; - const sigHeader = req.headers['x-hub-signature'] ?? ''; - - if (secret && sigHeader) { - const hexSig = sigHeader.replace(/^sha256=/, ''); - const expected = crypto.createHmac('sha256', secret).update(rawBody).digest('hex'); - const valid = hexSig.length === expected.length && - crypto.timingSafeEqual(Buffer.from(hexSig), Buffer.from(expected)); - if (!valid) return res.status(401).json({ error: 'Invalid Bitbucket webhook signature' }); - } - - let payload; - try { payload = JSON.parse(rawBody.toString()); } - catch { return res.status(400).json({ error: 'Invalid JSON' }); } - - const event = req.headers['x-event-key']; - if (event !== 'pullrequest:fulfilled') { - return res.json({ received: true, status: 'ignored', event }); - } - - const repoFullName = payload.repository?.full_name; - if (!repoFullName) return res.json({ received: true, status: 'ignored', reason: 'no repo' }); - - console.log(`[webhook:bitbucket] PR #${payload.pullrequest?.id} merged into ${repoFullName}`); - - const streams = await getStreamsBySource('bitbucket', repoFullName); - if (!streams.length) return res.json({ received: true, status: 'skipped', reason: 'no streams for this repo' }); - - for (const row of streams) { - const [companyProfile, contractorProfile] = await Promise.all([ - getProfile(row.sender).catch(() => null), - getProfile(row.recipient).catch(() => null), - ]); - const { ok, eventRef, reason } = await verifyBitbucketWebhook(payload, companyProfile, contractorProfile); - if (!ok) { - console.log(`[webhook:bitbucket] Stream ${row.stream_id?.slice(0, 10)}… not verified: ${reason}`); - continue; - } - extendFromEvent(row, eventRef, 'bitbucket').catch(err => - console.error(`[webhook:bitbucket] extendFromEvent failed for ${row.stream_id?.slice(0, 10)}…: ${err.message}`), - ); - } - - return res.json({ received: true, status: 'verifying', streams: streams.map(r => r.stream_id) }); -} catch (err) { - console.error('[webhook:bitbucket] Unhandled error:', err); - return next(err); -}}); - -// ─── Figma webhook auto-registration ───────────────────────────────────────── -// Requires Figma Organization or Enterprise plan. Registers a FILE_COMMENT -// webhook on the authenticated user's teams. Non-fatal if the plan doesn't -// support it — the error is logged and silently ignored. - -async function registerFigmaWebhook(accessToken) { - // Fetch the user's teams to find a valid team ID for webhook registration - const meRes = await fetch('https://api.figma.com/v1/me', { - headers: { Authorization: `Bearer ${accessToken}` }, - signal: AbortSignal.timeout(8000), - }); - if (!meRes.ok) throw new Error(`Figma /me returned ${meRes.status}`); - const me = await meRes.json(); - - // Figma webhooks are scoped to a team — use the first team available - const teamId = me.organizations?.[0]?.id ?? me.team?.id ?? null; - if (!teamId) throw new Error('No Figma team ID found — user may not belong to an Org plan team'); - - const webhookUrl = `${AGENT_PUBLIC_URL}/api/v1/webhook/figma`; - const res = await fetch(`https://api.figma.com/v2/webhooks`, { - method: 'POST', - headers: { Authorization: `Bearer ${accessToken}`, 'Content-Type': 'application/json' }, - body: JSON.stringify({ - event_type: 'FILE_COMMENT', - team_id: teamId, - endpoint: webhookUrl, - passcode: process.env.FIGMA_WEBHOOK_SECRET, - description: 'CronStream approval comment listener', - }), - signal: AbortSignal.timeout(10000), - }); - if (!res.ok) { - const text = await res.text().catch(() => ''); - throw new Error(`Figma webhook registration failed (${res.status}): ${text}`); - } - const data = await res.json(); - console.log(`[oauth:figma] ✓ Figma webhook registered — id=${data.id} team=${teamId}`); -} - -// ─── POST /api/v1/webhook/figma ─────────────────────────────────────────────── -// Receives FILE_COMMENT events. Verifies passcode, runs approval gate, -// then calls extendFromEvent for each matching stream. - -app.use('/api/v1/webhook/figma', express.json({ limit: '2mb' })); - -app.post('/api/v1/webhook/figma', async (req, res, next) => { try { - // Figma sends the passcode in the payload body (not a header signature) - const passcode = process.env.FIGMA_WEBHOOK_SECRET; - if (passcode && req.body?.passcode !== passcode) { - return res.status(401).json({ error: 'Invalid Figma webhook passcode' }); - } - - const payload = req.body; - if (payload?.event_type !== 'FILE_COMMENT') { - return res.json({ received: true, status: 'ignored', event_type: payload?.event_type }); - } - - const fileKey = payload.file_key; - if (!fileKey) return res.json({ received: true, status: 'ignored', reason: 'no file_key' }); - - console.log(`[webhook:figma] FILE_COMMENT on file ${fileKey}`); - - // Match streams by file key or Figma URL containing the key - const streams = await getStreamsBySource('figma', fileKey) - .catch(() => []); - - // Also try matching by full URL pattern stored at registration - const allFigmaStreams = streams.length ? streams - : (await getStreamsBySource('figma', `https://www.figma.com/file/${fileKey}`).catch(() => [])); - - if (!allFigmaStreams.length) { - return res.json({ received: true, status: 'skipped', reason: 'no streams for this file' }); - } - - for (const row of allFigmaStreams) { - const { ok, eventRef, reason } = verifyFigmaWebhook(payload, row.verification_target); - if (!ok) { - console.log(`[webhook:figma] Stream ${row.stream_id?.slice(0, 10)}… not verified: ${reason}`); - continue; - } - extendFromEvent(row, eventRef, 'figma').catch(err => - console.error(`[webhook:figma] extendFromEvent failed for ${row.stream_id?.slice(0, 10)}…: ${err.message}`), - ); - } - - return res.json({ received: true, status: 'verifying', streams: allFigmaStreams.map(r => r.stream_id) }); -} catch (err) { - console.error('[webhook:figma] Unhandled error:', err); - return next(err); -}}); - -// ─── Platform pickers — fetch projects/repos/files for connected accounts ──── -// Used by the create-stream modal to let companies select from their connected -// platforms instead of typing a target manually. - -// GET /api/v1/platforms/github/repos -app.get('/api/v1/platforms/github/repos', verifyJwt, async (req, res) => { - try { - const profile = await getProfile(req.callerAddress); - const installationId = profile?.github_installation_id; - if (!installationId) return res.json({ items: [] }); - - const token = await getInstallationToken(installationId).catch(() => null); - if (!token) return res.json({ items: [] }); - - const r = await fetch( - `https://api.github.com/installation/repositories?per_page=100`, - { headers: { Authorization: `Bearer ${token}`, Accept: 'application/vnd.github+json' }, signal: AbortSignal.timeout(8000) }, - ); - if (!r.ok) return res.status(r.status).json({ error: `GitHub API ${r.status}` }); - const data = await r.json(); - const items = (data.repositories ?? []).map(repo => ({ - fullName: repo.full_name, - description: repo.description ?? null, - language: repo.language ?? null, - isPrivate: repo.private, - })); - return res.json({ items }); - } catch (err) { - return res.status(500).json({ error: err.message }); - } -}); - -async function refreshAtlassianTokenIfNeeded(address, profile) { - const now = Math.floor(Date.now() / 1000); - const expiresAt = profile?.atlassian_expires_at; - // Refresh if expired or expiring within 5 minutes - if (expiresAt && now < expiresAt - 300) return profile?.atlassian_access_token; - const refreshToken = profile?.atlassian_refresh_token; - if (!refreshToken) return profile?.atlassian_access_token; - try { - const r = await fetch('https://auth.atlassian.com/oauth/token', { - method: 'POST', - headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ grant_type: 'refresh_token', client_id: process.env.ATLASSIAN_CLIENT_ID, client_secret: process.env.ATLASSIAN_CLIENT_SECRET, refresh_token: refreshToken }), - }); - const d = await r.json(); - if (!d.access_token) { console.warn('[atlassian] Token refresh failed:', d.error ?? d); return profile?.atlassian_access_token; } - const newExpiry = d.expires_in ? now + d.expires_in : null; - await saveOAuthTokens(address, 'atlassian', { accessToken: d.access_token, refreshToken: d.refresh_token ?? refreshToken, cloudId: profile?.atlassian_cloud_id, expiresAt: newExpiry }); - console.log('[atlassian] Token refreshed successfully'); - return d.access_token; - } catch (e) { - console.warn('[atlassian] Token refresh error:', e.message); - return profile?.atlassian_access_token; - } -} - -async function refreshBitbucketTokenIfNeeded(address, profile) { - const refreshToken = profile?.bitbucket_refresh_token; - if (!refreshToken) return profile?.bitbucket_oauth_token; - // Bitbucket tokens expire after 2h — always try a refresh for picker calls - // to avoid stale-token 401s without storing expiry (Bitbucket doesn't return expires_in reliably) - try { - const creds = Buffer.from(`${process.env.BITBUCKET_CLIENT_ID}:${process.env.BITBUCKET_CLIENT_SECRET}`).toString('base64'); - const r = await fetch('https://bitbucket.org/site/oauth2/access_token', { - method: 'POST', - headers: { Authorization: `Basic ${creds}`, 'Content-Type': 'application/x-www-form-urlencoded' }, - body: new URLSearchParams({ grant_type: 'refresh_token', refresh_token: refreshToken }), - }); - const d = await r.json(); - if (!d.access_token) { console.warn('[bitbucket] Token refresh failed:', d.error ?? d); return profile?.bitbucket_oauth_token; } - await saveOAuthTokens(address, 'bitbucket', { accessToken: d.access_token, refreshToken: d.refresh_token ?? refreshToken }); - console.log('[bitbucket] Token refreshed successfully'); - return d.access_token; - } catch (e) { - console.warn('[bitbucket] Token refresh error:', e.message); - return profile?.bitbucket_oauth_token; - } -} - -// GET /api/v1/platforms/jira/projects -app.get('/api/v1/platforms/jira/projects', verifyJwt, async (req, res) => { - try { - const profile = await getProfile(req.callerAddress); - const cloudId = profile?.atlassian_cloud_id; - if (!cloudId) return res.json({ items: [] }); - const token = await refreshAtlassianTokenIfNeeded(req.callerAddress, profile); - if (!token) return res.json({ items: [] }); - - const r = await fetch( - `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/project/search?maxResults=100&orderBy=name`, - { headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }, signal: AbortSignal.timeout(8000) }, - ); - if (!r.ok) return res.status(r.status).json({ error: `Jira API ${r.status}` }); - const data = await r.json(); - const items = (data.values ?? []).map(p => ({ - key: p.key, - name: p.name, - type: p.projectTypeKey, - avatarUrl: p.avatarUrls?.['24x24'] ?? null, - })); - return res.json({ items }); - } catch (err) { - return res.status(500).json({ error: err.message }); - } -}); - -// GET /api/v1/platforms/bitbucket/repos -app.get('/api/v1/platforms/bitbucket/repos', verifyJwt, async (req, res) => { - try { - const profile = await getProfile(req.callerAddress); - if (!profile?.bitbucket_oauth_token && !profile?.bitbucket_refresh_token) return res.json({ items: [] }); - const token = await refreshBitbucketTokenIfNeeded(req.callerAddress, profile); - const workspace = profile?.bitbucket_workspace; - if (!token) return res.json({ items: [] }); - - const url = workspace - ? `https://api.bitbucket.org/2.0/repositories/${workspace}?pagelen=100&sort=-updated_on` - : `https://api.bitbucket.org/2.0/repositories?role=member&pagelen=100&sort=-updated_on`; - - const r = await fetch(url, { - headers: { Authorization: `Bearer ${token}`, Accept: 'application/json' }, - signal: AbortSignal.timeout(8000), - }); - if (!r.ok) return res.status(r.status).json({ error: `Bitbucket API ${r.status}` }); - const data = await r.json(); - const items = (data.values ?? []).map(repo => ({ - fullName: repo.full_name, - name: repo.name, - language: repo.language ?? null, - isPrivate: repo.is_private, - })); - return res.json({ items }); - } catch (err) { - return res.status(500).json({ error: err.message }); - } -}); - -// GET /api/v1/platforms/figma/files -// Figma's app review guidelines prohibit enumeration of files via the projects -// endpoints. Users must paste the file URL manually instead. -app.get('/api/v1/platforms/figma/files', verifyJwt, (_req, res) => { - return res.json({ items: [], manual: true }); -}); - -// ─── GET /api/v1/username/check/:username ───────────────────────────────────── -// Returns { available: bool } — called during signup to validate uniqueness. - -app.get('/api/v1/username/check/:username', async (req, res) => { - const { username } = req.params; - const { address } = req.query; // optional — exclude this address from the check - - if (!/^[a-z0-9_-]{3,30}$/.test(username)) { - return res.json({ available: false, reason: '3–30 chars, letters/numbers/_/- only' }); - } - try { - const taken = await isUsernameTaken(username, address || null); - return res.json({ available: !taken }); - } catch (err) { - console.error('[username:check]', err); - return res.status(500).json({ error: 'Check failed' }); - } -}); - -// ─── GET /api/v1/profile/:address ─────────────────────────────────────────── -// Returns a user's profile. Used by the frontend on every load. - -app.get('/api/v1/profile/:address', async (req, res) => { - const { address } = req.params; - if (!/^0x[a-fA-F0-9]{40}$/.test(address)) { - return res.status(400).json({ error: 'Invalid address' }); - } - try { - const profile = await getProfile(address); - if (!profile) return res.status(404).json({ error: 'Profile not found' }); - // Strip credentials — return connection status booleans instead - return res.json({ profile: publicProfile(profile) }); - } catch (err) { - console.error('[profile:get]', err); - return res.status(500).json({ error: 'Failed to fetch profile' }); - } -}); - -// ─── POST /api/v1/profile ───────────────────────────────────────────────────── -// Create or update a profile. Role is set once and cannot be changed via this -// endpoint if the profile already exists (enforced below). - -app.post('/api/v1/profile', verifyJwt, async (req, res) => { - const { address, role, name, github, twitter, linkedin, farcaster, website, avatarUrl, apiKey, - jira_url: jiraUrl, jira_email: jiraEmail, jira_token: jiraToken, - bitbucket_workspace: bitbucketWorkspace, bitbucket_user: bitbucketUser, bitbucket_password: bitbucketPassword, - figma_token: figmaToken, display_currency: displayCurrency } = req.body; - - if (!address || !/^0x[a-fA-F0-9]{40}$/.test(address)) { - return res.status(400).json({ error: 'Valid address required' }); - } - // JWT caller must match the address they're updating — prevents cross-wallet profile overwrites - if (req.callerAddress?.toLowerCase() !== address.toLowerCase()) { - return res.status(403).json({ error: 'You can only update your own profile' }); - } - if (!role || !['company', 'contractor'].includes(role)) { - return res.status(400).json({ error: 'role must be "company" or "contractor"' }); - } - - try { - // If profile exists, preserve the original role — role is immutable after creation - const existing = await getProfile(address); - const finalRole = existing ? existing.role : role; - - // Username is required for new profiles; optional (but validated) for updates - const { username } = req.body; - if (!existing && !username) { - return res.status(400).json({ error: 'Username is required when creating a profile' }); - } - if (username) { - if (!/^[a-z0-9_-]{3,30}$/.test(username)) { - return res.status(400).json({ error: 'Username: 3–30 chars, lowercase letters/numbers/_/- only' }); - } - const taken = await isUsernameTaken(username, address); - if (taken) { - return res.status(409).json({ error: 'Username already taken' }); - } - } - - await upsertProfile({ address, username, role: finalRole, name, github, twitter, linkedin, farcaster, website, avatarUrl, apiKey, - jiraUrl, jiraEmail, jiraToken, bitbucketWorkspace, bitbucketUser, bitbucketPassword, figmaToken, displayCurrency }); - const profile = await getProfile(address); - // Strip credentials before sending to client - return res.json({ profile: publicProfile(profile) }); - } catch (err) { - console.error('[profile:post]', err); - return res.status(500).json({ error: 'Failed to save profile' }); - } -}); - -// ─── GET /api/v1/contractor/lookup ─────────────────────────────────────────── -// Search for contractors by GitHub username, name, or wallet address. -// Query params: ?q= -// Used by companies on the dashboard to find and hire contractors. - -app.get('/api/v1/contractor/lookup', async (req, res) => { - const { q } = req.query; - if (!q || q.trim().length < 2) { - return res.status(400).json({ error: 'Query must be at least 2 characters' }); - } - - const term = q.trim(); - - try { - // Wallet address — exact - if (/^0x[a-fA-F0-9]{40}$/i.test(term)) { - const profile = await getProfile(term); - return res.json({ results: profile ? [publicProfile(profile)] : [] }); - } - - // @username or plain username — exact match first (unique) - const uname = term.startsWith('@') ? term.slice(1) : term; - const byUsername = await searchProfiles({ username: uname, role: 'contractor' }); - if (byUsername.length > 0) return res.json({ results: byUsername.map(publicProfile) }); - - // GitHub handle — partial LIKE match - const byGithub = await searchProfiles({ github: uname, role: 'contractor' }); - if (byGithub.length > 0) return res.json({ results: byGithub.map(publicProfile) }); - - // Name — partial LIKE match - const byName = await searchProfiles({ name: term, role: 'contractor' }); - return res.json({ results: byName.map(publicProfile) }); - } catch (err) { - console.error('[contractor:lookup]', err); - return res.status(500).json({ error: 'Lookup failed' }); - } -}); - -// ─── GET /api/v1/u/:username ───────────────────────────────────────────────── -// Public contractor profile by username — used by the /p/:username frontend route. -// Returns only public fields; never returns api_key or encrypted credentials. - -app.get('/api/v1/u/:username', async (req, res) => { - const { username } = req.params; - if (!username || username.length < 2) { - return res.status(400).json({ error: 'Invalid username' }); - } - try { - const profile = await getProfileByUsername(username); - if (!profile) return res.status(404).json({ error: 'Profile not found' }); - return res.json({ profile: publicProfile(profile) }); - } catch (err) { - console.error('[profile:public]', err); - return res.status(500).json({ error: 'Failed to fetch profile' }); - } -}); - -// ─── POST /api/v1/register-stream ──────────────────────────────────────────── -// -// Called by the frontend immediately after a createStream tx confirms. -// Tells the agent which GitHub repo to watch for this stream. -// -// Request body: -// { -// streamId: "0x<64 hex>", -// repo: "owner/repo", -// recipient: "0x<40 hex>", -// ratePerSecond: "1234" (bigint as string) -// } - -// register-stream — JWT-protected and ownership-checked. -// The frontend calls this immediately after the createStream tx confirms. -// Auth: the caller must hold a valid SIWE JWT AND be the stream's on-chain -// `sender` — otherwise anyone could overwrite a stream's verification routing or -// hours-per-week (which sizes extensions). The on-chain read is the source of -// truth, so you can only register/update a stream you actually created. -app.post('/api/v1/register-stream', verifyJwt, async (req, res) => { - const { - streamId, - repo, - verificationSource, - verificationTarget, - recipient, - sender, - ratePerSecond, - token, - chainId: bodyChainId, - extensionDurationSeconds, - hoursPerWeek, - } = req.body; - - console.log(`[register-stream] ← POST stream=${(streamId ?? '?').slice(0, 12)}… target=${verificationTarget ?? repo ?? 'none'} period=${extensionDurationSeconds ?? 'none'} hrs/wk=${hoursPerWeek ?? 'none'}`); - - const resolvedTarget = verificationTarget ?? repo ?? null; - const resolvedChainId = Number(bodyChainId ?? 421614); - - const missing = []; - if (!streamId) missing.push('streamId'); - if (!resolvedTarget) missing.push('verificationTarget'); - if (!bodyChainId) missing.push('chainId'); - if (!extensionDurationSeconds) missing.push('extensionDurationSeconds'); - if (missing.length) { - console.warn(`[register-stream] ✗ Rejected — missing: ${missing.join(', ')}`); - return res.status(400).json({ error: `Missing required fields: ${missing.join(', ')}` }); - } - - if (!/^0x[a-fA-F0-9]{64}$/.test(streamId)) { - return res.status(400).json({ error: 'Invalid streamId format' }); - } - - // Ownership: the JWT caller must be the stream's on-chain sender. The chain is - // the source of truth, so you can only register/update a stream you created. - let onChainSender; - try { - const [onChain] = await readStreamBatch([streamId], resolvedChainId); - onChainSender = onChain?.sender ?? null; - if (!onChainSender || onChainSender === '0x0000000000000000000000000000000000000000') { - console.warn(`[register-stream] ✗ Rejected — stream ${streamId.slice(0, 12)}… not found on-chain`); - return res.status(404).json({ error: 'Stream not found on-chain' }); - } - if (onChainSender.toLowerCase() !== req.callerAddress.toLowerCase()) { - console.warn(`[register-stream] ✗ Rejected — caller ${req.callerAddress.slice(0, 8)}… is not the stream creator`); - return res.status(403).json({ error: 'Only the stream creator can register this stream' }); - } - } catch (err) { - console.error('[register-stream] ownership check failed:', err.message); - return res.status(502).json({ error: 'Could not verify stream ownership on-chain' }); - } - - try { - const contractAddress = resolvedChainId === 46630 - ? (process.env.CONTRACT_ADDRESS_ROBINHOOD || process.env.CONTRACT_ADDRESS || null) - : (process.env.CONTRACT_ADDRESS_ARB_SEPOLIA || process.env.CONTRACT_ADDRESS || null); - - await registerStream({ - streamId, - chainId: resolvedChainId, - githubRepo: verificationSource === 'github' || !verificationSource ? resolvedTarget : null, - verificationSource: verificationSource ?? 'github', - verificationTarget: resolvedTarget, - sender: onChainSender, - recipient: recipient ?? null, - ratePerSecond: ratePerSecond ?? null, - token: token ?? null, - contractAddress, - periodSeconds: extensionDurationSeconds ?? null, - hoursPerWeek: hoursPerWeek != null ? Number(hoursPerWeek) : null, - }); - - console.log( - `[register-stream] ✓ Registered stream=${streamId} ` + - `source=${verificationSource ?? 'github'} target=${resolvedTarget}`, - ); - - // Auto-register platform webhooks where needed: - // - GitHub: handled by the App installation, no per-stream action needed. - // - Bitbucket: webhook is per-repo, register now using the company's OAuth token. - // - Jira: webhook is per-workspace, registered at OAuth callback — nothing to do here. - if (verificationSource === 'bitbucket' && resolvedTarget && process.env.BITBUCKET_WEBHOOK_SECRET) { - const parts = resolvedTarget.split('/').filter(Boolean); - if (parts.length >= 2) { - const [bbWorkspace, bbRepo] = parts; - const companyProfile = sender ? await getProfile(sender).catch(() => null) : null; - const bbToken = companyProfile?.bitbucket_oauth_token; - if (bbToken) { - registerBitbucketWebhook(bbWorkspace, bbRepo, bbToken).catch(err => - console.warn(`[register-stream] Bitbucket webhook auto-register failed (non-fatal): ${err.message}`), - ); - } else { - console.warn(`[register-stream] No Bitbucket OAuth token for sender ${sender?.slice(0, 8) ?? '?'} — webhook not auto-registered`); - } - } - } - - return res.json({ - success: true, streamId, - verificationSource: verificationSource ?? 'github', - verificationTarget: resolvedTarget, - }); - } catch (err) { - console.error('[register-stream] DB error:', err); - return res.status(500).json({ error: 'Failed to register stream' }); - } -}); - -// ─── GET /api/v1/stream-data/:streamId ────────────────────────────────────── -// -// Returns full on-chain data for a single stream — streams() struct + balanceOf. -// Used by the StreamDetail page so it never needs a frontend RPC call. - -app.get('/api/v1/stream-data/:streamId', async (req, res) => { - const { streamId } = req.params; - if (!/^0x[a-fA-F0-9]{64}$/.test(streamId)) { - return res.status(400).json({ error: 'Invalid streamId format' }); - } - - // Try DB first for chainId — default to Arb Sepolia - let chainId = 421614; - try { - const dbStream = await getStream(streamId); - if (dbStream?.chain_id) chainId = Number(dbStream.chain_id); - } catch { /* DB unavailable — use default */ } - - const results = await readStreamBatch([streamId], chainId); - const data = results[0]; - - if (!data || data.sender === '0x0000000000000000000000000000000000000000') { - return res.status(404).json({ error: 'Stream not found' }); - } - - return res.json({ streamId, chainId, ...data }); -}); - -// ─── GET /api/v1/stream-status/:streamId ──────────────────────────────────── -// -// Returns agent-side metadata for a stream: registered repo + extension history. - -app.get('/api/v1/stream-status/:streamId', async (req, res) => { - const { streamId } = req.params; - - if (!/^0x[a-fA-F0-9]{64}$/.test(streamId)) { - return res.status(400).json({ error: 'Invalid streamId format' }); - } - - try { - const db = getDb(); - const [stream, extResult, banked] = await Promise.all([ - getStream(streamId), - db.execute({ - sql: 'SELECT * FROM processed_extensions WHERE stream_id = ? ORDER BY created_at DESC', - args: [streamId], - }), - getBankedWork(streamId), - ]); - - // banked = verified deliverables earned but not yet applied on-chain (queued - // behind the runway / weekly cap). Returned newest-first to match extensions. - return res.json({ streamId, stream, extensions: extResult.rows, banked: [...banked].reverse() }); - } catch (err) { - console.error('[stream-status] Error:', err); - return res.status(500).json({ error: 'Failed to fetch stream status' }); - } -}); - -// ─── GET /api/v1/streams?address=0x... ─────────────────────────────────────── -// -// Returns all streams for an address, enriched with live on-chain data -// (ratePerSecond, startTime, streamValidUntil, totalDeposited, totalWithdrawn, -// token, balance). The server reads the chain so the frontend never has to worry -// about MetaMask's selected chain being different from the stream's chain. -// -// Results are cached for 30 s per address to avoid hammering the RPC. - -// Simple in-process cache — keyed by lowercase address -const _streamsCache = new Map(); // address → { streams, ts } -const STREAMS_CACHE_TTL_MS = 30_000; - -app.get('/api/v1/streams', async (req, res) => { - const { address } = req.query; - - if (!address || !/^0x[a-fA-F0-9]{40}$/i.test(address)) { - return res.status(400).json({ error: 'Invalid or missing address' }); - } - - const addrKey = address.toLowerCase(); - - // Serve from cache if still fresh - const hit = _streamsCache.get(addrKey); - if (hit && Date.now() - hit.ts < STREAMS_CACHE_TTL_MS) { - return res.json({ address, streams: hit.streams }); - } - - try { - const dbStreams = await getStreamsForAddress(address); - - if (!dbStreams.length) { - return res.json({ address, streams: [] }); - } - - // Group by chainId so we make one batch per chain - const byChain = {}; - for (const s of dbStreams) { - const cid = s.chain_id ?? 421614; - (byChain[cid] ??= []).push(s); - } - - const enriched = []; - for (const [chainId, group] of Object.entries(byChain)) { - const onChain = await readStreamBatch(group.map(s => s.stream_id), Number(chainId)); - for (let i = 0; i < group.length; i++) { - // On-chain data wins for overlapping fields (sender/recipient may be - // checksummed on-chain but lowercase in DB — use on-chain version) - enriched.push({ ...group[i], ...(onChain[i] ?? {}) }); - } - } - - _streamsCache.set(addrKey, { streams: enriched, ts: Date.now() }); - return res.json({ address, streams: enriched }); - } catch (err) { - console.error('[streams] Error:', err); - return res.status(500).json({ error: 'Failed to fetch streams' }); - } -}); - -// ─── GET /api/v1/stream/:streamId/balance ──────────────────────────────────── -// Returns live on-chain withdrawable balance for a stream. -// Developers use this to poll earnings without calling the contract directly. - -app.get('/api/v1/stream/:streamId/balance', async (req, res) => { - const { streamId } = req.params; - if (!/^0x[a-fA-F0-9]{64}$/.test(streamId)) { - return res.status(400).json({ error: 'Invalid streamId format' }); - } - try { - const record = await getStream(streamId); - const chainId = Number(record?.chain_id ?? 421614); - const [onChain] = await readStreamBatch([streamId], chainId); - - if (!onChain || onChain.sender === '0x0000000000000000000000000000000000000000') { - return res.status(404).json({ error: 'Stream not found on-chain' }); - } - - const now = Math.floor(Date.now() / 1000); - const isActive = now < Number(onChain.streamValidUntil); - - return res.json({ - streamId, chainId, - balance: onChain.balance, - ratePerSecond: onChain.ratePerSecond, - streamValidUntil: onChain.streamValidUntil, - totalDeposited: onChain.totalDeposited, - totalWithdrawn: onChain.totalWithdrawn, - earnedSnapshot: onChain.earnedSnapshot, - isActive, - }); - } catch (err) { - console.error('[stream/balance] Error:', err); - return res.status(500).json({ error: 'Failed to read balance' }); - } -}); - -// ─── DELETE /api/v1/stream/:streamId ──────────────────────────────────────── -// Removes a stream from the agent's monitoring registry. -// Does NOT touch the on-chain contract — only removes the DB entry. -// Only the company (sender) can deactivate their own stream. - -app.delete('/api/v1/stream/:streamId', verifyApiKey, async (req, res) => { - const { streamId } = req.params; - if (!/^0x[a-fA-F0-9]{64}$/.test(streamId)) { - return res.status(400).json({ error: 'Invalid streamId format' }); - } - try { - const record = await getStream(streamId); - if (!record) return res.status(404).json({ error: 'Stream not found in registry' }); - - if (record.sender && req.callerAddress && - record.sender.toLowerCase() !== req.callerAddress.toLowerCase()) { - return res.status(403).json({ error: 'Only the stream sender can deactivate it' }); - } - - const db = getDb(); - await db.execute({ sql: 'DELETE FROM stream_registry WHERE stream_id = ?', args: [streamId] }); - - return res.json({ success: true, streamId, message: 'Stream removed from agent registry' }); - } catch (err) { - console.error('[stream/delete] Error:', err); - return res.status(500).json({ error: 'Failed to deactivate stream' }); - } -}); - -// ─── GET /api/v1/streams/pending ───────────────────────────────────────────── -// Returns streams that are expired on-chain (need agent extension or reclaim). -// Useful for company dashboards polling for streams requiring attention. - -app.get('/api/v1/streams/pending', verifyApiKey, async (req, res) => { - const { address } = req.query; - if (!address || !/^0x[a-fA-F0-9]{40}$/i.test(address)) { - return res.status(400).json({ error: 'address query param required' }); - } - try { - const dbStreams = await getStreamsForAddress(address); - if (!dbStreams.length) return res.json({ address, pending: [] }); - - const byChain = {}; - for (const s of dbStreams) { - const cid = s.chain_id ?? 421614; - (byChain[cid] ??= []).push(s); - } - - const pending = []; - const now = Math.floor(Date.now() / 1000); - - for (const [chainId, group] of Object.entries(byChain)) { - const onChain = await readStreamBatch(group.map(s => s.stream_id), Number(chainId)); - for (let i = 0; i < group.length; i++) { - const oc = onChain[i]; - if (!oc) continue; - const isExpired = now >= Number(oc.streamValidUntil); - const hasBalance = BigInt(oc.balance ?? 0) > 0n; - const hasUnearned = BigInt(oc.totalDeposited ?? 0) > BigInt(oc.totalWithdrawn ?? 0) + BigInt(oc.balance ?? 0); - - if (isExpired) { - pending.push({ - streamId: group[i].stream_id, - chainId: Number(chainId), - recipient: oc.recipient, - streamValidUntil: oc.streamValidUntil, - balance: oc.balance, - totalDeposited: oc.totalDeposited, - reclaimable: hasUnearned, - contractorOwed: hasBalance, - verificationSource: group[i].verification_source, - verificationTarget: group[i].verification_target, - }); - } - } - } - - return res.json({ address, count: pending.length, pending }); - } catch (err) { - console.error('[streams/pending] Error:', err); - return res.status(500).json({ error: 'Failed to fetch pending streams' }); - } -}); - -// ─── Waitlist email helper ──────────────────────────────────────────────────── - -function generateInviteCode(email) { - // Deterministic short code: first 4 chars of sha256(email + secret) - const secret = process.env.INVITE_CODE_SECRET ?? 'cronstream'; - const hash = crypto.createHmac('sha256', secret).update(email).digest('hex'); - return 'CS-' + hash.slice(0, 6).toUpperCase(); -} - -async function sendWaitlistEmail({ email, role, inviteCode, position }) { - const apiKey = process.env.RESEND_API_KEY; - if (!apiKey) { - console.warn('[waitlist] RESEND_API_KEY not set — skipping confirmation email'); - return; - } - - const roleLabel = role === 'contractor' ? 'contractor' : 'company'; - const html = ` -
- -

You're on the list.

-

- Thanks for joining the CronStream waitlist as a ${roleLabel}. - We're opening access in waves — you'll be among the first to automate payroll on-chain. -

- -
-

Your invite code

-

${inviteCode}

-

Share this code with your team or contractors — they'll skip the queue.

-
- -

- While you wait, the app is live on testnet at - cronstream.xyz/app. - Connect your wallet and explore. -

- -
-

- CronStream · Programmable payroll for business
- Privacy · - You received this because you signed up at cronstream.xyz -

-
- `; - - try { - const res = await fetch('https://api.resend.com/emails', { - method: 'POST', - headers: { - 'Authorization': `Bearer ${apiKey}`, - 'Content-Type': 'application/json', - }, - body: JSON.stringify({ - from: 'CronStream ', - to: [email], - subject: `You're on the CronStream waitlist — invite code inside`, - html, - }), - }); - if (!res.ok) { - const err = await res.text(); - console.warn('[waitlist] Email send failed:', err); - } else { - console.log(`[waitlist] ✉ Confirmation sent to ${email}`); - } - } catch (err) { - console.warn('[waitlist] Email error:', err.message); - } -} - -// ─── POST /api/v1/waitlist ──────────────────────────────────────────────────── -// Public — no auth required. Accepts email + optional role + company name. - -app.post('/api/v1/waitlist', async (req, res) => { - const { email, role, companyName } = req.body; - - if (!email || !/^[^\s@]+@[^\s@]+\.[^\s@]+$/.test(email)) { - return res.status(400).json({ error: 'Valid email required' }); - } - - try { - const result = await addToWaitlist({ email, role, companyName }); - if (!result.inserted) { - return res.status(409).json({ error: 'Already on the waitlist', alreadyRegistered: true }); - } - const count = await getWaitlistCount(); - const inviteCode = generateInviteCode(email); - console.log(`[waitlist] ✓ ${email} joined — position ${count} — code ${inviteCode}`); - - // Fire confirmation email (non-blocking — don't fail the request if email fails) - sendWaitlistEmail({ email, role, inviteCode, position: count }).catch(() => {}); - - return res.json({ success: true, position: count, inviteCode }); - } catch (err) { - console.error('[waitlist]', err); - return res.status(500).json({ error: 'Failed to join waitlist' }); - } -}); - -// ─── 404 Fallback ───────────────────────────────────────────────────────────── - -app.use((_req, res) => { - res.status(404).json({ error: 'Not found' }); -}); - -// ─── Start ──────────────────────────────────────────────────────────────────── - -const PORT = Number(process.env.PORT ?? 5000); - -// ─── Init DB then start server ──────────────────────────────────────────────── -try { - await initDb(); -} catch (err) { - console.warn('[db] ⚠ Failed to initialize database:', err.message); - console.warn('[db] ⚠ Server will start in degraded mode — profile features unavailable'); -} - -// Start on-chain stream event listeners (non-blocking) -startStreamListeners().catch(err => - console.warn('[listener] Failed to start stream listeners:', err.message), -); - -process.on('SIGTERM', () => { - console.log('[agent] ✗ Instance shutting down (SIGTERM received — Render is replacing this instance)'); - process.exit(0); -}); - -app.listen(PORT, async () => { - console.log('═══════════════════════════════════════════════════'); - console.log(' CronStream Agent Node — NEW INSTANCE STARTED'); - console.log('═══════════════════════════════════════════════════'); - console.log(` Port: ${PORT}`); - const arbAddr = process.env.CONTRACT_ADDRESS_ARB_SEPOLIA || process.env.CONTRACT_ADDRESS; - const rhAddr = process.env.CONTRACT_ADDRESS_ROBINHOOD || process.env.CONTRACT_ADDRESS; - console.log(` Arb Sepolia: ${arbAddr ?? 'NOT SET ⚠'}`); - console.log(` Robinhood: ${rhAddr ?? 'NOT SET ⚠'}`); - console.log(` DB: ${process.env.TURSO_DATABASE_URL ? '✓ configured' : '⚠ not configured (degraded mode)'}`); - console.log(` Encrypt: ${process.env.ENCRYPTION_KEY ? '✓ configured' : '⚠ NOT SET — credential storage disabled'}`); - - try { - const addr = getSignerAddress(); - const balances = await getAllBalances(); - console.log(` Signer: ${addr}`); - for (const [chain, bal] of Object.entries(balances)) { - const warn = bal !== 'unavailable' && parseFloat(bal) < 0.01 ? ' ⚠ LOW' : ''; - console.log(` ${chain}: ${bal} ETH${warn}`); - } - } catch { - console.warn(' Signer: NOT SET — AGENT_SIGNER_PRIVATE_KEY or RPC_URL missing ⚠'); - } - - console.log('═══════════════════════════════════════════════════'); - - // Periodic banked-work drainer — applies earned work that couldn't be applied - // when it was verified (weekly cap hit, or stream still had runway) once a new - // week resets the cap or the runway frees up, even with no new webhook. - const DRAIN_INTERVAL_MS = parseInt(process.env.BANK_DRAIN_INTERVAL_MS ?? String(10 * 60 * 1000), 10); - setInterval(() => { - drainAllBankedWork().catch(err => console.error('[drain] sweep failed:', err.message)); - }, DRAIN_INTERVAL_MS).unref(); -}); diff --git a/agent-node/src/streamListener.js b/agent-node/src/streamListener.js deleted file mode 100644 index 5b7a423..0000000 --- a/agent-node/src/streamListener.js +++ /dev/null @@ -1,179 +0,0 @@ -/** - * streamListener.js - * ───────────────────────────────────────────────────────────────────────────── - * Listens for StreamCreated events on every configured chain and automatically - * registers new streams in the Turso DB so the agent can monitor them. - * - * Flow: - * 1. On startup, scan past blocks for any StreamCreated events since the - * last known block (stored in DB — graceful restart with no missed events). - * 2. Subscribe to live StreamCreated events going forward. - * 3. For each event, upsert the stream into stream_registry. - * - * No API call, no payment — a company's gas spend on createStream() is enough. - */ - -import { ethers } from 'ethers'; -import { registerStream, getDb } from './db.js'; - -// ─── ABI — only the events we care about ───────────────────────────────────── - -const ROUTER_ABI = [ - 'event StreamCreated(bytes32 indexed streamId, address indexed sender, address indexed recipient, uint256 ratePerSecond)', - 'event StreamExtended(bytes32 indexed streamId, uint256 newValidUntil, uint256 newNonce)', -]; - -// ─── Chain configs (mirror chainSubmitter.js) ───────────────────────────────── - -const CHAINS = { - 421614: { - name: 'Arbitrum Sepolia', - rpcUrl: () => process.env.ARBITRUM_RPC_URL || 'https://sepolia-rollup.arbitrum.io/rpc', - contractAddress: () => process.env.CONTRACT_ADDRESS_ARB_SEPOLIA || process.env.CONTRACT_ADDRESS, - maxBlocksPerPoll: 490, - }, - 46630: { - name: 'Robinhood Chain', - rpcUrl: () => process.env.ROBINHOOD_RPC_URL, - contractAddress: () => process.env.CONTRACT_ADDRESS_ROBINHOOD || process.env.CONTRACT_ADDRESS, - maxBlocksPerPoll: 10, // Alchemy Robinhood endpoint rejects ranges > 10 blocks - }, -}; - -// ─── Per-chain listener ─────────────────────────────────────────────────────── - -async function startChainListener(chainId, config) { - const rpcUrl = config.rpcUrl(); - const address = config.contractAddress(); - - if (!rpcUrl || !address) { - console.warn(`[listener:${config.name}] ⚠ Missing RPC URL or contract address — skipping`); - return; - } - - // batchMaxCount: 1 disables request coalescing — public RPCs reject batched - // JSON-RPC arrays and ethers.js v6 throws "could not coalesce error" otherwise. - const provider = new ethers.JsonRpcProvider(rpcUrl, undefined, { batchMaxCount: 1 }); - const contract = new ethers.Contract(address, ROUTER_ABI, provider); - - // ── Catch-up: replay missed events since last restart ────────────────────── - // Try progressively smaller block ranges to handle free-tier RPC limits. - const latestBlock = await provider.getBlockNumber(); - const RANGES = config.maxBlocksPerPoll <= 10 - ? [10] - : [10_000, 1_000, 100, 10]; // shrink until the RPC accepts it - - let caught = false; - for (const range of RANGES) { - const fromBlock = Math.max(0, latestBlock - range); - try { - console.log(`[listener:${config.name}] Scanning blocks ${fromBlock}–${latestBlock} for missed events…`); - const pastEvents = await contract.queryFilter( - contract.filters.StreamCreated(), - fromBlock, - latestBlock, - ); - for (const evt of pastEvents) { - await handleStreamCreated(chainId, config.name, evt, address); - } - console.log(`[listener:${config.name}] ✓ Catch-up complete — ${pastEvents.length} event(s) in last ${range} blocks`); - caught = true; - break; - } catch (err) { - const isRangeError = err.message?.includes('block range') || err.code === 'UNKNOWN_ERROR'; - if (isRangeError && range > 10) { - console.warn(`[listener:${config.name}] Block range ${range} too large for this RPC — retrying with ${RANGES[RANGES.indexOf(range) + 1]}…`); - continue; - } - console.warn(`[listener:${config.name}] Catch-up skipped (non-fatal):`, err.shortMessage ?? err.message); - break; - } - } - if (!caught) { - console.warn(`[listener:${config.name}] Could not replay past events — live listener will catch new ones`); - } - - // ── Live polling — getLogs every 30s with 500-block windows ────────────── - // ethers.js provider.on() uses eth_getFilterChanges which has a 500-block cap - // on Alchemy free tier. Polling getLogs with bounded ranges avoids that limit. - const POLL_INTERVAL_MS = 30_000; - const MAX_BLOCKS_PER_POLL = config.maxBlocksPerPoll ?? 490; - let lastPolledBlock = latestBlock; - - async function pollNewEvents() { - try { - const currentBlock = await provider.getBlockNumber(); - if (currentBlock <= lastPolledBlock) return; - - // Process in chunks so we never exceed the block range limit - let from = lastPolledBlock + 1; - while (from <= currentBlock) { - const to = Math.min(from + MAX_BLOCKS_PER_POLL - 1, currentBlock); - const events = await contract.queryFilter(contract.filters.StreamCreated(), from, to); - for (const evt of events) { - await handleStreamCreated(chainId, config.name, evt, address); - } - from = to + 1; - } - lastPolledBlock = currentBlock; - } catch (err) { - // Non-fatal — next poll will retry - console.warn(`[listener:${config.name}] Poll error (non-fatal):`, err.shortMessage ?? err.message, err.error?.message ?? ''); - } - } - - const pollTimer = setInterval(pollNewEvents, POLL_INTERVAL_MS); - - // Graceful shutdown — callers can await startStreamListeners() and attach a - // SIGTERM handler if needed; for now the timer runs until process exit. - console.log(`[listener:${config.name}] ✓ Polling every ${POLL_INTERVAL_MS / 1000}s (max ${MAX_BLOCKS_PER_POLL} blocks/poll) on ${address}`); -} - -// ─── Event handler ──────────────────────────────────────────────────────────── - -async function handleStreamCreated(chainId, chainName, evt, contractAddress) { - try { - const { streamId, sender, recipient, ratePerSecond } = evt.args ?? evt; - if (!streamId) return; - - await registerStream({ - streamId, - chainId, - sender: sender ?? null, - recipient: recipient ?? null, - ratePerSecond: ratePerSecond != null ? ratePerSecond.toString() : null, - // verificationSource / target not known yet — company sets these via - // the frontend after creation (or leaves them blank for manual approval) - verificationSource: 'github', - verificationTarget: null, - contractAddress: contractAddress ?? null, - }); - - console.log(`[listener:${chainName}] ✓ Auto-registered stream ${streamId.slice(0, 10)}… (sender: ${sender?.slice(0, 8)}…)`); - } catch (err) { - // DB errors are non-fatal — stream may already be registered (INSERT OR REPLACE handles it) - if (!err.message?.includes('UNIQUE')) { - console.warn(`[listener] handleStreamCreated error:`, err.message); - } - } -} - -// ─── Start all listeners ────────────────────────────────────────────────────── - -export async function startStreamListeners() { - const db = getDb(); - if (!db) { - console.warn('[listener] ⚠ No DB configured — stream auto-registration disabled'); - return; - } - - console.log('[listener] Starting on-chain stream listeners…'); - - const starts = Object.entries(CHAINS).map(([chainId, config]) => - startChainListener(Number(chainId), config).catch(err => - console.warn(`[listener:${config.name}] Failed to start:`, err.message), - ), - ); - - await Promise.all(starts); -} diff --git a/agent-node/src/verificationEngine.js b/agent-node/src/verificationEngine.js deleted file mode 100644 index 0305494..0000000 --- a/agent-node/src/verificationEngine.js +++ /dev/null @@ -1,611 +0,0 @@ -/** - * verificationEngine.js - * - * Event-driven verification. The GitHub App webhook calls checkStream() for a - * stream when a merge / push arrives. checkStream looks at on-chain state and, - * if the stream is: - * - Pending (deposit locked, first window never opened) - * - Expiring (streamValidUntil within the next WARN_WINDOW_S seconds) - * - Frozen (streamValidUntil in the past, deposit still unreclaimed) - * it queries the verification source, and if qualifying work is found, signs + - * submits an extension on-chain. A stream with healthy runway is left alone, so - * frequent merges never stack runaway windows. - * - * Rule-based. No LLM. Deterministic. No scheduled polling — triggered by events. - */ - -import { getLastExtensionTime, isAlreadyProcessed, recordExtension, getProfile, getInstallationIdForRepo, getWeeklyExtendedSeconds, bankWork, isWorkBanked, getBankedWork, deleteBankedWork, getStreamsWithBankedWork } from './db.js'; -import { readStreamBatch, submitExtension } from './chainSubmitter.js'; -import { signExtensionVoucher } from './agentSigner.js'; -import { getInstallationToken } from './githubApp.js'; -import { isQualifyingCodeFile } from './codeDiff.js'; - -const WARN_WINDOW_S = 48 * 3600; // top up streams expiring within 48h -const FROZEN_LOOKBACK_S = 7 * 24 * 3600; // ignore streams frozen more than 7 days ago -const GITHUB_API_BASE = 'https://api.github.com'; -const VOUCHER_TTL_S = Number(process.env.VOUCHER_TTL_SECONDS ?? 3600); - -// ─── GitHub helpers ─────────────────────────────────────────────────────────── - -async function ghGet(path, token) { - const t = token ?? process.env.GITHUB_TOKEN; - if (!t) throw new Error('No GitHub token available'); - const res = await fetch(`${GITHUB_API_BASE}${path}`, { - headers: { - Authorization: `Bearer ${t}`, - Accept: 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - 'User-Agent': 'CronStream-Agent/1.0', - }, - signal: AbortSignal.timeout(8000), - }); - if (!res.ok) throw new Error(`GitHub API ${res.status} for ${path}`); - return res.json(); -} - -function hasQualifyingDiff(files) { - return files.some(f => f.additions > 0 && isQualifyingCodeFile(f.filename)); -} - -// ─── GitHub webhook verification ───────────────────────────────────────────── - -/** - * Verify a GitHub pull_request.closed (merged) webhook payload. - * Only PR merges count — direct commits are excluded because they have no - * company approval gate (a merged PR requires someone to review and merge it). - * - * 3-layer gate: - * 1. PR author matches the contractor's registered GitHub handle - * 2. PR contains qualifying code changes in /src or /contracts - * 3. CI passed on the merge commit (skipped if no CI runs exist) - * - * @param {object} payload - GitHub pull_request webhook payload - * @param {object} contractorProfile - contractor profile row (needs .github handle) - * @param {string} token - GitHub installation token for API calls - * @returns {{ ok: boolean, eventRef?: string, prNumber?: number, reason?: string }} - */ -export async function verifyGitHubWebhook(payload, contractorProfile, token) { - const pr = payload.pull_request; - const repoName = payload.repository?.full_name; - const prNumber = pr?.number; - const author = (pr?.user?.login ?? '').toLowerCase(); - const mergeSha = pr?.merge_commit_sha; - - // Layer 1 — author matches registered contractor - const contractorHandle = (contractorProfile?.github ?? '').toLowerCase(); - if (!contractorHandle) { - return { ok: false, reason: 'Contractor has no GitHub handle registered' }; - } - if (author !== contractorHandle) { - return { ok: false, reason: `PR author '${author}' does not match contractor '${contractorHandle}'` }; - } - - if (!repoName || !prNumber) { - return { ok: false, reason: 'Missing repo or PR number in payload' }; - } - - // Layer 2 — qualifying code diff - let files; - try { - files = await ghGet(`/repos/${repoName}/pulls/${prNumber}/files?per_page=100`, token); - } catch (err) { - return { ok: false, reason: `Could not fetch PR diff: ${err.message}` }; - } - if (!hasQualifyingDiff(files)) { - return { ok: false, reason: `PR #${prNumber} has no qualifying code changes in /src or /contracts` }; - } - - // Layer 3 — CI passed on merge commit - if (mergeSha) { - try { - const runs = await ghGet( - `/repos/${repoName}/actions/runs?head_sha=${mergeSha}&status=completed&per_page=10`, - token, - ); - const completedRuns = runs.workflow_runs ?? []; - if (completedRuns.length > 0 && !completedRuns.every(r => r.conclusion === 'success')) { - return { ok: false, reason: `CI did not pass on merge commit ${mergeSha.slice(0, 7)}` }; - } - } catch { /* CI unavailable — allow through */ } - } - - console.log(`[verify:github] ✓ PR #${prNumber} in ${repoName} — author + diff + CI verified`); - return { ok: true, eventRef: `GH#PR#${prNumber}`, prNumber }; -} - -// ─── Jira ───────────────────────────────────────────────────────────────────── - -// ─── Jira webhook verification ──────────────────────────────────────────────── - -const JIRA_DONE_CATEGORIES = new Set(['done']); -const JIRA_VALID_TYPES = new Set(['Story', 'Task', 'Bug', 'Feature', 'Improvement', 'Sub-task']); - -/** - * Verify a Jira `jira:issue_updated` webhook payload against a registered stream. - * 3-layer gate: - * 1. Issue type is a billable deliverable (not Epic) - * 2. Status transitioned to a Done category - * 3. Assignee matches the contractor's registered Jira email / account ID - * - * Returns { ok, eventRef, reason } — no API calls needed, all data is in the payload. - */ -export function verifyJiraWebhook(payload, contractorProfile) { - const issue = payload.issue; - const changelog = payload.changelog; - - // Layer 1 — deliverable type - const issueType = issue?.fields?.issuetype?.name; - if (!JIRA_VALID_TYPES.has(issueType)) { - return { ok: false, reason: `Issue type '${issueType}' is not a billable deliverable` }; - } - - // Layer 2 — status transition to Done - const statusChange = changelog?.items?.find(i => i.field === 'status'); - if (!statusChange) { - return { ok: false, reason: 'No status transition in this event' }; - } - // Jira changelog items use `toString` as the field name (destination status name) - // and `toStatus` is not a standard field — check the category via statusCategory on the issue - const statusCategory = issue?.fields?.status?.statusCategory?.key; - if (!JIRA_DONE_CATEGORIES.has(statusCategory)) { - return { ok: false, reason: `Status category '${statusCategory}' is not Done` }; - } - - // Layer 3 — assignee matches registered contractor - const assigneeEmail = issue?.fields?.assignee?.emailAddress?.toLowerCase(); - const assigneeAccountId = issue?.fields?.assignee?.accountId; - const contractorEmail = contractorProfile?.jira_email?.toLowerCase(); - const contractorAccount = contractorProfile?.jira_account_id; - - const emailMatch = contractorEmail && assigneeEmail && assigneeEmail === contractorEmail; - const accountMatch = contractorAccount && assigneeAccountId && assigneeAccountId === contractorAccount; - - if (!emailMatch && !accountMatch) { - return { ok: false, reason: 'Assignee does not match registered contractor' }; - } - - const issueKey = issue?.key ?? 'unknown'; - console.log(`[verify:jira] ✓ Ticket ${issueKey} — type=${issueType} | assignee verified`); - return { ok: true, eventRef: `JIRA#${issueKey}` }; -} - -// ─── Bitbucket webhook verification ────────────────────────────────────────── - -/** - * Verify a Bitbucket `pullrequest:fulfilled` webhook payload. - * 3-layer gate: - * 1. PR author matches the contractor's registered Bitbucket username / UUID - * 2. PR contains real code changes (any non-doc/config file, checked via diffstat API) - * 3. Latest pipeline on the merge commit passed - * - * @param {object} payload - raw Bitbucket webhook body - * @param {object} companyCredentials - company profile (has bitbucket auth) - * @param {object} contractorProfile - contractor profile (has bitbucket identity) - */ -export async function verifyBitbucketWebhook(payload, companyCredentials, contractorProfile) { - const pr = payload.pullrequest; - const repo = payload.repository; - - const workspace = repo?.workspace?.slug ?? repo?.full_name?.split('/')[0]; - const repoSlug = repo?.slug ?? repo?.full_name?.split('/')[1]; - const prId = pr?.id; - const mergeHash = pr?.merge_commit?.hash; - const authorUUID = pr?.author?.uuid; - const authorNick = pr?.author?.nickname?.toLowerCase(); - - // Layer 1 — author matches registered contractor - const contractorUUID = contractorProfile?.bitbucket_uuid; - const contractorNick = contractorProfile?.bitbucket_user?.toLowerCase(); - const authorMatches = (contractorUUID && authorUUID === contractorUUID) - || (contractorNick && authorNick === contractorNick); - if (!authorMatches) { - return { ok: false, reason: 'PR author does not match registered contractor' }; - } - - if (!workspace || !repoSlug || !prId) { - return { ok: false, reason: 'Missing repo metadata in webhook payload' }; - } - - // Build auth header from company credentials - const { bitbucket_oauth_token: oauthToken, bitbucket_user, bitbucket_password } = companyCredentials ?? {}; - if (!oauthToken && !(bitbucket_user && bitbucket_password)) { - return { ok: false, reason: 'No Bitbucket credentials on company profile' }; - } - const authHeader = oauthToken - ? `Bearer ${oauthToken}` - : `Basic ${Buffer.from(`${bitbucket_user}:${bitbucket_password}`).toString('base64')}`; - - // Layer 2 — real code diff in /src or /contracts - try { - const diffRes = await fetch( - `https://api.bitbucket.org/2.0/repositories/${workspace}/${repoSlug}/pullrequests/${prId}/diffstat`, - { headers: { Authorization: authHeader, Accept: 'application/json' }, signal: AbortSignal.timeout(8000) }, - ); - if (!diffRes.ok) return { ok: false, reason: `Bitbucket diffstat API returned ${diffRes.status}` }; - const diffData = await diffRes.json(); - const files = diffData.values ?? []; - const hasCode = files.some(f => isQualifyingCodeFile(f.new?.path ?? f.old?.path ?? '')); - if (!hasCode) { - return { ok: false, reason: `No qualifying code changes across ${files.length} file(s)` }; - } - } catch (err) { - return { ok: false, reason: `Diffstat check failed: ${err.message}` }; - } - - // Layer 3 — pipeline passed on merge commit - if (mergeHash) { - try { - const pipeRes = await fetch( - `https://api.bitbucket.org/2.0/repositories/${workspace}/${repoSlug}/pipelines/?target.commit.hash=${mergeHash}&sort=-created_on&pagelen=1`, - { headers: { Authorization: authHeader, Accept: 'application/json' }, signal: AbortSignal.timeout(8000) }, - ); - if (pipeRes.ok) { - const pipeData = await pipeRes.json(); - const pipeline = pipeData.values?.[0]; - if (pipeline) { - const state = pipeline.state?.result?.name; - if (state && state !== 'SUCCESSFUL') { - return { ok: false, reason: `Pipeline state: ${state}` }; - } - } - } - } catch { /* pipeline check failure is non-fatal — log and continue */ } - } - - console.log(`[verify:bitbucket] ✓ PR#${prId} in ${workspace}/${repoSlug} — author + diff + pipeline verified`); - return { ok: true, eventRef: `BB#PR#${prId}` }; -} - -// ─── Figma webhook verification ─────────────────────────────────────────────── - -const APPROVAL_KEYWORDS = new Set(['approved', 'lgtm', 'ready to ship', 'ship it', 'looks good']); -const APPROVAL_EMOJI = new Set(['✅', '👍', ':white_check_mark:']); - -/** - * Verify a Figma FILE_COMMENT webhook event. - * Figma webhooks require an Organization or Enterprise plan — registered at - * OAuth callback. Falls back to polling for lower-tier plans (handled in server.js). - * - * 2-layer gate: - * 1. Comment contains an approval keyword or emoji - * 2. The file key matches the registered verification target - * - * Note: Figma comment webhooks don't expose the commenter's identity reliably, - * so author matching is skipped — the company controls who can comment in their file. - * - * @param {object} payload - Figma FILE_COMMENT webhook payload - * @param {string} registeredTarget - the file URL or key stored at stream registration - * @returns {{ ok: boolean, eventRef?: string, reason?: string }} - */ -export function verifyFigmaWebhook(payload, registeredTarget) { - const eventType = payload.event_type; - if (eventType !== 'FILE_COMMENT') { - return { ok: false, reason: `Event type '${eventType}' is not FILE_COMMENT` }; - } - - const fileKey = payload.file_key; - if (!fileKey) { - return { ok: false, reason: 'No file_key in Figma webhook payload' }; - } - - // Extract registered file key from URL or raw key - const urlMatch = (registeredTarget ?? '').match(/figma\.com\/(?:file|design|proto)\/([A-Za-z0-9]+)/); - const registeredKey = urlMatch ? urlMatch[1] : registeredTarget?.trim(); - if (registeredKey && fileKey !== registeredKey) { - return { ok: false, reason: `File key '${fileKey}' does not match registered target` }; - } - - // Layer 1 — approval keyword or emoji in comment text - const commentText = (payload.comment?.[0]?.text ?? '').toLowerCase(); - const hasKeyword = [...APPROVAL_KEYWORDS].some(kw => commentText.includes(kw)); - const hasEmoji = [...APPROVAL_EMOJI].some(e => commentText.includes(e)); - - if (!hasKeyword && !hasEmoji) { - return { ok: false, reason: 'Comment does not contain an approval keyword' }; - } - - const commentId = payload.comment_id ?? fileKey; - console.log(`[verify:figma] ✓ Approval comment on file ${fileKey} — "${commentText.slice(0, 50)}"`); - return { ok: true, eventRef: `FIGMA#COMMENT#${commentId}` }; -} - -// ─── Core verification ───────────────────────────────────────────────────────── - -// Prevents duplicate concurrent checks when both `pull_request.closed` and `push` -// events arrive within milliseconds of each other for the same merge. -const _inFlight = new Set(); - -export async function checkStream(dbRow) { - const { stream_id: streamId, chain_id: chainId, verification_source: source, verification_target: target, sender, period_seconds: periodSeconds, hours_per_week: hoursPerWeek } = dbRow; - if (!target || !sender) return; - - if (_inFlight.has(streamId)) { - console.log(`[verify] Skipping duplicate check for ${streamId?.slice(0, 10)}… (already in-flight)`); - return; - } - _inFlight.add(streamId); - - try { - await _checkStream({ streamId, chainId, source, target, sender, periodSeconds, hoursPerWeek }); - } finally { - _inFlight.delete(streamId); - } -} - -async function _checkStream({ streamId, chainId, source, target, sender, periodSeconds, hoursPerWeek }) { - // Read on-chain state - let onChain; - try { - const results = await readStreamBatch([streamId], Number(chainId ?? 421614)); - onChain = results[0]; - } catch { return; } - - if (!onChain || onChain.sender === '0x0000000000000000000000000000000000000000') return; - - const now = Math.floor(Date.now() / 1000); - const streamValidUntil = Number(onChain.streamValidUntil ?? 0n); - const startTime = Number(onChain.startTime ?? 0n); - const totalDeposited = BigInt(onChain.totalDeposited ?? 0n); - const nonce = Number(onChain.nonce ?? 0n); - - const period = Number(periodSeconds ?? 604800); - - // If hours_per_week is set: each verified event unlocks one day's worth of - // working hours (hrs/week ÷ 5 × 3600s). This ties streaming directly to - // progress — contractor must keep delivering to keep funds flowing. - // Without hours_per_week (legacy streams): fall back to one full period. - const dailyWorkSeconds = hoursPerWeek ? Math.round((hoursPerWeek / 5) * 3600) : null; - const extensionSeconds = dailyWorkSeconds ?? period; - - const isPending = streamValidUntil <= startTime && totalDeposited > 0n; - const isExpiring = !isPending && streamValidUntil > now && (streamValidUntil - now) <= WARN_WINDOW_S; - const isFrozen = !isPending && streamValidUntil <= now && (now - streamValidUntil) <= FROZEN_LOOKBACK_S && totalDeposited > 0n; - - if (!isPending && !isExpiring && !isFrozen) return; - - // ── Continuous-delivery model (no arrears wait) ───────────────────────────── - // We do NOT wait for a full period to elapse before opening a window. As soon - // as qualifying work is verified, the contractor's stream starts (pending) or - // is kept alive (expiring/frozen). Liquidity flows continuously as work ships; - // if delivery stops, the window decays and the stream freezes within a period. - // - // Runaway extension is impossible because the engine only ever acts in these - // three states — a stream with healthy runway (> WARN_WINDOW_S ahead) is - // skipped above, so we top up roughly once per period, never stacking. - - const stateLabel = isPending ? 'pending' : isExpiring ? 'expiring' : 'frozen'; - console.log(`[verify] Checking ${stateLabel} stream ${streamId.slice(0, 10)}… source=${source} target=${target}`); - - // Get last verified timestamp — search since then for new work - const lastExtTime = await getLastExtensionTime(streamId); - const sinceTimestamp = lastExtTime ?? startTime; - - // Load company credentials - let credentials = null; - try { credentials = await getProfile(sender); } catch { /* no credentials */ } - - // All sources (GitHub, Jira, Bitbucket, Figma) are now webhook-only. - // Their handlers call extendFromEvent() directly after verification. - // checkStream is kept as the _inFlight guard entry point but has no poll work left. - console.log(`[verify] Source '${source ?? 'unknown'}' is webhook-only — nothing to poll for ${streamId.slice(0, 10)}…`); - return; - - if (!found) { - console.log(`[verify] No qualifying work found for stream ${streamId.slice(0, 10)}…`); - return; - } - - const { eventRef } = found; - const repo = target; - - // Replay guard - if (await isAlreadyProcessed(streamId, repo, eventRef)) { - console.log(`[verify] Already processed ${eventRef} for stream ${streamId.slice(0, 10)}… — skipping`); - return; - } - - // Weekly hours cap — only enforced when hours_per_week is set. - // Rolls weekly from stream startTime so each 7-day window resets cleanly. - if (hoursPerWeek && dailyWorkSeconds) { - const weekDuration = 7 * 86400; - const weeksSinceStart = Math.floor((now - startTime) / weekDuration); - const weekStart = startTime + weeksSinceStart * weekDuration; - const weekSecondsUsed = await getWeeklyExtendedSeconds(streamId, weekStart); - const maxWeeklySeconds = Math.round(hoursPerWeek * 3600); - - if (weekSecondsUsed + extensionSeconds > maxWeeklySeconds) { - console.log(`[verify] Weekly cap reached for ${streamId.slice(0, 10)}… (${weekSecondsUsed}/${maxWeeklySeconds}s used this week) — skipping`); - return; - } - console.log(`[verify] Weekly budget: ${weekSecondsUsed + extensionSeconds}/${maxWeeklySeconds}s after this extension`); - } - - const extensionDurationSeconds = extensionSeconds; - const expiry = Math.floor(Date.now() / 1000) + VOUCHER_TTL_S; - - let signature; - try { - signature = await signExtensionVoucher({ streamId, extensionDurationSeconds, nonce, expiry }); - } catch (err) { - console.error(`[verify] Signing failed for ${streamId.slice(0, 10)}…: ${err.message}`); - return; - } - - // Submit on-chain - let onChainResult; - try { - onChainResult = await submitExtension({ streamId, extensionDurationSeconds, nonce, expiry, signature }); - } catch (err) { - console.error(`[verify] On-chain submission failed for ${streamId.slice(0, 10)}…: ${err.message}`); - return; - } - - // Persist - await recordExtension({ - streamId, - repository: repo, - prNumber: found.prNumber ?? null, - eventRef, - chainId: onChainResult.chainId, - chainName: onChainResult.chainName, - txHash: onChainResult.txHash, - blockNumber: onChainResult.blockNumber, - gasUsed: onChainResult.gasUsed, - voucherExpiry: expiry, - extensionSeconds, - }); - - console.log(`[verify] ✓ Extended stream ${streamId.slice(0, 10)}… +${extensionSeconds}s via ${stateLabel} event | tx=${onChainResult.txHash}`); -} - -/** - * extendFromEvent — called by Jira and Bitbucket webhook handlers after their - * own verification passes. Handles the on-chain state check, replay guard, - * voucher signing, and submission — the same path as GitHub but skipping the - * work-discovery step (the webhook payload is already the evidence). - * - * @param {object} dbRow - stream registry row - * @param {string} eventRef - unique event identifier for replay guard (e.g. 'JIRA#ENG-42') - * @param {string} sourceLabel - log prefix e.g. 'jira' | 'bitbucket' - */ -export async function extendFromEvent(dbRow, eventRef, sourceLabel) { - const { stream_id: streamId, verification_target: target, period_seconds: periodSeconds, hours_per_week: hoursPerWeek } = dbRow; - - if (_inFlight.has(streamId)) { - console.log(`[verify:${sourceLabel}] Skipping duplicate for ${streamId?.slice(0, 10)}… (already in-flight)`); - return; - } - _inFlight.add(streamId); - - try { - // Replay guard — already applied on-chain, or already waiting in the bank? - if (await isAlreadyProcessed(streamId, target, eventRef)) { - console.log(`[verify:${sourceLabel}] Already processed ${eventRef} for ${streamId?.slice(0, 10)}… — skipping`); - return; - } - if (await isWorkBanked(streamId, eventRef)) { - console.log(`[verify:${sourceLabel}] Already banked ${eventRef} for ${streamId?.slice(0, 10)}… — skipping`); - return; - } - - // Bank the verified deliverable first so it can never be lost, then drain. - const dailyWorkSeconds = hoursPerWeek ? Math.round((hoursPerWeek / 5) * 3600) : null; - const extensionSeconds = dailyWorkSeconds ?? Number(periodSeconds ?? 604800); - await bankWork({ streamId, source: sourceLabel, repository: target, eventRef, extensionSeconds }); - console.log(`[verify:${sourceLabel}] Banked ${eventRef} (+${extensionSeconds}s earned) for ${streamId?.slice(0, 10)}…`); - - await applyBankedWork(dbRow, sourceLabel); - } finally { - _inFlight.delete(streamId); - } -} - -/** - * Draw down a stream's banked work onto the chain, FIFO, respecting: - * - runway: only while the stream is pending/expiring/frozen (never stacks - * beyond WARN_WINDOW_S of runway), - * - weekly cap: never exceeds hours_per_week × 3600 in a rolling 7-day window. - * Whatever can't be applied now stays banked for a later week / when runway frees. - * - * The caller is responsible for the _inFlight guard. - */ -async function applyBankedWork(dbRow, sourceLabel = 'drain') { - const { stream_id: streamId, chain_id: chainId, hours_per_week: hoursPerWeek } = dbRow; - - const banked = await getBankedWork(streamId); - if (!banked.length) return; - - let onChain; - try { - const results = await readStreamBatch([streamId], Number(chainId ?? 421614)); - onChain = results[0]; - } catch { return; } - if (!onChain || onChain.sender === '0x0000000000000000000000000000000000000000') return; - - let nonce = Number(onChain.nonce ?? 0n); - let streamValidUntil = Number(onChain.streamValidUntil ?? 0n); - const startTime = Number(onChain.startTime ?? 0n); - const totalDeposited = BigInt(onChain.totalDeposited ?? 0n); - if (totalDeposited === 0n) return; - - const maxWeeklySeconds = hoursPerWeek ? Math.round(hoursPerWeek * 3600) : null; - - for (const entry of banked) { - const now = Math.floor(Date.now() / 1000); - - const isPending = streamValidUntil <= startTime; - const isExpiring = !isPending && streamValidUntil > now && (streamValidUntil - now) <= WARN_WINDOW_S; - const isFrozen = !isPending && streamValidUntil <= now && (now - streamValidUntil) <= FROZEN_LOOKBACK_S; - if (!isPending && !isExpiring && !isFrozen) { - // Healthy runway — leave the remaining entries banked for later. - break; - } - - // Weekly cap — overflow carries to a later week. - if (maxWeeklySeconds) { - const weekDuration = 7 * 86400; - const weeksSinceStart = Math.floor((now - startTime) / weekDuration); - const weekStart = startTime + weeksSinceStart * weekDuration; - const weekSecondsUsed = await getWeeklyExtendedSeconds(streamId, weekStart); - if (weekSecondsUsed + entry.extension_seconds > maxWeeklySeconds) { - console.log(`[verify:${sourceLabel}] Weekly cap reached for ${streamId?.slice(0, 10)}… — ${banked.length} item(s) stay banked`); - break; - } - } - - const stateLabel = isPending ? 'pending' : isExpiring ? 'expiring' : 'frozen'; - const expiry = now + VOUCHER_TTL_S; - - let signature, onChainResult; - try { - signature = await signExtensionVoucher({ streamId, extensionDurationSeconds: entry.extension_seconds, nonce, expiry }); - } catch (err) { - console.error(`[verify:${sourceLabel}] Signing failed for ${streamId?.slice(0, 10)}…: ${err.message}`); - break; - } - try { - onChainResult = await submitExtension({ streamId, extensionDurationSeconds: entry.extension_seconds, nonce, expiry, signature }); - } catch (err) { - console.error(`[verify:${sourceLabel}] On-chain submission failed for ${streamId?.slice(0, 10)}…: ${err.message}`); - break; - } - - await recordExtension({ - streamId, - repository: entry.repository, - prNumber: null, - eventRef: entry.event_ref, - chainId: onChainResult.chainId, - chainName: onChainResult.chainName, - txHash: onChainResult.txHash, - blockNumber: onChainResult.blockNumber, - gasUsed: onChainResult.gasUsed, - voucherExpiry: expiry, - extensionSeconds: entry.extension_seconds, - }); - await deleteBankedWork(entry.id); - console.log(`[verify:${sourceLabel}] ✓ Applied banked ${entry.event_ref} (${stateLabel}) +${entry.extension_seconds}s | tx=${onChainResult.txHash}`); - - // Advance local state for the next iteration's runway check. - nonce += 1; - const base = (isFrozen || isPending) ? now : streamValidUntil; - streamValidUntil = base + entry.extension_seconds; - } -} - -/** - * Periodic drainer — applies banked work for any stream that now has room - * (runway freed up, or a new week reset the cap) even if no new webhook arrived. - * Called on an interval from server startup. - */ -export async function drainAllBankedWork() { - let streams; - try { streams = await getStreamsWithBankedWork(); } catch { return; } - for (const row of streams) { - if (_inFlight.has(row.stream_id)) continue; - _inFlight.add(row.stream_id); - try { await applyBankedWork(row, 'drain'); } - catch (err) { console.error(`[drain] ${row.stream_id?.slice(0, 10)}…: ${err.message}`); } - finally { _inFlight.delete(row.stream_id); } - } -} diff --git a/agent-node/src/verifyMilestone.js b/agent-node/src/verifyMilestone.js deleted file mode 100644 index f633b18..0000000 --- a/agent-node/src/verifyMilestone.js +++ /dev/null @@ -1,457 +0,0 @@ -/** - * verifyMilestone.js - * Multi-source milestone verification for the CronStream agent node. - * - * Sources: - * github — 3 layers: PR merged + CI green + real code diff (any non-doc file) - * jira — ticket statusCategory is 'done' - * bitbucket — PR merged + optional pipeline success - * figma — approval comment (approved / lgtm / ✅) within 30 days - * - * The company stores their own API credentials in their CronStream profile. - * This agent reads those credentials from the DB and uses them to call - * each platform's API on behalf of the company — no CronStream-level - * platform API keys required. - */ - -import { isQualifyingCodeFile } from './codeDiff.js'; - -// ─── Custom Error ───────────────────────────────────────────────────────────── - -export class VerificationError extends Error { - /** - * @param {number} layer — 0 = config, 1 = source check, 2 = merge gate, 3 = CI gate - * @param {string} message — human-readable failure reason - */ - constructor(layer, message) { - super(message); - this.name = 'VerificationError'; - this.layer = layer; - } -} - -// ───────────────────────────────────────────────────────────────────────────── -// GitHub -// ───────────────────────────────────────────────────────────────────────────── - -const GITHUB_API_BASE = 'https://api.github.com'; - -async function githubGet(path, token) { - const resolvedToken = token ?? process.env.GITHUB_TOKEN; - if (!resolvedToken) throw new Error('[verifyMilestone] No GitHub token available — connect GitHub in Settings or set GITHUB_TOKEN'); - - const res = await fetch(`${GITHUB_API_BASE}${path}`, { - headers: { - Authorization: `Bearer ${resolvedToken}`, - Accept: 'application/vnd.github+json', - 'X-GitHub-Api-Version': '2022-11-28', - 'User-Agent': 'CronStream-Agent-Node/1.0', - }, - }); - - if (!res.ok) { - const body = await res.text(); - throw new Error(`[verifyMilestone] GitHub API ${res.status} for ${path}: ${body}`); - } - return res.json(); -} - -function checkPullRequestMerged(githubPayload) { - const pr = githubPayload.pull_request; - if (!pr) throw new VerificationError(2, 'Missing pull_request object in payload'); - if (pr.merged !== true) { - throw new VerificationError(2, `PR #${pr.number ?? '?'} has not been merged (merged=${pr.merged})`); - } -} - -function checkCiStatus(githubPayload) { - const run = githubPayload.workflow_run; - if (!run) throw new VerificationError(3, 'Missing workflow_run object in payload'); - if (run.conclusion !== 'success') { - throw new VerificationError(3, `CI/CD did not succeed — conclusion: "${run.conclusion ?? 'null'}"`); - } -} - -async function checkCodeDiff(owner, repo, prNumber, token) { - let page = 1, allFiles = []; - while (true) { - const files = await githubGet( - `/repos/${owner}/${repo}/pulls/${prNumber}/files?per_page=100&page=${page}`, - token, - ); - if (!Array.isArray(files) || files.length === 0) break; - allFiles = allFiles.concat(files); - if (files.length < 100) break; - page++; - } - - const qualifying = allFiles.filter( - file => file.additions > 0 && isQualifyingCodeFile(file.filename), - ); - - if (qualifying.length === 0) { - throw new VerificationError( - 1, - 'No qualifying code changes — PR only touches docs, config, or assets', - ); - } - return qualifying; -} - -async function verifyGitHub({ streamId, contractorAddress, githubPayload, githubToken }) { - if (!githubPayload) { - throw new VerificationError(0, 'githubPayload is required for source "github"'); - } - - const repo = githubPayload?.repository; - const owner = repo?.owner?.login; - const repoName = repo?.name; - if (!owner || !repoName) { - throw new VerificationError(0, 'Missing repository.owner.login or .name in githubPayload'); - } - - const prNumber = githubPayload?.pull_request?.number; - if (!prNumber) throw new VerificationError(0, 'Missing pull_request.number in githubPayload'); - - // githubToken is a resolved GitHub App installation token (private repos); - // falls back to the agent's env token for public repos inside githubGet. - - console.log( - `[verifyMilestone:github] stream=${streamId} | contractor=${contractorAddress} | ` + - `repo=${owner}/${repoName} | PR#${prNumber} | token=${githubToken ? 'installation' : 'agent-env'}`, - ); - - // Layer 2 — PR merged? (no API call, fail fast) - checkPullRequestMerged(githubPayload); - console.log('[verifyMilestone:github] ✓ Layer 2 — PR is merged'); - - // Layer 3 — CI passing? (no API call, fail fast) - checkCiStatus(githubPayload); - console.log('[verifyMilestone:github] ✓ Layer 3 — CI/CD succeeded'); - - // Layer 1 — Code diff in /src or /contracts? - const qualifyingFiles = await checkCodeDiff(owner, repoName, prNumber, githubToken); - console.log(`[verifyMilestone:github] ✓ Layer 1 — ${qualifyingFiles.length} qualifying file(s) changed`); - - return { - source: 'github', - passed: true, - qualifyingFiles: qualifyingFiles.length, - prNumber, - repository: `${owner}/${repoName}`, - }; -} - -// ───────────────────────────────────────────────────────────────────────────── -// Jira -// ───────────────────────────────────────────────────────────────────────────── - -async function verifyJira({ streamId, target, credentials }) { - const oauthToken = credentials?.atlassian_access_token; - const cloudId = credentials?.atlassian_cloud_id; - const { jira_url: jiraUrl, jira_email: jiraEmail, jira_token: jiraToken } = credentials ?? {}; - - const hasOAuth = !!oauthToken && !!cloudId; - const hasBasic = !!jiraUrl && !!jiraEmail && !!jiraToken; - - if (!hasOAuth && !hasBasic) { - throw new VerificationError( - 0, - 'Jira not connected — connect Atlassian in Settings → Integrations', - ); - } - - const ticketKey = target.split(/[\s/]+/).filter(Boolean).pop().toUpperCase(); - if (!/^[A-Z][A-Z0-9]+-\d+$/.test(ticketKey)) { - throw new VerificationError(0, `Invalid Jira ticket key "${ticketKey}" — expected format: PROJECT-123`); - } - - console.log(`[verifyMilestone:jira] stream=${streamId} | ticket=${ticketKey} | auth=${hasOAuth ? 'oauth' : 'basic'}`); - - let url, headers; - if (hasOAuth) { - url = `https://api.atlassian.com/ex/jira/${cloudId}/rest/api/3/issue/${ticketKey}`; - headers = { Authorization: `Bearer ${oauthToken}`, Accept: 'application/json' }; - } else { - const auth = Buffer.from(`${jiraEmail}:${jiraToken}`).toString('base64'); - url = `${jiraUrl.replace(/\/$/, '')}/rest/api/3/issue/${ticketKey}`; - headers = { Authorization: `Basic ${auth}`, Accept: 'application/json' }; - } - - const res = await fetch(url, { headers }); - - if (res.status === 401) { - throw new VerificationError(1, 'Jira authentication failed — check your email and API token'); - } - if (res.status === 404) { - throw new VerificationError(1, `Jira ticket ${ticketKey} not found — verify the project key and ticket number`); - } - if (!res.ok) { - const body = await res.text(); - throw new VerificationError(1, `Jira API error ${res.status}: ${body.slice(0, 300)}`); - } - - const data = await res.json(); - const statusCategory = data.fields?.status?.statusCategory?.key; // 'new' | 'indeterminate' | 'done' - const statusName = data.fields?.status?.name ?? 'unknown'; - const assignee = data.fields?.assignee?.displayName ?? 'unassigned'; - - if (statusCategory !== 'done') { - throw new VerificationError( - 1, - `Jira ticket ${ticketKey} is not Done — current status: "${statusName}" (category: ${statusCategory ?? 'unknown'})`, - ); - } - - console.log(`[verifyMilestone:jira] ✓ Ticket ${ticketKey} is Done (${statusName}) — assignee: ${assignee}`); - - return { - source: 'jira', - passed: true, - ticketKey, - status: statusName, - assignee, - }; -} - -// ───────────────────────────────────────────────────────────────────────────── -// Bitbucket -// ───────────────────────────────────────────────────────────────────────────── - -async function checkBitbucketPipeline({ base, auth, workspace, repo, commitHash }) { - let res; - try { - res = await fetch( - `${base}/repositories/${workspace}/${repo}/pipelines/` + - `?target.commit.hash=${commitHash}&sort=-created_on&pagelen=1`, - { headers: { Authorization: auth, Accept: 'application/json' } }, - ); - } catch { - return; // network error — skip pipeline check gracefully - } - - if (!res.ok) return; // pipelines may not be enabled on this repo - - const data = await res.json(); - const pipeline = data.values?.[0]; - if (!pipeline) return; // no pipeline for this commit - - const result = pipeline.state?.result?.name; - if (result && result !== 'SUCCESSFUL') { - throw new VerificationError( - 3, - `Bitbucket pipeline did not succeed — result: "${result}"`, - ); - } - console.log('[verifyMilestone:bitbucket] ✓ Layer 3 — pipeline successful'); -} - -async function verifyBitbucket({ streamId, target, credentials }) { - const oauthToken = credentials?.bitbucket_oauth_token; - const { - bitbucket_workspace: storedWorkspace, - bitbucket_user: user, - bitbucket_password: password, - } = credentials ?? {}; - - const hasOAuth = !!oauthToken; - const hasBasic = !!storedWorkspace && !!user && !!password; - - if (!hasOAuth && !hasBasic) { - throw new VerificationError( - 0, - 'Bitbucket not connected — connect Bitbucket in Settings → Integrations', - ); - } - - const [repoPath, prNumStr] = target.split('#'); - const repoParts = repoPath.trim().split('/').filter(Boolean); - const repo = repoParts.pop(); - const workspace = (storedWorkspace ?? repoParts[0] ?? '').trim(); - - if (!repo) { - throw new VerificationError( - 0, - `Invalid Bitbucket target "${target}" — expected format: workspace/repo or workspace/repo#42`, - ); - } - - const authHeader = hasOAuth - ? `Bearer ${oauthToken}` - : `Basic ${Buffer.from(`${user}:${password}`).toString('base64')}`; - const base = 'https://api.bitbucket.org/2.0'; - const auth = authHeader; // alias for readability below - - console.log( - `[verifyMilestone:bitbucket] stream=${streamId} | repo=${workspace}/${repo}` + - (prNumStr ? ` | PR#${prNumStr}` : ' | latest merged PR'), - ); - - if (prNumStr) { - // Check a specific PR number - const prNum = parseInt(prNumStr, 10); - const res = await fetch( - `${base}/repositories/${workspace}/${repo}/pullrequests/${prNum}`, - { headers: { Authorization: auth, Accept: 'application/json' } }, - ); - if (res.status === 401) throw new VerificationError(1, 'Bitbucket authentication failed — check credentials'); - if (res.status === 404) throw new VerificationError(1, `Bitbucket PR #${prNum} not found in ${workspace}/${repo}`); - if (!res.ok) throw new VerificationError(1, `Bitbucket API error ${res.status} for PR #${prNum}`); - - const pr = await res.json(); - if (pr.state !== 'MERGED') { - throw new VerificationError(2, `Bitbucket PR #${prNum} is not merged — state: "${pr.state}"`); - } - console.log(`[verifyMilestone:bitbucket] ✓ Layer 2 — PR #${prNum} merged`); - - const commitHash = pr.merge_commit?.hash; - if (commitHash) await checkBitbucketPipeline({ base, auth, workspace, repo, commitHash }); - - return { source: 'bitbucket', passed: true, prNumber: prNum, state: pr.state, repository: `${workspace}/${repo}` }; - - } else { - // Check the most recently merged PR - const res = await fetch( - `${base}/repositories/${workspace}/${repo}/pullrequests?state=MERGED&pagelen=1&sort=-updated_on`, - { headers: { Authorization: auth, Accept: 'application/json' } }, - ); - if (res.status === 401) throw new VerificationError(1, 'Bitbucket authentication failed — reconnect Bitbucket in Settings'); - if (!res.ok) throw new VerificationError(1, `Bitbucket API error ${res.status} for ${workspace}/${repo}`); - - const data = await res.json(); - const pr = data.values?.[0]; - if (!pr) { - throw new VerificationError(1, `No merged PRs found in ${workspace}/${repo}`); - } - console.log(`[verifyMilestone:bitbucket] ✓ Layer 2 — PR #${pr.id} merged`); - - const commitHash = pr.merge_commit?.hash; - if (commitHash) await checkBitbucketPipeline({ base, auth, workspace, repo, commitHash }); - - return { source: 'bitbucket', passed: true, prNumber: pr.id, state: pr.state, repository: `${workspace}/${repo}` }; - } -} - -// ───────────────────────────────────────────────────────────────────────────── -// Figma -// ───────────────────────────────────────────────────────────────────────────── - -const APPROVAL_KEYWORDS = [ - 'approved', 'lgtm', '✅', ':white_check_mark:', - 'ready to ship', 'ship it', 'looks good', '👍', -]; -const THIRTY_DAYS_MS = 30 * 24 * 60 * 60 * 1000; - -async function verifyFigma({ streamId, target, credentials }) { - const token = credentials?.figma_oauth_token ?? credentials?.figma_token ?? null; - - if (!token) { - throw new VerificationError( - 0, - 'Figma not connected — connect Figma in Settings → Integrations', - ); - } - - // Extract file key from URL or use the value directly. - // Figma URLs: https://www.figma.com/file/KEY/... or /design/KEY/... - let fileKey = target.trim(); - const urlMatch = target.match(/figma\.com\/(?:file|design|proto)\/([A-Za-z0-9]+)/); - if (urlMatch) fileKey = urlMatch[1]; - - if (!fileKey) { - throw new VerificationError(0, `Cannot extract Figma file key from "${target}"`); - } - - console.log(`[verifyMilestone:figma] stream=${streamId} | file=${fileKey}`); - - const figmaHeaders = credentials?.figma_oauth_token - ? { Authorization: `Bearer ${token}` } - : { 'X-Figma-Token': token }; - - const res = await fetch(`https://api.figma.com/v1/files/${fileKey}/comments`, { - headers: figmaHeaders, - }); - - if (res.status === 403) throw new VerificationError(1, 'Figma authentication failed — check your personal access token'); - if (res.status === 404) throw new VerificationError(1, `Figma file ${fileKey} not found or not accessible`); - if (!res.ok) { - const body = await res.text(); - throw new VerificationError(1, `Figma API error ${res.status}: ${body.slice(0, 300)}`); - } - - const data = await res.json(); - const comments = data.comments ?? []; - const cutoff = Date.now() - THIRTY_DAYS_MS; - - // Find an approval comment posted within the last 30 days - const approvedComment = comments.find(c => { - const text = (c.message ?? '').toLowerCase(); - const date = new Date(c.created_at).getTime(); - return date > cutoff && APPROVAL_KEYWORDS.some(kw => text.includes(kw)); - }); - - if (!approvedComment) { - throw new VerificationError( - 1, - 'No approval found in Figma file — a comment containing "approved", "LGTM", or ✅ ' + - 'from the last 30 days is required', - ); - } - - const approvedBy = approvedComment.user?.handle ?? approvedComment.user?.name ?? 'unknown'; - console.log(`[verifyMilestone:figma] ✓ Approved by ${approvedBy}: "${approvedComment.message}"`); - - return { - source: 'figma', - passed: true, - fileKey, - approvedBy, - comment: approvedComment.message, - }; -} - -// ───────────────────────────────────────────────────────────────────────────── -// Public API -// ───────────────────────────────────────────────────────────────────────────── - -/** - * Verify a contractor milestone using the stream's configured verification source. - * - * @param {object} params - * @param {string} params.streamId — bytes32 stream ID (for logging) - * @param {string} params.contractorAddress — contractor wallet / GitHub login - * @param {string} [params.verificationSource] — 'github' | 'jira' | 'bitbucket' | 'figma' (default: 'github') - * @param {string} [params.verificationTarget] — repo path, ticket key, Figma URL, etc. - * @param {object} [params.githubPayload] — GitHub webhook payload (required for source='github') - * @param {object} [params.companyCredentials] — company profile row with integration credentials - * @param {string} [params.githubToken] — resolved GitHub App installation token - * - * @returns {Promise} Verification summary - * @throws {VerificationError} If any verification check fails - */ -export async function verifyMilestone({ - streamId, - contractorAddress, - verificationSource = 'github', - verificationTarget, - githubPayload, - companyCredentials, - githubToken, -}) { - switch (verificationSource) { - case 'github': - return verifyGitHub({ streamId, contractorAddress, githubPayload, githubToken }); - - case 'jira': - return verifyJira({ streamId, target: verificationTarget, credentials: companyCredentials }); - - case 'bitbucket': - return verifyBitbucket({ streamId, target: verificationTarget, credentials: companyCredentials }); - - case 'figma': - return verifyFigma({ streamId, target: verificationTarget, credentials: companyCredentials }); - - default: - throw new VerificationError(0, `Unknown verification source: "${verificationSource}"`); - } -} diff --git a/agent-node/test-x402.js b/agent-node/test-x402.js deleted file mode 100644 index 608aa01..0000000 --- a/agent-node/test-x402.js +++ /dev/null @@ -1,66 +0,0 @@ -/** - * test-x402.js - * Quick smoke-test for the x402 public API. - * Run against local agent (npm run dev) or production. - * - * Usage: - * node test-x402.js # test local (port 3000) - * BASE_URL=https://api.cronstream.xyz node test-x402.js - * STREAM_ID=0x<64hex> node test-x402.js - */ - -const BASE_URL = process.env.BASE_URL ?? 'http://localhost:3000'; -const STREAM_ID = process.env.STREAM_ID ?? null; - -async function get(path) { - const r = await fetch(`${BASE_URL}${path}`); - return { status: r.status, body: await r.json().catch(() => ({})) }; -} - -async function run() { - console.log(`\nTesting CronStream x402 API at ${BASE_URL}\n${'─'.repeat(50)}`); - - // 1. Free info endpoint — should always 200 - const info = await get('/api/public/info'); - console.log(`[GET /api/public/info] ${info.status === 200 ? '✓ 200' : `✗ ${info.status}`}`); - if (info.status === 200) { - console.log(` payTo: ${info.body.payTo}`); - console.log(` network: ${info.body.network}`); - } - - // 2. Paid endpoints without X-PAYMENT header — should return 402 - const endpoints = [ - STREAM_ID ? `/api/public/stream/${STREAM_ID}` : '/api/public/stream/0x' + '0'.repeat(64), - STREAM_ID ? `/api/public/balance/${STREAM_ID}` : '/api/public/balance/0x' + '0'.repeat(64), - '/api/public/streams/company/0x' + '0'.repeat(40), - '/api/public/streams/contractor/0x' + '0'.repeat(40), - ]; - - for (const path of endpoints) { - const r = await get(path); - const label = path.replace(STREAM_ID ?? '', '').replace('0x' + '0'.repeat(64), '').replace('0x' + '0'.repeat(40), ''); - if (r.status === 402) { - const pays = r.body?.accepts?.[0]; - console.log(`[GET ${label.padEnd(45)}] ✓ 402 Payment Required`); - if (pays) console.log(` pay $${pays.maxAmountRequired / 1e6} USDC → ${pays.payTo?.slice(0, 10)}…`); - } else if (r.status === 200) { - console.log(`[GET ${label.padEnd(45)}] ✗ Got 200 — payment middleware not active`); - } else { - console.log(`[GET ${label.padEnd(45)}] ? ${r.status}`); - } - } - - // 3. POST verify-milestone without payment — should 402 - const vm = await fetch(`${BASE_URL}/api/public/verify-milestone`, { - method: 'POST', headers: { 'Content-Type': 'application/json' }, - body: JSON.stringify({ streamId: '0x' + '0'.repeat(64), prNumber: 1 }), - }); - const vmBody = await vm.json().catch(() => ({})); - console.log(`[POST /api/public/verify-milestone] ${vm.status === 402 ? '✓ 402 Payment Required' : `✗ ${vm.status} — payment middleware not active`}`); - - console.log('\n' + '─'.repeat(50)); - console.log('To test a paid call: get Base Sepolia USDC from https://faucet.circle.com'); - console.log('Then use x402-fetch: https://github.com/coinbase/x402\n'); -} - -run().catch(console.error); diff --git a/frontend/package.json b/frontend/package.json index 222faf2..6711548 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -1,43 +1,21 @@ { - "name": "frontend", + "name": "cronstream-protocol", "private": true, - "version": "0.0.0", + "version": "2.0.0", "type": "module", "scripts": { "dev": "vite", "build": "vite build", - "lint": "eslint .", "preview": "vite preview" }, "dependencies": { - "@rainbow-me/rainbowkit": "^2.2.11", - "@react-pdf/renderer": "^4.5.1", - "@tanstack/react-query": "^5.100.14", - "@vercel/analytics": "^2.0.1", - "@vercel/speed-insights": "^2.0.0", - "buffer": "^6.0.3", - "framer-motion": "^12.40.0", - "lucide-react": "^1.16.0", "react": "^19.2.6", - "react-dom": "^19.2.6", - "react-is": "^19.2.6", - "react-router-dom": "^7.15.1", - "recharts": "^3.8.1", - "viem": "^2.51.0", - "wagmi": "^3.6.15" + "react-dom": "^19.2.6" }, "devDependencies": { - "@eslint/js": "^10.0.1", "@types/react": "^19.2.14", "@types/react-dom": "^19.2.3", "@vitejs/plugin-react": "^6.0.1", - "autoprefixer": "^10.5.0", - "eslint": "^10.3.0", - "eslint-plugin-react-hooks": "^7.1.1", - "eslint-plugin-react-refresh": "^0.5.2", - "globals": "^17.6.0", - "postcss": "^8.5.15", - "tailwindcss": "^3.4.19", "vite": "^8.0.12" } } diff --git a/frontend/postcss.config.js b/frontend/postcss.config.js deleted file mode 100644 index 2e7af2b..0000000 --- a/frontend/postcss.config.js +++ /dev/null @@ -1,6 +0,0 @@ -export default { - plugins: { - tailwindcss: {}, - autoprefixer: {}, - }, -} diff --git a/frontend/src/App.jsx b/frontend/src/App.jsx index 7402ada..9a21582 100644 --- a/frontend/src/App.jsx +++ b/frontend/src/App.jsx @@ -1,82 +1,21 @@ -import { useState, useEffect } from 'react'; -import { Routes, Route, Navigate } from 'react-router-dom'; -import { useAccount } from 'wagmi'; - -import Landing from './pages/Landing'; -import Faucet from './pages/Faucet'; -import SiwePrompt from './components/SiwePrompt'; -import Privacy from './pages/Privacy'; -import Terms from './pages/Terms'; -import PublicProfile from './pages/PublicProfile'; -import NotFound from './pages/NotFound'; -import ErrorPage from './pages/ErrorPage'; -import Setup from './pages/app/Setup'; -import Dashboard from './pages/app/Dashboard'; -import StreamDetail from './pages/app/StreamDetail'; -import Settings from './pages/app/Settings'; -import Profile from './pages/app/Profile'; -import IncomeHistory from './pages/app/IncomeHistory'; -import StreamHistory from './pages/app/StreamHistory'; -import CompanyHistory from './pages/app/CompanyHistory'; -import AppShell from './components/AppShell'; -import LogoLoader from './components/LogoLoader'; - -function ProtectedRoute({ children }) { - const { isConnected, isConnecting, isReconnecting } = useAccount(); - const [timedOut, setTimedOut] = useState(false); - - // If wagmi is still reconnecting after 3s, stop waiting and let it resolve - useEffect(() => { - if (!isConnecting && !isReconnecting) { setTimedOut(false); return; } - const t = setTimeout(() => setTimedOut(true), 3000); - return () => clearTimeout(t); - }, [isConnecting, isReconnecting]); - - const stillWaiting = (isConnecting || isReconnecting) && !timedOut; - if (stillWaiting) return ; - if (!isConnected) return ; - return ( - <> - - {children} - - ); -} - export default function App() { return ( - - {/* Public */} - } /> - } /> - } /> - } /> - } /> - - {/* App - wallet required */} - - - - }> - } /> - } /> - } /> - {/* /app/stream/create now opens the modal - redirect to dashboard */} - } /> - } /> - } /> - } /> - } /> - } /> - } /> - } /> - - - {/* Dev preview only - remove before launch */} - {import.meta.env.DEV && } />} - - } /> - +
+
In Development
+

Cronstream Protocol

+

+ A Uniswap v4 hook that automatically distributes yield from RWA and + rebasing stablecoin pools directly to liquidity providers. + Atomic settlement. Zero custody. No protocol fee. +

+ +
+ Base · Arbitrum · Unichain +
+
); } diff --git a/frontend/src/components/AppShell.jsx b/frontend/src/components/AppShell.jsx deleted file mode 100644 index a73c6ae..0000000 --- a/frontend/src/components/AppShell.jsx +++ /dev/null @@ -1,247 +0,0 @@ -import { useState } from 'react'; -import { Outlet, NavLink, useNavigate } from 'react-router-dom'; -import { ConnectButton } from '@rainbow-me/rainbowkit'; -import { useAccount, useDisconnect } from 'wagmi'; -import { LayoutDashboard, Settings, Plus, LogOut, Clock, TrendingUp, UserCircle2, SlidersHorizontal } from 'lucide-react'; -import { useProfile } from '../hooks/useProfile'; -import { useCreateStream } from '../context/CreateStreamContext'; -import CreateStreamModal from './CreateStreamModal'; -import Watermark from './Watermark'; -import { LimelightNav } from './LimelightNav'; - -const CHAIN_ICONS = { - 421614: '/arb.png', // Arbitrum Sepolia - 46630: '/robinhood.png', // Robinhood Chain -}; - -export default function AppShell() { - const { address } = useAccount(); - const { profile } = useProfile(address); - const navigate = useNavigate(); - const { openModal } = useCreateStream(); - const { disconnect } = useDisconnect(); - const [sidebarOpen, setSidebarOpen] = useState(false); - - const isCompany = profile?.role === 'company'; - const isContractor = profile?.role === 'contractor'; - - const NAV = [ - { to: '/app/dashboard', label: 'Dashboard', icon: , show: true }, - { to: '/app/income', label: 'Income', icon: , show: isContractor }, - { to: '/app/history', label: 'History', icon: , show: isContractor }, - { to: '/app/company-history', label: 'History', icon: , show: isCompany }, - { to: '/app/profile', label: 'Profile', icon: , show: true }, - { to: '/app/settings', label: 'Settings', icon: , show: isCompany }, - ].filter(n => n.show); - - // Limelight nav items for mobile - const mobileNavItems = [ - { id: 'dashboard', to: '/app/dashboard', label: 'Dashboard', icon: }, - ...(isContractor ? [{ id: 'income', to: '/app/income', label: 'Income', icon: }] : []), - ...(isContractor ? [{ id: 'history', to: '/app/history', label: 'History', icon: }] : []), - ...(isCompany ? [{ id: 'stream', label: 'Stream', icon: , onClick: openModal }] : []), - ...(isCompany ? [{ id: 'company-history', to: '/app/company-history', label: 'History', icon: }] : []), - { id: 'profile', to: '/app/profile', label: 'Profile', icon: }, - ...(isCompany ? [{ id: 'settings', to: '/app/settings', label: 'Settings', icon: }] : []), - ]; - - const initials = profile?.name - ? profile.name.split(' ').map(w => w[0]).join('').toUpperCase().slice(0, 2) - : address?.slice(2, 4).toUpperCase() ?? '??'; - - const SidebarContent = () => ( - <> - {/* Logo */} -
{ navigate('/'); setSidebarOpen(false); }} - > -
- CronStream - CronStream -
-
- - {/* Company CTA */} - {isCompany && ( -
- -
- )} - - {/* Nav */} - - - {/* User identity footer */} -
- - {({ account, chain, openAccountModal, openChainModal, mounted }) => { - if (!mounted || !account) return null; - return ( -
- {/* Chain row */} - - - {/* Profile row */} - - - {/* Disconnect row */} - -
- ); - }} -
-
- - ); - - return ( -
- - {/* ── Desktop sidebar ─────────────────────────────── */} - - - {/* ── Mobile sidebar overlay ───────────────────────── */} - {sidebarOpen && ( -
setSidebarOpen(false)} - > -
- -
- )} - - {/* ── Main ────────────────────────────────────────── */} -
- - {/* Mobile top bar */} -
- {/* Left - hamburger */} -
- -
- - {/* Centre - logo always centred */} -
- CronStream -
- - {/* Right - wallet */} -
- - {({ account, chain, openAccountModal, openChainModal, mounted }) => { - if (!mounted || !account) return
; - return ( -
- {(CHAIN_ICONS[chain?.id] || (chain?.hasIcon && chain.iconUrl)) && ( - - )} - -
- ); - }} - -
-
- - {/* Page content */} -
- -
- - {/* ── Mobile bottom tab bar - limelight ────────── */} -
- -
-
- - {/* Tiled page watermark - z-index 0, sits behind everything */} - - - {/* Global modal */} - -
- ); -} diff --git a/frontend/src/components/CreateStreamModal.jsx b/frontend/src/components/CreateStreamModal.jsx deleted file mode 100644 index 822b916..0000000 --- a/frontend/src/components/CreateStreamModal.jsx +++ /dev/null @@ -1,784 +0,0 @@ -import { useState, useEffect, useRef } from 'react'; -import { useAccount, useWriteContract, useWaitForTransactionReceipt, useReadContract, useChainId } from 'wagmi'; -import { parseUnits, formatUnits, parseAbiItem, parseAbi, decodeEventLog } from 'viem'; -import { getContractAddress, ROUTER_ABI } from '../lib/wagmi'; -import { registerStreamWithAgent } from '../hooks/useAgentStatus'; -import { useCreateStream } from '../context/CreateStreamContext'; -import { useAuth } from '../context/AuthContext'; -import { useProfile } from '../hooks/useProfile'; -import RepoPicker from './RepoPicker'; -import PlatformPicker from './PlatformPicker'; -import Watermark from './Watermark'; -import { useWalletTokens } from '../hooks/useWalletTokens'; - -const AGENT_URL = import.meta.env.VITE_AGENT_URL ?? 'http://localhost:3000'; - -const ERC20_ABI = parseAbi([ - 'function approve(address spender, uint256 amount) external returns (bool)', - 'function allowance(address owner, address spender) external view returns (uint256)', -]); - -// Milestone window options - how long each validation window lasts before the stream -// freezes if the agent hasn't verified a deliverable. -const WINDOW_OPTIONS = [ - { label: 'Weekly', sublabel: 'paid every week', seconds: 604800n }, - { label: 'Bi-weekly', sublabel: 'paid every 2 weeks', seconds: 1209600n }, - { label: 'Monthly', sublabel: 'paid once a month', seconds: 2592000n }, -]; - -// ─── Step dots ──────────────────────────────────────────────────────────────── -function StepDots({ current, total }) { - return ( -
- {Array.from({ length: total }).map((_, i) => ( -
- ))} -
- ); -} - -// ─── Contractor picker ──────────────────────────────────────────────────────── -function ContractorPicker({ selected, onSelect }) { - const [query, setQuery] = useState(''); - const [results, setResults] = useState([]); - const [searching, setSearching] = useState(false); - const [open, setOpen] = useState(false); - const [showManual, setShowManual] = useState(false); - const [manual, setManual] = useState(''); - const debounce = useRef(null); - const wrapperRef = useRef(null); - - // Close dropdown on outside click - useEffect(() => { - function onDown(e) { - if (wrapperRef.current && !wrapperRef.current.contains(e.target)) setOpen(false); - } - document.addEventListener('mousedown', onDown); - return () => document.removeEventListener('mousedown', onDown); - }, []); - - async function search(q) { - if (q.length < 2) { setResults([]); setOpen(false); return; } - setSearching(true); - try { - const res = await fetch(`${AGENT_URL}/api/v1/contractor/lookup?q=${encodeURIComponent(q)}`); - if (res.ok) { - const { results: rows } = await res.json(); - setResults(rows); - setOpen(rows.length > 0); - } - } catch { /* agent offline - manual fallback available */ } - finally { setSearching(false); } - } - - function handleQuery(e) { - const val = e.target.value; - setQuery(val); - if (val.length < 2) { setResults([]); setOpen(false); } - clearTimeout(debounce.current); - debounce.current = setTimeout(() => search(val), 350); - } - - function handleManual(e) { - e.preventDefault(); - if (/^0x[a-fA-F0-9]{40}$/.test(manual)) { - onSelect({ address: manual, name: null, github: null }); - setShowManual(false); - setManual(''); - } - } - - // ── Selected state ────────────────────────────────────────────────────────── - if (selected) { - const selInitials = selected.name - ? selected.name.split(' ').map(w=>w[0]).join('').toUpperCase().slice(0,2) - : '??'; - return ( -
-
- {selected.avatar_url - ? - : {selInitials} - } -
-
-
{selected.name || 'Contractor'}
-
- {selected.github && `@${selected.github} · `}{selected.address.slice(0,8)}…{selected.address.slice(-6)} -
-
- -
- ); - } - - // ── Search state ──────────────────────────────────────────────────────────── - return ( -
- {/* Input + floating dropdown */} -
- results.length > 0 && setOpen(true)} - placeholder="Search by name, GitHub username…" - className="input pr-10" - /> - {searching && ( -
- )} - - {/* Floating results list */} - {open && results.length > 0 && ( -
- {results.map(r => { - const initials = r.name ? r.name.split(' ').map(w=>w[0]).join('').toUpperCase().slice(0,2) : '??'; - return ( - - ); - })} -
- )} -
- - {query.length >= 2 && results.length === 0 && !searching && ( -

No registered contractors found.

- )} - - {/* Manual fallback */} - {!showManual ? ( - - ) : ( -
- setManual(e.target.value)} - placeholder="0x…" className="input text-xs flex-1 py-2" /> - - -
- )} -
- ); -} - -// ─── Main modal ─────────────────────────────────────────────────────────────── -export default function CreateStreamModal() { - const { open, prefill, closeModal } = useCreateStream(); - const { address } = useAccount(); - const { profile } = useProfile(address); - const { authFetch, isAuthed, signIn } = useAuth(); - const chainId = useChainId(); - const { tokens: walletTokens, isLoading: tokensLoading } = useWalletTokens(address, chainId); - - const [step, setStep] = useState(0); - const [createdStreamId, setCreatedStreamId] = useState(null); - const [selectedContractor, setSelectedContractor] = useState(null); - const [regStatus, setRegStatus] = useState(null); // 'ok' | 'failed' - const [regArgs, setRegArgs] = useState(null); // cached payload for retry - const [regBusy, setRegBusy] = useState(false); - - // Cached create args so the approve→create chain never reads stale closure values - const createArgsRef = useRef(null); - // Tracks whether a click started ON the backdrop. Prevents accidental close - // when a drag begins inside the panel (e.g. selecting input text) and the - // mouse is released over the backdrop. - const backdropDownRef = useRef(false); - - const VERIFICATION_SOURCES = [ - { key: 'github', label: 'GitHub', placeholder: 'owner/repo', hint: 'Merged PRs + passing CI' }, - { key: 'jira', label: 'Jira', placeholder: 'https://acme.atlassian.net / ABC', hint: 'Ticket moved to Done' }, - { key: 'bitbucket', label: 'Bitbucket', placeholder: 'workspace/repo', hint: 'Merged PRs + pipelines' }, - { key: 'figma', label: 'Figma', placeholder: 'https://figma.com/file/…', hint: 'Approved frames / published' }, - ]; - - // Default to USDC on Arb Sepolia; updates to first available wallet token once loaded - const DEFAULT_TOKEN = '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d'; - const [form, setForm] = useState({ - token: DEFAULT_TOKEN, - milestoneAmount: '', // value paid per milestone (e.g. 150 USDC) - milestoneCount: '3', // number of milestones in the contract - milestoneWindow: '604800', // seconds per window - default 1 week - verificationSource: 'github', - verificationTarget: '', - }); - - // Optional hourly calculator. Fills milestoneAmount = rate/hr × hours per period. - // The per-period amount stays the source of truth; this is just a convenience. - const [hourly, setHourly] = useState({ rate: '', hours: '' }); - - // When wallet tokens load, auto-select the first one if current selection not in list - useEffect(() => { - if (!walletTokens.length) return; - const found = walletTokens.find(t => t.address === form.token); - if (!found) setForm(f => ({ ...f, token: walletTokens[0].address })); - }, [walletTokens]); - - // Apply prefill (e.g. from CompanyDashboard contractor search or public profile link) - useEffect(() => { - if (open && prefill?.recipient) { - setSelectedContractor({ - address: prefill.recipient, - name: prefill.name ?? null, - github: prefill.github ?? null, - avatar_url: prefill.avatar_url ?? null, - }); - } - }, [open, prefill]); - - const selectedToken = walletTokens.find(t => t.address === form.token) ?? walletTokens[0] ?? { symbol: 'USDC', address: DEFAULT_TOKEN, decimals: 6 }; - const { decimals } = selectedToken; - const recipientAddr = selectedContractor?.address ?? ''; - - // ── Derived numbers ────────────────────────────────────────────────────────── - // Milestone window in seconds (BigInt) - const windowSeconds = BigInt(form.milestoneWindow || '604800'); - const milestoneCountInt = Math.max(1, parseInt(form.milestoneCount || '1', 10)); - const milestoneCount = BigInt(milestoneCountInt); - - // Total deposit is exactly milestoneAmount × periods - no ceiling rounding - const milestoneRaw = form.milestoneAmount ? parseUnits(form.milestoneAmount, decimals) : 0n; - const totalCostRaw = milestoneRaw * milestoneCount; - - // When hours_per_week is set: rate is based on total WORKING seconds, not calendar time. - // This ensures each verified day of work earns exactly (hrs/week ÷ 5 × hourlyRate). - // Without hourly data: fall back to calendar duration (legacy / manual entry). - const hoursPerWeekNum = parseFloat(hourly.hours); - const usingHourlyRate = hoursPerWeekNum > 0; - const workingSecondsPerWeek = usingHourlyRate ? Math.round(hoursPerWeekNum * 3600) : null; - const totalWorkingSeconds = workingSecondsPerWeek - ? BigInt(workingSecondsPerWeek) * milestoneCount - : windowSeconds * milestoneCount; - const durationSeconds = totalWorkingSeconds; - - // ratePerSecond = floor(totalDeposit / totalWorkingDuration) - const ratePerSecond = totalCostRaw > 0n && durationSeconds > 0n - ? totalCostRaw / durationSeconds - : 0n; - - // Per-event extension: one day of working hours when hourly rate is set, - // otherwise the full payout window (weekly / bi-weekly / monthly). - const extensionDurationSeconds = usingHourlyRate - ? Math.round((hoursPerWeekNum / 5) * 3600) - : Number(windowSeconds); - const totalCostFloat = totalCostRaw > 0n ? parseFloat(formatUnits(totalCostRaw, decimals)) : 0; - - const totalCostDisplay = totalCostRaw > 0n - ? totalCostFloat.toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 2 }) - : null; - - // Human-readable pay schedule label derived from the selected cadence - const scheduleOption = WINDOW_OPTIONS.find(w => w.seconds.toString() === form.milestoneWindow) ?? WINDOW_OPTIONS[0]; - const scheduleLabel = scheduleOption.label; // "Weekly" | "Bi-weekly" | "Monthly" - const scheduleSubLabel = scheduleOption.sublabel; // "paid every week" … - - // Duration field - label and hint adapt to the selected pay schedule so the - // number the company types always means what they expect. - const durationConfig = { - '604800': { label: 'Duration (weeks)', placeholder: '26', hint: n => `${n} wks = ~${Math.round(n / 4.33)} months` }, - '1209600': { label: 'No. of payments', placeholder: '13', hint: n => `${n} payments = ~${Math.round(n * 2)} weeks` }, - '2592000': { label: 'No. of months', placeholder: '6', hint: n => `${n} months contract` }, - }[form.milestoneWindow] ?? { label: 'Duration', placeholder: '26', hint: () => '' }; - - // Calculated contract end date - shown live so company knows exactly when it runs out - const contractEndDate = milestoneCountInt > 0 && windowSeconds > 0n - ? new Date(Date.now() + Number(windowSeconds) * milestoneCountInt * 1000) - .toLocaleDateString('en-US', { month: 'short', day: 'numeric', year: 'numeric' }) - : null; - - // Show the amount the user typed as per-period - ceiling rounding is absorbed into the total deposit - const perMilestoneDisplay = form.milestoneAmount - ? parseFloat(form.milestoneAmount).toLocaleString('en-US', { minimumFractionDigits: 2, maximumFractionDigits: 6 }) - : null; - - function handleClose() { - const inProgress = approvePending || approveConfirming || createPending || createConfirming; - if (inProgress) return; - setStep(0); - setForm({ - token: walletTokens[0]?.address ?? DEFAULT_TOKEN, - milestoneAmount: '', - milestoneCount: '3', - milestoneWindow: '604800', - verificationSource: 'github', - verificationTarget: '', - }); - setHourly({ rate: '', hours: '' }); - setSelectedContractor(null); - setCreatedStreamId(null); - setRegStatus(null); - setRegArgs(null); - createArgsRef.current = null; - closeModal(); - } - - // Allowance - only needed on step 2 (review/deploy) - const { data: allowance, refetch: refetchAllowance } = useReadContract({ - address: form.token, abi: ERC20_ABI, functionName: 'allowance', - args: [address, getContractAddress(chainId)], - query: { enabled: !!address && step >= 2 }, - }); - const needsApproval = allowance != null && totalCostRaw > 0n && allowance < totalCostRaw; - - // Approve - const { writeContract: doApprove, data: approveTxHash, isPending: approvePending, error: approveError } = useWriteContract(); - const { isLoading: approveConfirming, isSuccess: approveSuccess } = useWaitForTransactionReceipt({ hash: approveTxHash }); - - // After approval confirms, immediately pop wallet for create - no intermediate step. - // createArgsRef holds the args so we never read stale closure state here. - useEffect(() => { - if (!approveSuccess || !createArgsRef.current) return; - refetchAllowance(); - doCreate({ address: getContractAddress(chainId), abi: ROUTER_ABI, functionName: 'createStream', args: createArgsRef.current }); - }, [approveSuccess]); - - // Create - const { writeContract: doCreate, data: createTxHash, isPending: createPending, error: createError } = useWriteContract(); - const { isLoading: createConfirming, isSuccess: createSuccess, isError: createReceiptError, data: createReceipt } = useWaitForTransactionReceipt({ hash: createTxHash }); - - useEffect(() => { - if (!createSuccess || !createReceipt) return; - async function finish() { - try { - const event = parseAbiItem('event StreamCreated(bytes32 indexed streamId, address indexed sender, address indexed recipient, uint256 ratePerSecond)'); - const log = createReceipt.logs.find(l => l.address.toLowerCase() === getContractAddress(chainId).toLowerCase()); - if (!log) { setRegStatus('failed'); return; } - - const { args: eventArgs } = decodeEventLog({ abi: [event], data: log.data, topics: log.topics }); - const streamId = eventArgs.streamId; - if (!streamId) { setRegStatus('failed'); return; } - setCreatedStreamId(streamId); - - const args = { - streamId, - repo: form.verificationSource === 'github' ? form.verificationTarget : null, - verificationSource: form.verificationSource, - verificationTarget: form.verificationTarget, - sender: address, - recipient: recipientAddr, - ratePerSecond: ratePerSecond.toString(), - token: form.token, - extensionDurationSeconds, - hoursPerWeek: usingHourlyRate ? hoursPerWeekNum : undefined, - chainId, - }; - setRegArgs(args); - - // Retry up to 4 times with increasing delays. - // The agent may be waking from a cold start or the RPC may need a moment. - const delays = [0, 2000, 4000, 8000]; - for (const delay of delays) { - if (delay) await new Promise(r => setTimeout(r, delay)); - const result = await registerStreamWithAgent({ ...args, authFetch }); - if (result?.success) { setRegStatus('ok'); return; } - } - setRegStatus('failed'); - } catch (e) { - console.warn('Post-create registration error:', e); - setRegStatus('failed'); - } - } - finish(); - setStep(3); - }, [createSuccess]); - - async function retryRegister() { - if (!regArgs) return; - setRegBusy(true); - const result = await registerStreamWithAgent({ ...regArgs, authFetch }); - setRegBusy(false); - setRegStatus(result?.success ? 'ok' : 'failed'); - } - - if (!open) return null; - - // Step 0 → 1: need contractor + delivery target - const canStep0 = !!recipientAddr && !!form.verificationTarget; - - // Step 1 → 2: need all payment fields - const canStep1 = !!form.milestoneAmount && ratePerSecond > 0n && milestoneCount >= 1n && !!form.token; - - // Step 2 deploy: every field the agent needs - mirrors the server's required-field check - const canCreate = canStep0 && canStep1 && !!chainId && windowSeconds > 0n; - - return ( -
{ backdropDownRef.current = e.target === e.currentTarget; }} - onClick={e => { - // Only close when BOTH press and release happened on the backdrop — - // never on a drag that merely ended there. - if (e.target === e.currentTarget && backdropDownRef.current) handleClose(); - backdropDownRef.current = false; - }} - > -
- - {/* Header */} -
-
- {step > 0 && step < 3 && !(approvePending || approveConfirming || createPending || createConfirming) && ( - - )} -
-

- {step === 0 ? 'Who and where' : step === 1 ? 'Payment terms' : step === 3 ? 'Stream live' : 'Review and deploy'} -

- {step < 3 && } -
-
- -
- -
- - {/* ── Step 0: Who + where ── */} - {step === 0 && ( -
{ e.preventDefault(); setStep(1); }} className="flex flex-col gap-5"> -
- - -
- -
- -
- {VERIFICATION_SOURCES.map(src => ( - - ))} -
- {form.verificationSource === 'github' ? ( - setForm(f => ({ ...f, verificationTarget: val }))} - /> - ) : ['jira', 'bitbucket', 'figma'].includes(form.verificationSource) ? ( - setForm(f => ({ ...f, verificationTarget: val }))} - isConnected={ - form.verificationSource === 'jira' ? !!profile?.jira_connected : - form.verificationSource === 'bitbucket' ? !!profile?.bitbucket_connected : - form.verificationSource === 'figma' ? !!profile?.figma_connected : - false - } - /> - ) : ( -
- setForm(f => ({ ...f, verificationTarget: e.target.value }))} - placeholder={VERIFICATION_SOURCES.find(s => s.key === form.verificationSource)?.placeholder} - className="input" - /> -

- {VERIFICATION_SOURCES.find(s => s.key === form.verificationSource)?.hint} -

-
- )} -
- - -
- )} - - {/* ── Step 1: Payment terms ── */} - {step === 1 && ( -
{ e.preventDefault(); refetchAllowance(); setStep(2); }} className="flex flex-col gap-5"> - - {/* Hourly rate × hrs/week = charge per pay cycle */} -
- - {/* On mobile: rate + hours on one row, result below. On sm+: single row */} -
-
-
- $/hr - { - const v = e.target.value; - if (v !== '' && !/^\d*\.?\d*$/.test(v)) return; - const next = { ...hourly, rate: v }; - setHourly(next); - const r = parseFloat(next.rate), h = parseFloat(next.hours); - if (r > 0 && h > 0) setForm(f => ({ ...f, milestoneAmount: parseFloat((r * h).toFixed(6)).toString() })); - }} - className="input pl-9 text-sm" /> -
-
×
-
- hrs/wk - { - const v = e.target.value; - if (v !== '' && !/^\d*\.?\d*$/.test(v)) return; - const next = { ...hourly, hours: v }; - setHourly(next); - const r = parseFloat(next.rate), h = parseFloat(next.hours); - if (r > 0 && h > 0) setForm(f => ({ ...f, milestoneAmount: parseFloat((r * h).toFixed(6)).toString() })); - }} - className="input pl-16 text-sm" /> -
-
-
=
-
- = charge - { - const v = e.target.value; - if (v === '' || /^\d*\.?\d*$/.test(v)) { - setForm(f => ({ ...f, milestoneAmount: v })); - setHourly({ rate: '', hours: '' }); - } - }} - className="input sm:pr-16 pr-16 pl-16 sm:pl-3 text-sm font-semibold" required /> - /charge -
-
-

Fill rate and hours or type the agreed charge directly.

-
- - {/* Token + duration + pay schedule - 2-col on mobile, 3-col on sm+ */} -
-
- - {tokensLoading - ?
Loading…
- :
- {walletTokens.slice(0, 3).map(t => ( - - ))} - {walletTokens.length === 0 &&

No tokens found.

} -
- } -
- -
- - setForm(f => ({ ...f, milestoneCount: e.target.value }))} - className="input text-sm" placeholder={durationConfig.placeholder} required /> -

{durationConfig.hint(milestoneCountInt)}

- {contractEndDate && ( -

ends {contractEndDate}

- )} -
- -
- -
- {WINDOW_OPTIONS.map(w => ( - - ))} -
-
-
- - -
- )} - - {/* ── Step 2: Review + Approve & Deploy (combined) ── */} - {step === 2 && ( -
- {/* Summary */} -
- {[ - ['Contractor', selectedContractor?.name ? `${selectedContractor.name} · ${recipientAddr.slice(0,8)}…` : `${recipientAddr.slice(0,8)}…${recipientAddr.slice(-6)}`], - ['Delivers via', `${VERIFICATION_SOURCES.find(s=>s.key===form.verificationSource)?.label ?? ''} · ${form.verificationTarget}`], - ['Currency', selectedToken.symbol], - ['Charge', `${perMilestoneDisplay} ${selectedToken.symbol} ${scheduleSubLabel}`], - ['Duration', contractEndDate ? `${scheduleLabel} · ends ${contractEndDate}` : `${form.milestoneCount} · ${scheduleLabel}`], - ['Total deposit', `${totalCostDisplay} ${selectedToken.symbol}`], - ].map(([label, value], i, arr) => ( -
- {label} - {value} -
- ))} -
- - {/* Approve status (only shown while approving) */} - {needsApproval && (approvePending || approveConfirming) && ( -
-
- {approvePending ? 'Waiting for approval confirmation…' : 'Approval confirming - wallet will open for deposit next…'} -
- )} - - {ratePerSecond === 0n && ( -
- Rate rounds to zero - increase the amount or use a shorter period. -
- )} - - {(approveError || createError) && ( -
- {(approveError ?? createError)?.shortMessage ?? (approveError ?? createError)?.message ?? 'Transaction failed'} -
- )} - - {/* Single CTA - approve then immediately create, or create directly if already approved */} - {/* Gate on isAuthed - the JWT is required for post-create agent registration. - If the session expired or sign was skipped, prompt re-sign before deploying. */} - {!isAuthed ? ( - - ) : ( - - )} -
- )} - - {/* ── Step 3: Success ── */} - {step === 3 && (() => { - const periodLabel = milestoneCountInt === 1 - ? `1 ${scheduleLabel.toLowerCase().replace('ly','').replace('bi-week','2-week')} period` - : `${milestoneCountInt} ${scheduleLabel.toLowerCase()} periods`; - return ( -
- {/* Icon */} -
- - - -
- - {/* Title + subtitle */} -
-

Stream is live

-

- {perMilestoneDisplay} {selectedToken.symbol} {scheduleSubLabel} · {periodLabel} -

-
- - {/* Agent status */} - {form.verificationTarget && regStatus === 'ok' && ( -
- Agent watching {VERIFICATION_SOURCES.find(s => s.key === form.verificationSource)?.label} · {form.verificationTarget} -
- )} - {regStatus === 'failed' && ( -
- Agent registration pending — open the stream to complete setup. -
- )} - - {/* Tx hash — short + copy */} - {createTxHash && ( - - )} - - {/* Actions */} -
- - {createdStreamId && ( - - )} -
-
- ); - })()} -
- -
-
- ); -} diff --git a/frontend/src/components/DownloadStatement.jsx b/frontend/src/components/DownloadStatement.jsx deleted file mode 100644 index 21e0966..0000000 --- a/frontend/src/components/DownloadStatement.jsx +++ /dev/null @@ -1,106 +0,0 @@ -import { useState, useCallback } from 'react'; -import { pdf } from '@react-pdf/renderer'; -import { Download, Loader2 } from 'lucide-react'; -import { StreamStatementDocument } from './StreamStatementPDF'; -import { fetchFromServer } from '../hooks/useProfile'; - -const MONTH_NAMES = [ - 'January','February','March','April','May','June', - 'July','August','September','October','November','December', -]; - -const LOGO_URL = `${window.location.origin}/cronstream.png`; - -// Resolve display names for a list of addresses in parallel -async function resolveNames(addresses) { - const unique = [...new Set(addresses.filter(Boolean).map(a => a.toLowerCase()))]; - const results = await Promise.allSettled(unique.map(addr => fetchFromServer(addr))); - const map = {}; - unique.forEach((addr, i) => { - const p = results[i].status === 'fulfilled' ? results[i].value : null; - map[addr] = p?.name ?? p?.username ?? null; - }); - return map; -} - -export default function DownloadStatement({ streams, role, ownerName, ownerAddress }) { - const now = new Date(); - const [month, setMonth] = useState(now.getMonth()); - const [year, setYear] = useState(now.getFullYear()); - const [generating, setGenerating] = useState(false); - - const years = []; - for (let y = now.getFullYear(); y >= now.getFullYear() - 3; y--) years.push(y); - - const handleDownload = useCallback(async () => { - setGenerating(true); - try { - // Resolve counterparty display names - const addrs = streams.map(s => (role === 'company' ? s.recipient : s.sender)); - const counterpartyNames = await resolveNames(addrs); - - const doc = ( - - ); - - const blob = await pdf(doc).toBlob(); - const url = URL.createObjectURL(blob); - const a = document.createElement('a'); - a.href = url; - a.download = `cronstream-${role === 'company' ? 'payroll' : 'income'}-${year}-${String(month + 1).padStart(2, '0')}.pdf`; - a.click(); - URL.revokeObjectURL(url); - } catch (err) { - console.error('PDF generation failed:', err); - } finally { - setGenerating(false); - } - }, [streams, role, month, year, ownerName, ownerAddress]); - - return ( -
- {/* Month */} - - - {/* Year */} - - - {/* Download */} - -
- ); -} diff --git a/frontend/src/components/ErrorBoundary.jsx b/frontend/src/components/ErrorBoundary.jsx deleted file mode 100644 index 5d9ce7d..0000000 --- a/frontend/src/components/ErrorBoundary.jsx +++ /dev/null @@ -1,40 +0,0 @@ -import { Component } from 'react'; -import ErrorPage from '../pages/ErrorPage'; - -/** - * ErrorBoundary - wraps the whole app tree. - * Catches any unhandled render/lifecycle error and shows ErrorPage. - */ -export default class ErrorBoundary extends Component { - constructor(props) { - super(props); - this.state = { hasError: false, error: null }; - } - - static getDerivedStateFromError(error) { - return { hasError: true, error }; - } - - componentDidCatch(error, info) { - console.error('[ErrorBoundary]', error, info.componentStack); - } - - reset() { - this.setState({ hasError: false, error: null }); - } - - render() { - if (this.state.hasError) { - return ( - { - this.reset(); - window.location.href = '/'; - }} - /> - ); - } - return this.props.children; - } -} diff --git a/frontend/src/components/FlowDiagram.jsx b/frontend/src/components/FlowDiagram.jsx deleted file mode 100644 index b709024..0000000 --- a/frontend/src/components/FlowDiagram.jsx +++ /dev/null @@ -1,228 +0,0 @@ -/** - * FlowDiagram - animated "How it works" visualization. - * - * Shows the 5-step CronStream flow as a canvas animation: - * Node circles connected by animated flowing lines. - * A glowing pulse travels along each connector in sequence. - * Active node pulses and highlights its label. - */ - -import { useEffect, useRef, useState } from 'react'; - -const ACCENT = '#00D4AA'; -const ACCENT_DIM = 'rgba(0,212,170,0.15)'; - -const STEPS = [ - { step: '01', title: 'Company creates stream', desc: 'Deposit full budget upfront. Set rate per second and duration.', icon: 'https://img.icons8.com/color/48/commercial-development-management.png' }, - { step: '02', title: 'Contractor ships code', desc: 'Push commits, open PR, pass CI. Work is verifiable on GitHub.', icon: 'https://img.icons8.com/color/48/source-code.png' }, - { step: '03', title: 'Agent verifies milestone', desc: '3-layer check: code diff, merged PR, CI pass. All must pass.', icon: 'https://img.icons8.com/color/48/artificial-intelligence.png' }, - { step: '04', title: 'Stream window extends', desc: 'Agent signs EIP-712 voucher and submits on-chain. Contractor earns another window.', icon: 'https://img.icons8.com/color/48/time.png' }, - { step: '05', title: 'Contractor withdraws', desc: 'Pull earned tokens anytime. Protocol fee deducted automatically.', icon: 'https://img.icons8.com/color/48/receive-cash.png' }, -]; - -// ─── Canvas pulse animation ─────────────────────────────────────────────────── - -function PulseCanvas({ activeIdx }) { - const canvasRef = useRef(null); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - const ctx = canvas.getContext('2d'); - const dpr = window.devicePixelRatio || 1; - - let animId; - let t = 0; - - function resize() { - const rect = canvas.getBoundingClientRect(); - canvas.width = rect.width * dpr; - canvas.height = rect.height * dpr; - ctx.scale(dpr, dpr); - } - - function draw() { - const W = canvas.getBoundingClientRect().width; - const H = canvas.getBoundingClientRect().height; - ctx.clearRect(0, 0, W, H); - - const n = STEPS.length; - const pad = 40; - const nodeR = 18; - - // Evenly space nodes across the width - const xs = STEPS.map((_, i) => pad + (i / (n - 1)) * (W - pad * 2)); - const y = H / 2; - - // Draw connector lines - for (let i = 0; i < n - 1; i++) { - const x1 = xs[i] + nodeR; - const x2 = xs[i + 1] - nodeR; - - // Base line - ctx.beginPath(); - ctx.moveTo(x1, y); - ctx.lineTo(x2, y); - ctx.strokeStyle = i < activeIdx ? ACCENT : 'rgba(255,255,255,0.08)'; - ctx.lineWidth = 1.5; - ctx.stroke(); - - // Animated pulse on the active connector - if (i === activeIdx) { - const progress = (t % 1); - const px = x1 + (x2 - x1) * progress; - - // Trail - const tg = ctx.createLinearGradient(px - 40, y, px, y); - tg.addColorStop(0, 'rgba(0,212,170,0)'); - tg.addColorStop(1, 'rgba(0,212,170,0.8)'); - ctx.beginPath(); - ctx.moveTo(px - 40, y); - ctx.lineTo(px, y); - ctx.strokeStyle = tg; - ctx.lineWidth = 2.5; - ctx.stroke(); - - // Pulse head - ctx.beginPath(); - ctx.arc(px, y, 4, 0, Math.PI * 2); - ctx.fillStyle = ACCENT; - ctx.shadowBlur = 16; - ctx.shadowColor = ACCENT; - ctx.fill(); - ctx.shadowBlur = 0; - } - } - - // Draw nodes - STEPS.forEach((s, i) => { - const x = xs[i]; - const isActive = i === activeIdx; - const isPast = i < activeIdx; - - // Outer glow for active - if (isActive) { - const glow = ctx.createRadialGradient(x, y, 0, x, y, nodeR * 3); - glow.addColorStop(0, 'rgba(0,212,170,0.25)'); - glow.addColorStop(1, 'rgba(0,212,170,0)'); - ctx.beginPath(); - ctx.arc(x, y, nodeR * 3, 0, Math.PI * 2); - ctx.fillStyle = glow; - ctx.fill(); - } - - // Ring - ctx.beginPath(); - ctx.arc(x, y, nodeR, 0, Math.PI * 2); - ctx.strokeStyle = isActive ? ACCENT : isPast ? 'rgba(0,212,170,0.5)' : 'rgba(255,255,255,0.12)'; - ctx.lineWidth = isActive ? 2 : 1.5; - ctx.stroke(); - - // Fill - ctx.beginPath(); - ctx.arc(x, y, nodeR - 2, 0, Math.PI * 2); - ctx.fillStyle = isActive ? 'rgba(0,212,170,0.15)' : isPast ? 'rgba(0,212,170,0.06)' : 'rgba(255,255,255,0.03)'; - ctx.fill(); - - // Step number - ctx.fillStyle = isActive ? ACCENT : isPast ? 'rgba(0,212,170,0.7)' : 'rgba(255,255,255,0.3)'; - ctx.font = `${isActive ? 700 : 500} 10px "JetBrains Mono", monospace`; - ctx.textAlign = 'center'; - ctx.textBaseline = 'middle'; - ctx.fillText(s.step, x, y); - }); - - t += 0.008; - animId = requestAnimationFrame(draw); - } - - resize(); - const ro = new ResizeObserver(() => { resize(); }); - ro.observe(canvas); - - animId = requestAnimationFrame(draw); - return () => { cancelAnimationFrame(animId); ro.disconnect(); }; - }, [activeIdx]); - - return ( - - ); -} - -// ─── Main export ────────────────────────────────────────────────────────────── - -export default function FlowDiagram() { - const [activeIdx, setActiveIdx] = useState(0); - - // Auto-advance through steps - useEffect(() => { - const id = setInterval(() => { - setActiveIdx(i => (i + 1) % STEPS.length); - }, 2200); - return () => clearInterval(id); - }, []); - - const active = STEPS[activeIdx]; - - return ( -
-

How it works

-

Five steps. Zero trust required.

- - {/* Canvas flow */} -
- - {/* Step dots + connectors */} -
- -
- - {/* Step labels row */} -
- {STEPS.map((s, i) => ( - - ))} -
- - {/* Active step detail */} -
-
-
- {active.step} -
-
-

{active.title}

-

{active.desc}

-
-
- - {/* Progress dots */} -
- {STEPS.map((_, i) => ( -
-
-
-
- ); -} diff --git a/frontend/src/components/LimelightNav.jsx b/frontend/src/components/LimelightNav.jsx deleted file mode 100644 index 63553de..0000000 --- a/frontend/src/components/LimelightNav.jsx +++ /dev/null @@ -1,80 +0,0 @@ -import { useState, useRef, useLayoutEffect, cloneElement } from 'react'; -import { useLocation, useNavigate } from 'react-router-dom'; - -/** - * LimelightNav - animated bottom nav bar with a glowing limelight indicator. - * Adapted from the limelight-nav component to work with React Router NavLinks. - */ -export function LimelightNav({ items = [], className = '' }) { - const location = useLocation(); - const navigate = useNavigate(); - - // Derive active index from current route - const activeIndex = (() => { - const idx = items.findLastIndex(item => item.to && location.pathname.startsWith(item.to)); - return idx >= 0 ? idx : 0; - })(); - - const [isReady, setIsReady] = useState(false); - const itemRefs = useRef([]); - const limelightRef = useRef(null); - - useLayoutEffect(() => { - if (!items.length) return; - const limelight = limelightRef.current; - const activeItem = itemRefs.current[activeIndex]; - if (!limelight || !activeItem) return; - - const newLeft = activeItem.offsetLeft + activeItem.offsetWidth / 2 - limelight.offsetWidth / 2; - limelight.style.left = `${newLeft}px`; - if (!isReady) setTimeout(() => setIsReady(true), 50); - }, [activeIndex, isReady, items]); - - return ( - - ); -} diff --git a/frontend/src/components/LiveBalance.jsx b/frontend/src/components/LiveBalance.jsx deleted file mode 100644 index 21493ca..0000000 --- a/frontend/src/components/LiveBalance.jsx +++ /dev/null @@ -1,100 +0,0 @@ -import { useState, useEffect, useRef } from 'react'; -import { useReadContract, useChainId } from 'wagmi'; -import { formatUnits } from 'viem'; -import { getContractAddress, ROUTER_ABI } from '../lib/wagmi'; - -/** - * LiveBalance - ticks up in real-time using ratePerSecond interpolation. - * - * Two modes: - * 1. Controlled - parent passes `balance` (BigInt). LiveBalance uses it as the - * anchor and ticks from there. Internal useReadContract is disabled. - * Use this inside dashboards where the parent owns the on-chain reads via - * useContractReadsForChain (bypasses MetaMask chain-routing bugs). - * - * 2. Standalone - no `balance` prop. LiveBalance fetches balanceOf itself via - * wagmi's useReadContract. Works when the wallet IS on the correct chain - * (e.g. StreamDetail page after we fixed it to read from the right chain). - */ -export default function LiveBalance({ - streamId, - ratePerSecond, // BigInt - rate per second - streamValidUntil, // BigInt - unix timestamp - decimals = 6, - className = '', - showTicker = true, - balance: controlledBalance = null, // BigInt | null - if provided, skip internal read -}) { - const chainId = useChainId(); - const [display, setDisplay] = useState(null); - const baseRef = useRef(null); // { value: number, fetchedAt: number } - const frameRef = useRef(null); - - // 0n is falsy in JS so we must check explicitly. - // Any streamValidUntil that is zero or in the past = expired. - const nowSec = BigInt(Math.floor(Date.now() / 1000)); - const isExpired = !streamValidUntil || nowSec >= streamValidUntil; - - // ── Internal read - only fires when no controlled balance is provided ──────── - const { data: onChainBalance } = useReadContract({ - address: getContractAddress(chainId), - abi: ROUTER_ABI, - functionName: 'balanceOf', - args: [streamId], - query: { - refetchInterval: isExpired ? false : 10_000, - enabled: !!streamId && controlledBalance == null, - }, - }); - - // The effective balance - controlled prop wins over internal fetch. - // Treat 0n as a valid balance (stream fully withdrawn), only skip if truly null/undefined. - const effectiveBalance = (controlledBalance != null) ? controlledBalance : (onChainBalance ?? null); - - // Anchor the ticker whenever the balance value changes - useEffect(() => { - if (effectiveBalance == null) return; - baseRef.current = { - value: parseFloat(formatUnits(effectiveBalance, decimals)), - fetchedAt: Date.now(), - }; - }, [effectiveBalance, decimals]); - - // RAF loop - interpolates between anchors - useEffect(() => { - if (isExpired) { - if (effectiveBalance != null) { - setDisplay(parseFloat(formatUnits(effectiveBalance, decimals))); - } - return; - } - - const rate = ratePerSecond ? parseFloat(formatUnits(ratePerSecond, decimals)) : 0; - - function tick() { - if (!baseRef.current) { frameRef.current = requestAnimationFrame(tick); return; } - const elapsed = (Date.now() - baseRef.current.fetchedAt) / 1000; - const live = baseRef.current.value + rate * elapsed; - setDisplay(live); - frameRef.current = requestAnimationFrame(tick); - } - - frameRef.current = requestAnimationFrame(tick); - return () => cancelAnimationFrame(frameRef.current); - }, [ratePerSecond, isExpired, decimals, effectiveBalance]); - - if (display == null) return -; - - const formatted = display.toFixed(4); - const [int, dec] = formatted.split('.'); - - return ( - - {int} - .{dec} - {showTicker && !isExpired && ( - - )} - - ); -} diff --git a/frontend/src/components/LogoLoader.jsx b/frontend/src/components/LogoLoader.jsx deleted file mode 100644 index 64352d7..0000000 --- a/frontend/src/components/LogoLoader.jsx +++ /dev/null @@ -1,175 +0,0 @@ -/** - * LogoLoader - full-screen loading screen using the real CronStream icon. - * - * Design: - * - cronstream.png (transparent background) centred with a teal glow pulse - * - Two SVG arcs spinning in opposite directions outside the icon - * - Three staggered stream bars below - * - Pure CSS animations - no canvas, no RAF loop - */ -export default function LogoLoader({ label = 'Loading…' }) { - return ( -
- - {/* ── Keyframes ─────────────────────────────────────────────────────── */} - - - {/* ── Icon + spinner rings ───────────────────────────────────────────── */} -
- - {/* Soft ambient radial glow behind everything */} -
- - {/* Outer arc - CW, slow */} - - - - - - - - - - - - - {/* Inner arc - CCW, faster */} - - - - - - - - - - - - - {/* The actual icon - centred, transparent bg, pulsing glow */} - - -
- - {/* ── Label + stream bars ───────────────────────────────────────────── */} -
- -

- CronStream -

- - {/* Stream bars */} -
- {[0, 1, 2].map(i => ( -
-
-
- ))} -
- -

- {label} -

- -
- -
- ); -} diff --git a/frontend/src/components/MagneticDock.jsx b/frontend/src/components/MagneticDock.jsx deleted file mode 100644 index de6c6ac..0000000 --- a/frontend/src/components/MagneticDock.jsx +++ /dev/null @@ -1,113 +0,0 @@ -import { useState, useRef, useContext, createContext, useEffect } from 'react'; -import { motion, useMotionValue, useTransform, useSpring } from 'framer-motion'; -import { Globe } from 'lucide-react'; - -// Brand SVG icons (not in this lucide version) -const GithubIcon = () => ( - - - -); - -const TwitterIcon = () => ( - - - -); - -const LinkedinIcon = () => ( - - - -); - -const FarcasterIcon = () => ( - Farcaster -); - -const MouseContext = createContext({ x: 0, y: 0 }); - -function DockIcon({ icon, label, href, onClick }) { - const ref = useRef(null); - const mouse = useContext(MouseContext); - const distance = useMotionValue(Infinity); - - useEffect(() => { - if (!ref.current || (mouse.x === 0 && mouse.y === 0)) { - distance.set(Infinity); - return; - } - const iconRect = ref.current.getBoundingClientRect(); - const parentRect = ref.current.parentElement?.getBoundingClientRect(); - if (!parentRect) return; - const iconCenterX = iconRect.left + iconRect.width / 2; - const mouseXAbs = parentRect.left + mouse.x; - distance.set(Math.abs(mouseXAbs - iconCenterX)); - }, [mouse, distance]); - - const size = useTransform(distance, [0, 80], [36, 26]); - const springS = useSpring(size, { mass: 0.1, stiffness: 180, damping: 14 }); - - function handleClick() { - if (href) window.open(href, '_blank', 'noopener noreferrer'); - onClick?.(); - } - - return ( -
- - {icon} - - - {label} - -
- ); -} - -/** - * MagneticDock - shows contractor social links as a macOS-style dock. - * Pass in the profile object; only icons with data are rendered. - */ -export default function MagneticDock({ profile }) { - const [pos, setPos] = useState({ x: 0, y: 0 }); - - const items = [ - profile?.github && { id: 'github', label: 'GitHub', icon: , href: `https://github.com/${profile.github}` }, - profile?.twitter && { id: 'twitter', label: 'X', icon: , href: `https://x.com/${profile.twitter}` }, - profile?.linkedin && { id: 'linkedin', label: 'LinkedIn', icon: , href: `https://linkedin.com/in/${profile.linkedin}` }, - profile?.farcaster && { id: 'farcaster', label: 'Farcaster', icon: , href: `https://warpcast.com/${profile.farcaster}` }, - profile?.website && { id: 'website', label: 'Website', icon: , href: profile.website }, - ].filter(Boolean); - - if (!items.length) return null; - - return ( - -
{ - const { clientX, currentTarget } = e; - const { left } = currentTarget.getBoundingClientRect(); - setPos({ x: clientX - left, y: 0 }); - }} - onMouseLeave={() => setPos({ x: 0, y: 0 })} - className="flex items-end gap-2 pb-1" - > - {items.map(item => ( - - ))} -
-
- ); -} diff --git a/frontend/src/components/PlatformPicker.jsx b/frontend/src/components/PlatformPicker.jsx deleted file mode 100644 index ca5778d..0000000 --- a/frontend/src/components/PlatformPicker.jsx +++ /dev/null @@ -1,159 +0,0 @@ -import { useState, useEffect, useRef } from 'react'; -import { useAuth } from '../context/AuthContext'; - -const AGENT_URL = import.meta.env.VITE_AGENT_URL ?? 'http://localhost:3000'; - -const CONFIG = { - jira: { - endpoint: '/api/v1/platforms/jira/projects', - placeholder: 'Search Jira projects…', - manual: 'PROJECT-KEY', - label: item => item.name, - sub: item => item.key, - value: item => item.key, - notConnected: 'Connect Jira in Settings to browse your projects.', - }, - bitbucket: { - endpoint: '/api/v1/platforms/bitbucket/repos', - placeholder: 'Search Bitbucket repos…', - manual: 'workspace/repo-slug', - label: item => item.fullName, - sub: item => item.language ?? (item.isPrivate ? 'private' : ''), - value: item => item.fullName, - notConnected: 'Connect Bitbucket in Settings to browse your repos.', - }, - figma: { - endpoint: '/api/v1/platforms/figma/files', - placeholder: 'https://www.figma.com/file/...', - manual: 'https://www.figma.com/file/...', - label: item => item.name, - sub: item => item.projectName ?? '', - value: item => item.url, - notConnected: 'Paste your Figma file URL below.', - }, -}; - -export default function PlatformPicker({ source, value, onChange, isConnected }) { - const { authFetch } = useAuth(); - const cfg = CONFIG[source]; - const [items, setItems] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [manual, setManual] = useState(false); - const [query, setQuery] = useState(''); - const [open, setOpen] = useState(false); - const wrapperRef = useRef(null); - - useEffect(() => { - function onDown(e) { - if (wrapperRef.current && !wrapperRef.current.contains(e.target)) setOpen(false); - } - document.addEventListener('mousedown', onDown); - return () => document.removeEventListener('mousedown', onDown); - }, []); - - useEffect(() => { - if (!isConnected || manual || !cfg) return; - setLoading(true); - setError(null); - authFetch(`${AGENT_URL}${cfg.endpoint}`) - .then(r => r.json()) - .then(data => { - setItems(data.items ?? []); - if (!data.items?.length) setManual(true); - }) - .catch(err => { setError(err.message); setManual(true); }) - .finally(() => setLoading(false)); - }, [source, isConnected, manual]); - - if (!cfg) return null; - - const filtered = items.filter(item => - !query || cfg.label(item).toLowerCase().includes(query.toLowerCase()), - ); - - // Not connected — show manual input with hint - if (!isConnected || manual) { - return ( -
- onChange(e.target.value)} - placeholder={cfg.manual} - className="input" - /> - {!isConnected ? ( -

{cfg.notConnected}

- ) : ( - - )} - {error &&

{error}

} -
- ); - } - - return ( -
- {/* Selected item */} - {value && ( -
- {value} - -
- )} - - {!value && ( -
- {loading ? ( -
-
- Fetching {source}… -
- ) : ( - { setQuery(e.target.value); setOpen(true); }} - onFocus={() => filtered.length > 0 && setOpen(true)} - placeholder={cfg.placeholder} - className="input" - /> - )} - - {open && filtered.length > 0 && ( -
- {filtered.map((item, i) => ( - - ))} -
- )} - - {items.length === 0 && !loading && ( -

No items found.

- )} -
- )} - - -
- ); -} diff --git a/frontend/src/components/RepoPicker.jsx b/frontend/src/components/RepoPicker.jsx deleted file mode 100644 index 7b284b0..0000000 --- a/frontend/src/components/RepoPicker.jsx +++ /dev/null @@ -1,128 +0,0 @@ -import { useState, useEffect, useRef } from 'react'; -import { useAuth } from '../context/AuthContext'; - -const AGENT_URL = import.meta.env.VITE_AGENT_URL ?? 'http://localhost:3000'; - -export default function RepoPicker({ isConnected, value, onChange }) { - const { authFetch } = useAuth(); - const [items, setItems] = useState([]); - const [loading, setLoading] = useState(false); - const [error, setError] = useState(null); - const [manual, setManual] = useState(false); - const [query, setQuery] = useState(''); - const [open, setOpen] = useState(false); - const wrapperRef = useRef(null); - - useEffect(() => { - function onDown(e) { - if (wrapperRef.current && !wrapperRef.current.contains(e.target)) setOpen(false); - } - document.addEventListener('mousedown', onDown); - return () => document.removeEventListener('mousedown', onDown); - }, []); - - useEffect(() => { - if (!isConnected || manual) return; - setLoading(true); - setError(null); - authFetch(`${AGENT_URL}/api/v1/platforms/github/repos`) - .then(r => r.json()) - .then(data => { - setItems(data.items ?? []); - if (!data.items?.length) setManual(true); - }) - .catch(err => { setError(err.message); setManual(true); }) - .finally(() => setLoading(false)); - }, [isConnected, manual]); - - const filtered = items.filter(r => - !query || r.fullName.toLowerCase().includes(query.toLowerCase()), - ); - - if (!isConnected || manual) { - return ( -
- onChange(e.target.value)} - placeholder="owner/repo" - className="input" - /> - {!isConnected ? ( -

Connect GitHub in Settings to browse your repos.

- ) : ( - - )} - {error &&

{error}

} -
- ); - } - - return ( -
- {value && ( -
- {value} - -
- )} - - {!value && ( -
- {loading ? ( -
-
- Fetching repos… -
- ) : ( - { setQuery(e.target.value); setOpen(true); }} - onFocus={() => filtered.length > 0 && setOpen(true)} - placeholder="Search repos…" - className="input" - /> - )} - - {open && filtered.length > 0 && ( -
- {filtered.map(repo => ( - - ))} -
- )} - - {items.length === 0 && !loading && ( -

No repos found.

- )} -
- )} - - -
- ); -} diff --git a/frontend/src/components/SiwePrompt.jsx b/frontend/src/components/SiwePrompt.jsx deleted file mode 100644 index 4b4ecc1..0000000 --- a/frontend/src/components/SiwePrompt.jsx +++ /dev/null @@ -1,33 +0,0 @@ -/** - * SiwePrompt - * - * No modal - the wallet pops automatically on connect (AuthContext). - * This component only renders a slim retry banner if the user rejected - * the signature and is stuck without a session. - */ - -import { useAuth } from '../context/AuthContext'; -import { useAccount } from 'wagmi'; - -export default function SiwePrompt() { - const { isAuthed, signing, signIn, signError } = useAuth(); - const { isConnected } = useAccount(); - - if (isAuthed || !isConnected) return null; - - return ( -
- - {signError ? signError : 'Wallet signature needed'} - -
- ); -} diff --git a/frontend/src/components/StreamBackground.jsx b/frontend/src/components/StreamBackground.jsx deleted file mode 100644 index 343b29f..0000000 --- a/frontend/src/components/StreamBackground.jsx +++ /dev/null @@ -1,234 +0,0 @@ -/** - * StreamBackground - animated canvas background for the landing page hero. - * - * Visualises CronStream's core concept: - * • Horizontal stream lanes - payment channels between company → contractor - * • Glowing particles with trails - tokens flowing per-second - * • Pulsing milestone nodes - verification checkpoints on each lane - * • Burst rings - fired when a particle passes through a node (milestone hit) - * • Faint vertical grid - structural depth - */ - -import { useEffect, useRef } from 'react'; - -const ACCENT = '#00D4AA'; -const ACCENT0 = 'rgba(0,212,170,0)'; - -// ─── helpers ───────────────────────────────────────────────────────────────── -const rand = (a, b) => a + Math.random() * (b - a); -const randInt = (a, b) => Math.floor(rand(a, b + 1)); - -export default function StreamBackground() { - const canvasRef = useRef(null); - - useEffect(() => { - const canvas = canvasRef.current; - if (!canvas) return; - - const ctx = canvas.getContext('2d'); - const dpr = window.devicePixelRatio || 1; - let animId, W, H; - let streams = [], particles = [], nodes = [], bursts = []; - let lastBurst = {}; // cooldown map keyed by nodeId - - // ── setup ────────────────────────────────────────────────────────────── - function init() { - const rect = canvas.getBoundingClientRect(); - W = rect.width; - H = rect.height; - canvas.width = W * dpr; - canvas.height = H * dpr; - ctx.scale(dpr, dpr); - - streams = []; - particles = []; - nodes = []; - bursts = []; - - const N_STREAMS = Math.max(5, Math.floor(H / 110)); - - for (let i = 0; i < N_STREAMS; i++) { - const y = (H / (N_STREAMS + 1)) * (i + 1) + rand(-20, 20); - const opa = rand(0.06, 0.16); - streams.push({ y, opa }); - - // particles per lane - const nP = randInt(2, 4); - for (let j = 0; j < nP; j++) { - particles.push({ - si: i, // stream index - x: rand(0, W), - speed: rand(0.6, 2.2), - r: rand(1.8, 3.2), - opa: rand(0.55, 1), - }); - } - - // milestone nodes per lane - const nN = randInt(1, 3); - for (let j = 0; j < nN; j++) { - nodes.push({ - id: `${i}-${j}`, - si: i, - x: (W / (nN + 1)) * (j + 1) + rand(-60, 60), - phi: rand(0, Math.PI * 2), // phase offset for pulse - br: rand(3.5, 6), // base radius - }); - } - } - } - - // ── burst factory ────────────────────────────────────────────────────── - function spawnBurst(x, y) { - const n = randInt(1, 2); - for (let i = 0; i < n; i++) { - bursts.push({ - x, y, - r: 0, - maxR: rand(24, 48), - opa: rand(0.5, 0.8), - spd: rand(0.7, 1.3), - }); - } - } - - // ── main draw ────────────────────────────────────────────────────────── - function draw(t) { - ctx.clearRect(0, 0, W, H); - - // faint vertical grid lines - const gridStep = 80; - ctx.lineWidth = 0.5; - for (let x = 0; x < W; x += gridStep) { - ctx.beginPath(); - ctx.moveTo(x, 0); - ctx.lineTo(x, H); - ctx.strokeStyle = 'rgba(0,212,170,0.04)'; - ctx.stroke(); - } - - // ── stream lanes ──────────────────────────────────────────────────── - streams.forEach(({ y, opa }) => { - ctx.beginPath(); - ctx.moveTo(0, y); - ctx.lineTo(W, y); - ctx.strokeStyle = `rgba(0,212,170,${opa * 0.5})`; - ctx.lineWidth = 0.8; - ctx.stroke(); - }); - - // ── milestone nodes ───────────────────────────────────────────────── - nodes.forEach(node => { - const { x, y: sy } = { x: node.x, y: streams[node.si].y }; - node.phi += 0.025; - const r = node.br + Math.sin(node.phi) * 1.8; - - // outer glow halo - const g = ctx.createRadialGradient(x, sy, 0, x, sy, r * 5); - g.addColorStop(0, `rgba(0,212,170,0.18)`); - g.addColorStop(1, ACCENT0); - ctx.beginPath(); - ctx.arc(x, sy, r * 5, 0, Math.PI * 2); - ctx.fillStyle = g; - ctx.fill(); - - // inner ring - ctx.beginPath(); - ctx.arc(x, sy, r + 3, 0, Math.PI * 2); - ctx.strokeStyle = `rgba(0,212,170,0.25)`; - ctx.lineWidth = 1; - ctx.stroke(); - - // core dot - ctx.beginPath(); - ctx.arc(x, sy, r, 0, Math.PI * 2); - ctx.fillStyle = `rgba(0,212,170,0.85)`; - ctx.shadowBlur = 12; - ctx.shadowColor = ACCENT; - ctx.fill(); - ctx.shadowBlur = 0; - }); - - // ── particles ─────────────────────────────────────────────────────── - particles.forEach(p => { - const sy = streams[p.si].y; - p.x += p.speed; - if (p.x > W + 40) p.x = -40; - - // collision check with nodes on same lane - with cooldown - nodes.forEach(node => { - if (node.si !== p.si) return; - if (Math.abs(p.x - node.x) < 10) { - const now = Date.now(); - const last = lastBurst[node.id] ?? 0; - if (now - last > 800) { - spawnBurst(node.x, sy); - lastBurst[node.id] = now; - } - } - }); - - // trail gradient - const trailLen = 40 + p.speed * 14; - const tg = ctx.createLinearGradient(p.x - trailLen, sy, p.x, sy); - tg.addColorStop(0, ACCENT0); - tg.addColorStop(1, `rgba(0,212,170,${p.opa * 0.7})`); - ctx.beginPath(); - ctx.moveTo(p.x - trailLen, sy); - ctx.lineTo(p.x, sy); - ctx.strokeStyle = tg; - ctx.lineWidth = p.r * 0.7; - ctx.stroke(); - - // particle head - ctx.beginPath(); - ctx.arc(p.x, sy, p.r, 0, Math.PI * 2); - ctx.fillStyle = `rgba(0,212,170,${p.opa})`; - ctx.shadowBlur = 14; - ctx.shadowColor = ACCENT; - ctx.fill(); - ctx.shadowBlur = 0; - }); - - // ── burst rings ───────────────────────────────────────────────────── - bursts = bursts.filter(b => b.opa > 0.01); - bursts.forEach(b => { - b.r += b.spd; - b.opa *= 0.94; - ctx.beginPath(); - ctx.arc(b.x, b.y, b.r, 0, Math.PI * 2); - ctx.strokeStyle = `rgba(0,212,170,${b.opa})`; - ctx.lineWidth = 1.5; - ctx.stroke(); - }); - - animId = requestAnimationFrame(draw); - } - - // ── resize ───────────────────────────────────────────────────────────── - const ro = new ResizeObserver(() => init()); - ro.observe(canvas); - - init(); - animId = requestAnimationFrame(draw); - - return () => { - cancelAnimationFrame(animId); - ro.disconnect(); - }; - }, []); - - return ( - - ); -} diff --git a/frontend/src/components/StreamCard.jsx b/frontend/src/components/StreamCard.jsx deleted file mode 100644 index 6688f1a..0000000 --- a/frontend/src/components/StreamCard.jsx +++ /dev/null @@ -1,325 +0,0 @@ -import { useState } from 'react'; -import { useNavigate } from 'react-router-dom'; -import { useReadContract, useWriteContract, useWaitForTransactionReceipt, useChainId } from 'wagmi'; -import { formatUnits } from 'viem'; -import { getContractAddress, ROUTER_ABI } from '../lib/wagmi'; -import { useAddressLabel } from '../hooks/useProfile'; -import LiveBalance from './LiveBalance'; -import WithdrawModal from './WithdrawModal'; - -const TOKEN_LABELS = { - '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d': 'USDC', - '0x2Ca6e6FbAA8D0Bc27a64Ca079aFa6bf5cc8C7ad1': 'CRM', - '0x0000000000000000000000000000000000000001': 'TSLA', - '0x0000000000000000000000000000000000000002': 'AMZN', -}; - -function short(addr) { - return addr ? `${addr.slice(0, 6)}…${addr.slice(-4)}` : '-'; -} - -function timeRemaining(until) { - const secs = Number(until) - Math.floor(Date.now() / 1000); - if (secs <= 0) return null; - const d = Math.floor(secs / 86400); - const h = Math.floor((secs % 86400) / 3600); - const m = Math.floor((secs % 3600) / 60); - if (d > 0) return `${d}d ${h}h`; - if (h > 0) return `${h}h ${m}m`; - return `${m}m`; -} - -/** - * StreamCard - shows full stream info + live balance + contextual actions. - * - * @param {object} props - * @param {string} props.streamId - * @param {'company'|'contractor'} props.role - * @param {function} [props.onRefresh] - * @param {number} [props.chainId] - stream's chain (from DB); falls back to wallet chain - * @param {Array} [props.streamData] - pre-fetched tuple from parent batch read - * @param {bigint} [props.rawBalance] - pre-fetched balanceOf result - * @param {boolean} [props.batchManaged] - true means parent owns the reads; skip internal useReadContract - * @param {boolean} [props.batchLoading] - true while parent batch fetch is still in-flight - */ -export default function StreamCard({ streamId, role, onRefresh, chainId: propChainId, streamData, rawBalance: propRawBalance, batchManaged, batchLoading }) { - const navigate = useNavigate(); - const walletChainId = useChainId(); - const chainId = propChainId ?? walletChainId; - const [showWithdraw, setShowWithdraw] = useState(false); - const [reclaimSuccess, setReclaimSuccess] = useState(false); - - // When the parent dashboard batch-fetches all streams (batchManaged=true), it passes - // batchLoading=true while the fetch is in flight, then sets it false once results arrive. - // Decoupling "is loading" from "result is undefined" lets us distinguish a pending fetch - // from a completed-but-failed read (the latter should hide the card, not spin forever). - const { data: fetchedStream, isLoading } = useReadContract({ - chainId, - address: getContractAddress(chainId), - abi: ROUTER_ABI, - functionName: 'streams', - args: [streamId], - query: { enabled: !batchManaged, refetchInterval: 30_000 }, - }); - - const stream = batchManaged ? streamData : fetchedStream; - // batchLoading=true → fetch still in flight → show skeleton - // batchLoading=false → fetch done; if stream is still undefined the read failed → hide card - const loading = batchManaged ? (batchLoading ?? stream === undefined) : isLoading; - - // Resolve counterpart address early so useAddressLabel is called unconditionally - const _counterpartAddr = stream - ? (role === 'company' - ? (stream[1] ?? stream.recipient) - : (stream[0] ?? stream.sender)) - : null; - const counterpartLabel = useAddressLabel(_counterpartAddr); - - const { writeContract: doReclaim, data: reclaimHash, isPending: reclaimPending } = useWriteContract(); - const { isLoading: reclaimConfirming } = useWaitForTransactionReceipt({ - hash: reclaimHash, - onSuccess: () => { setReclaimSuccess(true); onRefresh?.(); }, - }); - - if (loading) { - return ( -
-
-
-
-
- ); - } - - if (!stream) return null; - - // stream can be an array (from useReadContract) or a named object (from readContract). - // Support both by trying numeric indices first, then named properties. - const sender = stream[0] ?? stream.sender; - const recipient = stream[1] ?? stream.recipient; - const token = stream[2] ?? stream.token; - const ratePerSecond = stream[3] ?? stream.ratePerSecond; - const startTime = stream[4] ?? stream.startTime; - const streamValidUntil = stream[5] ?? stream.streamValidUntil; - const totalDeposited = stream[6] ?? stream.totalDeposited; - const totalWithdrawn = stream[7] ?? stream.totalWithdrawn; - - if (!sender || sender === '0x0000000000000000000000000000000000000000') return null; - - const now = BigInt(Math.floor(Date.now() / 1000)); - const isActive = now < streamValidUntil; - // Pending = deposit exists but agent hasn't opened the first period yet - const isPending = !isActive && (totalDeposited ?? 0n) > 0n && (streamValidUntil === 0n || streamValidUntil <= startTime); - const isExpired = !isActive && !isPending; - - const duration = streamValidUntil - startTime; - const elapsed = isActive ? now - startTime : duration; - const progressPct = duration > 0n ? Math.min(Number((elapsed * 100n) / duration), 100) : 100; - - const tokenLabel = TOKEN_LABELS[token] ?? short(token); - const ratePerDay = parseFloat(formatUnits(ratePerSecond, 6)) * 86400; - const timeLeft = timeRemaining(streamValidUntil); - const counterpart = role === 'company' ? recipient : sender; - const unearned = (totalDeposited ?? 0n) > 0n - ? (totalDeposited ?? 0n) - ((propRawBalance ?? 0n) + (totalWithdrawn ?? 0n)) - : 0n; - const hasUnearned = unearned > 0n; - - function handleReclaim() { - doReclaim({ - chainId, - address: getContractAddress(chainId), - abi: ROUTER_ABI, - functionName: role === 'company' ? 'reclaimUnearned' : 'withdrawFromStream', - args: [streamId], - }); - } - - return ( - <> -
navigate(`/app/stream/${streamId}`)} - > - {/* Active indicator strip */} - {isActive && ( -
- )} - -
- {/* Left: identity + rate */} -
- {/* Status + address + balance (mobile: all in one row) */} -
-
- {isActive ? ( - - - Active - - ) : isPending ? ( - - - Pending - - ) : ( - Ended - )} - {counterpartLabel} -
- - {/* Balance - mobile only (inline with status) */} -
e.stopPropagation()}> - - {tokenLabel} -
-
- -
- {ratePerDay.toFixed(2)} {tokenLabel}/day - {timeLeft && · {timeLeft} left} -
- - {/* 3D extruded progress bar */} -
- {/* Track */} -
- {/* Fill - top face */} -
- {/* Bottom extrusion shadow - gives depth illusion */} -
- {/* Moving shimmer on active streams */} - {isActive && progressPct > 0 && ( -
- )} -
-
-
- 0% - {progressPct.toFixed(0)}% -
- - -
- - {/* Right: live balance - desktop only */} -
e.stopPropagation()}> -
- {role === 'contractor' ? 'Available' : 'Unearned'} -
- -
{tokenLabel}
- - {/* Action button - desktop */} -
- {role === 'contractor' && (isActive || propRawBalance > 0n) && ( - - )} - {role === 'company' && isExpired && hasUnearned && !reclaimSuccess && ( - - )} - {reclaimSuccess && ✓ Reclaimed} -
-
- - {/* Action button - mobile only (below progress bar) */} -
e.stopPropagation()}> - {role === 'contractor' && (isActive || propRawBalance > 0n) && ( - - )} - {role === 'company' && isExpired && hasUnearned && !reclaimSuccess && ( - - )} - {reclaimSuccess && ✓ Reclaimed} -
-
-
- - {showWithdraw && ( - setShowWithdraw(false)} - onSuccess={() => { setShowWithdraw(false); onRefresh?.(); }} - /> - )} - - ); -} diff --git a/frontend/src/components/StreamStatementPDF.jsx b/frontend/src/components/StreamStatementPDF.jsx deleted file mode 100644 index 3a140a9..0000000 --- a/frontend/src/components/StreamStatementPDF.jsx +++ /dev/null @@ -1,415 +0,0 @@ -/** - * StreamStatementPDF - * - * Generates a CronStream payroll / income statement for a given month + year. - * Used by both company (payroll disbursement) and contractor (income statement). - * - * @react-pdf/renderer renders this server-side in a Web Worker — no DOM APIs. - */ -import { - Document, Page, Text, View, Image, StyleSheet, Font, -} from '@react-pdf/renderer'; -import { formatUnits } from 'viem'; - -const MONTH_NAMES = [ - 'January','February','March','April','May','June', - 'July','August','September','October','November','December', -]; - -const CHAIN_NAMES = { - 421614: 'Arbitrum Sepolia', - 42161: 'Arbitrum One', - 1: 'Ethereum Mainnet', -}; - -const TOKEN_LABELS = { - '0x75faf114eafb1BDbe2F0316DF893fd58CE46AA4d': 'USDC', - '0x2Ca6e6FbAA8D0Bc27a64Ca079aFa6bf5cc8C7ad1': 'CRM', -}; - -function tokenSymbol(addr) { - return TOKEN_LABELS[addr] ?? (addr ? addr.slice(0, 6) + '…' : '?'); -} - -function fmt(bigintVal, decimals = 6, places = 4) { - if (bigintVal == null) return '0.0000'; - return parseFloat(formatUnits(bigintVal, decimals)).toFixed(places); -} - -function shortAddr(addr) { - return addr ? `${addr.slice(0, 8)}…${addr.slice(-6)}` : '-'; -} - -function fmtDate(ts) { - if (!ts || ts === 0n) return '-'; - return new Date(Number(ts) * 1000).toLocaleDateString('en-GB', { - day: '2-digit', month: 'short', year: 'numeric', - }); -} - -// ─── Styles ─────────────────────────────────────────────────────────────────── - -const C = { - dark: '#0A0B0F', - surface: '#111318', - border: '#1E2028', - accent: '#00D4AA', - muted: '#6B7280', - white: '#F9FAFB', - yellow: '#EAB308', -}; - -const s = StyleSheet.create({ - page: { - backgroundColor: C.dark, - color: C.white, - fontFamily: 'Helvetica', - padding: 40, - fontSize: 9, - }, - - // Header - header: { flexDirection: 'row', justifyContent: 'space-between', alignItems: 'flex-start', marginBottom: 28 }, - logo: { width: 32, height: 32, borderRadius: 8 }, - headerRight: { alignItems: 'flex-end' }, - docTitle: { fontSize: 16, fontFamily: 'Helvetica-Bold', color: C.white, marginBottom: 2 }, - docSub: { fontSize: 9, color: C.muted }, - - // Info grid - infoGrid: { flexDirection: 'row', gap: 12, marginBottom: 24 }, - infoCard: { - flex: 1, backgroundColor: C.surface, - borderRadius: 8, padding: 12, - border: `1px solid ${C.border}`, - }, - infoLabel: { fontSize: 7, color: C.muted, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 4 }, - infoValue: { fontSize: 10, fontFamily: 'Helvetica-Bold', color: C.white }, - infoSub: { fontSize: 8, color: C.muted, marginTop: 2 }, - - // Period banner - periodBanner: { - backgroundColor: C.surface, borderRadius: 8, padding: '10 14', - border: `1px solid ${C.border}`, marginBottom: 20, - flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - }, - periodLabel: { fontSize: 7, color: C.muted, textTransform: 'uppercase', letterSpacing: 1 }, - periodValue: { fontSize: 13, fontFamily: 'Helvetica-Bold', color: C.accent, marginTop: 2 }, - generatedAt: { fontSize: 7, color: C.muted }, - - // Table - tableHeader: { - flexDirection: 'row', backgroundColor: C.surface, - borderRadius: '6 6 0 0', padding: '7 10', - border: `1px solid ${C.border}`, borderBottom: 0, - }, - tableRow: { - flexDirection: 'row', padding: '9 10', - borderLeft: `1px solid ${C.border}`, - borderRight: `1px solid ${C.border}`, - borderBottom: `1px solid ${C.border}`, - }, - tableRowAlt: { - backgroundColor: '#0E1015', - }, - thText: { fontSize: 7, color: C.muted, textTransform: 'uppercase', letterSpacing: 0.8, fontFamily: 'Helvetica-Bold' }, - tdText: { fontSize: 8, color: C.white }, - tdMono: { fontSize: 8, color: C.muted, fontFamily: 'Helvetica' }, - tdAccent: { fontSize: 8, color: C.accent, fontFamily: 'Helvetica-Bold' }, - - // Column widths - colCounterparty: { flex: 2 }, - colStream: { flex: 1.6 }, - colPeriod: { flex: 2 }, - colRate: { flex: 1.2 }, - colAmount: { flex: 1.2, alignItems: 'flex-end' }, - - // Summary - summaryBox: { - backgroundColor: C.surface, borderRadius: 8, - border: `1px solid ${C.border}`, padding: 14, marginTop: 20, - }, - summaryTitle: { fontSize: 8, color: C.muted, textTransform: 'uppercase', letterSpacing: 1, marginBottom: 10 }, - summaryRow: { flexDirection: 'row', justifyContent: 'space-between', marginBottom: 6 }, - summaryLabel: { fontSize: 9, color: C.muted }, - summaryValue: { fontSize: 10, fontFamily: 'Helvetica-Bold', color: C.white }, - summaryTotal: { - flexDirection: 'row', justifyContent: 'space-between', - borderTop: `1px solid ${C.border}`, paddingTop: 8, marginTop: 4, - }, - summaryTotalLabel: { fontSize: 10, fontFamily: 'Helvetica-Bold', color: C.white }, - summaryTotalValue: { fontSize: 12, fontFamily: 'Helvetica-Bold', color: C.accent }, - - // Status badge - badgeActive: { fontSize: 7, color: C.accent, backgroundColor: '#00D4AA15', borderRadius: 4, padding: '2 5' }, - badgePending: { fontSize: 7, color: C.yellow, backgroundColor: '#EAB30815', borderRadius: 4, padding: '2 5' }, - badgeEnded: { fontSize: 7, color: C.muted, backgroundColor: '#1E202820', borderRadius: 4, padding: '2 5' }, - - // Footer - footer: { - marginTop: 28, paddingTop: 12, - borderTop: `1px solid ${C.border}`, - flexDirection: 'row', justifyContent: 'space-between', alignItems: 'center', - }, - footerLeft: { fontSize: 7, color: C.muted }, - footerRight: { fontSize: 7, color: C.muted, textAlign: 'right' }, - footerAccent: { color: C.accent }, - - // Empty state - emptyBox: { - alignItems: 'center', padding: 40, - backgroundColor: C.surface, borderRadius: 8, - border: `1px solid ${C.border}`, - }, - emptyText: { fontSize: 10, color: C.muted }, -}); - -// ─── Helpers ────────────────────────────────────────────────────────────────── - -function streamStatus(stream, nowSec) { - const until = Number(stream.streamValidUntil ?? 0n); - const start = Number(stream.startTime ?? 0n); - if (until > nowSec) return 'active'; - if (start > 0 && until <= start) return 'pending'; - return 'ended'; -} - -function StatusBadge({ status }) { - const style = status === 'active' ? s.badgeActive - : status === 'pending' ? s.badgePending - : s.badgeEnded; - const label = status === 'active' ? 'Active' - : status === 'pending' ? 'Pending' - : 'Ended'; - return {label}; -} - -// Filter streams active during the selected month -function filterByMonth(streams, year, month) { - const start = new Date(year, month, 1).getTime() / 1000; // start of month - const end = new Date(year, month + 1, 0, 23, 59, 59).getTime() / 1000; // end of month - - return streams.filter(s => { - const sStart = Number(s.startTime ?? 0n); - const sUntil = Number(s.streamValidUntil ?? 0n); - // Include if stream overlaps the month at all - // Either started before month end AND (still active OR ended after month start) - if (sStart === 0) return false; - if (sStart > end) return false; - if (sUntil > 0 && sUntil < start) return false; - return true; - }); -} - -// Group amounts by token symbol -function sumByToken(streams, field) { - const totals = {}; - for (const s of streams) { - const sym = tokenSymbol(s.token); - const val = parseFloat(fmt(s[field] ?? 0n)); - totals[sym] = (totals[sym] ?? 0) + val; - } - return totals; -} - -// ─── Table row ──────────────────────────────────────────────────────────────── - -function StreamRow({ stream, role, index, nowSec, counterpartyName }) { - const status = streamStatus(stream, nowSec); - const sym = tokenSymbol(stream.token); - const ratePerDay = parseFloat(formatUnits(stream.ratePerSecond ?? 0n, 6)) * 86400; - const amount = role === 'company' - ? fmt(stream.totalWithdrawn ?? 0n) // paid out - : fmt(stream.totalWithdrawn ?? 0n); // received - - const periodStart = fmtDate(stream.startTime); - const periodEnd = stream.streamValidUntil && stream.streamValidUntil > stream.startTime - ? fmtDate(stream.streamValidUntil) - : status === 'active' ? 'Ongoing' : '-'; - - return ( - - - {counterpartyName} - - - - {stream.streamId ? stream.streamId.slice(0, 10) + '…' : '-'} - - - {periodStart} – {periodEnd} - - - {ratePerDay.toFixed(2)} {sym}/day - - - {amount} - {sym} - - - ); -} - -// ─── Document ───────────────────────────────────────────────────────────────── - -export function StreamStatementDocument({ - streams, // enriched stream objects - role, // 'company' | 'contractor' - month, // 0-indexed month - year, - ownerName, // display name of the logged-in user - ownerAddress, - counterpartyNames, // Map - logoUrl, // absolute URL or data URL for the logo -}) { - const nowSec = Math.floor(Date.now() / 1000); - const filtered = filterByMonth(streams, year, month); - const monthName = MONTH_NAMES[month]; - const isCompany = role === 'company'; - - const docTitle = isCompany ? 'Payroll Disbursement Statement' : 'Income Statement'; - const partyLabel = isCompany ? 'Company' : 'Contractor'; - const amountLabel = isCompany ? 'Total Paid' : 'Total Received'; - const counterpartyLabel = isCompany ? 'Contractor' : 'Company'; - - const totals = sumByToken(filtered, 'totalWithdrawn'); - const totalEntries = Object.entries(totals); - const generatedAt = new Date().toLocaleString('en-GB', { - day: '2-digit', month: 'short', year: 'numeric', - hour: '2-digit', minute: '2-digit', - }); - - const chainName = filtered[0]?.chainId - ? (CHAIN_NAMES[filtered[0].chainId] ?? `Chain ${filtered[0].chainId}`) - : 'Arbitrum Sepolia'; - - return ( - - - - {/* ── Header ─────────────────────────────────────────────────────── */} - - - {logoUrl && } - - CronStream - Protocol Infrastructure - - - - {docTitle} - Generated {generatedAt} - - - - {/* ── Period + entity info ────────────────────────────────────────── */} - - - Statement Period - {monthName} {year} - - - Network: {chainName} - Document ref: CS-{year}{String(month + 1).padStart(2, '0')}-{ownerAddress?.slice(2, 8).toUpperCase()} - - - - - - {partyLabel} - {ownerName || shortAddr(ownerAddress)} - {shortAddr(ownerAddress)} - - - Streams this period - {filtered.length} - {streams.length} total all time - - {totalEntries.slice(0, 1).map(([sym, val]) => ( - - {amountLabel} ({sym}) - {val.toFixed(4)} - {sym} · milestone-verified - - ))} - - - {/* ── Table ──────────────────────────────────────────────────────── */} - - {counterpartyLabel} - Stream ID - Period - Rate - {amountLabel} - - - {filtered.length === 0 ? ( - - No streams active during {monthName} {year} - - ) : ( - filtered.map((stream, i) => { - const cpAddr = isCompany ? stream.recipient : stream.sender; - const cpName = counterpartyNames?.[cpAddr?.toLowerCase()] ?? shortAddr(cpAddr); - return ( - - ); - }) - )} - - {/* ── Summary ────────────────────────────────────────────────────── */} - {filtered.length > 0 && ( - - Summary - {totalEntries.map(([sym, val]) => ( - - {amountLabel} · {sym} - {val.toFixed(4)} {sym} - - ))} - - Active streams - {filtered.filter(s => streamStatus(s, nowSec) === 'active').length} - - - Completed streams - {filtered.filter(s => streamStatus(s, nowSec) === 'ended').length} - - {totalEntries.length > 0 && ( - - Total · {monthName} {year} - - {totalEntries.map(([sym, val]) => `${val.toFixed(4)} ${sym}`).join(' + ')} - - - )} - - )} - - {/* ── Footer ─────────────────────────────────────────────────────── */} - - - This document is generated by CronStream Protocol and reflects - on-chain verified milestone payments. All figures are auditable on {chainName}. - - - cronstream.xyz - thecronstream@gmail.com - Not financial advice. Verify on-chain. - - - - - - ); -} diff --git a/frontend/src/components/Watermark.jsx b/frontend/src/components/Watermark.jsx deleted file mode 100644 index a500e9c..0000000 --- a/frontend/src/components/Watermark.jsx +++ /dev/null @@ -1,45 +0,0 @@ -/** - * Watermark - tiled logo pattern stamped across an entire surface. - * - * variant="page" - fixed full-viewport overlay (AppShell, behind all content) - * variant="modal" - absolute fill inside a modal panel (needs position:relative on parent) - */ -export default function Watermark({ variant = 'page' }) { - const shared = { - backgroundImage: 'url(/logo.png)', - backgroundRepeat: 'repeat', - backgroundSize: '52px 52px', - opacity: 0.028, - pointerEvents: 'none', - userSelect: 'none', - }; - - if (variant === 'page') { - return ( -