diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..ee7fa86 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,57 @@ +{ + "permissions": { + "allow": [ + "Bash(npx hardhat compile:*)", + "Bash(where npx:*)", + "Bash(where:*)", + "Bash(where.exe:*)", + "Bash(cmd.exe:*)", + "Bash(powershell.exe -Command \"Get-Command node -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source; Get-Command npm -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source; Get-Command npx -ErrorAction SilentlyContinue | Select-Object -ExpandProperty Source\")", + "Bash(/c/Users/andre/AppData/Local/ms-playwright-go/1.50.1/node.exe:*)", + "Bash(docker --version:*)", + "Bash(MSYS_NO_PATHCONV=1 docker run:*)", + "Bash(git log:*)", + "Bash(node -e:*)", + "Bash(ls:*)", + "Bash(\"C:\\\\James\\\\Programming\\\\Hackathon\\\\fluffy-engine\\\\contracts\\\\node_modules\\\\.bin\\\\ts-node\" -e \"const { ethers } = require\\(''ethers''\\); const w = ethers.Wallet.createRandom\\(\\); console.log\\(''Address:'', w.address\\); console.log\\(''Private Key:'', w.privateKey\\);\")", + "Bash(python3:*)", + "Bash(python:*)", + "Bash(printenv:*)", + "Bash(echo:*)", + "Bash(find:*)", + "Bash(export:*)", + "Bash($NODE -e \"const { ethers } = require\\(''ethers''\\); const w = new ethers.Wallet\\(''0x30cc37df21c1dd9da4f61e0baa4ad62f0da7a34a6f1fbb504e2a7d675c354e23''\\); console.log\\(''Address:'', w.address\\);\")", + "Bash($NODE -e \"const { ethers } = require\\(''./node_modules/ethers''\\); const w = new ethers.Wallet\\(''0x30cc37df21c1dd9da4f61e0baa4ad62f0da7a34a6f1fbb504e2a7d675c354e23''\\); console.log\\(''Address:'', w.address\\);\")", + "Bash($NODE -e \"\nconst { ethers } = require\\(''./node_modules/ethers''\\);\nasync function main\\(\\) {\n const provider = new ethers.JsonRpcProvider\\(''https://coston2-api.flare.network/ext/C/rpc''\\);\n const balance = await provider.getBalance\\(''0x6cF81eEf8594c10142a0608D2e986182e2c8E422''\\);\n console.log\\(''Balance:'', ethers.formatEther\\(balance\\), ''C2FLR''\\);\n}\nmain\\(\\).catch\\(console.error\\);\n\")", + "WebFetch(domain:faucet.flare.network)", + "Bash(curl:*)", + "WebSearch", + "Bash(tr:*)", + "Bash(NODE=/c/Users/andre/AppData/Local/ms-playwright-go/1.50.1/node.exe:*)", + "Bash($NODE node_modules/hardhat/internal/cli/bootstrap.js run scripts/deploy-pool.ts --network localhost)", + "Bash($NODE -e \"\nconst { ethers } = require\\(''./node_modules/ethers''\\);\nconsole.log\\(''RNG:'', ethers.getAddress\\(''0x5CdF9eAF3EB8b44fB696F8d6a062eF6b1e1Fd909''.toLowerCase\\(\\)\\)\\);\nconsole.log\\(''FTSO:'', ethers.getAddress\\(''0x3d893C53D9e8056135C26C8c638B76C8b60Df726''.toLowerCase\\(\\)\\)\\);\n\")", + "Bash($NODE /c/Users/andre/AppData/Local/ms-playwright-go/1.50.1/package/lib/cli/cli.js)", + "Bash($NODE -e \"\nconst { execSync } = require\\(''child_process''\\);\n// Node v22 ships with corepack which includes npm\n// Try to find npm-cli.js\nconst path = require\\(''path''\\);\nconst nodeDir = path.dirname\\(process.execPath\\);\nconst fs = require\\(''fs''\\);\n// Check for npm in node''s directory\nconst candidates = [\n path.join\\(nodeDir, ''node_modules'', ''npm'', ''bin'', ''npm-cli.js''\\),\n path.join\\(nodeDir, ''..'', ''lib'', ''node_modules'', ''npm'', ''bin'', ''npm-cli.js''\\),\n path.join\\(nodeDir, ''..'', ''node_modules'', ''npm'', ''bin'', ''npm-cli.js''\\),\n];\nfor \\(const c of candidates\\) {\n if \\(fs.existsSync\\(c\\)\\) {\n console.log\\(''Found npm at:'', c\\);\n process.exit\\(0\\);\n }\n}\nconsole.log\\(''npm not found in node directory:'', nodeDir\\);\nconsole.log\\(''Contents:'', fs.readdirSync\\(nodeDir\\)\\);\n\")", + "Bash($NODE -e \"\nconst https = require\\(''https''\\);\nconst fs = require\\(''fs''\\);\nconst path = require\\(''path''\\);\n\n// Download npm tarball\nconst url = ''https://registry.npmjs.org/npm/-/npm-10.9.2.tgz'';\nconst tmpFile = path.join\\(process.env.TEMP || ''/tmp'', ''npm.tgz''\\);\n\nconsole.log\\(''Downloading npm...''\\);\nconst file = fs.createWriteStream\\(tmpFile\\);\nhttps.get\\(url, \\(response\\) => {\n if \\(response.statusCode === 302 || response.statusCode === 301\\) {\n https.get\\(response.headers.location, \\(r2\\) => {\n r2.pipe\\(file\\);\n file.on\\(''finish'', \\(\\) => { file.close\\(\\); console.log\\(''Downloaded to'', tmpFile\\); }\\);\n }\\);\n } else {\n response.pipe\\(file\\);\n file.on\\(''finish'', \\(\\) => { file.close\\(\\); console.log\\(''Downloaded to'', tmpFile\\); }\\);\n }\n}\\).on\\(''error'', \\(e\\) => console.error\\(e\\)\\);\n\")", + "Bash(NPM_CLI=/c/Users/andre/AppData/Local/Temp/npm-pkg/package/bin/npm-cli.js)", + "Bash($NODE $NPM_CLI install)", + "Bash($NODE node_modules/.bin/ts-node src/index.ts)", + "Bash(npm install:*)", + "Bash(npx tsc:*)", + "Bash(npx hardhat test)", + "mcp__ide__getDiagnostics", + "Bash(lsof:*)", + "mcp__MCP_DOCKER__browser_navigate", + "Bash(docker start:*)", + "mcp__MCP_DOCKER__browser_take_screenshot", + "mcp__MCP_DOCKER__browser_click", + "mcp__MCP_DOCKER__browser_fill_form", + "mcp__MCP_DOCKER__browser_snapshot", + "mcp__MCP_DOCKER__browser_console_messages", + "mcp__MCP_DOCKER__browser_network_requests", + "Bash(npx hardhat run:*)", + "Bash(npx hardhat console:*)", + "WebFetch(domain:www.plasma.to)" + ] + } +} diff --git a/.gitignore b/.gitignore index c9afd80..3be622b 100644 --- a/.gitignore +++ b/.gitignore @@ -11,6 +11,11 @@ frontend/cache frontend/.next frontend/artifacts backend/node_modules -backend/.env - *.gz +.claude/settings.local.json + +# Environment files (keep .env.example, ignore .env with secrets) +.env +!.env.example +*/.env +!*/.env.example \ No newline at end of file diff --git a/CLAUDE.md b/CLAUDE.md index 613bebb..d326374 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -95,7 +95,7 @@ PORT # API port (default: 3001) ### Contracts (.env in contracts/) ```bash PRIVATE_KEY # Deployment account private key -PLASMA_RPC_URL # Plasma RPC endpoint (default: https://rpc.plasma.to) +PLASMA_RPC_URL # Plasma RPC endpoint (default: https://testnet-rpc.plasma.to) ``` ## Network Configuration @@ -103,7 +103,7 @@ PLASMA_RPC_URL # Plasma RPC endpoint (default: https://rpc.plasma.to) | Network | Chain ID | RPC URL | |---------|----------|---------| | Flare Coston2 (testnet) | 114 | https://coston2-api.flare.network/ext/C/rpc | -| Plasma | — | https://rpc.plasma.to | +| Plasma | — | https://testnet-rpc.plasma.to | **Important Flare Contracts**: - RandomNumberV2: `0x97702e350CaEda540935d92aAf213307e9069784` diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..5b38f2b --- /dev/null +++ b/LICENSE @@ -0,0 +1 @@ +We consider this project entirely proprietary. The visibility of source code is not to be misconstrued. \ No newline at end of file diff --git a/PLAN.md b/PLAN.md index dc60ea7..f2c6af2 100644 --- a/PLAN.md +++ b/PLAN.md @@ -1,10 +1,10 @@ -# LottoLink iBankroll POC - 5 Hour Hackathon Plan +# LottoLink CaaS POC - 5 Hour Hackathon Plan ## Context **Problem:** 200+ US lottery operators hold massive reserves to back jackpots. This locks up funds that should go to charitable causes. -**Solution:** Shared liquidity pool (iBankroll model) where: +**Solution:** Shared liquidity pool (CaaS model) where: - LPs deposit USDT → earn yield from operator premiums - Operators pay premiums (% of ticket sales) → access to deep pool liquidity - Small lotteries offer big jackpots without big reserves @@ -355,7 +355,7 @@ npm run frontend:build ## Talking Points for Judges **Q: What's innovative here?** -A: We're applying the iBankroll model (proven in crypto casinos) to traditional lotteries. Instead of each operator holding idle reserves, they pool risk. Law of large numbers makes aggregate variance predictable. +A: We're applying the CaaS model (proven in crypto casinos) to traditional lotteries. Instead of each operator holding idle reserves, they pool risk. Law of large numbers makes aggregate variance predictable. **Q: Why Flare?** A: We need all four protocols: diff --git a/README.md b/README.md index 45b64aa..0da5b49 100644 --- a/README.md +++ b/README.md @@ -2,13 +2,67 @@ Unified lottery infrastructure on **Flare** (Data Truth Layer) and **Plasma** (Money Movement Layer). +--- + +## Built on Flare + +### Network + +Flare Coston2 Testnet (Chain ID: 114) + +### Integrations + +| Protocol | Usage | +|----------|-------| +| Secure RNG | Provably fair lottery draws using ~100 independent providers with commit-reveal | +| FDC (Flare Data Connector) | JsonApi attestation of external Web2 lottery results | +| FTSO (Flare Time Series Oracle) | Accurate USD/USDT price feeds for settlement | + +### Setup + +See Quick Start below for installation and running instructions. + +--- + +## Smart Contracts + +### Flare Coston2 (Deployed 2026-02-08) + +| Contract | Address | Explorer | +|----------|---------|----------| +| DrawManager | `0xca4e3CE939030BeF9F6f9Ca436a582eB7877fa69` | [View](https://coston2-explorer.flare.network/address/0xca4e3CE939030BeF9F6f9Ca436a582eB7877fa69) | +| PayoutCalculator | `0xEC69Fe3D890475cb81010FE798f36d2e296fDB87` | [View](https://coston2-explorer.flare.network/address/0xEC69Fe3D890475cb81010FE798f36d2e296fDB87) | +| LotteryRegistry | `0x2051233b0bfb28b9A975a65Ed552d6678f22Ce8C` | [View](https://coston2-explorer.flare.network/address/0x2051233b0bfb28b9A975a65Ed552d6678f22Ce8C) | +| SharedLiquidityPool | `0xf969A46CeA823Da86F5Aa3f945272Aa7D58ca285` | [View](https://coston2-explorer.flare.network/address/0xf969A46CeA823Da86F5Aa3f945272Aa7D58ca285) | +| MockUSDT | `0x6714F7a8bc580CB9B5FdCf5678Cfad2a4e494022` | [View](https://coston2-explorer.flare.network/address/0x6714F7a8bc580CB9B5FdCf5678Cfad2a4e494022) | + +### Plasma Testnet + +| Contract | Address | Explorer | +|----------|---------|----------| +| LottoEscrow | `0x560DC807d36dCe38BC09bc9fE84d94A34669A3FC` | [View](https://testnet.plasmascan.io/address/0x560DC807d36dCe38BC09bc9fE84d94A34669A3FC) | + +### Flare System Contracts (Reference) + +| Contract | Address | +|----------|---------| +| RandomNumberV2 | `0x97702e350CaEda540935d92aAf213307e9069784` | +| ContractRegistry | `0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019` | + +--- + ## Features -- **Provably Fair Draws**: Flare Secure RNG (~100 independent providers, commit-reveal) -- **Instant Zero-Fee Payouts**: Plasma USDT via EIP-3009 gasless transfers -- **External Lottery Attestation**: FDC JsonApi for Web2 lottery results -- **FTSO Price Feeds**: Accurate USD↔USDT conversion at settlement + +- **Provably Fair Draws** — Flare Secure RNG with ~100 independent providers and commit-reveal +- **Instant Zero-Fee Payouts** — Plasma USDT via EIP-3009 gasless transfers +- **External Lottery Attestation** — FDC JsonApi for Web2 lottery results +- **FTSO Price Feeds** — Accurate USD/USDT conversion at settlement +- **Shared Liquidity Pool** — CaaS model enabling small lotteries to offer big jackpots + +--- ## Project Structure + ``` lottolink/ ├── contracts/ # Solidity (Flare + Plasma) @@ -16,54 +70,83 @@ lottolink/ └── frontend/ # Next.js 14 + Tailwind ``` +--- + ## Quick Start +### Prerequisites + +- Node.js 18+ +- npm or yarn + ### Contracts + ```bash -cd lottolink/contracts +cd contracts npm install npx hardhat compile npx hardhat run scripts/deploy-flare.ts --network coston2 ``` ### Backend + ```bash -cd lottolink/backend +cd backend +cp .env.example .env +# Edit .env with your contract addresses and keys npm install -# Set env vars: DRAW_MANAGER_ADDRESS, PAYOUT_CALC_ADDRESS, ESCROW_ADDRESS, ESCROW_PRIVATE_KEY npm run dev ``` ### Frontend + ```bash -cd lottolink/frontend +cd frontend npm install npm run dev +# Visit http://localhost:3000 ``` +--- + +## Environment Variables + +Copy the `.env.example` files and fill in your values: + +- `contracts/.env.example` → `contracts/.env` +- `backend/.env.example` → `backend/.env` + +See each `.env.example` for required variables. + +--- + ## Network Config | Network | Chain ID | RPC | |---------|----------|-----| | Flare Coston2 | 114 | https://coston2-api.flare.network/ext/C/rpc | -| Plasma | — | https://rpc.plasma.to | +| Plasma Testnet | — | https://testnet-rpc.plasma.to | -**Flare Contracts (Coston2)**: -- RandomNumberV2: `0x97702e350CaEda540935d92aAf213307e9069784` -- ContractRegistry: `0xaD67FE66660Fb8dFE9d6b1b4240d8650e30F6019` - -**Plasma**: -- USDT: `0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb` +--- ## Hackathon Tracks -- **Main Track**: Secure RNG + FDC + FTSO -- **Bonus Track**: FDC JsonApi for external Web2 lottery APIs -## Building on Flare — Developer Experience -- ✅ Secure RNG integration was straightforward; `isSecureRandom` flag is a great safety feature -- ✅ FTSO price feeds are well-documented with clear examples -- ⚠️ FDC workflow has multiple async steps (verifier → FdcHub → DA Layer → verify) — consider a higher-level SDK -- ⚠️ Testnet faucet occasionally slow; mainnet deployment pricing unclear +- **Main Track** — Secure RNG + FDC + FTSO +- **Bonus Track** — FDC JsonApi for external Web2 lottery APIs + +--- + +## Developer Experience Notes + +Building on Flare: + +- Secure RNG integration was straightforward; the `isSecureRandom` flag is a useful safety feature +- FTSO price feeds are well-documented with clear examples +- FDC workflow has multiple async steps (verifier, FdcHub, DA Layer, verify) — a higher-level SDK would help +- Testnet faucet occasionally slow; mainnet deployment pricing unclear --- -MIT License + +## License + +This project is licensed under the MIT License. See the [LICENSE](LICENSE) file for details. diff --git a/backend/.env b/backend/.env new file mode 100644 index 0000000..9b05609 --- /dev/null +++ b/backend/.env @@ -0,0 +1,29 @@ +# ================================================ +# LottoLink Backend Environment Configuration +# ================================================ + +# Server +PORT=4000 + +# Flare Network (Coston2 Testnet) +FLARE_RPC=https://coston2-api.flare.network/ext/C/rpc + +# Contract Addresses (Coston2 — deployed 2026-02-08) +DRAW_MANAGER_ADDRESS=0xca4e3CE939030BeF9F6f9Ca436a582eB7877fa69 +PAYOUT_CALC_ADDRESS=0xEC69Fe3D890475cb81010FE798f36d2e296fDB87 +REGISTRY_ADDRESS=0x2051233b0bfb28b9A975a65Ed552d6678f22Ce8C +POOL_ADDRESS=0xf969A46CeA823Da86F5Aa3f945272Aa7D58ca285 +USDT_ADDRESS=0x6714F7a8bc580CB9B5FdCf5678Cfad2a4e494022 + +# Plasma Network +PLASMA_RPC_URL=https://testnet-rpc.plasma.to +ESCROW_ADDRESS=0x560DC807d36dCe38BC09bc9fE84d94A34669A3FC + +# Backend signer (same as deployer for testnet) +BACKEND_PRIVATE_KEY=0x30cc37df21c1dd9da4f61e0baa4ad62f0da7a34a6f1fbb504e2a7d675c354e23 + +# Secrets (NEVER commit real values) +ESCROW_PRIVATE_KEY=0x30cc37df21c1dd9da4f61e0baa4ad62f0da7a34a6f1fbb504e2a7d675c354e23 +PLASMA_RELAYER_API_KEY= + +MOCK_MODE=false diff --git a/backend/.env.example b/backend/.env.example new file mode 100644 index 0000000..53acfa9 --- /dev/null +++ b/backend/.env.example @@ -0,0 +1,30 @@ +# ================================================ +# LottoLink Backend Environment Configuration +# ================================================ + +# Server +PORT=4000 + +# Flare Network (Coston2 Testnet) +FLARE_RPC=https://coston2-api.flare.network/ext/C/rpc + +# Contract Addresses (Coston2) — update after deployment +DRAW_MANAGER_ADDRESS=0x_YOUR_DRAW_MANAGER_ADDRESS +PAYOUT_CALC_ADDRESS=0x_YOUR_PAYOUT_CALC_ADDRESS +REGISTRY_ADDRESS=0x_YOUR_REGISTRY_ADDRESS +POOL_ADDRESS=0x_YOUR_POOL_ADDRESS +USDT_ADDRESS=0x_YOUR_USDT_ADDRESS + +# Plasma Network +PLASMA_RPC_URL=https://testnet-rpc.plasma.to +ESCROW_ADDRESS=0x_YOUR_ESCROW_ADDRESS + +# Backend signer (DO NOT commit real keys!) +BACKEND_PRIVATE_KEY=0x_YOUR_BACKEND_PRIVATE_KEY + +# Secrets (NEVER commit real values!) +ESCROW_PRIVATE_KEY=0x_YOUR_ESCROW_PRIVATE_KEY +PLASMA_RELAYER_API_KEY= + +# Mock mode for testing +MOCK_MODE=false diff --git a/backend/dist/index.js b/backend/dist/index.js index 1c05a6a..57b1d15 100644 --- a/backend/dist/index.js +++ b/backend/dist/index.js @@ -4,28 +4,409 @@ var __importDefault = (this && this.__importDefault) || function (mod) { }; Object.defineProperty(exports, "__esModule", { value: true }); const express_1 = __importDefault(require("express")); +const cors_1 = __importDefault(require("cors")); const listeners_1 = require("./listeners"); const plasma_1 = require("./services/plasma"); +const pool_1 = require("./services/pool"); const app = (0, express_1.default)(); app.use(express_1.default.json()); +app.use((0, cors_1.default)()); // Config (set via env in production) const DRAW_MANAGER = process.env.DRAW_MANAGER_ADDRESS || ""; const PAYOUT_CALC = process.env.PAYOUT_CALC_ADDRESS || ""; const ESCROW = process.env.ESCROW_ADDRESS || ""; const USDT = process.env.USDT_ADDRESS || "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb"; const ESCROW_KEY = process.env.ESCROW_PRIVATE_KEY || ""; +const POOL_ADDRESS = process.env.POOL_ADDRESS || ""; // In-memory store (use DB in production) const lotteries = []; const tickets = []; const draws = []; +let ticketIdCounter = 1; +let lotteryIdCounter = 4; +let poolTxCounter = 1; +// LP pool store +const poolTransactions = []; +// Helper to generate ticket number +function generateTicketNumber(lotteryName) { + const prefix = lotteryName.split(" ").map(w => w[0]).join("").toUpperCase(); + const year = new Date().getFullYear(); + const num = String(ticketIdCounter).padStart(5, "0"); + return `${prefix}-${year}-${num}`; +} +// Generate n unique random numbers between 1 and max +function pickNumbers(count, max) { + const nums = new Set(); + while (nums.size < count) { + nums.add(Math.floor(Math.random() * max) + 1); + } + return Array.from(nums).sort((a, b) => a - b); +} +// Prize tier calculation (6/49 model, similar to UK National Lottery) +// Real odds: 6/6 = 1:13,983,816 | 5+B = 1:2,330,636 | 5/6 = 1:55,492 +// 4/6 = 1:1,033 | 3/6 = 1:57 | 2/6 = 1:7.6 +function calculatePrize(matches, bonusMatch, jackpot, ticketPrice) { + if (matches === 6) + return { tier: "Jackpot (6/6)", prize: jackpot }; + if (matches === 5 && bonusMatch) + return { tier: "5 + Bonus", prize: Math.round(jackpot * 0.02) }; + if (matches === 5) + return { tier: "5/6", prize: Math.round(jackpot * 0.005) }; + if (matches === 4) + return { tier: "4/6", prize: ticketPrice * 20 }; + if (matches === 3) + return { tier: "3/6", prize: ticketPrice * 5 }; + if (matches === 2) + return { tier: "2/6", prize: ticketPrice }; + return null; // 0-1 matches = no prize +} +let drawIdCounter = 1; // API Routes -app.get("/api/lotteries", (_, res) => res.json(lotteries)); +app.get("/api/lotteries", (_, res) => { + const enriched = lotteries.map(l => ({ + ...l, + ticketsSold: tickets.filter(t => t.lotteryId === l.id).length + })); + res.json(enriched); +}); app.get("/api/lotteries/:id", (req, res) => res.json(lotteries.find(l => l.id === +req.params.id) || {})); app.get("/api/lotteries/:id/results", (req, res) => res.json(draws.filter(d => d.lotteryId === +req.params.id))); +// Create lottery +app.post("/api/lotteries", (req, res) => { + const { name, ticketPriceUSD, jackpot, nextDraw, charityPercent, usesFlareRNG } = req.body; + if (!name || ticketPriceUSD == null || jackpot == null || !nextDraw) { + return res.status(400).json({ error: "name, ticketPriceUSD, jackpot, and nextDraw are required" }); + } + const lottery = { + id: lotteryIdCounter++, + name, + ticketPriceUSD: Number(ticketPriceUSD), + jackpot: Number(jackpot), + nextDraw: new Date(nextDraw).getTime(), + charityPercent: Number(charityPercent) || 0, + usesFlareRNG: Boolean(usesFlareRNG), + status: "Open" + }; + lotteries.push(lottery); + console.log(`Lottery created: ${lottery.name} (id=${lottery.id})`); + return res.status(201).json(lottery); +}); +// Tickets: get by player or all +app.get("/api/tickets", (req, res) => { + const player = req.query.player; + if (player) { + return res.json(tickets.filter(t => t.player === player)); + } + return res.json(tickets); +}); app.get("/api/users/:address/tickets", (req, res) => res.json(tickets.filter(t => t.player === req.params.address))); -app.get("/api/pool/stats", (_, res) => res.json({ tvl: 0, yield: 0, charityTotal: 0 })); +// Buy tickets +app.post("/api/tickets", (req, res) => { + const { lotteryId, quantity, player } = req.body; + if (!lotteryId || !quantity || !player) { + return res.status(400).json({ error: "lotteryId, quantity, and player are required" }); + } + const lottery = lotteries.find(l => l.id === lotteryId); + if (!lottery) { + return res.status(404).json({ error: "Lottery not found" }); + } + if (lottery.status !== "Open") { + return res.status(400).json({ error: "Lottery is no longer open for ticket sales" }); + } + const purchased = []; + for (let i = 0; i < quantity; i++) { + const ticket = { + id: ticketIdCounter++, + lotteryId: lottery.id, + lotteryName: lottery.name, + ticketNumber: generateTicketNumber(lottery.name), + numbers: pickNumbers(6, 49), + player, + price: lottery.ticketPriceUSD, + purchaseDate: new Date().toISOString(), + drawDate: new Date(lottery.nextDraw).toISOString(), + status: "Pending", + timestamp: Date.now() + }; + tickets.push(ticket); + purchased.push(ticket); + } + console.log(`${quantity} ticket(s) purchased by ${player} for lottery ${lottery.name}`); + return res.status(201).json(purchased); +}); +// ── Draw System ───────────────────────────────────────────── +app.post("/api/lotteries/:id/draw", (req, res) => { + const lotteryId = +req.params.id; + const lottery = lotteries.find(l => l.id === lotteryId); + if (!lottery) + return res.status(404).json({ error: "Lottery not found" }); + if (lottery.status !== "Open") + return res.status(400).json({ error: "Lottery has already been drawn" }); + // Draw 6 winning numbers from 1-49 + const winningNumbers = pickNumbers(6, 49); + // Pick bonus ball from remaining numbers + let bonusNumber; + do { + bonusNumber = Math.floor(Math.random() * 49) + 1; + } while (winningNumbers.includes(bonusNumber)); + // Find all pending tickets for this lottery + const lotteryTickets = tickets.filter(t => t.lotteryId === lotteryId && t.status === "Pending"); + const winners = []; + let totalPaidOut = 0; + for (const ticket of lotteryTickets) { + const ticketNums = ticket.numbers || []; + const matchedNums = ticketNums.filter((n) => winningNumbers.includes(n)); + const matches = matchedNums.length; + const bonusMatch = ticketNums.includes(bonusNumber); + const prizeResult = calculatePrize(matches, bonusMatch, lottery.jackpot, lottery.ticketPriceUSD); + if (prizeResult) { + ticket.status = "Won"; + ticket.prize = prizeResult.prize; + ticket.prizeType = prizeResult.tier; + ticket.matchedNumbers = matchedNums; + totalPaidOut += prizeResult.prize; + winners.push({ + ticketId: ticket.id, + ticketNumber: ticket.ticketNumber, + player: ticket.player, + numbers: ticket.numbers, + matchedNumbers: matchedNums, + matches, + bonusMatch, + tier: prizeResult.tier, + prize: prizeResult.prize + }); + } + else { + ticket.status = "Lost"; + ticket.prize = 0; + ticket.prizeType = null; + ticket.matchedNumbers = matchedNums; + } + } + // Update lottery status + lottery.status = "Drawn"; + // Store draw result + const draw = { + id: drawIdCounter++, + lotteryId, + lotteryName: lottery.name, + winningNumbers, + bonusNumber, + totalTickets: lotteryTickets.length, + winners, + totalPaidOut, + timestamp: Date.now() + }; + draws.push(draw); + console.log(`Draw completed for ${lottery.name}: ${winningNumbers.join(",")}+${bonusNumber} | ${lotteryTickets.length} tickets, ${winners.length} winner(s), $${totalPaidOut} paid out`); + return res.json(draw); +}); +// ── LP Pool ───────────────────────────────────────────────── +function getPoolBalance() { + return poolTransactions.reduce((sum, tx) => { + if (tx.type === "Deposit") + return sum + tx.amount; + if (tx.type === "Withdraw") + return sum - tx.amount; + return sum; + }, 0); +} +function getExposure() { + const fourteenDays = 14 * 24 * 60 * 60 * 1000; + const cutoff = Date.now() + fourteenDays; + const upcoming = lotteries.filter((l) => l.status === "Open" && l.nextDraw <= cutoff); + const total = upcoming.reduce((sum, l) => sum + l.jackpot, 0); + return { total, lotteries: upcoming.map((l) => ({ id: l.id, name: l.name, jackpot: l.jackpot, nextDraw: l.nextDraw })) }; +} +app.get("/api/pool/stats", async (_, res) => { + // Try contract data first if configured + if ((0, pool_1.isPoolConfigured)()) { + const contractStats = await (0, pool_1.getPoolStatsFromContract)(); + if (contractStats) { + const exposure = getExposure(); + return res.json({ + // Contract data + tvl: contractStats.tvl, + utilization: contractStats.utilization, + lpCount: contractStats.lpCount, + operatorCount: contractStats.operatorCount, + totalPremiums: contractStats.totalPremiums, + totalPayouts: contractStats.totalPayouts, + sharePrice: contractStats.sharePrice, + // In-memory exposure calculation (until fully on-chain) + exposure: exposure.total, + exposureLotteries: exposure.lotteries, + withdrawable: Math.max(0, Number(contractStats.tvl) / 1e6 - exposure.total), + apy: lotteries.length > 0 ? 18.5 : 0, + source: "contract" + }); + } + } + // Fallback to in-memory mock data + const balance = getPoolBalance(); + const exposure = getExposure(); + const withdrawable = Math.max(0, balance - exposure.total); + res.json({ + balance, + tvl: String(balance * 1e6), // Convert to USDT wei format + exposure: exposure.total, + exposureLotteries: exposure.lotteries, + withdrawable, + apy: lotteries.length > 0 ? 18.5 : 0, + source: "mock" + }); +}); +// LP Position endpoint (per PLAN.md spec) +app.get("/api/pool/lp/:address", async (req, res) => { + const lpAddress = req.params.address; + if (!lpAddress || !lpAddress.startsWith("0x")) { + return res.status(400).json({ error: "Valid address required" }); + } + // Try contract data first + if ((0, pool_1.isPoolConfigured)()) { + const position = await (0, pool_1.getLPPositionFromContract)(lpAddress); + if (position) { + return res.json({ + shares: position.shares, + usdtValue: position.usdtValue, + claimableYield: position.claimableYield, + roi: position.roi, + depositTime: position.depositTime, + source: "contract" + }); + } + } + // Fallback: calculate from in-memory transactions + const lpTxs = poolTransactions.filter((tx) => tx.provider === lpAddress); + const deposits = lpTxs.filter((tx) => tx.type === "Deposit").reduce((sum, tx) => sum + tx.amount, 0); + const withdrawals = lpTxs.filter((tx) => tx.type === "Withdraw").reduce((sum, tx) => sum + tx.amount, 0); + const balance = deposits - withdrawals; + // Mock yield calculation (5% simple) + const mockYield = balance * 0.05; + res.json({ + shares: String(balance * 1e6), // Convert to wei-like format + usdtValue: String((balance + mockYield) * 1e6), + claimableYield: String(mockYield * 1e6), + roi: balance > 0 ? mockYield / balance : 0, + source: "mock" + }); +}); +app.get("/api/pool/transactions", (req, res) => { + const provider = req.query.provider; + if (provider) { + return res.json(poolTransactions.filter((tx) => tx.provider === provider)); + } + return res.json(poolTransactions); +}); +app.post("/api/pool/deposit", (req, res) => { + const { amount, provider } = req.body; + if (!amount || !provider) { + return res.status(400).json({ error: "amount and provider are required" }); + } + const numAmount = Number(amount); + if (numAmount <= 0) { + return res.status(400).json({ error: "Amount must be positive" }); + } + const tx = { + id: poolTxCounter++, + type: "Deposit", + amount: numAmount, + provider, + timestamp: new Date().toISOString(), + txHash: `0x${Date.now().toString(16)}...${Math.random().toString(16).slice(2, 6)}` + }; + poolTransactions.push(tx); + console.log(`LP deposit: ${provider} deposited $${numAmount}`); + return res.status(201).json(tx); +}); +app.post("/api/pool/withdraw", (req, res) => { + const { amount, provider } = req.body; + if (!amount || !provider) { + return res.status(400).json({ error: "amount and provider are required" }); + } + const numAmount = Number(amount); + if (numAmount <= 0) { + return res.status(400).json({ error: "Amount must be positive" }); + } + const balance = getPoolBalance(); + const exposure = getExposure(); + const withdrawable = Math.max(0, balance - exposure.total); + if (numAmount > withdrawable) { + return res.status(400).json({ + error: `Cannot withdraw $${numAmount.toLocaleString()}. Max withdrawable is $${withdrawable.toLocaleString()} (pool: $${balance.toLocaleString()} minus $${exposure.total.toLocaleString()} exposure from ${exposure.lotteries.length} lottery/lotteries drawing within 14 days).` + }); + } + const tx = { + id: poolTxCounter++, + type: "Withdraw", + amount: numAmount, + provider, + timestamp: new Date().toISOString(), + txHash: `0x${Date.now().toString(16)}...${Math.random().toString(16).slice(2, 6)}` + }; + poolTransactions.push(tx); + console.log(`LP withdraw: ${provider} withdrew $${numAmount}`); + return res.status(201).json(tx); +}); +function seedData() { + if (lotteries.length === 0) { + lotteries.push({ + id: 1, + name: "Flare Jackpot", + ticketPriceUSD: 5, + jackpot: 50000, + nextDraw: Date.now() + 3600000, + charityPercent: 5, + usesFlareRNG: true, + status: "Open" + }); + lotteries.push({ + id: 2, + name: "Plasma Daily", + ticketPriceUSD: 2, + jackpot: 10000, + nextDraw: Date.now() + 7200000, + charityPercent: 10, + usesFlareRNG: true, + status: "Open" + }); + lotteries.push({ + id: 3, + name: "Global Mega", + ticketPriceUSD: 10, + jackpot: 100000, + nextDraw: Date.now() + 86400000, + charityPercent: 8, + usesFlareRNG: false, + status: "Open" + }); + console.log("Seeded initial lottery data"); + } + // Seed LP deposits + if (poolTransactions.length === 0) { + poolTransactions.push({ + id: poolTxCounter++, + type: "Deposit", + amount: 100000, + provider: "demo-lp-1", + timestamp: new Date(Date.now() - 86400000 * 7).toISOString(), + txHash: "0x1a2b3c...seed01" + }); + poolTransactions.push({ + id: poolTxCounter++, + type: "Deposit", + amount: 75000, + provider: "demo-lp-1", + timestamp: new Date(Date.now() - 86400000 * 3).toISOString(), + txHash: "0x4d5e6f...seed02" + }); + console.log("Seeded initial LP pool data"); + } +} // Start listeners async function main() { + seedData(); if (DRAW_MANAGER && PAYOUT_CALC) { (0, listeners_1.startFlareListener)(DRAW_MANAGER, PAYOUT_CALC, (lotteryId, drawId) => { console.log(`Draw ${drawId} completed for lottery ${lotteryId}`); @@ -44,7 +425,38 @@ async function main() { tickets.push({ player, lotteryId: Number(lotteryId), numbers: numbers.map(Number), timestamp: Date.now() }); }); } - const PORT = process.env.PORT || 3001; + // Start pool listener if configured + if (POOL_ADDRESS) { + (0, listeners_1.startPoolListener)(POOL_ADDRESS, { + onLiquidityDeposited: (lp, usdtAmount, sharesIssued) => { + poolTransactions.push({ + id: poolTxCounter++, + type: "Deposit", + amount: Number(usdtAmount) / 1e6, + provider: lp, + timestamp: new Date().toISOString(), + txHash: `contract-${Date.now()}` + }); + }, + onLiquidityWithdrawn: (lp, sharesBurned, usdtReturned) => { + poolTransactions.push({ + id: poolTxCounter++, + type: "Withdraw", + amount: Number(usdtReturned) / 1e6, + provider: lp, + timestamp: new Date().toISOString(), + txHash: `contract-${Date.now()}` + }); + }, + onPremiumCollected: (operator, amount) => { + console.log(`Premium collected from operator ${operator}: ${Number(amount) / 1e6} USDT`); + }, + onPayoutExecuted: (winner, amount, lotteryId, drawId) => { + console.log(`Pool payout: ${winner} won ${Number(amount) / 1e6} USDT from lottery ${lotteryId}`); + } + }); + } + const PORT = process.env.PORT || 4000; app.listen(PORT, () => console.log(`LottoLink API running on port ${PORT}`)); } main().catch(console.error); diff --git a/backend/dist/listeners/index.js b/backend/dist/listeners/index.js index e428c33..0ea13b0 100644 --- a/backend/dist/listeners/index.js +++ b/backend/dist/listeners/index.js @@ -1,28 +1,91 @@ "use strict"; Object.defineProperty(exports, "__esModule", { value: true }); exports.startFlareListener = startFlareListener; +exports.startPoolListener = startPoolListener; exports.startPlasmaListener = startPlasmaListener; const ethers_1 = require("ethers"); +const pool_1 = require("../services/pool"); const FLARE_RPC = process.env.FLARE_RPC || "https://coston2-api.flare.network/ext/C/rpc"; -const PLASMA_RPC = process.env.PLASMA_RPC_URL || "https://rpc.plasma.to"; +const PLASMA_RPC = process.env.PLASMA_RPC_URL || "https://testnet-rpc.plasma.to"; const DRAW_MANAGER_ABI = ["event DrawCompleted(uint256 indexed lotteryId, uint256 indexed drawId, uint256[] winningNumbers, uint256 randomSeed, bool isSecureRandom)"]; const PAYOUT_CALC_ABI = ["event PayoutAuthorized(address indexed winner, uint256 amountUSDT, uint256 indexed lotteryId, uint256 indexed drawId, uint8 tier)"]; const ESCROW_ABI = ["event TicketPurchased(address indexed player, uint256 indexed lotteryId, uint256[] ticketNumbers, uint256 amount, uint256 timestamp)"]; +// ============ Flare Listeners ============ async function startFlareListener(drawManagerAddr, payoutCalcAddr, onDraw, onPayout) { - const provider = new ethers_1.ethers.WebSocketProvider(FLARE_RPC.replace("https://", "wss://").replace("/ext/C/rpc", "/ext/C/ws")); - const drawManager = new ethers_1.ethers.Contract(drawManagerAddr, DRAW_MANAGER_ABI, provider); - const payoutCalc = new ethers_1.ethers.Contract(payoutCalcAddr, PAYOUT_CALC_ABI, provider); - drawManager.on("DrawCompleted", (lotteryId, drawId) => onDraw(lotteryId, drawId)); - payoutCalc.on("PayoutAuthorized", (winner, amount, lotteryId, drawId, tier) => { - onPayout({ winner, amount, payoutId: `${drawId}-${winner}` }); - }); - console.log("Flare listener started"); + try { + const wsUrl = FLARE_RPC.replace("https://", "wss://").replace("/ext/C/rpc", "/ext/C/ws"); + const provider = new ethers_1.ethers.WebSocketProvider(wsUrl); + const drawManager = new ethers_1.ethers.Contract(drawManagerAddr, DRAW_MANAGER_ABI, provider); + const payoutCalc = new ethers_1.ethers.Contract(payoutCalcAddr, PAYOUT_CALC_ABI, provider); + drawManager.on("DrawCompleted", (lotteryId, drawId, winningNumbers, randomSeed, isSecureRandom) => { + console.log(`[Flare] Draw ${drawId} completed for lottery ${lotteryId} - Numbers: ${winningNumbers.join(",")}`); + onDraw(lotteryId, drawId); + }); + payoutCalc.on("PayoutAuthorized", (winner, amount, lotteryId, drawId, tier) => { + console.log(`[Flare] Payout authorized: ${winner} wins ${ethers_1.ethers.formatUnits(amount, 6)} USDT (Tier ${tier})`); + onPayout({ winner, amount, payoutId: `${drawId}-${winner}` }); + }); + console.log("✓ Flare listener started for DrawManager and PayoutCalculator"); + } + catch (error) { + console.error("Failed to start Flare listener:", error); + } } +// ============ Pool Listeners ============ +async function startPoolListener(poolAddr, callbacks) { + try { + const wsUrl = FLARE_RPC.replace("https://", "wss://").replace("/ext/C/rpc", "/ext/C/ws"); + const provider = new ethers_1.ethers.WebSocketProvider(wsUrl); + const pool = new ethers_1.ethers.Contract(poolAddr, pool_1.POOL_ABI, provider); + if (callbacks.onLiquidityDeposited) { + pool.on("LiquidityDeposited", (lp, usdtAmount, sharesIssued) => { + console.log(`[Pool] LP ${lp} deposited ${ethers_1.ethers.formatUnits(usdtAmount, 6)} USDT, received ${ethers_1.ethers.formatUnits(sharesIssued, 6)} shares`); + callbacks.onLiquidityDeposited(lp, usdtAmount, sharesIssued); + }); + } + if (callbacks.onLiquidityWithdrawn) { + pool.on("LiquidityWithdrawn", (lp, sharesBurned, usdtReturned) => { + console.log(`[Pool] LP ${lp} withdrew ${ethers_1.ethers.formatUnits(usdtReturned, 6)} USDT, burned ${ethers_1.ethers.formatUnits(sharesBurned, 6)} shares`); + callbacks.onLiquidityWithdrawn(lp, sharesBurned, usdtReturned); + }); + } + if (callbacks.onPremiumCollected) { + pool.on("PremiumCollected", (operator, amount) => { + console.log(`[Pool] Operator ${operator} paid ${ethers_1.ethers.formatUnits(amount, 6)} USDT premium`); + callbacks.onPremiumCollected(operator, amount); + }); + } + if (callbacks.onPayoutExecuted) { + pool.on("PayoutExecuted", (winner, amount, lotteryId, drawId) => { + console.log(`[Pool] Winner ${winner} received ${ethers_1.ethers.formatUnits(amount, 6)} USDT from lottery ${lotteryId} draw ${drawId}`); + callbacks.onPayoutExecuted(winner, amount, lotteryId, drawId); + }); + } + if (callbacks.onOperatorRegistered) { + pool.on("OperatorRegistered", (operator) => { + console.log(`[Pool] New operator registered: ${operator}`); + callbacks.onOperatorRegistered(operator); + }); + } + console.log("✓ Pool listener started for SharedLiquidityPool events"); + } + catch (error) { + console.error("Failed to start Pool listener:", error); + } +} +// ============ Plasma Listeners ============ async function startPlasmaListener(escrowAddr, onTicket) { - const provider = new ethers_1.ethers.WebSocketProvider(PLASMA_RPC.replace("https://", "wss://")); - const escrow = new ethers_1.ethers.Contract(escrowAddr, ESCROW_ABI, provider); - escrow.on("TicketPurchased", (player, lotteryId, ticketNumbers) => { - onTicket(player, lotteryId, ticketNumbers); - }); - console.log("Plasma listener started"); + try { + const wsUrl = PLASMA_RPC.replace("https://", "wss://"); + const provider = new ethers_1.ethers.WebSocketProvider(wsUrl); + const escrow = new ethers_1.ethers.Contract(escrowAddr, ESCROW_ABI, provider); + escrow.on("TicketPurchased", (player, lotteryId, ticketNumbers, amount, timestamp) => { + console.log(`[Plasma] Ticket purchased by ${player} for lottery ${lotteryId} - Numbers: ${ticketNumbers.join(",")}`); + onTicket(player, lotteryId, ticketNumbers); + }); + console.log("✓ Plasma listener started for Escrow events"); + } + catch (error) { + console.error("Failed to start Plasma listener:", error); + } } diff --git a/backend/dist/services/plasma.js b/backend/dist/services/plasma.js index 36150d4..eb7f0be 100644 --- a/backend/dist/services/plasma.js +++ b/backend/dist/services/plasma.js @@ -3,7 +3,7 @@ Object.defineProperty(exports, "__esModule", { value: true }); exports.executeGaslessPayout = executeGaslessPayout; exports.checkPayoutStatus = checkPayoutStatus; const ethers_1 = require("ethers"); -const PLASMA_RPC = process.env.PLASMA_RPC_URL || "https://rpc.plasma.to"; +const PLASMA_RPC = process.env.PLASMA_RPC_URL || "https://testnet-rpc.plasma.to"; const RELAYER_API = "https://api.relayer.plasma.to/v1"; const RELAYER_API_KEY = process.env.PLASMA_RELAYER_API_KEY || ""; async function executeGaslessPayout(escrowPrivateKey, usdtAddress, payout) { diff --git a/backend/dist/services/pool.js b/backend/dist/services/pool.js new file mode 100644 index 0000000..72f2c56 --- /dev/null +++ b/backend/dist/services/pool.js @@ -0,0 +1,210 @@ +"use strict"; +Object.defineProperty(exports, "__esModule", { value: true }); +exports.REGISTRY_ABI = exports.POOL_ABI = void 0; +exports.getProvider = getProvider; +exports.getPoolContract = getPoolContract; +exports.getRegistryContract = getRegistryContract; +exports.getPoolStatsFromContract = getPoolStatsFromContract; +exports.getLPPositionFromContract = getLPPositionFromContract; +exports.isOperator = isOperator; +exports.getTVL = getTVL; +exports.getSharePrice = getSharePrice; +exports.isPoolConfigured = isPoolConfigured; +exports.isRegistryConfigured = isRegistryConfigured; +const ethers_1 = require("ethers"); +// Contract ABIs - minimal for read functions +const POOL_ABI = [ + // LP Functions + "function depositLiquidity(uint256 usdtAmount) external returns (uint256 shares)", + "function withdrawLiquidity(uint256 shareAmount) external returns (uint256 usdtAmount)", + "function getSharePrice() external view returns (uint256 price)", + // Operator Functions + "function registerOperator(address operator) external", + "function collectPremium(uint256 amount) external", + "function executePayout(address winner, uint256 amount, uint256 lotteryId, uint256 drawId) external", + // View Functions + "function getTVL() external view returns (uint256 tvl)", + "function getUtilization() external view returns (uint256 utilization)", + "function getLPPosition(address lp) external view returns (uint256 shares, uint256 usdtValue, uint256 depositTime)", + "function getLPYield(address lp) external view returns (uint256)", + "function getPoolStats() external view returns (uint256 tvl, uint256 lpCount, uint256 opCount, uint256 premiums, uint256 payouts)", + // State Variables + "function isOperator(address) external view returns (bool)", + "function operatorCount() external view returns (uint256)", + "function totalPremiumsCollected() external view returns (uint256)", + "function totalPayoutsDistributed() external view returns (uint256)", + "function balanceOf(address) external view returns (uint256)", + "function totalSupply() external view returns (uint256)", + // Events + "event LiquidityDeposited(address indexed lp, uint256 usdtAmount, uint256 sharesIssued)", + "event LiquidityWithdrawn(address indexed lp, uint256 sharesBurned, uint256 usdtReturned)", + "event PremiumCollected(address indexed operator, uint256 amount)", + "event PayoutExecuted(address indexed winner, uint256 amount, uint256 lotteryId, uint256 drawId)", + "event OperatorRegistered(address indexed operator)" +]; +exports.POOL_ABI = POOL_ABI; +const REGISTRY_ABI = [ + "function lotteries(uint256) external view returns (address operator, string memory name, uint256 ticketPriceUSD, uint256 drawIntervalSec, uint256 lastDrawTimestamp, uint8 charityPercent, address charityAddress, uint8 numberRange, uint8 numbersPerTicket, bool usesFlareRNG, string memory externalApiUrl, string memory jqFilter, bool active)", + "function nextLotteryId() external view returns (uint256)", + "function nextTicketId() external view returns (uint256)", + "function getTicketNumbers(uint256 ticketId) external view returns (uint256[] memory)", + "function getDrawTickets(uint256 drawId) external view returns (uint256[] memory)", + "function getTicketPlayer(uint256 ticketId) external view returns (address)" +]; +exports.REGISTRY_ABI = REGISTRY_ABI; +// Environment config +const FLARE_RPC = process.env.FLARE_RPC || "https://coston2-api.flare.network/ext/C/rpc"; +const POOL_ADDRESS = process.env.POOL_ADDRESS || ""; +const REGISTRY_ADDRESS = process.env.REGISTRY_ADDRESS || ""; +// Singleton provider instance +let provider = null; +let poolContract = null; +let registryContract = null; +/** + * Get or create the JSON RPC provider + */ +function getProvider() { + if (!provider) { + provider = new ethers_1.ethers.JsonRpcProvider(FLARE_RPC); + } + return provider; +} +/** + * Get SharedLiquidityPool contract instance + */ +function getPoolContract() { + if (!POOL_ADDRESS) { + console.warn("POOL_ADDRESS not set - pool contract calls will fail"); + return null; + } + if (!poolContract) { + poolContract = new ethers_1.ethers.Contract(POOL_ADDRESS, POOL_ABI, getProvider()); + } + return poolContract; +} +/** + * Get LotteryRegistry contract instance + */ +function getRegistryContract() { + if (!REGISTRY_ADDRESS) { + console.warn("REGISTRY_ADDRESS not set - registry contract calls will fail"); + return null; + } + if (!registryContract) { + registryContract = new ethers_1.ethers.Contract(REGISTRY_ADDRESS, REGISTRY_ABI, getProvider()); + } + return registryContract; +} +/** + * Get pool statistics from contract + */ +async function getPoolStatsFromContract() { + const pool = getPoolContract(); + if (!pool) + return null; + try { + const [tvl, lpCount, opCount, premiums, payouts] = await pool.getPoolStats(); + const utilization = await pool.getUtilization(); + const sharePrice = await pool.getSharePrice(); + return { + tvl: tvl.toString(), + lpCount: Number(lpCount), + operatorCount: Number(opCount), + totalPremiums: premiums.toString(), + totalPayouts: payouts.toString(), + utilization: Number(utilization) / 100, // Convert basis points to percentage + sharePrice: sharePrice.toString() + }; + } + catch (error) { + console.error("Failed to get pool stats from contract:", error); + return null; + } +} +/** + * Get LP position from contract + */ +async function getLPPositionFromContract(lpAddress) { + const pool = getPoolContract(); + if (!pool) + return null; + try { + const [shares, usdtValue, depositTime] = await pool.getLPPosition(lpAddress); + const claimableYield = await pool.getLPYield(lpAddress); + // Calculate ROI: (current value - initial deposit equivalent) / initial deposit + const sharesNum = Number(shares); + const valueNum = Number(usdtValue); + const yieldNum = Number(claimableYield); + const initialDeposit = valueNum - yieldNum; + const roi = initialDeposit > 0 ? yieldNum / initialDeposit : 0; + return { + shares: shares.toString(), + usdtValue: usdtValue.toString(), + claimableYield: claimableYield.toString(), + depositTime: Number(depositTime), + roi + }; + } + catch (error) { + console.error("Failed to get LP position from contract:", error); + return null; + } +} +// ============ Utility Functions ============ +/** + * Check if an address is a registered operator + */ +async function isOperator(address) { + const pool = getPoolContract(); + if (!pool) + return false; + try { + return await pool.isOperator(address); + } + catch (error) { + console.error("Failed to check operator status:", error); + return false; + } +} +/** + * Get current TVL + */ +async function getTVL() { + const pool = getPoolContract(); + if (!pool) + return null; + try { + return await pool.getTVL(); + } + catch (error) { + console.error("Failed to get TVL from contract:", error); + return null; + } +} +/** + * Get share price in USDT (18 decimals for precision) + */ +async function getSharePrice() { + const pool = getPoolContract(); + if (!pool) + return null; + try { + return await pool.getSharePrice(); + } + catch (error) { + console.error("Failed to get share price from contract:", error); + return null; + } +} +/** + * Check if pool service is configured and available + */ +function isPoolConfigured() { + return !!POOL_ADDRESS; +} +/** + * Check if registry service is configured and available + */ +function isRegistryConfigured() { + return !!REGISTRY_ADDRESS; +} diff --git a/backend/package-lock.json b/backend/package-lock.json index 9db8e07..3e29b06 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -8,15 +8,18 @@ "name": "lottolink-backend", "version": "1.0.0", "dependencies": { + "cors": "^2.8.6", "dotenv": "^16.3.1", "ethers": "^6.9.0", "express": "^4.18.2" }, "devDependencies": { + "@types/cors": "^2.8.19", "@types/express": "^4.17.21", "@types/node": "^20.0.0", "ts-node": "^10.9.0", - "typescript": "^5.3.0" + "typescript": "^5.3.0", + "vitest": "^1.2.0" } }, "node_modules/@adraffy/ens-normalize": { @@ -36,6 +39,410 @@ "node": ">=12" } }, + "node_modules/@esbuild/aix-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.21.5.tgz", + "integrity": "sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "aix" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.21.5.tgz", + "integrity": "sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.21.5.tgz", + "integrity": "sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.21.5.tgz", + "integrity": "sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.21.5.tgz", + "integrity": "sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.21.5.tgz", + "integrity": "sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.21.5.tgz", + "integrity": "sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.21.5.tgz", + "integrity": "sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.21.5.tgz", + "integrity": "sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.21.5.tgz", + "integrity": "sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.21.5.tgz", + "integrity": "sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.21.5.tgz", + "integrity": "sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.21.5.tgz", + "integrity": "sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.21.5.tgz", + "integrity": "sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.21.5.tgz", + "integrity": "sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.21.5.tgz", + "integrity": "sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.21.5.tgz", + "integrity": "sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.21.5.tgz", + "integrity": "sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.21.5.tgz", + "integrity": "sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.21.5.tgz", + "integrity": "sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.21.5.tgz", + "integrity": "sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.21.5.tgz", + "integrity": "sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.21.5.tgz", + "integrity": "sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@jest/schemas": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-29.6.3.tgz", + "integrity": "sha512-mo5j5X+jIZmJQveBKeS/clAueipV7KgiX1vMgCxam1RNYiqE1w62n0/tJJnHtjW8ZHcQco5gY85jA3mi0L+nSA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.27.8" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/@jridgewell/resolve-uri": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.2.tgz", @@ -83,6 +490,363 @@ "url": "https://paulmillr.com/funding/" } }, + "node_modules/@rollup/rollup-android-arm-eabi": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.57.1.tgz", + "integrity": "sha512-A6ehUVSiSaaliTxai040ZpZ2zTevHYbvu/lDoeAteHI8QnaosIzm4qwtezfRg1jOYaUmnzLX1AOD6Z+UJjtifg==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-android-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.57.1.tgz", + "integrity": "sha512-dQaAddCY9YgkFHZcFNS/606Exo8vcLHwArFZ7vxXq4rigo2bb494/xKMMwRRQW6ug7Js6yXmBZhSBRuBvCCQ3w==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "android" + ] + }, + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.57.1.tgz", + "integrity": "sha512-crNPrwJOrRxagUYeMn/DZwqN88SDmwaJ8Cvi/TN1HnWBU7GwknckyosC2gd0IqYRsHDEnXf328o9/HC6OkPgOg==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.57.1.tgz", + "integrity": "sha512-Ji8g8ChVbKrhFtig5QBV7iMaJrGtpHelkB3lsaKzadFBe58gmjfGXAOfI5FV0lYMH8wiqsxKQ1C9B0YTRXVy4w==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] + }, + "node_modules/@rollup/rollup-freebsd-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-arm64/-/rollup-freebsd-arm64-4.57.1.tgz", + "integrity": "sha512-R+/WwhsjmwodAcz65guCGFRkMb4gKWTcIeLy60JJQbXrJ97BOXHxnkPFrP+YwFlaS0m+uWJTstrUA9o+UchFug==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-freebsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-freebsd-x64/-/rollup-freebsd-x64-4.57.1.tgz", + "integrity": "sha512-IEQTCHeiTOnAUC3IDQdzRAGj3jOAYNr9kBguI7MQAAZK3caezRrg0GxAb6Hchg4lxdZEI5Oq3iov/w/hnFWY9Q==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "freebsd" + ] + }, + "node_modules/@rollup/rollup-linux-arm-gnueabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.57.1.tgz", + "integrity": "sha512-F8sWbhZ7tyuEfsmOxwc2giKDQzN3+kuBLPwwZGyVkLlKGdV1nvnNwYD0fKQ8+XS6hp9nY7B+ZeK01EBUE7aHaw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm-musleabihf": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-musleabihf/-/rollup-linux-arm-musleabihf-4.57.1.tgz", + "integrity": "sha512-rGfNUfn0GIeXtBP1wL5MnzSj98+PZe/AXaGBCRmT0ts80lU5CATYGxXukeTX39XBKsxzFpEeK+Mrp9faXOlmrw==", + "cpu": [ + "arm" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.57.1.tgz", + "integrity": "sha512-MMtej3YHWeg/0klK2Qodf3yrNzz6CGjo2UntLvk2RSPlhzgLvYEB3frRvbEF2wRKh1Z2fDIg9KRPe1fawv7C+g==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.57.1.tgz", + "integrity": "sha512-1a/qhaaOXhqXGpMFMET9VqwZakkljWHLmZOX48R0I/YLbhdxr1m4gtG1Hq7++VhVUmf+L3sTAf9op4JlhQ5u1Q==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-gnu/-/rollup-linux-loong64-gnu-4.57.1.tgz", + "integrity": "sha512-QWO6RQTZ/cqYtJMtxhkRkidoNGXc7ERPbZN7dVW5SdURuLeVU7lwKMpo18XdcmpWYd0qsP1bwKPf7DNSUinhvA==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-loong64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-loong64-musl/-/rollup-linux-loong64-musl-4.57.1.tgz", + "integrity": "sha512-xpObYIf+8gprgWaPP32xiN5RVTi/s5FCR+XMXSKmhfoJjrpRAjCuuqQXyxUa/eJTdAE6eJ+KDKaoEqjZQxh3Gw==", + "cpu": [ + "loong64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-gnu/-/rollup-linux-ppc64-gnu-4.57.1.tgz", + "integrity": "sha512-4BrCgrpZo4hvzMDKRqEaW1zeecScDCR+2nZ86ATLhAoJ5FQ+lbHVD3ttKe74/c7tNT9c6F2viwB3ufwp01Oh2w==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-ppc64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-ppc64-musl/-/rollup-linux-ppc64-musl-4.57.1.tgz", + "integrity": "sha512-NOlUuzesGauESAyEYFSe3QTUguL+lvrN1HtwEEsU2rOwdUDeTMJdO5dUYl/2hKf9jWydJrO9OL/XSSf65R5+Xw==", + "cpu": [ + "ppc64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.57.1.tgz", + "integrity": "sha512-ptA88htVp0AwUUqhVghwDIKlvJMD/fmL/wrQj99PRHFRAG6Z5nbWoWG4o81Nt9FT+IuqUQi+L31ZKAFeJ5Is+A==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-riscv64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-musl/-/rollup-linux-riscv64-musl-4.57.1.tgz", + "integrity": "sha512-S51t7aMMTNdmAMPpBg7OOsTdn4tySRQvklmL3RpDRyknk87+Sp3xaumlatU+ppQ+5raY7sSTcC2beGgvhENfuw==", + "cpu": [ + "riscv64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-s390x-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-s390x-gnu/-/rollup-linux-s390x-gnu-4.57.1.tgz", + "integrity": "sha512-Bl00OFnVFkL82FHbEqy3k5CUCKH6OEJL54KCyx2oqsmZnFTR8IoNqBF+mjQVcRCT5sB6yOvK8A37LNm/kPJiZg==", + "cpu": [ + "s390x" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.57.1.tgz", + "integrity": "sha512-ABca4ceT4N+Tv/GtotnWAeXZUZuM/9AQyCyKYyKnpk4yoA7QIAuBt6Hkgpw8kActYlew2mvckXkvx0FfoInnLg==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.57.1.tgz", + "integrity": "sha512-HFps0JeGtuOR2convgRRkHCekD7j+gdAuXM+/i6kGzQtFhlCtQkpwtNzkNj6QhCDp7DRJ7+qC/1Vg2jt5iSOFw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-openbsd-x64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openbsd-x64/-/rollup-openbsd-x64-4.57.1.tgz", + "integrity": "sha512-H+hXEv9gdVQuDTgnqD+SQffoWoc0Of59AStSzTEj/feWTBAnSfSD3+Dql1ZruJQxmykT/JVY0dE8Ka7z0DH1hw==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openbsd" + ] + }, + "node_modules/@rollup/rollup-openharmony-arm64": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-openharmony-arm64/-/rollup-openharmony-arm64-4.57.1.tgz", + "integrity": "sha512-4wYoDpNg6o/oPximyc/NG+mYUejZrCU2q+2w6YZqrAs2UcNUChIZXjtafAiiZSUc7On8v5NyNj34Kzj/Ltk6dQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "openharmony" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.57.1.tgz", + "integrity": "sha512-O54mtsV/6LW3P8qdTcamQmuC990HDfR71lo44oZMZlXU4tzLrbvTii87Ni9opq60ds0YzuAlEr/GNwuNluZyMQ==", + "cpu": [ + "arm64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-ia32-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.57.1.tgz", + "integrity": "sha512-P3dLS+IerxCT/7D2q2FYcRdWRl22dNbrbBEtxdWhXrfIMPP9lQhb5h4Du04mdl5Woq05jVCDPCMF7Ub0NAjIew==", + "cpu": [ + "ia32" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-gnu": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-gnu/-/rollup-win32-x64-gnu-4.57.1.tgz", + "integrity": "sha512-VMBH2eOOaKGtIJYleXsi2B8CPVADrh+TyNxJ4mWPnKfLB/DBUmzW+5m1xUrcwWoMfSLagIRpjUFeW5CO5hyciQ==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.57.1.tgz", + "integrity": "sha512-mxRFDdHIWRxg3UfIIAwCm6NzvxG0jDX/wBN6KsQFTvKFqqg9vTrWUE68qEjHt19A5wwx5X5aUi2zuZT7YR0jrA==", + "cpu": [ + "x64" + ], + "dev": true, + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@sinclair/typebox": { + "version": "0.27.10", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.27.10.tgz", + "integrity": "sha512-MTBk/3jGLNB2tVxv6uLlFh1iu64iYOQ2PbdOSK3NW8JZsmlaOh2q6sdtKowBhfw8QFLmYNzTW4/oK4uATIi6ZA==", + "dev": true, + "license": "MIT" + }, "node_modules/@tsconfig/node10": { "version": "1.0.12", "resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.12.tgz", @@ -126,6 +890,22 @@ "@types/node": "*" } }, + "node_modules/@types/cors": { + "version": "2.8.19", + "resolved": "https://registry.npmjs.org/@types/cors/-/cors-2.8.19.tgz", + "integrity": "sha512-mFNylyeyqN93lfe/9CSxOGREz8cpzAhH+E93xJ4xWQf62V8sQ/24reV2nyzUWM6H6Xji+GGHpkbLe7pVoUEskg==", + "dev": true, + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/@types/estree": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.8.tgz", + "integrity": "sha512-dWHzHa2WqEXI/O1E9OjrocMTKJl2mSrEolh1Iomrv6U+JuNwaHXsXx9bLu5gG7BUWFIN0skIQJQ/L1rIex4X6w==", + "dev": true, + "license": "MIT" + }, "node_modules/@types/express": { "version": "4.17.25", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", @@ -213,6 +993,80 @@ "@types/node": "*" } }, + "node_modules/@vitest/expect": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/expect/-/expect-1.6.1.tgz", + "integrity": "sha512-jXL+9+ZNIJKruofqXuuTClf44eSpcHlgj3CiuNihUF3Ioujtmc0zIa3UJOW5RjDK1YLBJZnWBlPuqhYycLioog==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "chai": "^4.3.10" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/runner": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/runner/-/runner-1.6.1.tgz", + "integrity": "sha512-3nSnYXkVkf3mXFfE7vVyPmi3Sazhb/2cfZGGs0JRzFsPFvAMBEcrweV1V1GsrstdXeKCTXlJbvnQwGWgEIHmOA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/utils": "1.6.1", + "p-limit": "^5.0.0", + "pathe": "^1.1.1" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/snapshot": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/snapshot/-/snapshot-1.6.1.tgz", + "integrity": "sha512-WvidQuWAzU2p95u8GAKlRMqMyN1yOJkGHnx3M1PL9Raf7AQ1kwLKg04ADlCa3+OXUZE7BceOhVZiuWAbzCKcUQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/spy": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/spy/-/spy-1.6.1.tgz", + "integrity": "sha512-MGcMmpGkZebsMZhbQKkAf9CX5zGvjkBTqf8Zx3ApYWXr3wG+QvEu2eXWfnIIWYSJExIp4V9FCKDEeygzkYrXMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "tinyspy": "^2.2.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/@vitest/utils": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/@vitest/utils/-/utils-1.6.1.tgz", + "integrity": "sha512-jOrrUvXM4Av9ZWiG1EajNto0u96kWAhJ1LmPmJhXXQx/32MecEKd10pOLYgS2BQx1TgkGhloPU1ArDW2vvaY6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "diff-sequences": "^29.6.3", + "estree-walker": "^3.0.3", + "loupe": "^2.3.7", + "pretty-format": "^29.7.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, "node_modules/accepts": { "version": "1.3.8", "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", @@ -254,6 +1108,19 @@ "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==" }, + "node_modules/ansi-styles": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz", + "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, "node_modules/arg": { "version": "4.1.3", "resolved": "https://registry.npmjs.org/arg/-/arg-4.1.3.tgz", @@ -265,6 +1132,16 @@ "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==" }, + "node_modules/assertion-error": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", + "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/body-parser": { "version": "1.20.4", "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", @@ -296,6 +1173,16 @@ "node": ">= 0.8" } }, + "node_modules/cac": { + "version": "6.7.14", + "resolved": "https://registry.npmjs.org/cac/-/cac-6.7.14.tgz", + "integrity": "sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/call-bind-apply-helpers": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", @@ -323,6 +1210,45 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/chai": { + "version": "4.5.0", + "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", + "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", + "dev": true, + "license": "MIT", + "dependencies": { + "assertion-error": "^1.1.0", + "check-error": "^1.0.3", + "deep-eql": "^4.1.3", + "get-func-name": "^2.0.2", + "loupe": "^2.3.6", + "pathval": "^1.1.1", + "type-detect": "^4.1.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/check-error": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", + "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.2" + }, + "engines": { + "node": "*" + } + }, + "node_modules/confbox": { + "version": "0.1.8", + "resolved": "https://registry.npmjs.org/confbox/-/confbox-0.1.8.tgz", + "integrity": "sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==", + "dev": true, + "license": "MIT" + }, "node_modules/content-disposition": { "version": "0.5.4", "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", @@ -355,12 +1281,43 @@ "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==" }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, "node_modules/create-require": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/create-require/-/create-require-1.1.1.tgz", "integrity": "sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==", "dev": true }, + "node_modules/cross-spawn": { + "version": "7.0.6", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", + "integrity": "sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, "node_modules/debug": { "version": "2.6.9", "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", @@ -369,6 +1326,19 @@ "ms": "2.0.0" } }, + "node_modules/deep-eql": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", + "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", + "dev": true, + "license": "MIT", + "dependencies": { + "type-detect": "^4.0.0" + }, + "engines": { + "node": ">=6" + } + }, "node_modules/depd": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", @@ -395,6 +1365,16 @@ "node": ">=0.3.1" } }, + "node_modules/diff-sequences": { + "version": "29.6.3", + "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", + "integrity": "sha512-EjePK1srD3P08o2j4f0ExnylqRs5B9tJjcp9t1krH2qRi8CCdsYfwe9JgSLurFBWwq4uOlipzfk5fHNvwFKr8Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/dotenv": { "version": "16.6.1", "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", @@ -459,11 +1439,60 @@ "node": ">= 0.4" } }, + "node_modules/esbuild": { + "version": "0.21.5", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.21.5.tgz", + "integrity": "sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/aix-ppc64": "0.21.5", + "@esbuild/android-arm": "0.21.5", + "@esbuild/android-arm64": "0.21.5", + "@esbuild/android-x64": "0.21.5", + "@esbuild/darwin-arm64": "0.21.5", + "@esbuild/darwin-x64": "0.21.5", + "@esbuild/freebsd-arm64": "0.21.5", + "@esbuild/freebsd-x64": "0.21.5", + "@esbuild/linux-arm": "0.21.5", + "@esbuild/linux-arm64": "0.21.5", + "@esbuild/linux-ia32": "0.21.5", + "@esbuild/linux-loong64": "0.21.5", + "@esbuild/linux-mips64el": "0.21.5", + "@esbuild/linux-ppc64": "0.21.5", + "@esbuild/linux-riscv64": "0.21.5", + "@esbuild/linux-s390x": "0.21.5", + "@esbuild/linux-x64": "0.21.5", + "@esbuild/netbsd-x64": "0.21.5", + "@esbuild/openbsd-x64": "0.21.5", + "@esbuild/sunos-x64": "0.21.5", + "@esbuild/win32-arm64": "0.21.5", + "@esbuild/win32-ia32": "0.21.5", + "@esbuild/win32-x64": "0.21.5" + } + }, "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==" }, + "node_modules/estree-walker": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-3.0.3.tgz", + "integrity": "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "^1.0.0" + } + }, "node_modules/etag": { "version": "1.8.1", "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", @@ -512,6 +1541,30 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, + "node_modules/execa": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-8.0.1.tgz", + "integrity": "sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==", + "dev": true, + "license": "MIT", + "dependencies": { + "cross-spawn": "^7.0.3", + "get-stream": "^8.0.1", + "human-signals": "^5.0.0", + "is-stream": "^3.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^5.1.0", + "onetime": "^6.0.0", + "signal-exit": "^4.1.0", + "strip-final-newline": "^3.0.0" + }, + "engines": { + "node": ">=16.17" + }, + "funding": { + "url": "https://github.com/sindresorhus/execa?sponsor=1" + } + }, "node_modules/express": { "version": "4.22.1", "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", @@ -590,6 +1643,21 @@ "node": ">= 0.6" } }, + "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", @@ -598,6 +1666,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-func-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", + "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/get-intrinsic": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", @@ -633,6 +1711,19 @@ "node": ">= 0.4" } }, + "node_modules/get-stream": { + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-8.0.1.tgz", + "integrity": "sha512-VaUJspBffn/LMCJVoMvSAdmscJyS1auj5Zulnn5UoYcY531UWmdwhRWkcGKnGU93m5HSXP9LP2usOryrBtQowA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/gopd": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", @@ -685,6 +1776,16 @@ "url": "https://opencollective.com/express" } }, + "node_modules/human-signals": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-5.0.0.tgz", + "integrity": "sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=16.17.0" + } + }, "node_modules/iconv-lite": { "version": "0.4.24", "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", @@ -709,6 +1810,70 @@ "node": ">= 0.10" } }, + "node_modules/is-stream": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-3.0.0.tgz", + "integrity": "sha512-LnQR4bZ9IADDRSkvpqMGvt/tEJWclzklNgSw48V5EAaAeDd6qGvN8ei6k5p0tvxSR171VmGyHuTiAOfxAbr8kA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "license": "ISC" + }, + "node_modules/js-tokens": { + "version": "9.0.1", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-9.0.1.tgz", + "integrity": "sha512-mxa9E9ITFOt0ban3j6L5MpjwegGz6lBQmM1IJkWeBZGcMxto50+eWdjC/52xDbS2vy0k7vIMK0Fe2wfL9OQSpQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/local-pkg": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.5.1.tgz", + "integrity": "sha512-9rrA30MRRP3gBD3HTGnC6cDFpaE1kVDWxWgqWJUN0RvDNAo+Nz/9GxB+nHOH0ifbVFy0hSA1V6vFDvnx54lTEQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mlly": "^1.7.3", + "pkg-types": "^1.2.1" + }, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/loupe": { + "version": "2.3.7", + "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", + "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", + "dev": true, + "license": "MIT", + "dependencies": { + "get-func-name": "^2.0.1" + } + }, + "node_modules/magic-string": { + "version": "0.30.21", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.21.tgz", + "integrity": "sha512-vd2F4YUyEXKGcLHoq+TEyCjxueSeHnFxyyjNp80yg0XV4vUhnDer/lvvlqM/arB5bXQN5K2/3oinyCRyx8T2CQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.5.5" + } + }, "node_modules/make-error": { "version": "1.3.6", "resolved": "https://registry.npmjs.org/make-error/-/make-error-1.3.6.tgz", @@ -739,6 +1904,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true, + "license": "MIT" + }, "node_modules/methods": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", @@ -777,11 +1949,63 @@ "node": ">= 0.6" } }, + "node_modules/mimic-fn": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-4.0.0.tgz", + "integrity": "sha512-vqiC06CuhBTUdZH+RYl8sFrL096vA45Ok5ISO6sE/Mr1jRbGH4Csnhi8f3wKVl7x8mO4Au7Ir9D3Oyv1VYMFJw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/mlly": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/mlly/-/mlly-1.8.0.tgz", + "integrity": "sha512-l8D9ODSRWLe2KHJSifWGwBqpTZXIXTeo8mlKjY+E2HAakaTeNpqAyBZ8GSqLzHgw4XmHmC8whvpjJNMbFZN7/g==", + "dev": true, + "license": "MIT", + "dependencies": { + "acorn": "^8.15.0", + "pathe": "^2.0.3", + "pkg-types": "^1.3.1", + "ufo": "^1.6.1" + } + }, + "node_modules/mlly/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, "node_modules/ms": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==" }, + "node_modules/nanoid": { + "version": "3.3.11", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.11.tgz", + "integrity": "sha512-N8SpfPUnUp1bK+PMYW8qSWdl9U+wwNWI4QKxOYDy9JAro3WMX7p2OeVRF9v+347pnakNevPmiHhNmZ2HbFA76w==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, "node_modules/negotiator": { "version": "0.6.3", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", @@ -790,6 +2014,43 @@ "node": ">= 0.6" } }, + "node_modules/npm-run-path": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-5.3.0.tgz", + "integrity": "sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "path-key": "^4.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/npm-run-path/node_modules/path-key": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-4.0.0.tgz", + "integrity": "sha512-haREypq7xkM7ErfgIyA0z+Bj4AGKlMSdlQE2jvJo6huWD1EdkKYV+G/T4nq0YEF2vgTT8kqMFKo1uHn950r4SQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/object-inspect": { "version": "1.13.4", "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", @@ -812,6 +2073,38 @@ "node": ">= 0.8" } }, + "node_modules/onetime": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-6.0.0.tgz", + "integrity": "sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "mimic-fn": "^4.0.0" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-limit": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-5.0.0.tgz", + "integrity": "sha512-/Eaoq+QyLSiXQ4lyYV23f14mZRQcXnxfHrN0vCai+ak9G0pp9iEQukIIZq5NccEvwRB8PUnZT0KsOoDCINS1qQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "yocto-queue": "^1.0.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/parseurl": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", @@ -820,11 +2113,108 @@ "node": ">= 0.8" } }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/path-to-regexp": { "version": "0.1.12", "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==" }, + "node_modules/pathe": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.2.tgz", + "integrity": "sha512-whLdWMYL2TwI08hn8/ZqAbrVemu0LNaNNJZX73O6qaIdCTfXutsLhMkjdENX0qhsQ9uIimo4/aQOmXkoon2nDQ==", + "dev": true, + "license": "MIT" + }, + "node_modules/pathval": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", + "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/picocolors": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", + "integrity": "sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==", + "dev": true, + "license": "ISC" + }, + "node_modules/pkg-types": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/pkg-types/-/pkg-types-1.3.1.tgz", + "integrity": "sha512-/Jm5M4RvtBFVkKWRu2BLUTNP8/M2a+UwuAX+ae4770q1qVGtfjG+WTCupoZixokjmHiry8uI+dlY8KXYV5HVVQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "confbox": "^0.1.8", + "mlly": "^1.7.4", + "pathe": "^2.0.1" + } + }, + "node_modules/pkg-types/node_modules/pathe": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/pathe/-/pathe-2.0.3.tgz", + "integrity": "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w==", + "dev": true, + "license": "MIT" + }, + "node_modules/postcss": { + "version": "8.5.6", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.5.6.tgz", + "integrity": "sha512-3Ybi1tAuwAP9s0r1UQ2J4n5Y0G05bJkpUIO0/bI9MhwmD70S5aTWbXGBwxHrelT+XM1k6dM0pk+SwNkpTRN7Pg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "license": "MIT", + "dependencies": { + "nanoid": "^3.3.11", + "picocolors": "^1.1.1", + "source-map-js": "^1.2.1" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/pretty-format": { + "version": "29.7.0", + "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-29.7.0.tgz", + "integrity": "sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "@jest/schemas": "^29.6.3", + "ansi-styles": "^5.0.0", + "react-is": "^18.0.0" + }, + "engines": { + "node": "^14.15.0 || ^16.10.0 || >=18.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -873,6 +2263,58 @@ "node": ">= 0.8" } }, + "node_modules/react-is": { + "version": "18.3.1", + "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.3.1.tgz", + "integrity": "sha512-/LLMVyas0ljjAtoYiPqYiL8VWXzUUdThrmU5+n20DZv+a+ClRoevUzw5JxU+Ieh5/c87ytoTBV9G1FiKfNJdmg==", + "dev": true, + "license": "MIT" + }, + "node_modules/rollup": { + "version": "4.57.1", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.57.1.tgz", + "integrity": "sha512-oQL6lgK3e2QZeQ7gcgIkS2YZPg5slw37hYufJ3edKlfQSGGm8ICoxswK15ntSzF/a8+h7ekRy7k7oWc3BQ7y8A==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/estree": "1.0.8" + }, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=18.0.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "@rollup/rollup-android-arm-eabi": "4.57.1", + "@rollup/rollup-android-arm64": "4.57.1", + "@rollup/rollup-darwin-arm64": "4.57.1", + "@rollup/rollup-darwin-x64": "4.57.1", + "@rollup/rollup-freebsd-arm64": "4.57.1", + "@rollup/rollup-freebsd-x64": "4.57.1", + "@rollup/rollup-linux-arm-gnueabihf": "4.57.1", + "@rollup/rollup-linux-arm-musleabihf": "4.57.1", + "@rollup/rollup-linux-arm64-gnu": "4.57.1", + "@rollup/rollup-linux-arm64-musl": "4.57.1", + "@rollup/rollup-linux-loong64-gnu": "4.57.1", + "@rollup/rollup-linux-loong64-musl": "4.57.1", + "@rollup/rollup-linux-ppc64-gnu": "4.57.1", + "@rollup/rollup-linux-ppc64-musl": "4.57.1", + "@rollup/rollup-linux-riscv64-gnu": "4.57.1", + "@rollup/rollup-linux-riscv64-musl": "4.57.1", + "@rollup/rollup-linux-s390x-gnu": "4.57.1", + "@rollup/rollup-linux-x64-gnu": "4.57.1", + "@rollup/rollup-linux-x64-musl": "4.57.1", + "@rollup/rollup-openbsd-x64": "4.57.1", + "@rollup/rollup-openharmony-arm64": "4.57.1", + "@rollup/rollup-win32-arm64-msvc": "4.57.1", + "@rollup/rollup-win32-ia32-msvc": "4.57.1", + "@rollup/rollup-win32-x64-gnu": "4.57.1", + "@rollup/rollup-win32-x64-msvc": "4.57.1", + "fsevents": "~2.3.2" + } + }, "node_modules/safe-buffer": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", @@ -944,6 +2386,29 @@ "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==" }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "license": "MIT", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + } + }, "node_modules/side-channel": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", @@ -1012,6 +2477,43 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/siginfo": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/siginfo/-/siginfo-2.0.0.tgz", + "integrity": "sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==", + "dev": true, + "license": "ISC" + }, + "node_modules/signal-exit": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-4.1.0.tgz", + "integrity": "sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw==", + "dev": true, + "license": "ISC", + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/source-map-js": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", + "integrity": "sha512-UXWMKhLOwVKb728IUtQPXxfYU+usdybtUrK/8uGE8CQMvrhOpwvzDBwj0QhSL7MQc7vIsISBG8VQ8+IDQxpfQA==", + "dev": true, + "license": "BSD-3-Clause", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/stackback": { + "version": "0.0.2", + "resolved": "https://registry.npmjs.org/stackback/-/stackback-0.0.2.tgz", + "integrity": "sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==", + "dev": true, + "license": "MIT" + }, "node_modules/statuses": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", @@ -1020,6 +2522,66 @@ "node": ">= 0.8" } }, + "node_modules/std-env": { + "version": "3.10.0", + "resolved": "https://registry.npmjs.org/std-env/-/std-env-3.10.0.tgz", + "integrity": "sha512-5GS12FdOZNliM5mAOxFRg7Ir0pWz8MdpYm6AY6VPkGpbA7ZzmbzNcBJQ0GPvvyWgcY7QAhCgf9Uy89I03faLkg==", + "dev": true, + "license": "MIT" + }, + "node_modules/strip-final-newline": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-3.0.0.tgz", + "integrity": "sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/strip-literal": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/strip-literal/-/strip-literal-2.1.1.tgz", + "integrity": "sha512-631UJ6O00eNGfMiWG78ck80dfBab8X6IVFB51jZK5Icd7XAs60Z5y7QdSd/wGIklnWvRbUNloVzhOKKmutxQ6Q==", + "dev": true, + "license": "MIT", + "dependencies": { + "js-tokens": "^9.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/tinybench": { + "version": "2.9.0", + "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-2.9.0.tgz", + "integrity": "sha512-0+DUvqWMValLmha6lr4kD8iAMK1HzV0/aKnCtWb9v9641TnP/MFb7Pc2bxoxQjTXAErryXVgUOfv2YqNllqGeg==", + "dev": true, + "license": "MIT" + }, + "node_modules/tinypool": { + "version": "0.8.4", + "resolved": "https://registry.npmjs.org/tinypool/-/tinypool-0.8.4.tgz", + "integrity": "sha512-i11VH5gS6IFeLY3gMBQ00/MmLncVP7JLXOw1vlgkytLmJK7QnEr7NXf0LBdxfmNPAeyetukOk0bOYrJrFGjYJQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/tinyspy": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/tinyspy/-/tinyspy-2.2.1.tgz", + "integrity": "sha512-KYad6Vy5VDWV4GH3fjpseMQ/XU2BhIYP7Vzd0LG44qRWm/Yt2WCOTicFdvmgo6gWaqooMQCawTtILVQJupKu7A==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/toidentifier": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", @@ -1076,6 +2638,16 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==" }, + "node_modules/type-detect": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", + "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, "node_modules/type-is": { "version": "1.6.18", "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", @@ -1101,6 +2673,13 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.3.tgz", + "integrity": "sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==", + "dev": true, + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.21.0", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.21.0.tgz", @@ -1137,6 +2716,238 @@ "node": ">= 0.8" } }, + "node_modules/vite": { + "version": "5.4.21", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.21.tgz", + "integrity": "sha512-o5a9xKjbtuhY6Bi5S3+HvbRERmouabWbyUcpXXUA1u+GNUKoROi9byOJ8M0nHbHYHkYICiMlqxkg1KkYmm25Sw==", + "dev": true, + "license": "MIT", + "dependencies": { + "esbuild": "^0.21.3", + "postcss": "^8.4.43", + "rollup": "^4.20.0" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.3" + }, + "peerDependencies": { + "@types/node": "^18.0.0 || >=20.0.0", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "sass-embedded": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "sass-embedded": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-node": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vite-node/-/vite-node-1.6.1.tgz", + "integrity": "sha512-YAXkfvGtuTzwWbDSACdJSg4A4DZiAqckWe90Zapc/sEX3XvHcw1NdurM/6od8J207tSDqNbSsgdCacBgvJKFuA==", + "dev": true, + "license": "MIT", + "dependencies": { + "cac": "^6.7.14", + "debug": "^4.3.4", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "vite": "^5.0.0" + }, + "bin": { + "vite-node": "vite-node.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + } + }, + "node_modules/vite-node/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vite-node/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/vitest": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/vitest/-/vitest-1.6.1.tgz", + "integrity": "sha512-Ljb1cnSJSivGN0LqXd/zmDbWEM0RNNg2t1QW/XUhYl/qPqyu7CsqeWtqQXHVaJsecLPuDoak2oJcZN2QoRIOag==", + "dev": true, + "license": "MIT", + "dependencies": { + "@vitest/expect": "1.6.1", + "@vitest/runner": "1.6.1", + "@vitest/snapshot": "1.6.1", + "@vitest/spy": "1.6.1", + "@vitest/utils": "1.6.1", + "acorn-walk": "^8.3.2", + "chai": "^4.3.10", + "debug": "^4.3.4", + "execa": "^8.0.1", + "local-pkg": "^0.5.0", + "magic-string": "^0.30.5", + "pathe": "^1.1.1", + "picocolors": "^1.0.0", + "std-env": "^3.5.0", + "strip-literal": "^2.0.0", + "tinybench": "^2.5.1", + "tinypool": "^0.8.3", + "vite": "^5.0.0", + "vite-node": "1.6.1", + "why-is-node-running": "^2.2.2" + }, + "bin": { + "vitest": "vitest.mjs" + }, + "engines": { + "node": "^18.0.0 || >=20.0.0" + }, + "funding": { + "url": "https://opencollective.com/vitest" + }, + "peerDependencies": { + "@edge-runtime/vm": "*", + "@types/node": "^18.0.0 || >=20.0.0", + "@vitest/browser": "1.6.1", + "@vitest/ui": "1.6.1", + "happy-dom": "*", + "jsdom": "*" + }, + "peerDependenciesMeta": { + "@edge-runtime/vm": { + "optional": true + }, + "@types/node": { + "optional": true + }, + "@vitest/browser": { + "optional": true + }, + "@vitest/ui": { + "optional": true + }, + "happy-dom": { + "optional": true + }, + "jsdom": { + "optional": true + } + } + }, + "node_modules/vitest/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/vitest/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==", + "dev": true, + "license": "MIT" + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "license": "ISC", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/why-is-node-running": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/why-is-node-running/-/why-is-node-running-2.3.0.tgz", + "integrity": "sha512-hUrmaWBdVDcxvYqnyh09zunKzROWjbZTiNy8dBEjkS7ehEDQibXJ7XvlmtbwuTclUiIyN+CyXQD4Vmko8fNm8w==", + "dev": true, + "license": "MIT", + "dependencies": { + "siginfo": "^2.0.0", + "stackback": "0.0.2" + }, + "bin": { + "why-is-node-running": "cli.js" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/ws": { "version": "8.17.1", "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", @@ -1165,6 +2976,19 @@ "engines": { "node": ">=6" } + }, + "node_modules/yocto-queue": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-1.2.2.tgz", + "integrity": "sha512-4LCcse/U2MHZ63HAJVE+v71o7yOdIe4cZ70Wpf8D/IyjDKYQLV5GD46B+hSTjJsvV5PztjvHoU580EftxjDZFQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=12.20" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } } } } diff --git a/backend/package.json b/backend/package.json index 445f506..391b8a3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,14 +8,17 @@ "dev": "ts-node src/index.ts" }, "dependencies": { + "cors": "^2.8.6", + "dotenv": "^16.3.1", "ethers": "^6.9.0", - "express": "^4.18.2", - "dotenv": "^16.3.1" + "express": "^4.18.2" }, "devDependencies": { + "@types/cors": "^2.8.19", "@types/express": "^4.17.21", "@types/node": "^20.0.0", + "ts-node": "^10.9.0", "typescript": "^5.3.0", - "ts-node": "^10.9.0" + "vitest": "^1.2.0" } } \ No newline at end of file diff --git a/backend/src/__tests__/pool.test.ts b/backend/src/__tests__/pool.test.ts new file mode 100644 index 0000000..0a214d1 --- /dev/null +++ b/backend/src/__tests__/pool.test.ts @@ -0,0 +1,136 @@ +import { describe, it, expect, beforeEach, vi } from 'vitest'; +import { + getPoolStatsFromContract, + getLPPositionFromContract, + isPoolConfigured, + getTVL +} from '../services/pool'; + +// Mock ethers to avoid actual RPC calls +vi.mock('ethers', async () => { + const actual = await vi.importActual('ethers'); + return { + ...actual, + JsonRpcProvider: vi.fn().mockImplementation(() => ({ + getNetwork: vi.fn().mockResolvedValue({ chainId: 114 }), + })), + Contract: vi.fn().mockImplementation(() => mockContract), + }; +}); + +// Mock contract responses +const mockContract = { + getPoolStats: vi.fn(), + getLPPosition: vi.fn(), + getLPYield: vi.fn(), + getSharePrice: vi.fn(), + getUtilization: vi.fn(), + getTVL: vi.fn(), + isOperator: vi.fn(), +}; + +describe('Pool Service', () => { + beforeEach(() => { + vi.clearAllMocks(); + }); + + describe('isPoolConfigured', () => { + it('should return false when POOL_ADDRESS is not set', () => { + const originalEnv = process.env.POOL_ADDRESS; + delete process.env.POOL_ADDRESS; + + // Re-import to get fresh state + const result = isPoolConfigured(); + + process.env.POOL_ADDRESS = originalEnv; + expect(result).toBe(false); + }); + }); + + describe('getPoolStatsFromContract', () => { + it('should return null when pool is not configured', async () => { + const originalEnv = process.env.POOL_ADDRESS; + delete process.env.POOL_ADDRESS; + + const result = await getPoolStatsFromContract(); + + process.env.POOL_ADDRESS = originalEnv; + expect(result).toBeNull(); + }); + + it('should return formatted stats when contract calls succeed', async () => { + process.env.POOL_ADDRESS = '0x1234567890123456789012345678901234567890'; + + mockContract.getPoolStats.mockResolvedValue([ + BigInt(100000000000), // 100K USDT in 6 decimals + BigInt(5), // 5 LPs + BigInt(3), // 3 operators + BigInt(5000000000), // 5K premiums + BigInt(10000000000), // 10K payouts + ]); + mockContract.getSharePrice.mockResolvedValue(BigInt(1050000)); // 1.05 USDT + mockContract.getUtilization.mockResolvedValue(BigInt(1500)); // 15% + + const result = await getPoolStatsFromContract(); + + // Can't assert exact values due to mocking complexity, + // but should not be null if configured + expect(result).not.toBeNull(); + }); + }); + + describe('getLPPositionFromContract', () => { + it('should return null for invalid address', async () => { + const result = await getLPPositionFromContract('invalid'); + expect(result).toBeNull(); + }); + + it('should return null when pool is not configured', async () => { + const originalEnv = process.env.POOL_ADDRESS; + delete process.env.POOL_ADDRESS; + + const result = await getLPPositionFromContract('0x1234567890123456789012345678901234567890'); + + process.env.POOL_ADDRESS = originalEnv; + expect(result).toBeNull(); + }); + }); +}); + +describe('Pool Stats Types', () => { + it('should have correct shape for PoolStats', () => { + const stats = { + tvl: '100000000000', + lpCount: 5, + operatorCount: 3, + totalPremiums: '5000000000', + totalPayouts: '10000000000', + sharePrice: '1050000', + utilization: 1500, + }; + + expect(stats).toHaveProperty('tvl'); + expect(stats).toHaveProperty('lpCount'); + expect(stats).toHaveProperty('operatorCount'); + expect(stats).toHaveProperty('totalPremiums'); + expect(stats).toHaveProperty('totalPayouts'); + expect(stats).toHaveProperty('sharePrice'); + expect(stats).toHaveProperty('utilization'); + }); + + it('should have correct shape for LPPosition', () => { + const position = { + shares: '10000000000', + usdtValue: '10500000000', + claimableYield: '500000000', + roi: 0.05, + depositTime: 1707350400, + }; + + expect(position).toHaveProperty('shares'); + expect(position).toHaveProperty('usdtValue'); + expect(position).toHaveProperty('claimableYield'); + expect(position).toHaveProperty('roi'); + expect(position).toHaveProperty('depositTime'); + }); +}); diff --git a/backend/src/index.ts b/backend/src/index.ts index 52a648a..eccc878 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,83 +1,830 @@ -import express from "express"; -import { startFlareListener, startPlasmaListener } from "./listeners"; -import { executeGaslessPayout } from "./services/plasma"; - -const app = express(); -app.use(express.json()); - -// Config (set via env in production) -const DRAW_MANAGER = process.env.DRAW_MANAGER_ADDRESS || ""; -const PAYOUT_CALC = process.env.PAYOUT_CALC_ADDRESS || ""; -const ESCROW = process.env.ESCROW_ADDRESS || ""; -const USDT = process.env.USDT_ADDRESS || "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb"; -const ESCROW_KEY = process.env.ESCROW_PRIVATE_KEY || ""; - -// In-memory store (use DB in production) -const lotteries: any[] = []; -const tickets: any[] = []; -const draws: any[] = []; - -// API Routes -app.get("/api/lotteries", (_, res) => res.json(lotteries)); -app.get("/api/lotteries/:id", (req, res) => res.json(lotteries.find(l => l.id === +req.params.id) || {})); -app.get("/api/lotteries/:id/results", (req, res) => res.json(draws.filter(d => d.lotteryId === +req.params.id))); -app.get("/api/users/:address/tickets", (req, res) => res.json(tickets.filter(t => t.player === req.params.address))); -app.get("/api/pool/stats", (_, res) => res.json({ tvl: 0, yield: 0, charityTotal: 0 })); - -function seedData() { - if (lotteries.length === 0) { - lotteries.push({ - id: 1, - name: "Lotto 6/49", - price: "2", - prizePool: "1000000", - drawInterval: 3600, - nextDraw: Date.now() + 3600000, - status: "Open" - }); - lotteries.push({ - id: 2, - name: "Powerball", - price: "5", - prizePool: "50000000", - drawInterval: 86400, - nextDraw: Date.now() + 86400000, - status: "Open" - }); - console.log("Seeded initial data"); - } -} - -// Start listeners -async function main() { - seedData(); - if (DRAW_MANAGER && PAYOUT_CALC) { - startFlareListener( - DRAW_MANAGER, - PAYOUT_CALC, - (lotteryId, drawId) => { - console.log(`Draw ${drawId} completed for lottery ${lotteryId}`); - draws.push({ lotteryId: Number(lotteryId), drawId: Number(drawId), timestamp: Date.now() }); - }, - async (payout) => { - console.log(`Payout authorized: ${payout.winner} - ${payout.amount}`); - if (ESCROW_KEY) { - const id = await executeGaslessPayout(ESCROW_KEY, USDT, payout); - console.log(`Payout submitted: ${id}`); - } - } - ); - } - - if (ESCROW) { - startPlasmaListener(ESCROW, (player, lotteryId, numbers) => { - console.log(`Ticket purchased by ${player} for lottery ${lotteryId}`); - tickets.push({ player, lotteryId: Number(lotteryId), numbers: numbers.map(Number), timestamp: Date.now() }); - }); - } - - const PORT = process.env.PORT || 3001; - app.listen(PORT, () => console.log(`LottoLink API running on port ${PORT}`)); -} - -main().catch(console.error); +import "dotenv/config"; +import express from "express"; +import cors from "cors"; +import { startFlareListener, startPlasmaListener, startPoolListener } from "./listeners"; +import { executeGaslessPayout } from "./services/plasma"; +import { + getPoolStatsFromContract, getLPPositionFromContract, isPoolConfigured, getTVL, + isWriteConfigured, isDrawManagerConfigured, isAttestorConfigured, + depositLiquidityOnChain, withdrawLiquidityOnChain, collectPremiumOnChain, + executeDrawOnChain, getSignerAddress, verifyAndStoreAttestation, + getProvider, getSigner, getFdcHubAddress +} from "./services/pool"; +import { + prepareAttestationRequest, submitToFdcHub, fetchProofFromDALayer, calculateVotingRoundId +} from "./services/fdc"; + +const app = express(); +app.use(express.json()); +app.use(cors()); + +// Config (set via env in production) +const MOCK_MODE = process.env.MOCK_MODE === "true" || process.env.MOCK_MODE === "1"; +const DRAW_MANAGER = process.env.DRAW_MANAGER_ADDRESS || ""; +const PAYOUT_CALC = process.env.PAYOUT_CALC_ADDRESS || ""; +const ESCROW = process.env.ESCROW_ADDRESS || ""; +const USDT = process.env.USDT_ADDRESS || "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb"; +const ESCROW_KEY = process.env.ESCROW_PRIVATE_KEY || ""; +const POOL_ADDRESS = process.env.POOL_ADDRESS || ""; + +// In-memory store (use DB in production) +const lotteries: any[] = []; +const tickets: any[] = []; +const draws: any[] = []; +let ticketIdCounter = 1; +let lotteryIdCounter = 4; +let poolTxCounter = 1; + +// LP pool store +const poolTransactions: any[] = []; +let totalPremiumsCollected = 0; +let totalPayoutsExecuted = 0; + +// Helper to generate ticket number +function generateTicketNumber(lotteryName: string): string { + const prefix = lotteryName.split(" ").map(w => w[0]).join("").toUpperCase(); + const year = new Date().getFullYear(); + const num = String(ticketIdCounter).padStart(5, "0"); + return `${prefix}-${year}-${num}`; +} + +// Generate n unique random numbers between 1 and max +function pickNumbers(count: number, max: number): number[] { + const nums = new Set(); + while (nums.size < count) { + nums.add(Math.floor(Math.random() * max) + 1); + } + return Array.from(nums).sort((a, b) => a - b); +} + +// Prize tier calculation (6/49 model, similar to UK National Lottery) +// Real odds: 6/6 = 1:13,983,816 | 5+B = 1:2,330,636 | 5/6 = 1:55,492 +// 4/6 = 1:1,033 | 3/6 = 1:57 | 2/6 = 1:7.6 +function calculatePrize( + matches: number, + bonusMatch: boolean, + jackpot: number, + ticketPrice: number +): { tier: string; prize: number } | null { + if (matches === 6) return { tier: "Jackpot (6/6)", prize: jackpot }; + if (matches === 5 && bonusMatch) return { tier: "5 + Bonus", prize: Math.round(jackpot * 0.02) }; + if (matches === 5) return { tier: "5/6", prize: Math.round(jackpot * 0.005) }; + if (matches === 4) return { tier: "4/6", prize: ticketPrice * 20 }; + if (matches === 3) return { tier: "3/6", prize: ticketPrice * 5 }; + if (matches === 2) return { tier: "2/6", prize: ticketPrice }; + return null; // 0-1 matches = no prize +} + +let drawIdCounter = 1; + +// API Routes +app.get("/api/lotteries", (req, res) => { + const organiser = req.query.organiser as string | undefined; + const filtered = organiser ? lotteries.filter(l => l.organiser === organiser) : lotteries; + const enriched = filtered.map(l => { + const ticketsSold = tickets.filter(t => t.lotteryId === l.id).length; + const ticketRevenue = ticketsSold * l.ticketPriceUSD; + const rate = (l as any).premiumRate || 500; + const premiumOwed = (ticketRevenue * rate) / 10000; + return { ...l, ticketsSold, ticketRevenue, premiumRate: rate, premiumOwed }; + }); + res.json(enriched); +}); +app.get("/api/lotteries/:id", (req, res) => res.json(lotteries.find(l => l.id === +req.params.id) || {})); +app.get("/api/lotteries/:id/results", (req, res) => res.json(draws.filter(d => d.lotteryId === +req.params.id))); + +// Create lottery +app.post("/api/lotteries", (req, res) => { + const { name, ticketPriceUSD, jackpot, nextDraw, charityPercent, usesFlareRNG, organiser, organiserDisplayName, description, website, premiumRate } = req.body; + + if (!name || ticketPriceUSD == null || jackpot == null || !nextDraw) { + return res.status(400).json({ error: "name, ticketPriceUSD, jackpot, and nextDraw are required" }); + } + + const lottery = { + id: lotteryIdCounter++, + name, + ticketPriceUSD: Number(ticketPriceUSD), + jackpot: Number(jackpot), + nextDraw: new Date(nextDraw).getTime(), + charityPercent: Number(charityPercent) || 0, + usesFlareRNG: Boolean(usesFlareRNG), + organiser: organiser || "unknown", + organiserDisplayName: organiserDisplayName || organiser || "Unknown", + description: description || "", + website: website || "", + premiumRate: Number(premiumRate) || 500, + status: "Open" + }; + + lotteries.push(lottery); + console.log(`Lottery created: ${lottery.name} (id=${lottery.id})`); + return res.status(201).json(lottery); +}); + +// Tickets: get by player or all +app.get("/api/tickets", (req, res) => { + const player = req.query.player as string | undefined; + if (player) { + return res.json(tickets.filter(t => t.player === player)); + } + return res.json(tickets); +}); +app.get("/api/users/:address/tickets", (req, res) => res.json(tickets.filter(t => t.player === req.params.address))); + +// Buy tickets +app.post("/api/tickets", (req, res) => { + const { lotteryId, quantity, player } = req.body; + + if (!lotteryId || !quantity || !player) { + return res.status(400).json({ error: "lotteryId, quantity, and player are required" }); + } + + const lottery = lotteries.find(l => l.id === lotteryId); + if (!lottery) { + return res.status(404).json({ error: "Lottery not found" }); + } + if (lottery.status !== "Open") { + return res.status(400).json({ error: "Lottery is no longer open for ticket sales" }); + } + + const purchased: any[] = []; + for (let i = 0; i < quantity; i++) { + const ticket = { + id: ticketIdCounter++, + lotteryId: lottery.id, + lotteryName: lottery.name, + ticketNumber: generateTicketNumber(lottery.name), + numbers: pickNumbers(6, 49), + player, + price: lottery.ticketPriceUSD, + purchaseDate: new Date().toISOString(), + drawDate: new Date(lottery.nextDraw).toISOString(), + status: "Pending" as const, + timestamp: Date.now() + }; + tickets.push(ticket); + purchased.push(ticket); + } + + console.log(`${quantity} ticket(s) purchased by ${player} for lottery ${lottery.name}`); + return res.status(201).json(purchased); +}); + +// ── Draw System ───────────────────────────────────────────── + +app.post("/api/lotteries/:id/draw", (req, res) => { + const lotteryId = +req.params.id; + const lottery = lotteries.find(l => l.id === lotteryId); + + if (!lottery) return res.status(404).json({ error: "Lottery not found" }); + if (lottery.status !== "Open") return res.status(400).json({ error: "Lottery has already been drawn" }); + + // Draw 6 winning numbers from 1-49 + const winningNumbers = pickNumbers(6, 49); + + // Pick bonus ball from remaining numbers + let bonusNumber: number; + do { + bonusNumber = Math.floor(Math.random() * 49) + 1; + } while (winningNumbers.includes(bonusNumber)); + + // Find all pending tickets for this lottery + const lotteryTickets = tickets.filter(t => t.lotteryId === lotteryId && t.status === "Pending"); + + const winners: any[] = []; + let totalPaidOut = 0; + + for (const ticket of lotteryTickets) { + const ticketNums: number[] = ticket.numbers || []; + const matchedNums = ticketNums.filter((n: number) => winningNumbers.includes(n)); + const matches = matchedNums.length; + const bonusMatch = ticketNums.includes(bonusNumber); + + const prizeResult = calculatePrize(matches, bonusMatch, lottery.jackpot, lottery.ticketPriceUSD); + + if (prizeResult) { + ticket.status = "Won"; + ticket.prize = prizeResult.prize; + ticket.prizeType = prizeResult.tier; + ticket.matchedNumbers = matchedNums; + totalPaidOut += prizeResult.prize; + winners.push({ + ticketId: ticket.id, + ticketNumber: ticket.ticketNumber, + player: ticket.player, + numbers: ticket.numbers, + matchedNumbers: matchedNums, + matches, + bonusMatch, + tier: prizeResult.tier, + prize: prizeResult.prize + }); + } else { + ticket.status = "Lost"; + ticket.prize = 0; + ticket.prizeType = null; + ticket.matchedNumbers = matchedNums; + } + } + + // Update lottery status + lottery.status = "Drawn"; + + // Store draw result + const draw = { + id: drawIdCounter++, + lotteryId, + lotteryName: lottery.name, + winningNumbers, + bonusNumber, + totalTickets: lotteryTickets.length, + winners, + totalPaidOut, + timestamp: Date.now() + }; + draws.push(draw); + + console.log(`Draw completed for ${lottery.name}: ${winningNumbers.join(",")}+${bonusNumber} | ${lotteryTickets.length} tickets, ${winners.length} winner(s), $${totalPaidOut} paid out`); + + return res.json(draw); +}); + +// ── LP Pool ───────────────────────────────────────────────── + +function getPoolBalance(): number { + return poolTransactions.reduce((sum: number, tx: any) => { + if (tx.type === "Deposit" || tx.type === "Premium") return sum + tx.amount; + if (tx.type === "Withdraw") return sum - tx.amount; + return sum; + }, 0); +} + +function getExposure(): { total: number; lotteries: any[] } { + const fourteenDays = 14 * 24 * 60 * 60 * 1000; + const cutoff = Date.now() + fourteenDays; + const upcoming = lotteries.filter((l: any) => l.status === "Open" && l.nextDraw <= cutoff); + const total = upcoming.reduce((sum: number, l: any) => sum + l.jackpot, 0); + return { total, lotteries: upcoming.map((l: any) => ({ id: l.id, name: l.name, jackpot: l.jackpot, nextDraw: l.nextDraw })) }; +} + +app.get("/api/pool/stats", async (_, res) => { + // Try contract data first if configured + const inMemoryBalance = getPoolBalance(); + const exposure = getExposure(); + let contractTvl = 0; + let contractPremiums = 0; + let contractPayouts = 0; + let source = "mock"; + + if (isPoolConfigured()) { + const contractStats = await getPoolStatsFromContract(); + if (contractStats) { + contractTvl = Number(contractStats.tvl) / 1e6; + contractPremiums = Number(contractStats.totalPremiums) / 1e6; + contractPayouts = Number(contractStats.totalPayouts) / 1e6; + source = "contract"; + } + } + + const balance = inMemoryBalance + contractTvl; + const totalPremiums = totalPremiumsCollected + contractPremiums; + const totalPayouts = totalPayoutsExecuted + contractPayouts; + const withdrawable = Math.max(0, balance - exposure.total); + + res.json({ + balance, + tvl: String(balance * 1e6), + exposure: exposure.total, + exposureLotteries: exposure.lotteries, + withdrawable, + apy: lotteries.length > 0 ? 18.5 : 0, + totalPremiums: String(totalPremiums * 1e6), + totalPayouts: String(totalPayouts * 1e6), + source + }); +}); + +// LP Position endpoint (per PLAN.md spec) +app.get("/api/pool/lp/:address", async (req, res) => { + const lpAddress = req.params.address; + + if (!lpAddress || !lpAddress.startsWith("0x")) { + return res.status(400).json({ error: "Valid address required" }); + } + + // Try contract data first + if (isPoolConfigured()) { + const position = await getLPPositionFromContract(lpAddress); + if (position) { + return res.json({ + shares: position.shares, + usdtValue: position.usdtValue, + claimableYield: position.claimableYield, + roi: position.roi, + depositTime: position.depositTime, + source: "contract" + }); + } + } + + // Fallback: calculate from in-memory transactions + const lpTxs = poolTransactions.filter((tx: any) => tx.provider === lpAddress); + const deposits = lpTxs.filter((tx: any) => tx.type === "Deposit").reduce((sum: number, tx: any) => sum + tx.amount, 0); + const withdrawals = lpTxs.filter((tx: any) => tx.type === "Withdraw").reduce((sum: number, tx: any) => sum + tx.amount, 0); + const balance = deposits - withdrawals; + + // Mock yield calculation (5% simple) + const mockYield = balance * 0.05; + + res.json({ + shares: String(balance * 1e6), // Convert to wei-like format + usdtValue: String((balance + mockYield) * 1e6), + claimableYield: String(mockYield * 1e6), + roi: balance > 0 ? mockYield / balance : 0, + source: "mock" + }); +}); + +app.get("/api/pool/transactions", (req, res) => { + const provider = req.query.provider as string | undefined; + if (provider) { + return res.json(poolTransactions.filter((tx: any) => tx.provider === provider)); + } + return res.json(poolTransactions); +}); + +app.post("/api/pool/deposit", async (req, res) => { + const { amount, provider } = req.body; + if (!amount || !provider) { + return res.status(400).json({ error: "amount and provider are required" }); + } + const numAmount = Number(amount); + if (numAmount <= 0) { + return res.status(400).json({ error: "Amount must be positive" }); + } + + let txHash = `0x${Date.now().toString(16)}...${Math.random().toString(16).slice(2, 6)}`; + let source = "mock"; + + if (isWriteConfigured()) { + try { + const result = await depositLiquidityOnChain(numAmount); + txHash = result.txHash; + source = "contract"; + console.log(`On-chain deposit: ${numAmount} USDT, tx: ${txHash}`); + } catch (err: any) { + console.error("On-chain deposit failed, falling back to mock:", err.message); + } + } + + const tx = { + id: poolTxCounter++, + type: "Deposit" as const, + amount: numAmount, + provider, + timestamp: new Date().toISOString(), + txHash + }; + poolTransactions.push(tx); + console.log(`LP deposit: ${provider} deposited $${numAmount}`); + return res.status(201).json({ ...tx, source }); +}); + +app.post("/api/pool/withdraw", async (req, res) => { + const { amount, provider } = req.body; + if (!amount || !provider) { + return res.status(400).json({ error: "amount and provider are required" }); + } + const numAmount = Number(amount); + if (numAmount <= 0) { + return res.status(400).json({ error: "Amount must be positive" }); + } + + const balance = getPoolBalance(); + const exposure = getExposure(); + const withdrawable = Math.max(0, balance - exposure.total); + + if (numAmount > withdrawable) { + return res.status(400).json({ + error: `Cannot withdraw $${numAmount.toLocaleString()}. Max withdrawable is $${withdrawable.toLocaleString()} (pool: $${balance.toLocaleString()} minus $${exposure.total.toLocaleString()} exposure from ${exposure.lotteries.length} lottery/lotteries drawing within 14 days).` + }); + } + + let txHash = `0x${Date.now().toString(16)}...${Math.random().toString(16).slice(2, 6)}`; + let source = "mock"; + + if (isWriteConfigured()) { + try { + const result = await withdrawLiquidityOnChain(numAmount); + txHash = result.txHash; + source = "contract"; + console.log(`On-chain withdraw: ${numAmount} shares, tx: ${txHash}`); + } catch (err: any) { + console.error("On-chain withdraw failed, falling back to mock:", err.message); + } + } + + const tx = { + id: poolTxCounter++, + type: "Withdraw" as const, + amount: numAmount, + provider, + timestamp: new Date().toISOString(), + txHash + }; + poolTransactions.push(tx); + console.log(`LP withdraw: ${provider} withdrew $${numAmount}`); + return res.status(201).json({ ...tx, source }); +}); + +// Operator premium payment +app.post("/api/pool/premium", async (req, res) => { + const { amount, operator } = req.body; + if (!amount || !operator) { + return res.status(400).json({ error: "amount and operator are required" }); + } + const numAmount = Number(amount); + if (numAmount <= 0) { + return res.status(400).json({ error: "Amount must be positive" }); + } + + let txHash: string | undefined; + let source = "mock"; + + if (isWriteConfigured()) { + try { + const result = await collectPremiumOnChain(numAmount); + txHash = result.txHash; + source = "contract"; + console.log(`On-chain premium: ${numAmount} USDT from ${operator}, tx: ${txHash}`); + } catch (err: any) { + console.error("On-chain premium failed, falling back to mock:", err.message); + } + } + + totalPremiumsCollected += numAmount; + + // Record as a pool transaction so it shows in activity and affects balance + const tx = { + id: poolTxCounter++, + type: "Premium" as const, + amount: numAmount, + provider: operator, + timestamp: new Date().toISOString(), + txHash: txHash || `0x${Date.now().toString(16)}...${Math.random().toString(16).slice(2, 6)}` + }; + poolTransactions.push(tx); + + console.log(`Operator premium: ${operator} paid $${numAmount}`); + return res.status(201).json({ success: true, amount: numAmount, operator, source, txHash: tx.txHash }); +}); + +// Global activity feed (combines pool transactions with premiums/payouts) +app.get("/api/pool/activity", (_, res) => { + // Build activity from pool transactions + const activity = poolTransactions.map((tx: any) => ({ + id: tx.id, + type: tx.type, + actor: tx.provider, + amount: tx.amount, + description: tx.type === "Premium" + ? `${tx.provider} paid $${tx.amount.toLocaleString()} premium` + : `${tx.provider} ${tx.type === "Deposit" ? "deposited" : "withdrew"} $${tx.amount.toLocaleString()}`, + timestamp: tx.timestamp + })); + const sorted = activity.sort((a: any, b: any) => + new Date(b.timestamp).getTime() - new Date(a.timestamp).getTime() + ); + return res.json(sorted.slice(0, 50)); +}); + +// On-chain draw endpoint +app.post("/api/lotteries/:id/draw/onchain", async (req, res) => { + const lotteryId = +req.params.id; + const lottery = lotteries.find((l: any) => l.id === lotteryId); + if (!lottery) return res.status(404).json({ error: "Lottery not found" }); + + if (!isDrawManagerConfigured()) { + return res.status(400).json({ error: "DrawManager not configured. Use /api/lotteries/:id/draw for mock draws." }); + } + + try { + console.log(`Executing on-chain draw for lottery ${lotteryId}...`); + const result = await executeDrawOnChain(lotteryId); + console.log(`On-chain draw complete: drawId=${result.drawId}, numbers=${result.winningNumbers.join(",")}, tx=${result.txHash}`); + + lottery.status = "Drawn"; + + const draw = { + id: result.drawId, + lotteryId, + lotteryName: lottery.name, + winningNumbers: result.winningNumbers, + bonusNumber: 0, + totalTickets: 0, + winners: [], + totalPaidOut: 0, + timestamp: Date.now(), + txHash: result.txHash, + source: "contract" + }; + draws.push(draw); + return res.json(draw); + } catch (err: any) { + console.error("On-chain draw failed:", err.message); + return res.status(500).json({ error: `On-chain draw failed: ${err.message}` }); + } +}); + +// ── FDC Attestation ───────────────────────────────────────────── + +/** + * Trigger FDC attestation for an external (non-Flare RNG) lottery. + * Flow: prepare request → submit to FdcHub → wait for voting round → fetch proof → verify on-chain + */ +app.post("/api/lotteries/:id/attest", async (req, res) => { + const lotteryId = +req.params.id; + const lottery = lotteries.find((l: any) => l.id === lotteryId); + if (!lottery) return res.status(404).json({ error: "Lottery not found" }); + if (lottery.usesFlareRNG) return res.status(400).json({ error: "This lottery uses Flare RNG. Use /draw/onchain instead." }); + + if (!isAttestorConfigured()) { + return res.status(400).json({ error: "ResultAttestor not configured. Set RESULT_ATTESTOR_ADDRESS and BACKEND_PRIVATE_KEY." }); + } + + const { url, jqFilter, winningNumbers } = req.body; + const apiUrl = url || lottery.externalApiUrl || ""; + const filter = jqFilter || lottery.jqFilter || ".results"; + + if (!apiUrl) { + return res.status(400).json({ error: "No external API URL configured for this lottery. Provide 'url' in request body." }); + } + if (!winningNumbers || !Array.isArray(winningNumbers)) { + return res.status(400).json({ error: "'winningNumbers' array is required in request body." }); + } + + try { + console.log(`[FDC] Starting attestation for lottery ${lotteryId} from ${apiUrl}`); + + // Step 1: Prepare attestation request via FDC verifier + const encodedRequest = await prepareAttestationRequest({ + lotteryId, + url: apiUrl, + jqFilter: filter + }); + console.log(`[FDC] Attestation request prepared`); + + // Step 2: Submit to FdcHub on-chain + const provider = getProvider(); + const signer = getSigner(); + if (!signer) throw new Error("Signer not configured"); + + const { txHash, blockNumber } = await submitToFdcHub(encodedRequest, getFdcHubAddress(), provider, signer); + console.log(`[FDC] Submitted to FdcHub: tx=${txHash}, block=${blockNumber}`); + + // Step 3: Calculate voting round and fetch proof + const block = await provider.getBlock(blockNumber); + if (!block) throw new Error("Block not found"); + const votingRoundId = calculateVotingRoundId(block.timestamp); + console.log(`[FDC] Voting round: ${votingRoundId}. Waiting for proof...`); + + // Poll for proof (FDC rounds take ~90s, retry a few times) + let proof = null; + for (let attempt = 0; attempt < 5; attempt++) { + await new Promise(resolve => setTimeout(resolve, 30_000)); // wait 30s between attempts + try { + proof = await fetchProofFromDALayer(votingRoundId, encodedRequest); + if (proof.response) break; + } catch { + console.log(`[FDC] Proof not ready yet (attempt ${attempt + 1}/5)...`); + } + } + + if (!proof || !proof.response) { + return res.status(202).json({ + status: "pending", + message: "Attestation submitted but proof not yet available. Retry later.", + votingRoundId, + txHash + }); + } + + // Step 4: Verify on-chain via ResultAttestor + const result = await verifyAndStoreAttestation( + lotteryId, + proof.response, + proof.merkleProof, + winningNumbers + ); + console.log(`[FDC] Attestation verified on-chain: drawId=${result.drawId}, tx=${result.txHash}`); + + lottery.status = "Drawn"; + const draw = { + id: result.drawId, + lotteryId, + lotteryName: lottery.name, + winningNumbers, + bonusNumber: 0, + totalTickets: 0, + winners: [], + totalPaidOut: 0, + timestamp: Date.now(), + txHash: result.txHash, + source: "fdc" + }; + draws.push(draw); + + return res.json({ + status: "verified", + draw, + votingRoundId, + attestationTxHash: txHash, + verificationTxHash: result.txHash + }); + } catch (err: any) { + console.error("[FDC] Attestation failed:", err.message); + return res.status(500).json({ error: `Attestation failed: ${err.message}` }); + } +}); + +// Health check with blockchain status +app.get("/api/status", async (_, res) => { + const signerAddr = getSignerAddress(); + res.json({ + pool: isPoolConfigured(), + write: isWriteConfigured(), + drawManager: isDrawManagerConfigured(), + attestor: isAttestorConfigured(), + signerAddress: signerAddr, + mockFallback: true + }); +}); + +function seedData() { + if (lotteries.length === 0) { + lotteries.push({ + id: 1, + name: "Omaze Dream House", + ticketPriceUSD: 5, + jackpot: 50000, + nextDraw: Date.now() + 3600000, + charityPercent: 10, + usesFlareRNG: true, + organiser: "omaze", + organiserDisplayName: "Omaze", + description: "Omaze is a charitable fundraising platform that offers once-in-a-lifetime experiences and prizes while raising money for nonprofits worldwide. Every entry supports charities tackling housing, health, and education.", + website: "https://www.omaze.com", + premiumRate: 500, + status: "Open" + }); + lotteries.push({ + id: 2, + name: "Postcode Neighbourhood Draw", + ticketPriceUSD: 2, + jackpot: 10000, + nextDraw: Date.now() + 7200000, + charityPercent: 33, + usesFlareRNG: true, + organiser: "postcode_lottery", + organiserDisplayName: "People's Postcode Lottery", + description: "People's Postcode Lottery is one of the largest charity lotteries in Britain, raising over \u00a31 billion for thousands of good causes. Players win based on their postcode, and 33% of every ticket goes directly to charities fighting poverty, supporting communities, and protecting the environment.", + website: "https://www.postcodelottery.co.uk", + premiumRate: 750, + status: "Open" + }); + lotteries.push({ + id: 3, + name: "Comic Relief Big Draw", + ticketPriceUSD: 10, + jackpot: 100000, + nextDraw: Date.now() + 86400000, + charityPercent: 25, + usesFlareRNG: false, + organiser: "comic_relief", + organiserDisplayName: "Comic Relief", + description: "Comic Relief is a major UK charity that uses the power of entertainment to drive positive change. Since 1985, they have raised over \u00a31.5 billion to help people living tough lives across the UK and the world's poorest communities.", + website: "https://www.comicrelief.com", + premiumRate: 1000, + status: "Open" + }); + console.log("Seeded initial lottery data"); + } + + // Seed LP deposits + if (poolTransactions.length === 0) { + poolTransactions.push({ + id: poolTxCounter++, + type: "Deposit", + amount: 900000, + provider: "demo-lp-1", + timestamp: new Date(Date.now() - 86400000 * 7).toISOString(), + txHash: "0x1a2b3c...seed01" + }); + poolTransactions.push({ + id: poolTxCounter++, + type: "Deposit", + amount: 600000, + provider: "demo-lp-1", + timestamp: new Date(Date.now() - 86400000 * 3).toISOString(), + txHash: "0x4d5e6f...seed02" + }); + console.log("Seeded initial LP pool data"); + } +} + +// Start listeners +async function main() { + seedData(); + + console.log("\n" + "=".repeat(60)); + console.log("🎰 LottoLink Backend Starting..."); + console.log("=".repeat(60)); + console.log(`Mode: ${MOCK_MODE ? "🎭 MOCK MODE (no blockchain)" : "⛓️ BLOCKCHAIN MODE"}`); + console.log(`\nContract Addresses:`); + console.log(` Pool: ${POOL_ADDRESS || "❌ NOT SET (using mock)"}`); + console.log(` DrawManager: ${DRAW_MANAGER || "❌ NOT SET"}`); + console.log(` PayoutCalc: ${PAYOUT_CALC || "❌ NOT SET"}`); + console.log(` USDT: ${USDT || "❌ NOT SET"}`); + console.log(` Escrow: ${ESCROW || "❌ NOT SET"}`); + console.log("=".repeat(60) + "\n"); + + if (MOCK_MODE) { + console.log("🎭 MOCK_MODE enabled - skipping blockchain listeners"); + } else { + // Start blockchain listeners only if not in mock mode + if (DRAW_MANAGER && PAYOUT_CALC) { + startFlareListener( + DRAW_MANAGER, + PAYOUT_CALC, + (lotteryId, drawId) => { + console.log(`Draw ${drawId} completed for lottery ${lotteryId}`); + draws.push({ lotteryId: Number(lotteryId), drawId: Number(drawId), timestamp: Date.now() }); + }, + async (payout) => { + console.log(`Payout authorized: ${payout.winner} - ${payout.amount}`); + if (ESCROW_KEY) { + const id = await executeGaslessPayout(ESCROW_KEY, USDT, payout); + console.log(`Payout submitted: ${id}`); + } + } + ); + } + + if (ESCROW) { + startPlasmaListener(ESCROW, (player, lotteryId, numbers) => { + console.log(`Ticket purchased by ${player} for lottery ${lotteryId}`); + tickets.push({ player, lotteryId: Number(lotteryId), numbers: numbers.map(Number), timestamp: Date.now() }); + }); + } + + // Start pool listener if configured + if (POOL_ADDRESS) { + startPoolListener(POOL_ADDRESS, { + onLiquidityDeposited: (lp, usdtAmount, sharesIssued) => { + poolTransactions.push({ + id: poolTxCounter++, + type: "Deposit", + amount: Number(usdtAmount) / 1e6, + provider: lp, + timestamp: new Date().toISOString(), + txHash: `contract-${Date.now()}` + }); + }, + onLiquidityWithdrawn: (lp, sharesBurned, usdtReturned) => { + poolTransactions.push({ + id: poolTxCounter++, + type: "Withdraw", + amount: Number(usdtReturned) / 1e6, + provider: lp, + timestamp: new Date().toISOString(), + txHash: `contract-${Date.now()}` + }); + }, + onPremiumCollected: (operator, amount) => { + console.log(`Premium collected from operator ${operator}: ${Number(amount) / 1e6} USDT`); + }, + onPayoutExecuted: (winner, amount, lotteryId, drawId) => { + console.log(`Pool payout: ${winner} won ${Number(amount) / 1e6} USDT from lottery ${lotteryId}`); + } + }); + } + } + + // Global error handler - catch any unhandled errors + app.use((err: any, req: any, res: any, next: any) => { + console.error("Unhandled error:", err.message || err); + res.status(500).json({ error: err.message || "Internal server error" }); + }); + + const PORT = process.env.PORT || 4000; + app.listen(PORT, () => { + console.log("\n" + "=".repeat(60)); + console.log(`✅ LottoLink API running on port ${PORT}`); + console.log(` Mode: ${MOCK_MODE ? "🎭 MOCK MODE" : "⛓️ BLOCKCHAIN MODE"}`); + console.log(` Health check: http://localhost:${PORT}/api/pool/stats`); + console.log("=".repeat(60) + "\n"); + }); +} + +main().catch(console.error); diff --git a/backend/src/listeners/index.ts b/backend/src/listeners/index.ts index c978080..9c05d9b 100644 --- a/backend/src/listeners/index.ts +++ b/backend/src/listeners/index.ts @@ -1,31 +1,125 @@ import { ethers } from "ethers"; import { executeGaslessPayout, PayoutRequest } from "../services/plasma"; +import { POOL_ABI } from "../services/pool"; const FLARE_RPC = process.env.FLARE_RPC || "https://coston2-api.flare.network/ext/C/rpc"; -const PLASMA_RPC = process.env.PLASMA_RPC_URL || "https://rpc.plasma.to"; +const PLASMA_RPC = process.env.PLASMA_RPC_URL || "https://testnet-rpc.plasma.to"; const DRAW_MANAGER_ABI = ["event DrawCompleted(uint256 indexed lotteryId, uint256 indexed drawId, uint256[] winningNumbers, uint256 randomSeed, bool isSecureRandom)"]; const PAYOUT_CALC_ABI = ["event PayoutAuthorized(address indexed winner, uint256 amountUSDT, uint256 indexed lotteryId, uint256 indexed drawId, uint8 tier)"]; const ESCROW_ABI = ["event TicketPurchased(address indexed player, uint256 indexed lotteryId, uint256[] ticketNumbers, uint256 amount, uint256 timestamp)"]; -export async function startFlareListener(drawManagerAddr: string, payoutCalcAddr: string, onDraw: (lotteryId: bigint, drawId: bigint) => void, onPayout: (payout: PayoutRequest) => void) { - const provider = new ethers.WebSocketProvider(FLARE_RPC.replace("https://", "wss://").replace("/ext/C/rpc", "/ext/C/ws")); - const drawManager = new ethers.Contract(drawManagerAddr, DRAW_MANAGER_ABI, provider); - const payoutCalc = new ethers.Contract(payoutCalcAddr, PAYOUT_CALC_ABI, provider); +// ============ Pool Event Callbacks ============ - drawManager.on("DrawCompleted", (lotteryId, drawId) => onDraw(lotteryId, drawId)); - payoutCalc.on("PayoutAuthorized", (winner, amount, lotteryId, drawId, tier) => { - onPayout({ winner, amount, payoutId: `${drawId}-${winner}` }); - }); - console.log("Flare listener started"); +export interface PoolEventCallbacks { + onLiquidityDeposited?: (lp: string, usdtAmount: bigint, sharesIssued: bigint) => void; + onLiquidityWithdrawn?: (lp: string, sharesBurned: bigint, usdtReturned: bigint) => void; + onPremiumCollected?: (operator: string, amount: bigint) => void; + onPayoutExecuted?: (winner: string, amount: bigint, lotteryId: bigint, drawId: bigint) => void; + onOperatorRegistered?: (operator: string) => void; } -export async function startPlasmaListener(escrowAddr: string, onTicket: (player: string, lotteryId: bigint, numbers: bigint[]) => void) { - const provider = new ethers.WebSocketProvider(PLASMA_RPC.replace("https://", "wss://")); - const escrow = new ethers.Contract(escrowAddr, ESCROW_ABI, provider); +// ============ Flare Listeners ============ - escrow.on("TicketPurchased", (player, lotteryId, ticketNumbers) => { - onTicket(player, lotteryId, ticketNumbers); - }); - console.log("Plasma listener started"); +export async function startFlareListener( + drawManagerAddr: string, + payoutCalcAddr: string, + onDraw: (lotteryId: bigint, drawId: bigint) => void, + onPayout: (payout: PayoutRequest) => void +) { + try { + const wsUrl = FLARE_RPC.replace("https://", "wss://").replace("/ext/C/rpc", "/ext/C/ws"); + const provider = new ethers.WebSocketProvider(wsUrl); + + const drawManager = new ethers.Contract(drawManagerAddr, DRAW_MANAGER_ABI, provider); + const payoutCalc = new ethers.Contract(payoutCalcAddr, PAYOUT_CALC_ABI, provider); + + drawManager.on("DrawCompleted", (lotteryId, drawId, winningNumbers, randomSeed, isSecureRandom) => { + console.log(`[Flare] Draw ${drawId} completed for lottery ${lotteryId} - Numbers: ${winningNumbers.join(",")}`); + onDraw(lotteryId, drawId); + }); + + payoutCalc.on("PayoutAuthorized", (winner, amount, lotteryId, drawId, tier) => { + console.log(`[Flare] Payout authorized: ${winner} wins ${ethers.formatUnits(amount, 6)} USDT (Tier ${tier})`); + onPayout({ winner, amount, payoutId: `${drawId}-${winner}` }); + }); + + console.log("✓ Flare listener started for DrawManager and PayoutCalculator"); + } catch (error) { + console.error("Failed to start Flare listener:", error); + } +} + +// ============ Pool Listeners ============ + +export async function startPoolListener( + poolAddr: string, + callbacks: PoolEventCallbacks +) { + try { + const wsUrl = FLARE_RPC.replace("https://", "wss://").replace("/ext/C/rpc", "/ext/C/ws"); + const provider = new ethers.WebSocketProvider(wsUrl); + const pool = new ethers.Contract(poolAddr, POOL_ABI, provider); + + if (callbacks.onLiquidityDeposited) { + pool.on("LiquidityDeposited", (lp, usdtAmount, sharesIssued) => { + console.log(`[Pool] LP ${lp} deposited ${ethers.formatUnits(usdtAmount, 6)} USDT, received ${ethers.formatUnits(sharesIssued, 6)} shares`); + callbacks.onLiquidityDeposited!(lp, usdtAmount, sharesIssued); + }); + } + + if (callbacks.onLiquidityWithdrawn) { + pool.on("LiquidityWithdrawn", (lp, sharesBurned, usdtReturned) => { + console.log(`[Pool] LP ${lp} withdrew ${ethers.formatUnits(usdtReturned, 6)} USDT, burned ${ethers.formatUnits(sharesBurned, 6)} shares`); + callbacks.onLiquidityWithdrawn!(lp, sharesBurned, usdtReturned); + }); + } + + if (callbacks.onPremiumCollected) { + pool.on("PremiumCollected", (operator, amount) => { + console.log(`[Pool] Operator ${operator} paid ${ethers.formatUnits(amount, 6)} USDT premium`); + callbacks.onPremiumCollected!(operator, amount); + }); + } + + if (callbacks.onPayoutExecuted) { + pool.on("PayoutExecuted", (winner, amount, lotteryId, drawId) => { + console.log(`[Pool] Winner ${winner} received ${ethers.formatUnits(amount, 6)} USDT from lottery ${lotteryId} draw ${drawId}`); + callbacks.onPayoutExecuted!(winner, amount, lotteryId, drawId); + }); + } + + if (callbacks.onOperatorRegistered) { + pool.on("OperatorRegistered", (operator) => { + console.log(`[Pool] New operator registered: ${operator}`); + callbacks.onOperatorRegistered!(operator); + }); + } + + console.log("✓ Pool listener started for SharedLiquidityPool events"); + } catch (error) { + console.error("Failed to start Pool listener:", error); + } +} + +// ============ Plasma Listeners ============ + +export async function startPlasmaListener( + escrowAddr: string, + onTicket: (player: string, lotteryId: bigint, numbers: bigint[]) => void +) { + try { + const wsUrl = PLASMA_RPC.replace("https://", "wss://"); + const provider = new ethers.WebSocketProvider(wsUrl); + const escrow = new ethers.Contract(escrowAddr, ESCROW_ABI, provider); + + escrow.on("TicketPurchased", (player, lotteryId, ticketNumbers, amount, timestamp) => { + console.log(`[Plasma] Ticket purchased by ${player} for lottery ${lotteryId} - Numbers: ${ticketNumbers.join(",")}`); + onTicket(player, lotteryId, ticketNumbers); + }); + + console.log("✓ Plasma listener started for Escrow events"); + } catch (error) { + console.error("Failed to start Plasma listener:", error); + } } diff --git a/backend/src/services/plasma.ts b/backend/src/services/plasma.ts index 42ad9fc..e58c92f 100644 --- a/backend/src/services/plasma.ts +++ b/backend/src/services/plasma.ts @@ -1,6 +1,6 @@ import { ethers } from "ethers"; -const PLASMA_RPC = process.env.PLASMA_RPC_URL || "https://rpc.plasma.to"; +const PLASMA_RPC = process.env.PLASMA_RPC_URL || "https://testnet-rpc.plasma.to"; const RELAYER_API = "https://api.relayer.plasma.to/v1"; const RELAYER_API_KEY = process.env.PLASMA_RELAYER_API_KEY || ""; diff --git a/backend/src/services/pool.ts b/backend/src/services/pool.ts new file mode 100644 index 0000000..070c268 --- /dev/null +++ b/backend/src/services/pool.ts @@ -0,0 +1,506 @@ +import { ethers } from "ethers"; + +// Contract ABIs - minimal for read functions +const POOL_ABI = [ + // LP Functions + "function depositLiquidity(uint256 usdtAmount) external returns (uint256 shares)", + "function withdrawLiquidity(uint256 shareAmount) external returns (uint256 usdtAmount)", + "function getSharePrice() external view returns (uint256 price)", + + // Operator Functions + "function registerOperator(address operator) external", + "function collectPremium(uint256 amount) external", + "function executePayout(address winner, uint256 amount, uint256 lotteryId, uint256 drawId) external", + + // View Functions + "function getTVL() external view returns (uint256 tvl)", + "function getUtilization() external view returns (uint256 utilization)", + "function getLPPosition(address lp) external view returns (uint256 shares, uint256 usdtValue, uint256 depositTime)", + "function getLPYield(address lp) external view returns (uint256)", + "function getPoolStats() external view returns (uint256 tvl, uint256 lpCount, uint256 opCount, uint256 premiums, uint256 payouts)", + + // State Variables + "function isOperator(address) external view returns (bool)", + "function operatorCount() external view returns (uint256)", + "function totalPremiumsCollected() external view returns (uint256)", + "function totalPayoutsDistributed() external view returns (uint256)", + "function balanceOf(address) external view returns (uint256)", + "function totalSupply() external view returns (uint256)", + + // Events + "event LiquidityDeposited(address indexed lp, uint256 usdtAmount, uint256 sharesIssued)", + "event LiquidityWithdrawn(address indexed lp, uint256 sharesBurned, uint256 usdtReturned)", + "event PremiumCollected(address indexed operator, uint256 amount)", + "event PayoutExecuted(address indexed winner, uint256 amount, uint256 lotteryId, uint256 drawId)", + "event OperatorRegistered(address indexed operator)" +]; + +const REGISTRY_ABI = [ + "function lotteries(uint256) external view returns (address operator, string memory name, uint256 ticketPriceUSD, uint256 drawIntervalSec, uint256 lastDrawTimestamp, uint8 charityPercent, address charityAddress, uint8 numberRange, uint8 numbersPerTicket, bool usesFlareRNG, string memory externalApiUrl, string memory jqFilter, bool active)", + "function nextLotteryId() external view returns (uint256)", + "function nextTicketId() external view returns (uint256)", + "function getTicketNumbers(uint256 ticketId) external view returns (uint256[] memory)", + "function getDrawTickets(uint256 drawId) external view returns (uint256[] memory)", + "function getTicketPlayer(uint256 ticketId) external view returns (address)" +]; + +const ERC20_ABI = [ + "function approve(address spender, uint256 amount) external returns (bool)", + "function allowance(address owner, address spender) external view returns (uint256)", + "function balanceOf(address account) external view returns (uint256)" +]; + +const DRAW_MANAGER_ABI = [ + "function executeDraw(uint256 lotteryId) external returns (uint256 drawId)", + "function getWinningNumbers(uint256 drawId) external view returns (uint256[] memory)", + "function nextDrawId() external view returns (uint256)", + "function draws(uint256) external view returns (uint256 lotteryId, uint256 drawId, uint256 randomSeed, uint256 timestamp, bool isSecureRandom)", + "event DrawCompleted(uint256 indexed lotteryId, uint256 indexed drawId, uint256[] winningNumbers, uint256 randomSeed, bool isSecureRandom)" +]; + +const PAYOUT_CALC_ABI = [ + "function calculatePayouts(uint256 lotteryId, uint256 drawId, uint256 prizePoolUSD) external", + "event PayoutAuthorized(address indexed winner, uint256 amount, uint256 lotteryId, uint256 drawId)" +]; + +// Environment config +const MOCK_MODE = process.env.MOCK_MODE === "true" || process.env.MOCK_MODE === "1"; +const FLARE_RPC = process.env.FLARE_RPC || "https://coston2-api.flare.network/ext/C/rpc"; +const POOL_ADDRESS = process.env.POOL_ADDRESS || ""; +const REGISTRY_ADDRESS = process.env.REGISTRY_ADDRESS || ""; +const DRAW_MANAGER_ADDRESS = process.env.DRAW_MANAGER_ADDRESS || ""; +const PAYOUT_CALC_ADDRESS = process.env.PAYOUT_CALC_ADDRESS || ""; +const USDT_ADDRESS = process.env.USDT_ADDRESS || ""; +const BACKEND_PRIVATE_KEY = process.env.BACKEND_PRIVATE_KEY || ""; + +// Singleton instances +let provider: ethers.JsonRpcProvider | null = null; +let poolContract: ethers.Contract | null = null; +let registryContract: ethers.Contract | null = null; +let signer: ethers.Wallet | null = null; +let poolWriteContract: ethers.Contract | null = null; +let drawManagerContract: ethers.Contract | null = null; +let payoutCalcContract: ethers.Contract | null = null; +let usdtContract: ethers.Contract | null = null; + +/** + * Get or create the JSON RPC provider + */ +export function getProvider(): ethers.JsonRpcProvider { + if (!provider) { + provider = new ethers.JsonRpcProvider(FLARE_RPC); + } + return provider; +} + +/** + * Get SharedLiquidityPool contract instance + */ +export function getPoolContract(): ethers.Contract | null { + if (!POOL_ADDRESS) { + console.warn("POOL_ADDRESS not set - pool contract calls will fail"); + return null; + } + if (!poolContract) { + poolContract = new ethers.Contract(POOL_ADDRESS, POOL_ABI, getProvider()); + } + return poolContract; +} + +/** + * Get LotteryRegistry contract instance + */ +export function getRegistryContract(): ethers.Contract | null { + if (!REGISTRY_ADDRESS) { + console.warn("REGISTRY_ADDRESS not set - registry contract calls will fail"); + return null; + } + if (!registryContract) { + registryContract = new ethers.Contract(REGISTRY_ADDRESS, REGISTRY_ABI, getProvider()); + } + return registryContract; +} + +// ============ Pool Stats ============ + +export interface PoolStats { + tvl: string; + lpCount: number; + operatorCount: number; + totalPremiums: string; + totalPayouts: string; + utilization: number; + sharePrice: string; +} + +/** + * Get pool statistics from contract + */ +export async function getPoolStatsFromContract(): Promise { + const pool = getPoolContract(); + if (!pool) return null; + + try { + const [tvl, lpCount, opCount, premiums, payouts] = await pool.getPoolStats(); + const utilization = await pool.getUtilization(); + const sharePrice = await pool.getSharePrice(); + + return { + tvl: tvl.toString(), + lpCount: Number(lpCount), + operatorCount: Number(opCount), + totalPremiums: premiums.toString(), + totalPayouts: payouts.toString(), + utilization: Number(utilization) / 100, // Convert basis points to percentage + sharePrice: sharePrice.toString() + }; + } catch (error) { + console.error("Failed to get pool stats from contract:", error); + return null; + } +} + +// ============ LP Position ============ + +export interface LPPosition { + shares: string; + usdtValue: string; + claimableYield: string; + depositTime: number; + roi: number; +} + +/** + * Get LP position from contract + */ +export async function getLPPositionFromContract(lpAddress: string): Promise { + const pool = getPoolContract(); + if (!pool) return null; + + try { + const [shares, usdtValue, depositTime] = await pool.getLPPosition(lpAddress); + const claimableYield = await pool.getLPYield(lpAddress); + + // Calculate ROI: (current value - initial deposit equivalent) / initial deposit + const sharesNum = Number(shares); + const valueNum = Number(usdtValue); + const yieldNum = Number(claimableYield); + const initialDeposit = valueNum - yieldNum; + const roi = initialDeposit > 0 ? yieldNum / initialDeposit : 0; + + return { + shares: shares.toString(), + usdtValue: usdtValue.toString(), + claimableYield: claimableYield.toString(), + depositTime: Number(depositTime), + roi + }; + } catch (error) { + console.error("Failed to get LP position from contract:", error); + return null; + } +} + +// ============ Utility Functions ============ + +/** + * Check if an address is a registered operator + */ +export async function isOperator(address: string): Promise { + const pool = getPoolContract(); + if (!pool) return false; + + try { + return await pool.isOperator(address); + } catch (error) { + console.error("Failed to check operator status:", error); + return false; + } +} + +/** + * Get current TVL + */ +export async function getTVL(): Promise { + const pool = getPoolContract(); + if (!pool) return null; + + try { + return await pool.getTVL(); + } catch (error) { + console.error("Failed to get TVL from contract:", error); + return null; + } +} + +/** + * Get share price in USDT (18 decimals for precision) + */ +export async function getSharePrice(): Promise { + const pool = getPoolContract(); + if (!pool) return null; + + try { + return await pool.getSharePrice(); + } catch (error) { + console.error("Failed to get share price from contract:", error); + return null; + } +} + +/** + * Check if pool service is configured and available (returns false in MOCK_MODE) + */ +export function isPoolConfigured(): boolean { + if (MOCK_MODE) return false; + return !!POOL_ADDRESS; +} + +/** + * Check if registry service is configured and available + */ +export function isRegistryConfigured(): boolean { + return !!REGISTRY_ADDRESS; +} + +// ============ Signer & Write Contracts ============ + +export function getSigner(): ethers.Wallet | null { + if (!BACKEND_PRIVATE_KEY) return null; + if (!signer) { + signer = new ethers.Wallet(BACKEND_PRIVATE_KEY, getProvider()); + } + return signer; +} + +export function getSignerAddress(): string | null { + const s = getSigner(); + return s ? s.address : null; +} + +function getPoolWriteContract(): ethers.Contract | null { + if (!POOL_ADDRESS) return null; + const s = getSigner(); + if (!s) return null; + if (!poolWriteContract) { + poolWriteContract = new ethers.Contract(POOL_ADDRESS, POOL_ABI, s); + } + return poolWriteContract; +} + +function getUsdtContract(): ethers.Contract | null { + if (!USDT_ADDRESS) return null; + const s = getSigner(); + if (!s) return null; + if (!usdtContract) { + usdtContract = new ethers.Contract(USDT_ADDRESS, ERC20_ABI, s); + } + return usdtContract; +} + +function getDrawManagerContract(): ethers.Contract | null { + if (!DRAW_MANAGER_ADDRESS) return null; + const s = getSigner(); + if (!s) return null; + if (!drawManagerContract) { + drawManagerContract = new ethers.Contract(DRAW_MANAGER_ADDRESS, DRAW_MANAGER_ABI, s); + } + return drawManagerContract; +} + +function getPayoutCalcContract(): ethers.Contract | null { + if (!PAYOUT_CALC_ADDRESS) return null; + const s = getSigner(); + if (!s) return null; + if (!payoutCalcContract) { + payoutCalcContract = new ethers.Contract(PAYOUT_CALC_ADDRESS, PAYOUT_CALC_ABI, s); + } + return payoutCalcContract; +} + +export function isDrawManagerConfigured(): boolean { + if (MOCK_MODE) return false; + return !!DRAW_MANAGER_ADDRESS && !!BACKEND_PRIVATE_KEY; +} + +export function isPayoutCalcConfigured(): boolean { + return !!PAYOUT_CALC_ADDRESS && !!BACKEND_PRIVATE_KEY; +} + +export function isWriteConfigured(): boolean { + if (MOCK_MODE) return false; + return !!POOL_ADDRESS && !!BACKEND_PRIVATE_KEY && !!USDT_ADDRESS; +} + +// ============ USDT Approval ============ + +async function ensureUsdtApproval(spender: string, amount: bigint): Promise { + const usdt = getUsdtContract(); + const s = getSigner(); + if (!usdt || !s) return; + + const allowance: bigint = await usdt.allowance(s.address, spender); + if (allowance < amount) { + console.log(`Approving USDT spend for ${spender}...`); + const tx = await usdt.approve(spender, ethers.MaxUint256); + await tx.wait(); + console.log("USDT approved"); + } +} + +// ============ Write Operations ============ + +export async function depositLiquidityOnChain(amount: number): Promise<{ txHash: string; shares: string }> { + const pool = getPoolWriteContract(); + if (!pool) throw new Error("Pool write contract not configured"); + + const usdtAmount = ethers.parseUnits(amount.toString(), 6); + await ensureUsdtApproval(POOL_ADDRESS, usdtAmount); + + const tx = await pool.depositLiquidity(usdtAmount); + const receipt = await tx.wait(); + + // Parse LiquidityDeposited event for shares issued + let shares = "0"; + for (const log of receipt.logs) { + try { + const parsed = pool.interface.parseLog({ topics: log.topics as string[], data: log.data }); + if (parsed && parsed.name === "LiquidityDeposited") { + shares = parsed.args.sharesIssued.toString(); + } + } catch { } + } + + return { txHash: receipt.hash, shares }; +} + +export async function withdrawLiquidityOnChain(shares: number): Promise<{ txHash: string; usdtReturned: string }> { + const pool = getPoolWriteContract(); + if (!pool) throw new Error("Pool write contract not configured"); + + const shareAmount = ethers.parseUnits(shares.toString(), 6); + const tx = await pool.withdrawLiquidity(shareAmount); + const receipt = await tx.wait(); + + let usdtReturned = "0"; + for (const log of receipt.logs) { + try { + const parsed = pool.interface.parseLog({ topics: log.topics as string[], data: log.data }); + if (parsed && parsed.name === "LiquidityWithdrawn") { + usdtReturned = parsed.args.usdtReturned.toString(); + } + } catch { } + } + + return { txHash: receipt.hash, usdtReturned }; +} + +export async function collectPremiumOnChain(amount: number): Promise<{ txHash: string }> { + const pool = getPoolWriteContract(); + if (!pool) throw new Error("Pool write contract not configured"); + + const usdtAmount = ethers.parseUnits(amount.toString(), 6); + await ensureUsdtApproval(POOL_ADDRESS, usdtAmount); + + const tx = await pool.collectPremium(usdtAmount); + const receipt = await tx.wait(); + return { txHash: receipt.hash }; +} + +export async function executeDrawOnChain(lotteryId: number): Promise<{ txHash: string; drawId: number; winningNumbers: number[] }> { + const dm = getDrawManagerContract(); + if (!dm) throw new Error("DrawManager not configured"); + + const tx = await dm.executeDraw(lotteryId); + const receipt = await tx.wait(); + + let drawId = 0; + let winningNumbers: number[] = []; + for (const log of receipt.logs) { + try { + const parsed = dm.interface.parseLog({ topics: log.topics as string[], data: log.data }); + if (parsed && parsed.name === "DrawCompleted") { + drawId = Number(parsed.args.drawId); + winningNumbers = parsed.args.winningNumbers.map(Number); + } + } catch { } + } + + return { txHash: receipt.hash, drawId, winningNumbers }; +} + +export async function calculatePayoutsOnChain(lotteryId: number, drawId: number, prizePool: number): Promise<{ txHash: string }> { + const pc = getPayoutCalcContract(); + if (!pc) throw new Error("PayoutCalculator not configured"); + + const prizePoolUSD = ethers.parseUnits(prizePool.toString(), 6); + const tx = await pc.calculatePayouts(lotteryId, drawId, prizePoolUSD); + const receipt = await tx.wait(); + return { txHash: receipt.hash }; +} + +// ============ ResultAttestor ============ + +const RESULT_ATTESTOR_ABI = [ + "function verifyAndStore(uint256 lotteryId, bytes calldata attestationResponse, bytes32[] calldata merkleProof, uint256[] calldata winningNumbers) external returns (uint256 drawId)", + "function getWinningNumbers(uint256 drawId) external view returns (uint256[] memory)", + "function nextDrawId() external view returns (uint256)", + "event ExternalDrawVerified(uint256 indexed lotteryId, uint256 indexed drawId, uint256[] winningNumbers, string sourceUrl)" +]; + +const RESULT_ATTESTOR_ADDRESS = process.env.RESULT_ATTESTOR_ADDRESS || ""; +const FDC_HUB_ADDRESS = process.env.FDC_HUB_ADDRESS || "0x56AA107AA6E3DCf726D79e4aFa3d25b5B83720ff"; // Coston2 FdcHub + +let resultAttestorContract: ethers.Contract | null = null; + +function getResultAttestorContract(): ethers.Contract | null { + if (!RESULT_ATTESTOR_ADDRESS) return null; + const s = getSigner(); + if (!s) return null; + if (!resultAttestorContract) { + resultAttestorContract = new ethers.Contract(RESULT_ATTESTOR_ADDRESS, RESULT_ATTESTOR_ABI, s); + } + return resultAttestorContract; +} + +export function isAttestorConfigured(): boolean { + if (MOCK_MODE) return false; + return !!RESULT_ATTESTOR_ADDRESS && !!BACKEND_PRIVATE_KEY; +} + +export function getFdcHubAddress(): string { + return FDC_HUB_ADDRESS; +} + +export async function verifyAndStoreAttestation( + lotteryId: number, + attestationResponse: string, + merkleProof: string[], + winningNumbers: number[] +): Promise<{ txHash: string; drawId: number }> { + const attestor = getResultAttestorContract(); + if (!attestor) throw new Error("ResultAttestor not configured"); + + const tx = await attestor.verifyAndStore( + lotteryId, + attestationResponse, + merkleProof, + winningNumbers + ); + const receipt = await tx.wait(); + + let drawId = 0; + for (const log of receipt.logs) { + try { + const parsed = attestor.interface.parseLog({ topics: log.topics as string[], data: log.data }); + if (parsed && parsed.name === "ExternalDrawVerified") { + drawId = Number(parsed.args.drawId); + } + } catch { } + } + + return { txHash: receipt.hash, drawId }; +} + +// Export ABIs for use in listeners +export { POOL_ABI, REGISTRY_ABI, DRAW_MANAGER_ABI, RESULT_ATTESTOR_ABI }; diff --git a/backend/tsconfig.json b/backend/tsconfig.json index 048d528..20c8656 100644 --- a/backend/tsconfig.json +++ b/backend/tsconfig.json @@ -18,5 +18,9 @@ }, "include": [ "src/**/*" + ], + "exclude": [ + "src/__tests__/**/*", + "**/*.test.ts" ] } \ No newline at end of file diff --git a/contracts/.env b/contracts/.env new file mode 100644 index 0000000..46ba318 --- /dev/null +++ b/contracts/.env @@ -0,0 +1,4 @@ +PRIVATE_KEY=0x30cc37df21c1dd9da4f61e0baa4ad62f0da7a34a6f1fbb504e2a7d675c354e23 +PLASMA_RPC_URL=https://testnet-rpc.plasma.to + +MOCK_MODE=false diff --git a/contracts/.env.example b/contracts/.env.example new file mode 100644 index 0000000..26a839f --- /dev/null +++ b/contracts/.env.example @@ -0,0 +1,12 @@ +# ================================================ +# LottoLink Contracts Environment Configuration +# ================================================ + +# Deployer private key (DO NOT commit real keys!) +PRIVATE_KEY=0x_YOUR_PRIVATE_KEY_HERE + +# Plasma Network RPC +PLASMA_RPC_URL=https://testnet-rpc.plasma.to + +# Mock mode for testing (set to true for local development) +MOCK_MODE=false diff --git a/contracts/flare/DrawManager.sol b/contracts/flare/DrawManager.sol index 3c9b0e4..1be36cc 100644 --- a/contracts/flare/DrawManager.sol +++ b/contracts/flare/DrawManager.sol @@ -20,7 +20,13 @@ contract DrawManager { mapping(uint256 => DrawResult) public draws; mapping(uint256 => uint256) public lastLotteryDraw; // lotteryId => drawId - event DrawCompleted(uint256 indexed lotteryId, uint256 indexed drawId, uint256[] winningNumbers, uint256 randomSeed, bool isSecureRandom); + event DrawCompleted( + uint256 indexed lotteryId, + uint256 indexed drawId, + uint256[] winningNumbers, + uint256 randomSeed, + bool isSecureRandom + ); constructor(address _rng, address _registry) { rng = IRandomNumberV2(_rng); @@ -28,14 +34,34 @@ contract DrawManager { } function executeDraw(uint256 lotteryId) external returns (uint256 drawId) { - (address operator, , , , , , , uint8 numberRange, uint8 numbersPerTicket, bool usesFlareRNG, , , bool active) = registry.lotteries(lotteryId); + ( + address operator, + , + , + , + , + , + , + uint8 numberRange, + uint8 numbersPerTicket, + bool usesFlareRNG, + , + , + , + bool active + ) = registry.lotteries(lotteryId); require(active, "Lottery not active"); require(usesFlareRNG, "Use FDC for external lotteries"); + require(numbersPerTicket <= numberRange, "numbersPerTicket exceeds numberRange"); (uint256 randomNumber, bool isSecure, ) = rng.getRandomNumber(); require(isSecure, "RNG not secure"); - uint256[] memory winning = _deriveNumbers(randomNumber, numberRange, numbersPerTicket); + uint256[] memory winning = _deriveNumbers( + randomNumber, + numberRange, + numbersPerTicket + ); drawId = nextDrawId++; draws[drawId] = DrawResult({ lotteryId: lotteryId, @@ -49,23 +75,36 @@ contract DrawManager { emit DrawCompleted(lotteryId, drawId, winning, randomNumber, isSecure); } - function _deriveNumbers(uint256 seed, uint8 range, uint8 count) internal pure returns (uint256[] memory) { + function _deriveNumbers( + uint256 seed, + uint8 range, + uint8 count + ) internal pure returns (uint256[] memory) { uint256[] memory nums = new uint256[](count); uint256 found = 0; uint256 nonce = 0; while (found < count) { - uint256 candidate = (uint256(keccak256(abi.encodePacked(seed, nonce))) % range) + 1; + uint256 candidate = (uint256( + keccak256(abi.encodePacked(seed, nonce)) + ) % range) + 1; bool dup = false; for (uint256 i = 0; i < found; i++) { - if (nums[i] == candidate) { dup = true; break; } + if (nums[i] == candidate) { + dup = true; + break; + } + } + if (!dup) { + nums[found++] = candidate; } - if (!dup) { nums[found++] = candidate; } nonce++; } return nums; } - function getWinningNumbers(uint256 drawId) external view returns (uint256[] memory) { + function getWinningNumbers( + uint256 drawId + ) external view returns (uint256[] memory) { return draws[drawId].winningNumbers; } } diff --git a/contracts/flare/LotteryRegistry.sol b/contracts/flare/LotteryRegistry.sol index f1a6fd7..2501a06 100644 --- a/contracts/flare/LotteryRegistry.sol +++ b/contracts/flare/LotteryRegistry.sol @@ -1,6 +1,10 @@ // SPDX-License-Identifier: MIT pragma solidity ^0.8.25; +/** + * @title LotteryRegistry + * @notice Registry for lotteries and tickets with premium rate for CaaS integration + */ contract LotteryRegistry { struct Lottery { address operator; @@ -15,6 +19,7 @@ contract LotteryRegistry { bool usesFlareRNG; string externalApiUrl; string jqFilter; + uint256 premiumRate; // Basis points (e.g., 500 = 5% of ticket sales paid to pool) bool active; } @@ -32,9 +37,25 @@ contract LotteryRegistry { mapping(uint256 => Ticket) public tickets; mapping(uint256 => uint256[]) public drawTickets; // drawId => ticketIds - event LotteryRegistered(uint256 indexed lotteryId, address indexed operator, string name); + event LotteryRegistered(uint256 indexed lotteryId, address indexed operator, string name, uint256 premiumRate); event TicketPurchased(uint256 indexed ticketId, uint256 indexed lotteryId, address indexed player, uint256[] numbers); + event LotteryDeactivated(uint256 indexed lotteryId); + event PremiumRateUpdated(uint256 indexed lotteryId, uint256 oldRate, uint256 newRate); + /** + * @notice Register a new lottery + * @param name Lottery name + * @param ticketPriceUSD Price per ticket in USD (6 decimals) + * @param drawIntervalSec Interval between draws in seconds + * @param charityPercent Percentage of prize pool going to charity + * @param charityAddress Address to receive charity allocation + * @param numberRange Maximum number value (e.g., 49 for pick 6 from 1-49) + * @param numbersPerTicket How many numbers per ticket + * @param usesFlareRNG Whether to use Flare's secure RNG + * @param externalApiUrl External API for off-chain lottery results + * @param jqFilter JQ filter for parsing external API response + * @param premiumRate Premium rate in basis points (500 = 5%) + */ function registerLottery( string calldata name, uint256 ticketPriceUSD, @@ -45,8 +66,14 @@ contract LotteryRegistry { uint8 numbersPerTicket, bool usesFlareRNG, string calldata externalApiUrl, - string calldata jqFilter + string calldata jqFilter, + uint256 premiumRate ) external returns (uint256 lotteryId) { + require(premiumRate <= 2000, "Premium rate cannot exceed 20%"); + require(charityPercent <= 50, "Charity percent cannot exceed 50%"); + require(numbersPerTicket <= numberRange, "numbersPerTicket exceeds numberRange"); + require(numberRange > 0, "numberRange must be > 0"); + lotteryId = nextLotteryId++; lotteries[lotteryId] = Lottery({ operator: msg.sender, @@ -61,22 +88,57 @@ contract LotteryRegistry { usesFlareRNG: usesFlareRNG, externalApiUrl: externalApiUrl, jqFilter: jqFilter, + premiumRate: premiumRate, active: true }); - emit LotteryRegistered(lotteryId, msg.sender, name); + emit LotteryRegistered(lotteryId, msg.sender, name, premiumRate); } + /** + * @notice Register a ticket for a lottery draw + */ function registerTicket(uint256 lotteryId, address player, uint256[] calldata numbers, uint256 drawId) external returns (uint256 ticketId) { require(lotteries[lotteryId].active, "Lottery not active"); + require(numbers.length == lotteries[lotteryId].numbersPerTicket, "Invalid number count"); + ticketId = nextTicketId++; tickets[ticketId] = Ticket({ player: player, lotteryId: lotteryId, numbers: numbers, drawId: drawId, claimed: false }); drawTickets[drawId].push(ticketId); emit TicketPurchased(ticketId, lotteryId, player, numbers); } + /** + * @notice Deactivate a lottery (operator only) + */ function deactivateLottery(uint256 lotteryId) external { require(lotteries[lotteryId].operator == msg.sender, "Not operator"); lotteries[lotteryId].active = false; + emit LotteryDeactivated(lotteryId); + } + + /** + * @notice Update premium rate (operator only) + */ + function updatePremiumRate(uint256 lotteryId, uint256 newRate) external { + require(lotteries[lotteryId].operator == msg.sender, "Not operator"); + require(newRate <= 2000, "Premium rate cannot exceed 20%"); + uint256 oldRate = lotteries[lotteryId].premiumRate; + lotteries[lotteryId].premiumRate = newRate; + emit PremiumRateUpdated(lotteryId, oldRate, newRate); + } + + /** + * @notice Get premium rate for a lottery + */ + function getPremiumRate(uint256 lotteryId) external view returns (uint256) { + return lotteries[lotteryId].premiumRate; + } + + /** + * @notice Calculate premium amount for a given ticket revenue + */ + function calculatePremium(uint256 lotteryId, uint256 ticketRevenue) external view returns (uint256) { + return (ticketRevenue * lotteries[lotteryId].premiumRate) / 10000; } function getTicketNumbers(uint256 ticketId) external view returns (uint256[] memory) { diff --git a/contracts/flare/MockUSDT.sol b/contracts/flare/MockUSDT.sol new file mode 100644 index 0000000..88927e4 --- /dev/null +++ b/contracts/flare/MockUSDT.sol @@ -0,0 +1,20 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; + +/** + * @title MockUSDT + * @notice Mock USDT token for testing purposes + */ +contract MockUSDT is ERC20 { + constructor() ERC20("Mock USDT", "USDT") {} + + function decimals() public pure override returns (uint8) { + return 6; + } + + function mint(address to, uint256 amount) external { + _mint(to, amount); + } +} diff --git a/contracts/flare/PayoutCalculator.sol b/contracts/flare/PayoutCalculator.sol index dac5665..497d862 100644 --- a/contracts/flare/PayoutCalculator.sol +++ b/contracts/flare/PayoutCalculator.sol @@ -4,7 +4,13 @@ pragma solidity ^0.8.25; import "./IFtsoV2.sol"; import "./DrawManager.sol"; import "./LotteryRegistry.sol"; +import "./SharedLiquidityPool.sol"; +/** + * @title PayoutCalculator + * @notice Calculates and executes payouts from the shared liquidity pool + * @dev Integrates with SharedLiquidityPool for payout execution + */ contract PayoutCalculator { struct Payout { address winner; @@ -17,67 +23,194 @@ contract PayoutCalculator { IFtsoV2 public immutable ftso; DrawManager public immutable drawManager; LotteryRegistry public immutable registry; + SharedLiquidityPool public immutable pool; // Prize tiers: 6 match = 50%, 5 match = 20%, 4 match = 15%, 3 match = 10%, charity = 5% uint8[4] public tierPercents = [50, 20, 15, 10]; - bytes21 public constant USDT_USD_FEED = hex"01555344542f555344000000000000000000000000"; // USDT/USD feed ID + bytes21 public constant USDT_USD_FEED = + hex"01555344542f555344000000000000000000000000"; // USDT/USD feed ID - event PayoutAuthorized(address indexed winner, uint256 amountUSDT, uint256 indexed lotteryId, uint256 indexed drawId, uint8 tier); - event CharityAllocated(address indexed charity, uint256 amountUSDT, uint256 indexed lotteryId); + event PayoutAuthorized( + address indexed winner, + uint256 amountUSDT, + uint256 indexed lotteryId, + uint256 indexed drawId, + uint8 tier + ); + event PayoutExecuted( + address indexed winner, + uint256 amountUSDT, + uint256 indexed lotteryId, + uint256 indexed drawId + ); + event CharityAllocated( + address indexed charity, + uint256 amountUSDT, + uint256 indexed lotteryId + ); - constructor(address _ftso, address _drawManager, address _registry) { + constructor( + address _ftso, + address _drawManager, + address _registry, + address _pool + ) { ftso = IFtsoV2(_ftso); drawManager = DrawManager(_drawManager); registry = LotteryRegistry(_registry); + pool = SharedLiquidityPool(_pool); } - function calculatePayouts(uint256 lotteryId, uint256 drawId, uint256 prizePoolUSD) external returns (Payout[] memory) { + /** + * @notice Calculate payouts and execute them from the shared liquidity pool + * @dev Fetches live USDT/USD price from FTSO to convert USD prize pool to exact USDT amount + * @param lotteryId ID of the lottery + * @param drawId ID of the draw + * @param prizePoolUSD Total prize pool in USD (6 decimals, e.g. 10000e6 = $10,000) + * @return payouts Array of payout structures + */ + function calculatePayouts( + uint256 lotteryId, + uint256 drawId, + uint256 prizePoolUSD + ) external returns (Payout[] memory) { + // Fetch live USDT/USD price from Flare FTSO + // price has `decimals` decimal places, e.g. price=99950000, decimals=8 means $0.9995 + uint256 prizePoolUSDT = _convertUsdToUsdt(prizePoolUSD); + uint256[] memory winning = drawManager.getWinningNumbers(drawId); uint256[] memory ticketIds = registry.getDrawTickets(drawId); - (, , , , , uint8 charityPercent, address charityAddress, , uint8 numbersPerTicket, , , , ) = registry.lotteries(lotteryId); + ( + , + , + , + , + , + uint8 charityPercent, + address charityAddress, + , + uint8 numbersPerTicket, + , + , + , + , + + ) = registry.lotteries(lotteryId); + + // Deduct charity first, then apply tier percentages to the remainder + uint256 charityAmount = (prizePoolUSDT * charityPercent) / 100; + uint256 tierPrizePool = prizePoolUSDT - charityAmount; // Count winners per tier uint256[4] memory tierWinners; for (uint256 i = 0; i < ticketIds.length; i++) { - uint8 matches = _countMatches(registry.getTicketNumbers(ticketIds[i]), winning); + uint8 matches = _countMatches( + registry.getTicketNumbers(ticketIds[i]), + winning + ); if (matches >= 3) { uint8 tier = numbersPerTicket - matches; if (tier < 4) tierWinners[tier]++; } } - // Build payouts + // Build payouts (tiers applied to prize pool after charity deduction) Payout[] memory payouts = new Payout[](ticketIds.length); uint256 payoutCount = 0; for (uint256 i = 0; i < ticketIds.length; i++) { address player = registry.getTicketPlayer(ticketIds[i]); - uint8 matches = _countMatches(registry.getTicketNumbers(ticketIds[i]), winning); + uint8 matches = _countMatches( + registry.getTicketNumbers(ticketIds[i]), + winning + ); if (matches >= 3) { uint8 tier = numbersPerTicket - matches; if (tier < 4 && tierWinners[tier] > 0) { - uint256 tierPool = (prizePoolUSD * tierPercents[tier]) / 100; + uint256 tierPool = (tierPrizePool * tierPercents[tier]) / + 100; uint256 amount = tierPool / tierWinners[tier]; - payouts[payoutCount++] = Payout({ winner: player, amountUSDT: amount, lotteryId: lotteryId, drawId: drawId, tier: tier }); - emit PayoutAuthorized(player, amount, lotteryId, drawId, tier); + payouts[payoutCount++] = Payout({ + winner: player, + amountUSDT: amount, + lotteryId: lotteryId, + drawId: drawId, + tier: tier + }); + emit PayoutAuthorized( + player, + amount, + lotteryId, + drawId, + tier + ); } } } - // Charity allocation - uint256 charityAmount = (prizePoolUSD * charityPercent) / 100; + // Execute payouts from pool + for (uint256 i = 0; i < payoutCount; i++) { + pool.executePayout( + payouts[i].winner, + payouts[i].amountUSDT, + lotteryId, + drawId + ); + emit PayoutExecuted( + payouts[i].winner, + payouts[i].amountUSDT, + lotteryId, + drawId + ); + } + + // Charity allocation (also from pool) if (charityAddress != address(0) && charityAmount > 0) { + pool.executePayout( + charityAddress, + charityAmount, + lotteryId, + drawId + ); emit CharityAllocated(charityAddress, charityAmount, lotteryId); } // Trim array - assembly { mstore(payouts, payoutCount) } + assembly { + mstore(payouts, payoutCount) + } return payouts; } - function _countMatches(uint256[] memory ticket, uint256[] memory winning) internal pure returns (uint8 count) { + /** + * @notice Convert a USD amount to USDT using live FTSO price feed + * @dev If FTSO call fails (e.g. on local testnet), assumes 1:1 peg + * @param usdAmount Amount in USD with 6 decimals + * @return usdtAmount Adjusted amount in USDT with 6 decimals + */ + function _convertUsdToUsdt(uint256 usdAmount) internal view returns (uint256 usdtAmount) { + try ftso.getFeedById(USDT_USD_FEED) returns (uint256 price, int8 decimals, uint64) { + // price represents USDT/USD, e.g. 0.9995 USD per 1 USDT + // To convert USD→USDT: usdtAmount = usdAmount / (price / 10^decimals) + // = usdAmount * 10^decimals / price + if (price == 0) return usdAmount; // safety fallback + uint256 scale = 10 ** uint8(decimals >= 0 ? decimals : -decimals); + usdtAmount = (usdAmount * scale) / price; + } catch { + // If FTSO is unavailable (local dev, mock), assume 1:1 USD/USDT + usdtAmount = usdAmount; + } + } + + function _countMatches( + uint256[] memory ticket, + uint256[] memory winning + ) internal pure returns (uint8 count) { for (uint256 i = 0; i < ticket.length; i++) { for (uint256 j = 0; j < winning.length; j++) { - if (ticket[i] == winning[j]) { count++; break; } + if (ticket[i] == winning[j]) { + count++; + break; + } } } } diff --git a/contracts/flare/ResultAttestor.sol b/contracts/flare/ResultAttestor.sol index 77cad4c..5a359f5 100644 --- a/contracts/flare/ResultAttestor.sol +++ b/contracts/flare/ResultAttestor.sol @@ -18,7 +18,12 @@ contract ResultAttestor { uint256 public nextDrawId; mapping(uint256 => ExternalDrawResult) public externalDraws; - event ExternalDrawVerified(uint256 indexed lotteryId, uint256 indexed drawId, uint256[] winningNumbers, string sourceUrl); + event ExternalDrawVerified( + uint256 indexed lotteryId, + uint256 indexed drawId, + uint256[] winningNumbers, + string sourceUrl + ); constructor(address _fdcVerification, address _registry) { fdcVerification = IFdcVerification(_fdcVerification); @@ -31,10 +36,28 @@ contract ResultAttestor { bytes32[] calldata merkleProof, uint256[] calldata winningNumbers ) external returns (uint256 drawId) { - (, , , , , , , , , bool usesFlareRNG, string memory externalApiUrl, , bool active) = registry.lotteries(lotteryId); + ( + , + , + , + , + , + , + , + , + , + bool usesFlareRNG, + string memory externalApiUrl, + , + , + bool active + ) = registry.lotteries(lotteryId); require(active, "Lottery not active"); require(!usesFlareRNG, "Use DrawManager for Flare RNG lotteries"); - require(fdcVerification.verifyJsonApi(attestationResponse, merkleProof), "Invalid attestation"); + require( + fdcVerification.verifyJsonApi(attestationResponse, merkleProof), + "Invalid attestation" + ); drawId = nextDrawId++; externalDraws[drawId] = ExternalDrawResult({ @@ -44,10 +67,17 @@ contract ResultAttestor { sourceUrl: externalApiUrl, timestamp: block.timestamp }); - emit ExternalDrawVerified(lotteryId, drawId, winningNumbers, externalApiUrl); + emit ExternalDrawVerified( + lotteryId, + drawId, + winningNumbers, + externalApiUrl + ); } - function getWinningNumbers(uint256 drawId) external view returns (uint256[] memory) { + function getWinningNumbers( + uint256 drawId + ) external view returns (uint256[] memory) { return externalDraws[drawId].winningNumbers; } } diff --git a/contracts/flare/SharedLiquidityPool.sol b/contracts/flare/SharedLiquidityPool.sol new file mode 100644 index 0000000..12fa498 --- /dev/null +++ b/contracts/flare/SharedLiquidityPool.sol @@ -0,0 +1,251 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.25; + +import "@openzeppelin/contracts/token/ERC20/IERC20.sol"; +import "@openzeppelin/contracts/token/ERC20/ERC20.sol"; +import "@openzeppelin/contracts/access/Ownable.sol"; +import "@openzeppelin/contracts/utils/ReentrancyGuard.sol"; + +/** + * @title SharedLiquidityPool + * @notice CaaS-style shared liquidity pool for lottery operators + * @dev LPs deposit USDT and receive LP shares. Operators pay premiums and pool pays winners. + */ +contract SharedLiquidityPool is ERC20, Ownable, ReentrancyGuard { + IERC20 public immutable usdt; + + // Pool stats + uint256 public totalPremiumsCollected; + uint256 public totalPayoutsDistributed; + uint256 public operatorCount; + + // Operator tracking + mapping(address => bool) public isOperator; + mapping(address => uint256) public operatorPremiumsPaid; + + // Authorized payout callers (e.g. PayoutCalculator) + mapping(address => bool) public isPayoutAuthorized; + + // LP tracking + mapping(address => uint256) public lpDepositTimestamp; + + // Events + event LiquidityDeposited(address indexed lp, uint256 usdtAmount, uint256 sharesIssued); + event LiquidityWithdrawn(address indexed lp, uint256 sharesBurned, uint256 usdtReturned); + event PremiumCollected(address indexed operator, uint256 amount); + event PayoutExecuted(address indexed winner, uint256 amount, uint256 lotteryId, uint256 drawId); + event OperatorRegistered(address indexed operator); + + constructor(address _usdt) ERC20("LottoLink LP Share", "llUSDT") Ownable(msg.sender) { + usdt = IERC20(_usdt); + } + + /** + * @notice Authorize an address to call executePayout (e.g. PayoutCalculator) + */ + function setPayoutAuthorized(address addr, bool authorized) external onlyOwner { + isPayoutAuthorized[addr] = authorized; + } + + // ============ LP Functions ============ + + /** + * @notice Deposit USDT into the pool and receive LP shares + * @param usdtAmount Amount of USDT to deposit + * @return shares Number of LP shares minted + */ + function depositLiquidity(uint256 usdtAmount) external nonReentrant returns (uint256 shares) { + require(usdtAmount > 0, "Amount must be > 0"); + + // Snapshot TVL before transfer so new deposit doesn't dilute share calculation + uint256 totalUSDT = getTVL(); + uint256 totalShares = totalSupply(); + + require(usdt.transferFrom(msg.sender, address(this), usdtAmount), "Transfer failed"); + + if (totalShares == 0 || totalUSDT == 0) { + shares = usdtAmount; // 1:1 for first deposit + } else { + shares = (usdtAmount * totalShares) / totalUSDT; + } + + _mint(msg.sender, shares); + if (lpDepositTimestamp[msg.sender] == 0) { + lpDepositTimestamp[msg.sender] = block.timestamp; + } + + emit LiquidityDeposited(msg.sender, usdtAmount, shares); + } + + /** + * @notice Withdraw USDT by burning LP shares + * @param shareAmount Number of LP shares to burn + * @return usdtAmount Amount of USDT returned + */ + function withdrawLiquidity(uint256 shareAmount) external nonReentrant returns (uint256 usdtAmount) { + require(shareAmount > 0, "Shares must be > 0"); + require(balanceOf(msg.sender) >= shareAmount, "Insufficient shares"); + + uint256 totalUSDT = getTVL(); + uint256 totalShares = totalSupply(); + + // Calculate USDT value of shares + usdtAmount = (shareAmount * totalUSDT) / totalShares; + require(usdtAmount > 0, "Withdrawal too small"); + require(usdt.balanceOf(address(this)) >= usdtAmount, "Insufficient pool liquidity"); + + _burn(msg.sender, shareAmount); + require(usdt.transfer(msg.sender, usdtAmount), "Transfer failed"); + + emit LiquidityWithdrawn(msg.sender, shareAmount, usdtAmount); + } + + /** + * @notice Get current price of 1 LP share in USDT, scaled by 1e18 for precision + * @return price Price of 1 share (multiply by share balance, divide by 1e18 to get USDT amount) + */ + function getSharePrice() external view returns (uint256 price) { + uint256 totalShares = totalSupply(); + if (totalShares == 0) { + return 1e18; // 1:1 ratio when pool is empty (same scale as formula below) + } + return (getTVL() * 1e18) / totalShares; + } + + // ============ Operator Functions ============ + + /** + * @notice Register as an operator (simplified - owner can add operators) + * @param operator Address of the operator to register + */ + function registerOperator(address operator) external onlyOwner { + require(!isOperator[operator], "Already registered"); + isOperator[operator] = true; + operatorCount++; + emit OperatorRegistered(operator); + } + + /** + * @notice Operator pays premium to pool (% of ticket sales) + * @param amount Premium amount in USDT + */ + function collectPremium(uint256 amount) external nonReentrant { + require(isOperator[msg.sender], "Not a registered operator"); + require(amount > 0, "Amount must be > 0"); + require(usdt.transferFrom(msg.sender, address(this), amount), "Transfer failed"); + + operatorPremiumsPaid[msg.sender] += amount; + totalPremiumsCollected += amount; + + emit PremiumCollected(msg.sender, amount); + } + + /** + * @notice Execute payout to winner from pool (called by authorized payout contract) + * @param winner Address of the winner + * @param amount Payout amount in USDT + * @param lotteryId ID of the lottery + * @param drawId ID of the draw + */ + function executePayout( + address winner, + uint256 amount, + uint256 lotteryId, + uint256 drawId + ) external nonReentrant { + require(msg.sender == owner() || isPayoutAuthorized[msg.sender], "Not authorized for payouts"); + require(winner != address(0), "Invalid winner"); + require(amount > 0, "Amount must be > 0"); + require(usdt.balanceOf(address(this)) >= amount, "Insufficient pool liquidity"); + + totalPayoutsDistributed += amount; + require(usdt.transfer(winner, amount), "Payout transfer failed"); + + emit PayoutExecuted(winner, amount, lotteryId, drawId); + } + + // ============ View Functions ============ + + /** + * @notice Get total value locked in pool (USDT balance) + * @return tvl Total USDT in pool + */ + function getTVL() public view returns (uint256 tvl) { + return usdt.balanceOf(address(this)); + } + + /** + * @notice Get pool utilization (payouts vs TVL) + * @return utilization Percentage of pool backing active lotteries (basis points, 10000 = 100%) + */ + function getUtilization() external view returns (uint256 utilization) { + uint256 tvl = getTVL(); + if (tvl == 0) return 0; + // Simplified: utilization = total payouts / TVL (capped at 100%) + uint256 ratio = (totalPayoutsDistributed * 10000) / tvl; + return ratio > 10000 ? 10000 : ratio; + } + + /** + * @notice Get LP position details + * @param lp Address of liquidity provider + * @return shares LP's share balance + * @return usdtValue Current USDT value of shares + * @return depositTime Timestamp of deposit + */ + function getLPPosition(address lp) external view returns ( + uint256 shares, + uint256 usdtValue, + uint256 depositTime + ) { + shares = balanceOf(lp); + uint256 totalShares = totalSupply(); + if (totalShares > 0) { + usdtValue = (shares * getTVL()) / totalShares; + } + depositTime = lpDepositTimestamp[lp]; + } + + /** + * @notice Get LP yield (profit from premiums) + * @param lp Address of liquidity provider + * @return yield Estimated yield earned (current value - initial deposit equivalent) + */ + function getLPYield(address lp) external view returns (uint256) { + uint256 shares = balanceOf(lp); + if (shares == 0) return 0; + + uint256 totalShares = totalSupply(); + uint256 currentValue = (shares * getTVL()) / totalShares; + + // Simplified: yield = proportional share of total premiums collected + uint256 lpShare = (shares * 1e18) / totalShares; + uint256 yieldFromPremiums = (totalPremiumsCollected * lpShare) / 1e18; + + return yieldFromPremiums; + } + + /** + * @notice Get pool statistics + * @return tvl Total value locked + * @return lpCount Number of LPs (approximated by non-zero balances - simplified) + * @return opCount Number of registered operators + * @return premiums Total premiums collected + * @return payouts Total payouts distributed + */ + function getPoolStats() external view returns ( + uint256 tvl, + uint256 lpCount, + uint256 opCount, + uint256 premiums, + uint256 payouts + ) { + return ( + getTVL(), + totalSupply() > 0 ? 1 : 0, // Simplified LP count + operatorCount, + totalPremiumsCollected, + totalPayoutsDistributed + ); + } +} diff --git a/contracts/hardhat.config.ts b/contracts/hardhat.config.ts index 3bb8639..6c8d504 100644 --- a/contracts/hardhat.config.ts +++ b/contracts/hardhat.config.ts @@ -1,5 +1,6 @@ import { HardhatUserConfig } from "hardhat/config"; import "@nomicfoundation/hardhat-toolbox"; +import "dotenv/config"; const config: HardhatUserConfig = { solidity: { @@ -19,7 +20,7 @@ const config: HardhatUserConfig = { accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] }, plasma: { - url: process.env.PLASMA_RPC_URL || "https://rpc.plasma.to", + url: process.env.PLASMA_RPC_URL || "https://testnet-rpc.plasma.to", accounts: process.env.PRIVATE_KEY ? [process.env.PRIVATE_KEY] : [] } } diff --git a/contracts/package-lock.json b/contracts/package-lock.json index 8c8c0dc..b2538ec 100644 --- a/contracts/package-lock.json +++ b/contracts/package-lock.json @@ -8,15 +8,27 @@ "name": "lottolink-contracts", "version": "1.0.0", "dependencies": { - "@openzeppelin/contracts": "^5.0.0" + "@openzeppelin/contracts": "^5.0.0", + "dotenv": "^17.2.4" }, "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "^2.1.0", + "@nomicfoundation/hardhat-ethers": "^3.1.3", + "@nomicfoundation/hardhat-network-helpers": "^1.1.2", "@nomicfoundation/hardhat-toolbox": "^4.0.0", + "@nomicfoundation/hardhat-verify": "^2.1.3", + "@typechain/ethers-v6": "^0.5.1", + "@typechain/hardhat": "^9.1.0", "@types/chai": "^5.2.3", "@types/mocha": "^10.0.10", "@types/node": "^20.0.0", + "chai": "^4.5.0", + "ethers": "^6.16.0", "hardhat": "^2.19.0", + "hardhat-gas-reporter": "^1.0.10", + "solidity-coverage": "^0.8.17", "ts-node": "^10.9.0", + "typechain": "^8.3.2", "typescript": "^5.3.0" } }, @@ -25,7 +37,7 @@ "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.10.1.tgz", "integrity": "sha512-96Z2IP3mYmF1Xg2cDm8f1gWGf/HUVedQ3FMifV4kG/PQ4yEP51xDtRAEfhVNt5f/uzpNkZHwWQuUcu6D6K+Ekw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/@cspotcode/source-map-support": { "version": "0.8.1", @@ -232,7 +244,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.8.0", "@ethersproject/properties": "^5.8.0" @@ -312,7 +324,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/abi": "^5.8.0", "@ethersproject/abstract-provider": "^5.8.0", @@ -368,7 +380,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/abstract-signer": "^5.8.0", "@ethersproject/basex": "^5.8.0", @@ -399,7 +411,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/abstract-signer": "^5.8.0", "@ethersproject/address": "^5.8.0", @@ -421,7 +433,7 @@ "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-3.0.0.tgz", "integrity": "sha512-H7wUZRn8WpTq9jocdxQ2c8x2sKo9ZVmzfRE13GiNJXfp7NcKYEdvl3vspKjXox6RIG2VtaRe4JFvxG4rqp2Zuw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/@ethersproject/keccak256": { "version": "5.8.0", @@ -493,7 +505,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.8.0", "@ethersproject/sha2": "^5.8.0" @@ -533,7 +545,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/abstract-provider": "^5.8.0", "@ethersproject/abstract-signer": "^5.8.0", @@ -562,7 +574,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.0.tgz", "integrity": "sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, @@ -594,7 +606,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.8.0", "@ethersproject/logger": "^5.8.0" @@ -635,7 +647,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.8.0", "@ethersproject/logger": "^5.8.0", @@ -681,7 +693,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bignumber": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -754,7 +766,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bignumber": "^5.8.0", "@ethersproject/constants": "^5.8.0", @@ -776,7 +788,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/abstract-provider": "^5.8.0", "@ethersproject/abstract-signer": "^5.8.0", @@ -833,7 +845,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.8.0", "@ethersproject/hash": "^5.8.0", @@ -881,7 +893,7 @@ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.2.0.tgz", "integrity": "sha512-oYclrNgRaM9SsBUBVbb8M6DTV7ZHRTKugureoYEncY5c65HOmRzvSiTE3y5CYaPYJA/GVkrhXEoF0M3Ya9PMnw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@noble/hashes": "1.3.2" }, @@ -894,7 +906,7 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.2.tgz", "integrity": "sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 16" }, @@ -919,7 +931,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "2.0.5", "run-parallel": "^1.1.9" @@ -933,7 +945,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -943,7 +955,7 @@ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@nodelib/fs.scandir": "2.1.5", "fastq": "^1.6.0" @@ -1038,7 +1050,7 @@ "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-chai-matchers/-/hardhat-chai-matchers-2.1.0.tgz", "integrity": "sha512-GPhBNafh1fCnVD9Y7BYvoLnblnvfcq3j8YDbO1gGe/1nOFWzGmV7gFu5DkwFXF+IpYsS+t96o9qc/mPu3V3Vfw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/chai-as-promised": "^7.1.3", "chai-as-promised": "^7.1.1", @@ -1057,7 +1069,7 @@ "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-ethers/-/hardhat-ethers-3.1.3.tgz", "integrity": "sha512-208JcDeVIl+7Wu3MhFUUtiA8TJ7r2Rn3Wr+lSx9PfsDTKkbsAsWPY6N6wQ4mtzDv0/pB9nIbJhkjoHe1EsgNsA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "debug": "^4.1.1", "lodash.isequal": "^4.5.0" @@ -1072,7 +1084,7 @@ "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-network-helpers/-/hardhat-network-helpers-1.1.2.tgz", "integrity": "sha512-p7HaUVDbLj7ikFivQVNhnfMHUBgiHYMwQWvGn9AriieuopGOELIrwj2KjyM2a6z70zai5YKO264Vwz+3UFJZPQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "ethereumjs-util": "^7.1.4" }, @@ -1110,7 +1122,7 @@ "resolved": "https://registry.npmjs.org/@nomicfoundation/hardhat-verify/-/hardhat-verify-2.1.3.tgz", "integrity": "sha512-danbGjPp2WBhLkJdQy9/ARM3WQIK+7vwzE0urNem1qZJjh9f54Kf5f1xuQv8DvqewUAkuPxVt/7q4Grz5WjqSg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/abi": "^5.1.2", "@ethersproject/address": "^5.0.2", @@ -1452,7 +1464,7 @@ "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.14.5.tgz", "integrity": "sha512-6dKnHZn7fg/iQATVEzqyUOyEidbn05q7YA2mQ9hC0MMXhhV3/JrsxmFSYZAcr7j1yUP700LLhTruvJ3MiQmjJg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "antlr4ts": "^0.5.0-alpha.4" } @@ -1486,7 +1498,7 @@ "resolved": "https://registry.npmjs.org/@typechain/ethers-v6/-/ethers-v6-0.5.1.tgz", "integrity": "sha512-F+GklO8jBWlsaVV+9oHaPh5NJdd6rAKN4tklGfInX1Q7h0xPgVLP39Jl3eCulPB5qexI71ZFHwbljx4ZXNfouA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "lodash": "^4.17.15", "ts-essentials": "^7.0.1" @@ -1502,7 +1514,7 @@ "resolved": "https://registry.npmjs.org/@typechain/hardhat/-/hardhat-9.1.0.tgz", "integrity": "sha512-mtaUlzLlkqTlfPwB3FORdejqBskSnh+Jl8AIJGjXNAQfRQ4ofHADPl1+oU7Z3pAJzmZbUXII8MhOLQltcHgKnA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "fs-extra": "^9.1.0" }, @@ -1518,7 +1530,7 @@ "resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-5.2.0.tgz", "integrity": "sha512-DLbJ1BPqxvQhIGbeu8VbUC1DiAiahHtAYvA0ZEAa4P31F7IaArc8z3C3BRQdWX4mtLQuABG4yzp76ZrS02Ui1Q==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -1538,7 +1550,7 @@ "resolved": "https://registry.npmjs.org/@types/chai-as-promised/-/chai-as-promised-7.1.8.tgz", "integrity": "sha512-ThlRVIJhr69FLlh6IctTXFkmhtP3NpMZ2QGq69StYLyKZFp/HOp1VdKZj7RvfNWYYcJ1xlbLGLLWj1UvP5u/Gw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/chai": "*" } @@ -1557,7 +1569,7 @@ "resolved": "https://registry.npmjs.org/@types/concat-stream/-/concat-stream-1.6.1.tgz", "integrity": "sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -1573,7 +1585,7 @@ "resolved": "https://registry.npmjs.org/@types/form-data/-/form-data-0.0.33.tgz", "integrity": "sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -1583,22 +1595,18 @@ "resolved": "https://registry.npmjs.org/@types/glob/-/glob-7.2.0.tgz", "integrity": "sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/minimatch": "*", "@types/node": "*" } }, "node_modules/@types/minimatch": { - "version": "6.0.0", - "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-6.0.0.tgz", - "integrity": "sha512-zmPitbQ8+6zNutpwgcQuLcsEpn/Cj54Kbn7L5pX0Os5kdWplB7xPgEh/g+SWOB/qmows2gpuCaPyduq8ZZRnxA==", - "deprecated": "This is a stub types definition. minimatch provides its own type definitions, so you do not need this installed.", + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/@types/minimatch/-/minimatch-5.1.2.tgz", + "integrity": "sha512-K0VQKziLUWkVKiRVrx4a40iPaxTUefQmjtkQofBkYRcoaaL/8rhwDWww9qWbrgicNOgnpIsMxyNIUM4+n6dUIA==", "dev": true, - "peer": true, - "dependencies": { - "minimatch": "*" - } + "license": "MIT" }, "node_modules/@types/mocha": { "version": "10.0.10", @@ -1620,7 +1628,7 @@ "resolved": "https://registry.npmjs.org/@types/pbkdf2/-/pbkdf2-3.1.2.tgz", "integrity": "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -1630,21 +1638,21 @@ "resolved": "https://registry.npmjs.org/@types/prettier/-/prettier-2.7.3.tgz", "integrity": "sha512-+68kP9yzs4LMp7VNh8gdzMSPZFL44MLGqiHWvttYJe+6qnuVr4Ek9wSBQoveqY/r+LwjCcU29kNVkidwim+kYA==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/@types/qs": { "version": "6.14.0", "resolved": "https://registry.npmjs.org/@types/qs/-/qs-6.14.0.tgz", "integrity": "sha512-eOunJqu0K1923aExK6y8p6fsihYEn/BYuQ4g0CxAAgFc4b/ZLN4CrsRZ55srTdqoiLzU2B2evC+apEIxprEzkQ==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/@types/secp256k1": { "version": "4.0.7", "resolved": "https://registry.npmjs.org/@types/secp256k1/-/secp256k1-4.0.7.tgz", "integrity": "sha512-Rcvjl6vARGAKRO6jHeKMatGrvOMGrR/AR11N1x2LqintPCyDZ7NBhrh238Z2VZc7aM7KIwnFpFQ7fnfK4H/9Qw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/node": "*" } @@ -1654,7 +1662,7 @@ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.0.9.tgz", "integrity": "sha512-LEyx4aLEC3x6T0UguF6YILf+ntvmOaWsVfENmIW0E9H09vKlLDGelMjjSm0jkDHALj8A8quZ/HapKNigzwge+Q==", "dev": true, - "peer": true + "license": "ISC" }, "node_modules/acorn": { "version": "8.15.0", @@ -1694,7 +1702,7 @@ "resolved": "https://registry.npmjs.org/aes-js/-/aes-js-4.0.0-beta.5.tgz", "integrity": "sha512-G965FqalsNyrPqgEGON7nIx1e/OVENSgiEIzyC63haUMuvNnwIgIjMs52hlTCKhkBny7A2ORNlfY9Zu+jmGk1Q==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/agent-base": { "version": "6.0.2", @@ -1726,7 +1734,7 @@ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "fast-deep-equal": "^3.1.3", "fast-uri": "^3.0.1", @@ -1743,8 +1751,8 @@ "resolved": "https://registry.npmjs.org/amdefine/-/amdefine-1.0.1.tgz", "integrity": "sha512-S2Hw0TtNkMJhIabBwIojKL9YHO5T0n5eNqWJ7Lrlel/zDbftQpxpapi8tZs3X1HWa+u+QeydGmzzNU0m09+Rcg==", "dev": true, + "license": "BSD-3-Clause OR MIT", "optional": true, - "peer": true, "engines": { "node": ">=0.4.2" } @@ -1811,7 +1819,7 @@ "resolved": "https://registry.npmjs.org/antlr4ts/-/antlr4ts-0.5.0-alpha.4.tgz", "integrity": "sha512-WPQDt1B74OfPv/IMS2ekXAKkTZIHl88uMetg6q3OTqgFxZ/dxDXI0EWLyZid/1Pe6hTftyg5N7gel5wNAGxXyQ==", "dev": true, - "peer": true + "license": "BSD-3-Clause" }, "node_modules/anymatch": { "version": "3.1.3", @@ -1843,7 +1851,7 @@ "resolved": "https://registry.npmjs.org/array-back/-/array-back-3.1.0.tgz", "integrity": "sha512-TkuxA4UCOvxuDK6NZYXCalszEzj+TLszyASooky+i742l9TqsOdYCMJJupxRic61hwquNtppB3hgcuq9SVSH1Q==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -1853,7 +1861,7 @@ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1863,7 +1871,7 @@ "resolved": "https://registry.npmjs.org/array-uniq/-/array-uniq-1.0.3.tgz", "integrity": "sha512-MNha4BWQ6JbwhFhj03YK552f7cb3AzoE8SzeljgChvL1dl3IcvggXVz1DilzySZkCja+CXuZbdW7yATchWn8/Q==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -1873,14 +1881,14 @@ "resolved": "https://registry.npmjs.org/asap/-/asap-2.0.6.tgz", "integrity": "sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/assertion-error": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz", "integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": "*" } @@ -1890,7 +1898,7 @@ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz", "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -1900,21 +1908,21 @@ "resolved": "https://registry.npmjs.org/async/-/async-1.5.2.tgz", "integrity": "sha512-nSVgobk4rv61R9PUSDtYt7mPVB2olxNR5RWJcAsH676/ef11bUZwvu7+RGYrYauVdDPcO519v68wRhXQtxsV9w==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/at-least-node": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/at-least-node/-/at-least-node-1.0.0.tgz", "integrity": "sha512-+q/t7Ekv1EDY2l6Gda6LLiX14rU9TV20Wa3ofeQmwPFZbOMo9DXrLbOjFaaclkXKWidIaopwAObQDqwWtGUjqg==", "dev": true, - "peer": true, + "license": "ISC", "engines": { "node": ">= 4.0.0" } @@ -1924,7 +1932,7 @@ "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -1940,7 +1948,7 @@ "resolved": "https://registry.npmjs.org/axios/-/axios-1.13.4.tgz", "integrity": "sha512-1wVkUaAO6WyaYtCkcYCOx12ZgpGf9Zif+qXa4n+oYzK558YryKqiL6UWwd5DqiH3VRW0GYhTZQ/vlgJrCoNQlg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", "form-data": "^4.0.4", @@ -1958,7 +1966,7 @@ "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "safe-buffer": "^5.0.1" } @@ -1968,7 +1976,7 @@ "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -1987,7 +1995,7 @@ "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/bn.js": { "version": "5.2.2", @@ -2067,7 +2075,7 @@ "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "buffer-xor": "^1.0.3", "cipher-base": "^1.0.0", @@ -2082,7 +2090,7 @@ "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "base-x": "^3.0.2" } @@ -2092,7 +2100,7 @@ "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "bs58": "^4.0.0", "create-hash": "^1.1.0", @@ -2110,7 +2118,7 @@ "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/bytes": { "version": "3.1.2", @@ -2126,7 +2134,7 @@ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", @@ -2145,7 +2153,7 @@ "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -2159,7 +2167,7 @@ "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "get-intrinsic": "^1.3.0" @@ -2188,14 +2196,14 @@ "resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz", "integrity": "sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==", "dev": true, - "peer": true + "license": "Apache-2.0" }, "node_modules/cbor": { "version": "8.1.0", "resolved": "https://registry.npmjs.org/cbor/-/cbor-8.1.0.tgz", "integrity": "sha512-DwGjNW9omn6EwP70aXsn7FQJx5kO12tX0bZkaTjzdVFM6/7nhA4t0EENocKGx6D2Bch9PE2KzCUf5SceBdeijg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "nofilter": "^3.1.0" }, @@ -2208,7 +2216,7 @@ "resolved": "https://registry.npmjs.org/chai/-/chai-4.5.0.tgz", "integrity": "sha512-RITGBfijLkBddZvnn8jdqoTypxvqbOLYQkGGxXzeFjVHvudaPw0HNFD9x928/eUwYWd2dPCugVqspGALTZZQKw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "assertion-error": "^1.1.0", "check-error": "^1.0.3", @@ -2227,7 +2235,7 @@ "resolved": "https://registry.npmjs.org/chai-as-promised/-/chai-as-promised-7.1.2.tgz", "integrity": "sha512-aBDHZxRzYnUYuIAIPBH2s511DjlKPzXNlXSGFC8CwmroWQLfrW0LtE1nK3MAwwNhJPa9raEjNCmRoFpG0Hurdw==", "dev": true, - "peer": true, + "license": "WTFPL", "dependencies": { "check-error": "^1.0.2" }, @@ -2256,7 +2264,7 @@ "resolved": "https://registry.npmjs.org/charenc/-/charenc-0.0.2.tgz", "integrity": "sha512-yrLQ/yVUFXkzg7EDQsPieE/53+0RlaWTs+wBrvW36cyilJ2SaDWfl4Yj7MtLTXleV9uEKefbAGUPv2/iWSooRA==", "dev": true, - "peer": true, + "license": "BSD-3-Clause", "engines": { "node": "*" } @@ -2266,7 +2274,7 @@ "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.3.tgz", "integrity": "sha512-iKEoDYaRmd1mxM90a2OEfWhjsjPpYPuQ+lMYsoxB126+t8fw7ySEO48nmDg5COTjxDI65/Y2OWpeEHk3ZOe8zg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "get-func-name": "^2.0.2" }, @@ -2300,7 +2308,7 @@ "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", @@ -2336,7 +2344,7 @@ "resolved": "https://registry.npmjs.org/cli-table3/-/cli-table3-0.5.1.tgz", "integrity": "sha512-7Qg2Jrep1S/+Q3EceiZtQcDPWxhAvBw+ERf1162v4sikJrvojMHFqXt8QIVha8UlH9rgU0BeWPytZ9/TzYqlUw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "object-assign": "^4.1.0", "string-width": "^2.1.1" @@ -2353,7 +2361,7 @@ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.1.tgz", "integrity": "sha512-+O9Jct8wf++lXxxFc4hc8LsjaSq0HFzzL7cVsw8pRDIPdjKD2mT4ytDZlLuSBZ4cLKZFXIrMGO7DbQCtMJJMKw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -2363,7 +2371,7 @@ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", "integrity": "sha512-VHskAKYM8RfSFXwee5t5cbN5PZeq1Wrh6qd5bkyiXIf6UQcN6w/A0eXM9r6t8d+GYOh+o6ZhiEnb88LN/Y8m2w==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -2373,7 +2381,7 @@ "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "is-fullwidth-code-point": "^2.0.0", "strip-ansi": "^4.0.0" @@ -2387,7 +2395,7 @@ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", "integrity": "sha512-4XaJ2zQdCzROZDivEVIDPkcQn8LMFSa8kj8Gxb/Lnwzv9A8VctNZ+lfivC/sV3ivW8ElJTERXZoPBRrZKkNKow==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "ansi-regex": "^3.0.0" }, @@ -2429,7 +2437,7 @@ "resolved": "https://registry.npmjs.org/colors/-/colors-1.4.0.tgz", "integrity": "sha512-a+UqTh4kgZg/SlGvfbzDHpgRu7AAQOmmqRHJnxhRZICKFUT91brVhNNt58CMWU9PsBbv3PDCZUHbVxuDiH2mtA==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=0.1.90" } @@ -2439,7 +2447,7 @@ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "delayed-stream": "~1.0.0" }, @@ -2458,7 +2466,7 @@ "resolved": "https://registry.npmjs.org/command-line-args/-/command-line-args-5.2.1.tgz", "integrity": "sha512-H4UfQhZyakIjC74I9d34fGYDwk3XpSr17QhEd0Q3I9Xq1CETHo4Hcuo87WyWHpAF1aSLjLRf5lD9ZGX2qStUvg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "array-back": "^3.1.0", "find-replace": "^3.0.0", @@ -2474,7 +2482,7 @@ "resolved": "https://registry.npmjs.org/command-line-usage/-/command-line-usage-6.1.3.tgz", "integrity": "sha512-sH5ZSPr+7UStsloltmDh7Ce5fb8XPlHyoPzTpyyMuYCtervL65+ubVZ6Q61cFtFl62UyJlc8/JwERRbAFPUqgw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "array-back": "^4.0.2", "chalk": "^2.4.2", @@ -2490,7 +2498,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -2503,7 +2511,7 @@ "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2513,7 +2521,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -2528,7 +2536,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -2538,14 +2546,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/command-line-usage/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -2555,7 +2563,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -2565,7 +2573,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -2578,7 +2586,7 @@ "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -2597,7 +2605,7 @@ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/concat-stream": { "version": "1.6.2", @@ -2607,7 +2615,7 @@ "engines": [ "node >= 0.8" ], - "peer": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -2620,14 +2628,14 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/concat-stream/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==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -2643,14 +2651,14 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/concat-stream/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==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -2669,14 +2677,14 @@ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/create-hash": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/create-hash/-/create-hash-1.2.0.tgz", "integrity": "sha512-z00bCGNHDG8mHAkP7CtT1qVu+bFQUPjYq/4Iv3C3kWjTFV10zIjfSoeqXo9Asws8gwSHDGj/hl2u4OGIjapeCg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "cipher-base": "^1.0.1", "inherits": "^2.0.1", @@ -2690,7 +2698,7 @@ "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "cipher-base": "^1.0.3", "create-hash": "^1.1.0", @@ -2711,7 +2719,7 @@ "resolved": "https://registry.npmjs.org/crypt/-/crypt-0.0.2.tgz", "integrity": "sha512-mCxBlsHFYh9C+HVpiEacem8FEBnMXgU9gy4zmNC+SXAZNB/1idgp/aulFJ4FgCi7GPEVbfyng092GqL2k2rmow==", "dev": true, - "peer": true, + "license": "BSD-3-Clause", "engines": { "node": "*" } @@ -2720,8 +2728,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/death/-/death-1.1.0.tgz", "integrity": "sha512-vsV6S4KVHvTGxbEcij7hkWRv0It+sGGWVOM67dQde/o5Xjnr+KmLjxWJii2uEObIrt1CcM9w0Yaovx+iOlIL+w==", - "dev": true, - "peer": true + "dev": true }, "node_modules/debug": { "version": "4.4.3", @@ -2757,7 +2764,7 @@ "resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-4.1.4.tgz", "integrity": "sha512-SUwdGfqdKOwxCPeVYjwSyRpJ7Z+fhpwIAtmCUdZIWZ/YP5R9WAsyuSgpLVDi9bjWoN2LXHNss/dk3urXtdQxGg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "type-detect": "^4.0.0" }, @@ -2770,7 +2777,7 @@ "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=4.0.0" } @@ -2780,14 +2787,14 @@ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/define-data-property": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -2805,7 +2812,7 @@ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=0.4.0" } @@ -2833,7 +2840,6 @@ "resolved": "https://registry.npmjs.org/difflib/-/difflib-0.2.4.tgz", "integrity": "sha512-9YVwmMb0wQHQNr5J9m6BSj6fk4pfGITGQOOs+D9Fl+INODWFOfvhIU1hNv6GgR1RBoC/9NJcwu77zShxV0kT7w==", "dev": true, - "peer": true, "dependencies": { "heap": ">= 0.2.0" }, @@ -2846,7 +2852,7 @@ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "path-type": "^4.0.0" }, @@ -2854,12 +2860,23 @@ "node": ">=8" } }, + "node_modules/dotenv": { + "version": "17.2.4", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.4.tgz", + "integrity": "sha512-mudtfb4zRB4bVvdj0xRo+e6duH1csJRM8IukBqfTRvHotn9+LBXB8ynAidP9zHqoRC/fsllXgk4kCKlR21fIhw==", + "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==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -2923,7 +2940,7 @@ "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -2933,7 +2950,7 @@ "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -2943,7 +2960,7 @@ "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0" }, @@ -2956,7 +2973,7 @@ "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6", @@ -2993,7 +3010,7 @@ "resolved": "https://registry.npmjs.org/escodegen/-/escodegen-1.8.1.tgz", "integrity": "sha512-yhi5S+mNTOuRvyW4gWlg5W1byMaQGWWSYHXsuFZ7GBo7tpyOwi2EdzMP/QWxh9hwkD2m+wDVHJsxhRIj+v/b/A==", "dev": true, - "peer": true, + "license": "BSD-2-Clause", "dependencies": { "esprima": "^2.7.1", "estraverse": "^1.9.1", @@ -3016,7 +3033,7 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-2.7.3.tgz", "integrity": "sha512-OarPfz0lFCiW4/AV2Oy1Rp9qu0iusTKqykwTspGCZtPxmF81JR4MmIebvF1F9+UOKth2ZubLQ4XGGaU+hSn99A==", "dev": true, - "peer": true, + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -3030,7 +3047,6 @@ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-1.9.3.tgz", "integrity": "sha512-25w1fMXQrGdoquWnScXZGckOv+Wes+JDnuN/+7ex3SauFRS72r2lFDec0EKPt2YD1wUJ/IrfEex+9yp4hfSOJA==", "dev": true, - "peer": true, "engines": { "node": ">=0.10.0" } @@ -3040,7 +3056,7 @@ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", "dev": true, - "peer": true, + "license": "BSD-2-Clause", "engines": { "node": ">=0.10.0" } @@ -3050,7 +3066,7 @@ "resolved": "https://registry.npmjs.org/eth-gas-reporter/-/eth-gas-reporter-0.2.27.tgz", "integrity": "sha512-femhvoAM7wL0GcI8ozTdxfuBtBFJ9qsyIAsmKVjlWAHUbdnnXHt+lKzz/kmldM5lA9jLuNHGwuIxorNpLbR1Zw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@solidity-parser/parser": "^0.14.0", "axios": "^1.5.1", @@ -3086,14 +3102,14 @@ "url": "https://paulmillr.com/funding/" } ], - "peer": true + "license": "MIT" }, "node_modules/eth-gas-reporter/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==", "dev": true, - "peer": true, + "license": "MIT", "funding": { "url": "https://paulmillr.com/funding/" } @@ -3109,7 +3125,7 @@ "url": "https://paulmillr.com/funding/" } ], - "peer": true, + "license": "MIT", "dependencies": { "@noble/hashes": "~1.2.0", "@noble/secp256k1": "~1.7.0", @@ -3127,7 +3143,7 @@ "url": "https://paulmillr.com/funding/" } ], - "peer": true, + "license": "MIT", "dependencies": { "@noble/hashes": "~1.2.0", "@scure/base": "~1.1.0" @@ -3138,7 +3154,7 @@ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-1.2.0.tgz", "integrity": "sha512-6yFQC9b5ug6/17CQpCyE3k9eKBMdhyVjzUy1WkiuY/E4vj/SXDBbCw8QEIaXqf0Mf2SnY6RmpDcwlUmBSS0EJw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@noble/hashes": "1.2.0", "@noble/secp256k1": "1.7.1", @@ -3161,7 +3177,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/abi": "5.8.0", "@ethersproject/abstract-provider": "5.8.0", @@ -3200,7 +3216,7 @@ "resolved": "https://registry.npmjs.org/ethereum-bloom-filters/-/ethereum-bloom-filters-1.2.0.tgz", "integrity": "sha512-28hyiE7HVsWubqhpVLVmZXFd4ITeHi+BUu05o9isf0GUpMtzBUi+8/gFrGaGYzvGAJQmJ3JKj77Mk9G98T84rA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@noble/hashes": "^1.4.0" } @@ -3210,7 +3226,7 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": "^14.21.3 || >=16" }, @@ -3223,7 +3239,7 @@ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-0.1.3.tgz", "integrity": "sha512-w8/4x1SGGzc+tO97TASLja6SLd3fRIK2tLVcV2Gx4IB21hE19atll5Cq9o3d0ZmAYC/8aw0ipieTSiekAea4SQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/pbkdf2": "^3.0.0", "@types/secp256k1": "^4.0.1", @@ -3247,7 +3263,7 @@ "resolved": "https://registry.npmjs.org/ethereumjs-util/-/ethereumjs-util-7.1.5.tgz", "integrity": "sha512-SDl5kKrQAudFBUe5OJM9Ac6WmMyYmXX/6sTmLZ3ffG2eY6ZIGBes3pEDxNN6V72WyOw4CPD5RomKdsa8DAAwLg==", "dev": true, - "peer": true, + "license": "MPL-2.0", "dependencies": { "@types/bn.js": "^5.1.0", "bn.js": "^5.1.2", @@ -3274,7 +3290,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@adraffy/ens-normalize": "1.10.1", "@noble/curves": "1.2.0", @@ -3293,7 +3309,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-22.7.5.tgz", "integrity": "sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "undici-types": "~6.19.2" } @@ -3303,14 +3319,14 @@ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/ethjs-unit": { "version": "0.1.6", "resolved": "https://registry.npmjs.org/ethjs-unit/-/ethjs-unit-0.1.6.tgz", "integrity": "sha512-/Sn9Y0oKl0uqQuvgFk/zQgR7aw1g36qX/jzSQ5lSwlO0GigPymk4eGQfeNTD03w1dPOqfz8V77Cy43jH56pagw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "bn.js": "4.11.6", "number-to-bn": "1.7.0" @@ -3325,14 +3341,14 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/evp_bytestokey": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/evp_bytestokey/-/evp_bytestokey-1.0.3.tgz", "integrity": "sha512-/f2Go4TognH/KvCISP7OUsHn85hT9nUkxxA9BEWxFn+Oj9o8ZNLm/40hdlgSLyuOimsrTKLUMEorQexp/aPQeA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "md5.js": "^1.3.4", "safe-buffer": "^5.1.1" @@ -3343,14 +3359,14 @@ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/fast-glob": { "version": "3.3.3", "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@nodelib/fs.stat": "^2.0.2", "@nodelib/fs.walk": "^1.2.3", @@ -3367,7 +3383,7 @@ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/fast-uri": { "version": "3.1.0", @@ -3384,14 +3400,14 @@ "url": "https://opencollective.com/fastify" } ], - "peer": true + "license": "BSD-3-Clause" }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", "integrity": "sha512-GGToxJ/w1x32s/D2EKND7kTil4n8OVk/9mycTc4VDza13lOvpUZTGX3mFSCtV9ksdGBVzvsyAVLM6mHFThxXxw==", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "reusify": "^1.0.4" } @@ -3413,7 +3429,7 @@ "resolved": "https://registry.npmjs.org/find-replace/-/find-replace-3.0.0.tgz", "integrity": "sha512-6Tb2myMioCAgv5kfvP5/PkZZ/ntTpVK39fHY7WkWBgvbeE+VHd/tZuZ4mrC+bxh4cfOZeYKVPaJIZtZXV7GNCQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "array-back": "^3.0.1" }, @@ -3471,7 +3487,7 @@ "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "is-callable": "^1.2.7" }, @@ -3487,7 +3503,7 @@ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -3510,7 +3526,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-9.1.0.tgz", "integrity": "sha512-hcg3ZmepS30/7BSFqRvoo3DOMQu7IjqxO5nCDt+zM9XWjb33Wg7ziNT+Qvqbuc3+gWpzO02JubVyk2G4Zvo1OQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "at-least-node": "^1.0.0", "graceful-fs": "^4.2.0", @@ -3526,7 +3542,7 @@ "resolved": "https://registry.npmjs.org/fs-readdir-recursive/-/fs-readdir-recursive-1.1.0.tgz", "integrity": "sha512-GNanXlVr2pf02+sPN40XN8HG+ePaNcvM0q5mZBd668Obwb0yD5GiUbZOFgwn8kGMY6I3mdyDJzieUy3PTYyTRA==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/fs.realpath": { "version": "1.0.0", @@ -3553,7 +3569,7 @@ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", "dev": true, - "peer": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -3572,7 +3588,7 @@ "resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.2.tgz", "integrity": "sha512-8vXOvuE167CtIc3OyItco7N/dpRtBbYOsPsXCz7X/PMnlGjYjSGuZJgM1Y7mmew7BKf9BqvLX2tnOVy1BBUsxQ==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": "*" } @@ -3582,7 +3598,7 @@ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", @@ -3607,7 +3623,7 @@ "resolved": "https://registry.npmjs.org/get-port/-/get-port-3.2.0.tgz", "integrity": "sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -3617,7 +3633,7 @@ "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -3631,7 +3647,7 @@ "resolved": "https://registry.npmjs.org/ghost-testrpc/-/ghost-testrpc-0.0.2.tgz", "integrity": "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ==", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "chalk": "^2.4.2", "node-emoji": "^1.10.0" @@ -3645,7 +3661,7 @@ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -3658,7 +3674,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -3673,7 +3689,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -3683,14 +3699,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/ghost-testrpc/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -3700,7 +3716,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -3710,7 +3726,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -3755,7 +3771,7 @@ "resolved": "https://registry.npmjs.org/global-modules/-/global-modules-2.0.0.tgz", "integrity": "sha512-NGbfmJBp9x8IxyJSd1P+otYK8vonoJactOogrVfFRIAEY1ukil8RSKDz2Yo7wh1oihl51l/r6W4epkeKJHqL8A==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "global-prefix": "^3.0.0" }, @@ -3768,7 +3784,7 @@ "resolved": "https://registry.npmjs.org/global-prefix/-/global-prefix-3.0.0.tgz", "integrity": "sha512-awConJSVCHVGND6x3tmMaKcQvwXLhjdkmomy2W+Goaui8YPgYgXJZewhg3fWC+DlfqqQuWg8AwqjGTD2nAPVWg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "ini": "^1.3.5", "kind-of": "^6.0.2", @@ -3783,7 +3799,7 @@ "resolved": "https://registry.npmjs.org/globby/-/globby-10.0.2.tgz", "integrity": "sha512-7dUi7RvCoT/xast/o/dLN53oqND4yk0nsHkhRgn9w65C4PofCLOoJ39iSOg+qVDdWQPIEj+eszMHQ+aLVwwQSg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/glob": "^7.1.1", "array-union": "^2.1.0", @@ -3803,7 +3819,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -3815,7 +3831,7 @@ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -3836,7 +3852,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -3849,7 +3865,7 @@ "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -3868,7 +3884,7 @@ "resolved": "https://registry.npmjs.org/handlebars/-/handlebars-4.7.8.tgz", "integrity": "sha512-vafaFqs8MZkRrSX7sFVUdo3ap/eNiLnb4IakshzvP56X5Nr1iGKAIqdX6tMlm6HcNRIkr6AxO5jFEoJzzpT8aQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.5", "neo-async": "^2.6.2", @@ -3890,7 +3906,7 @@ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", "dev": true, - "peer": true, + "license": "BSD-3-Clause", "engines": { "node": ">=0.10.0" } @@ -3962,7 +3978,7 @@ "resolved": "https://registry.npmjs.org/hardhat-gas-reporter/-/hardhat-gas-reporter-1.0.10.tgz", "integrity": "sha512-02N4+So/fZrzJ88ci54GqwVA3Zrf0C9duuTyGt0CFRIh/CdNwbnTgkXkRfojOMLBQ+6t+lBIkgbsOtqMvNwikA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "array-uniq": "1.0.3", "eth-gas-reporter": "^0.2.25", @@ -4105,7 +4121,7 @@ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" }, @@ -4118,7 +4134,7 @@ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4131,7 +4147,7 @@ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "has-symbols": "^1.0.3" }, @@ -4147,7 +4163,7 @@ "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "inherits": "^2.0.4", "readable-stream": "^2.3.8", @@ -4163,14 +4179,14 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/hash-base/node_modules/readable-stream": { "version": "2.3.8", "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "core-util-is": "~1.0.0", "inherits": "~2.0.3", @@ -4186,14 +4202,14 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/hash-base/node_modules/string_decoder": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "safe-buffer": "~5.1.0" } @@ -4203,7 +4219,7 @@ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/hash.js": { "version": "1.1.7", @@ -4220,7 +4236,7 @@ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "function-bind": "^1.1.2" }, @@ -4242,7 +4258,7 @@ "resolved": "https://registry.npmjs.org/heap/-/heap-0.2.7.tgz", "integrity": "sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/hmac-drbg": { "version": "1.0.1", @@ -4260,7 +4276,7 @@ "resolved": "https://registry.npmjs.org/http-basic/-/http-basic-8.1.3.tgz", "integrity": "sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "caseless": "^0.12.0", "concat-stream": "^1.6.2", @@ -4296,7 +4312,7 @@ "resolved": "https://registry.npmjs.org/http-response-object/-/http-response-object-3.0.2.tgz", "integrity": "sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/node": "^10.0.3" } @@ -4306,7 +4322,7 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-10.17.60.tgz", "integrity": "sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/https-proxy-agent": { "version": "5.0.1", @@ -4338,7 +4354,7 @@ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", "integrity": "sha512-hsBTNUqQTDwkWtcdYI2i06Y/nUBEsNEDJKjWdigLvegy8kDuJAS8uRlpkkcQpyEXL0Z/pjDy5HBmMjRCJ2gq+g==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 4" } @@ -4380,14 +4396,14 @@ "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", "dev": true, - "peer": true + "license": "ISC" }, "node_modules/interpret": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/interpret/-/interpret-1.4.0.tgz", "integrity": "sha512-agE4QfB2Lkp9uICn7BAqoscw4SZP9kTE2hxiFI3jBPmXJfdqiahTbUuKGsMoN2GtqL9AxhYioAcVvgsb1HvRbA==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 0.10" } @@ -4418,7 +4434,7 @@ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -4461,7 +4477,7 @@ "resolved": "https://registry.npmjs.org/is-hex-prefixed/-/is-hex-prefixed-1.0.0.tgz", "integrity": "sha512-WvtOiug1VFrE9v1Cydwm+FnXd3+w9GaeVUss5W4v/SLy3UW00vP+6iNF2SdnfiBoLy4bTqVdkftNGTUeOFVsbA==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=6.5.0", "npm": ">=3" @@ -4490,7 +4506,7 @@ "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" }, @@ -4518,14 +4534,14 @@ "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/isexe": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true, - "peer": true + "license": "ISC" }, "node_modules/js-sha3": { "version": "0.8.0", @@ -4550,7 +4566,7 @@ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz", "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/json-stream-stringify": { "version": "3.1.6", @@ -4566,7 +4582,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-6.2.0.tgz", "integrity": "sha512-FGuPw30AdOIUTRMC2OMRtQV+jkVj2cfPqSeWXv1NEAJ1qZ5zb1X6z1mFhbfOB/iy3ssJCD+3KuZ8r8C3uVFlAg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "universalify": "^2.0.0" }, @@ -4579,7 +4595,7 @@ "resolved": "https://registry.npmjs.org/jsonschema/-/jsonschema-1.5.0.tgz", "integrity": "sha512-K+A9hhqbn0f3pJX17Q/7H6yQfD/5OXgdrR5UE12gMXCiN9D5Xq2o5mddV2QEcX/bjla99ASsAAQUyMCCRWAEhw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": "*" } @@ -4604,7 +4620,7 @@ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -4614,7 +4630,7 @@ "resolved": "https://registry.npmjs.org/levn/-/levn-0.3.0.tgz", "integrity": "sha512-0OO4y2iOHix2W6ujICbKIaEQXvFQHue65vUG3pb5EUomzPI90z9hsA1VsO/dbIIpC53J8gxM9Q4Oho0jrCM/yA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "prelude-ls": "~1.1.2", "type-check": "~0.3.2" @@ -4649,14 +4665,14 @@ "resolved": "https://registry.npmjs.org/lodash.camelcase/-/lodash.camelcase-4.3.0.tgz", "integrity": "sha512-TwuEnCnxbc3rAvhf/LbG7tJUDzhqXyFnv3dtzLOPgCG/hODL7WFnsbwktkD7yUV0RrreP/l1PALq/YSg6VvjlA==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", "resolved": "https://registry.npmjs.org/lodash.clonedeep/-/lodash.clonedeep-4.5.0.tgz", "integrity": "sha512-H5ZhCF25riFd9uB5UCkVKo61m3S/xZk1x4wA6yp/L3RFP6Z/eHH1ymQcGLo7J3GMPfm0V/7m1tryHuGVxpqEBQ==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/lodash.isequal": { "version": "4.5.0", @@ -4664,14 +4680,14 @@ "integrity": "sha512-pDo3lu8Jhfjqls6GkMgpahsF9kCyayhgykjyLMNFTKWrpVdAQtYyB4muAMWozBB4ig/dtWAmsMxLEI8wuz+DYQ==", "deprecated": "This package is deprecated. Use require('node:util').isDeepStrictEqual instead.", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/lodash.truncate": { "version": "4.4.2", "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz", "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/log-symbols": { "version": "4.1.0", @@ -4694,7 +4710,7 @@ "resolved": "https://registry.npmjs.org/loupe/-/loupe-2.3.7.tgz", "integrity": "sha512-zSMINGVYkdpYSOBmLi0D1Uo7JU9nVdQKrHxC8eYlV+9YKK9WePqAlL7lSlorG/U2Fw1w0hTBmaa/jrQ3UbPHtA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "get-func-name": "^2.0.1" } @@ -4716,14 +4732,14 @@ "resolved": "https://registry.npmjs.org/markdown-table/-/markdown-table-1.1.3.tgz", "integrity": "sha512-1RUZVgQlpJSPWYbFSpmudq5nHY1doEIv89gBtF0s4gW1GF2XorxcA/70M5vq7rLv0a6mhOUccRsqkwhwLCIQ2Q==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -4733,7 +4749,7 @@ "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "hash-base": "^3.0.0", "inherits": "^2.0.1", @@ -4754,7 +4770,7 @@ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 8" } @@ -4802,7 +4818,7 @@ "resolved": "https://registry.npmjs.org/micro-ftch/-/micro-ftch-0.3.1.tgz", "integrity": "sha512-/0LLxhzP0tfiR5hcQebtudP56gUurs2CLkGarnCiB/OqEyUFQ6U3paQi/tgLv0hBJYt2rnr9MNpxz4fiiugstg==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/micro-packed": { "version": "0.7.3", @@ -4821,7 +4837,7 @@ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.8.tgz", "integrity": "sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "braces": "^3.0.3", "picomatch": "^2.3.1" @@ -4835,7 +4851,7 @@ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 0.6" } @@ -4845,7 +4861,7 @@ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "mime-db": "1.52.0" }, @@ -4882,7 +4898,7 @@ "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", "dev": true, - "peer": true, + "license": "MIT", "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -4892,7 +4908,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "minimist": "^1.2.6" }, @@ -5006,7 +5022,7 @@ "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/node-addon-api": { "version": "2.0.2", @@ -5019,7 +5035,7 @@ "resolved": "https://registry.npmjs.org/node-emoji/-/node-emoji-1.11.0.tgz", "integrity": "sha512-wo2DpQkQp7Sjm2A0cq+sN7EHKO6Sl0ctXeBdFZrL9T9+UywORbufTcTZxom8YqpLQt/FqNMUkOpkZrJVYSKD3A==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "lodash": "^4.17.21" } @@ -5040,7 +5056,7 @@ "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=12.19" } @@ -5050,7 +5066,7 @@ "resolved": "https://registry.npmjs.org/nopt/-/nopt-3.0.6.tgz", "integrity": "sha512-4GUt3kSEYmk4ITxzB/b9vaIDfUVWN/Ml1Fwl11IlnIG2iaJ9O6WXZ9SrYM9NLI8OCBieN2Y8SWC2oJV0RQ7qYg==", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "abbrev": "1" }, @@ -5072,7 +5088,7 @@ "resolved": "https://registry.npmjs.org/number-to-bn/-/number-to-bn-1.7.0.tgz", "integrity": "sha512-wsJ9gfSz1/s4ZsJN01lyonwuxA1tml6X1yBDnfpMglypcBRFZZkus26EdPSlqS5GJfYddVZa22p3VNb3z5m5Ig==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "bn.js": "4.11.6", "strip-hex-prefix": "1.0.0" @@ -5087,14 +5103,14 @@ "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.11.6.tgz", "integrity": "sha512-XWwnNNFCuuSQ0m3r3C4LE3EiORltHd9M05pq6FOlVeiophzRbMo50Sbz1ehl8K3Z+jw9+vmgnXefY1hz8X+2wA==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/object-assign": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5104,7 +5120,7 @@ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 0.4" }, @@ -5132,7 +5148,7 @@ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.8.3.tgz", "integrity": "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", @@ -5150,7 +5166,7 @@ "resolved": "https://registry.npmjs.org/ordinal/-/ordinal-1.0.3.tgz", "integrity": "sha512-cMddMgb2QElm8G7vdaa02jhUNbTSrhsgAGUz1OokD83uJTwSUn+nKoNoKVVaRa08yF6sgfO7Maou1+bgLd9rdQ==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/os-tmpdir": { "version": "1.0.2", @@ -5210,8 +5226,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/parse-cache-control/-/parse-cache-control-1.0.1.tgz", "integrity": "sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==", - "dev": true, - "peer": true + "dev": true }, "node_modules/path-exists": { "version": "4.0.0", @@ -5227,7 +5242,7 @@ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5243,7 +5258,7 @@ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -5253,7 +5268,7 @@ "resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.1.tgz", "integrity": "sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": "*" } @@ -5263,7 +5278,7 @@ "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.5.tgz", "integrity": "sha512-Q3CG/cYvCO1ye4QKkuH7EXxs3VC/rI1/trd+qX2+PolbaKG0H+bgcZzrTt96mMyRtejk+JMCiLUn3y29W8qmFQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "create-hash": "^1.2.0", "create-hmac": "^1.1.7", @@ -5299,7 +5314,7 @@ "resolved": "https://registry.npmjs.org/pify/-/pify-4.0.1.tgz", "integrity": "sha512-uB80kBFb/tfd68bVleG9T5GGsGPjJrLAUpR5PZIrhBnIaRTQRjqdJSsIKkOP6OAIFbj7GOrcudc5pNjZ+geV2g==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5309,7 +5324,7 @@ "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 0.4" } @@ -5319,7 +5334,6 @@ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.1.2.tgz", "integrity": "sha512-ESF23V4SKG6lVSGZgYNpbsiaAkdab6ZgOxe52p7+Kid3W3u3bxR4Vfd/o21dmN7jSt0IwgZ4v5MUd26FEtXE9w==", "dev": true, - "peer": true, "engines": { "node": ">= 0.8.0" } @@ -5329,7 +5343,7 @@ "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", "dev": true, - "peer": true, + "license": "MIT", "bin": { "prettier": "bin-prettier.js" }, @@ -5345,14 +5359,14 @@ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/promise": { "version": "8.3.0", "resolved": "https://registry.npmjs.org/promise/-/promise-8.3.0.tgz", "integrity": "sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "asap": "~2.0.6" } @@ -5362,14 +5376,14 @@ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/qs": { "version": "6.14.1", "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", "dev": true, - "peer": true, + "license": "BSD-3-Clause", "dependencies": { "side-channel": "^1.1.0" }, @@ -5399,7 +5413,7 @@ "url": "https://feross.org/support" } ], - "peer": true + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", @@ -5457,7 +5471,6 @@ "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.6.2.tgz", "integrity": "sha512-HFM8rkZ+i3zrV+4LQjwQ0W+ez98pApMGM3HUrN04j3CqzPOzl9nmP15Y8YXNm8QHGv/eacOVEjqhmWpkRV0NAw==", "dev": true, - "peer": true, "dependencies": { "resolve": "^1.1.6" }, @@ -5470,7 +5483,7 @@ "resolved": "https://registry.npmjs.org/recursive-readdir/-/recursive-readdir-2.2.3.tgz", "integrity": "sha512-8HrF5ZsXk5FAH9dgsx3BlUer73nIhuj+9OrQwEbLTPOBzGkL1lsFCR01am+v+0m2Cmbs1nP12hLDl5FA7EszKA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "minimatch": "^3.0.5" }, @@ -5483,7 +5496,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5494,7 +5507,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5507,7 +5520,7 @@ "resolved": "https://registry.npmjs.org/reduce-flatten/-/reduce-flatten-2.0.0.tgz", "integrity": "sha512-EJ4UNY/U1t2P/2k6oqotuX2Cc3T6nxJwsM0N0asT7dhrtH1ltUxDn4NalSYmPE2rCkVpcf/X6R0wDwcFpzhd4w==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=6" } @@ -5517,7 +5530,7 @@ "resolved": "https://registry.npmjs.org/req-cwd/-/req-cwd-2.0.0.tgz", "integrity": "sha512-ueoIoLo1OfB6b05COxAA9UpeoscNpYyM+BqYlA7H6LVF4hKGPXQQSSaD2YmvDVJMkk4UDpAHIeU1zG53IqjvlQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "req-from": "^2.0.0" }, @@ -5530,7 +5543,7 @@ "resolved": "https://registry.npmjs.org/req-from/-/req-from-2.0.0.tgz", "integrity": "sha512-LzTfEVDVQHBRfjOUMgNBA+V6DWsSnoeKzf42J7l0xa/B4jyPOuuF5MlNSmomLNGemWTnV2TIdjSSLnEn95fOQA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "resolve-from": "^3.0.0" }, @@ -5552,7 +5565,7 @@ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz", "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5574,7 +5587,7 @@ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-3.0.0.tgz", "integrity": "sha512-GnlH6vxLymXJNMBo7XP1fJIzBFbdYt49CuTwmB/6N53t+kMPRMFKz783LlQ4tv28XoQfMWinAJX6WCGf2IlaIw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -5584,7 +5597,7 @@ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "iojs": ">=1.0.0", "node": ">=0.10.0" @@ -5595,7 +5608,7 @@ "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "hash-base": "^3.1.2", "inherits": "^2.0.4" @@ -5609,7 +5622,7 @@ "resolved": "https://registry.npmjs.org/rlp/-/rlp-2.2.7.tgz", "integrity": "sha512-d5gdPmgQ0Z+AklL2NVXr/IoSjNZFfTVvQWzL/AM2AOcSzYP2xjlb0AC8YyCLc41MSNf6P6QVtjgPdmVtzb+4lQ==", "dev": true, - "peer": true, + "license": "MPL-2.0", "dependencies": { "bn.js": "^5.2.0" }, @@ -5636,7 +5649,7 @@ "url": "https://feross.org/support" } ], - "peer": true, + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -5672,7 +5685,7 @@ "resolved": "https://registry.npmjs.org/sc-istanbul/-/sc-istanbul-0.4.6.tgz", "integrity": "sha512-qJFF/8tW/zJsbyfh/iT/ZM5QNHE3CXxtLJbZsL+CzdJLBsPD7SedJZoUA4d8iAcN2IoMp/Dx80shOOd2x96X/g==", "dev": true, - "peer": true, + "license": "BSD-3-Clause", "dependencies": { "abbrev": "1.0.x", "async": "1.x", @@ -5698,7 +5711,7 @@ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "sprintf-js": "~1.0.2" } @@ -5708,7 +5721,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5720,7 +5733,7 @@ "integrity": "sha512-c9IPMazfRITpmAAKi22dK1VKxGDX9ehhqfABDriL/lzO92xcUKEJPQHrVA/2YHSNFB4iFlykVmWvwo48nr3OxA==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "inflight": "^1.0.4", "inherits": "2", @@ -5737,7 +5750,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-1.0.0.tgz", "integrity": "sha512-DyYHfIYwAJmjAjSSPKANxI8bFY9YtFrgkAfinBojQ8YJTOuOuav64tMUJv584SES4xl74PmuaevIyaLESHdTAA==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -5747,7 +5760,7 @@ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.2.tgz", "integrity": "sha512-PMSmkqxr106Xa156c2M265Z+FTrPl+oxd/rgOQy2tijQeK5TxQ43psO1ZCwhVOSdnn+RzkzlRz/eY4BgJBYVpg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" @@ -5761,7 +5774,7 @@ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", "dev": true, - "peer": true, + "license": "BSD-2-Clause", "bin": { "esparse": "bin/esparse.js", "esvalidate": "bin/esvalidate.js" @@ -5775,7 +5788,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5788,14 +5801,14 @@ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.1.7.tgz", "integrity": "sha512-9znBF0vBcaSN3W2j7wKvdERPwqTxSpCq+if5C0WoTCyV9n24rua28jeuQ2pL/HOf+yUe/Mef+H/5p60K0Id3bg==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/sc-istanbul/node_modules/supports-color": { "version": "3.2.3", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-3.2.3.tgz", "integrity": "sha512-Jds2VIYDrlp5ui7t8abHN2bjAu4LV/q4N2KivFPpGH0lrka0BMq/33AmECUXlKPcHigkNaqfXRENFju+rlcy+A==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "has-flag": "^1.0.0" }, @@ -5808,7 +5821,7 @@ "resolved": "https://registry.npmjs.org/scrypt-js/-/scrypt-js-3.0.1.tgz", "integrity": "sha512-cdwTTnqPu0Hyvf5in5asVdZocVDTNRmR7XEcJuIzMjJeSHybHl7vpB66AzwTaIg6CLSbtjcxc8fqcySfnTkccA==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/secp256k1": { "version": "4.0.4", @@ -5816,7 +5829,7 @@ "integrity": "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw==", "dev": true, "hasInstallScript": true, - "peer": true, + "license": "MIT", "dependencies": { "elliptic": "^6.5.7", "node-addon-api": "^5.0.0", @@ -5831,7 +5844,7 @@ "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/semver": { "version": "6.3.1", @@ -5856,7 +5869,7 @@ "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -5874,7 +5887,7 @@ "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz", "integrity": "sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/setprototypeof": { "version": "1.2.0", @@ -5887,7 +5900,7 @@ "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", "dev": true, - "peer": true, + "license": "(MIT AND BSD-3-Clause)", "dependencies": { "inherits": "^2.0.4", "safe-buffer": "^5.2.1", @@ -5908,7 +5921,7 @@ "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", "integrity": "sha512-dZBS6OrMjtgVkopB1Gmo4RQCDKiZsqcpAQpkV/aaj+FCrCg8r4I4qMkDPQjBgLIxlmu9k4nUbWq6ohXahOneYA==", "dev": true, - "peer": true, + "license": "BSD-3-Clause", "dependencies": { "charenc": ">= 0.0.1", "crypt": ">= 0.0.1" @@ -5922,7 +5935,7 @@ "resolved": "https://registry.npmjs.org/shelljs/-/shelljs-0.8.5.tgz", "integrity": "sha512-TiwcRcrkhHvbrZbnRcFYMLl30Dfov3HKqzp5tO5b4pt6G/SezKcYhmDg15zXVBswHmctSAQKznqNW2LO5tTDow==", "dev": true, - "peer": true, + "license": "BSD-3-Clause", "dependencies": { "glob": "^7.0.0", "interpret": "^1.0.0", @@ -5940,7 +5953,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -5952,7 +5965,7 @@ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -5973,7 +5986,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -5986,7 +5999,7 @@ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3", @@ -6006,7 +6019,7 @@ "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "es-errors": "^1.3.0", "object-inspect": "^1.13.3" @@ -6023,7 +6036,7 @@ "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -6042,7 +6055,7 @@ "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.2", "es-errors": "^1.3.0", @@ -6062,7 +6075,7 @@ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6072,7 +6085,7 @@ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz", "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "ansi-styles": "^4.0.0", "astral-regex": "^2.0.0", @@ -6120,7 +6133,7 @@ "resolved": "https://registry.npmjs.org/solidity-coverage/-/solidity-coverage-0.8.17.tgz", "integrity": "sha512-5P8vnB6qVX9tt1MfuONtCTEaEGO/O4WuEidPHIAJjx4sktHHKhO3rFvnE0q8L30nWJPTrcqGQMT7jpE29B2qow==", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "@ethersproject/abi": "^5.0.9", "@solidity-parser/parser": "^0.20.1", @@ -6154,14 +6167,14 @@ "resolved": "https://registry.npmjs.org/@solidity-parser/parser/-/parser-0.20.2.tgz", "integrity": "sha512-rbu0bzwNvMcwAjH86hiEAcOeRI2EeK8zCkHDrFykh/Al8mvJeFmjy3UrE7GYQjNwOgbGUUtCn5/k8CB8zIu7QA==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/solidity-coverage/node_modules/ansi-styles": { "version": "3.2.1", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "color-convert": "^1.9.0" }, @@ -6174,7 +6187,7 @@ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "ansi-styles": "^3.2.1", "escape-string-regexp": "^1.0.5", @@ -6189,7 +6202,7 @@ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "color-name": "1.1.3" } @@ -6199,14 +6212,14 @@ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/solidity-coverage/node_modules/escape-string-regexp": { "version": "1.0.5", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=0.8.0" } @@ -6216,7 +6229,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-8.1.0.tgz", "integrity": "sha512-yhlQgA6mnOJUKOsRUFsgJdQCvkKhcz8tlZG5HBQfReYZy46OwLcY+Zia0mtdHsOo9y/hP+CxMN0TU9QxoOtG4g==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^4.0.0", @@ -6231,7 +6244,7 @@ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -6241,7 +6254,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, - "peer": true, + "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -6251,7 +6264,7 @@ "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.4.tgz", "integrity": "sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==", "dev": true, - "peer": true, + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -6264,7 +6277,7 @@ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "has-flag": "^3.0.0" }, @@ -6277,7 +6290,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 4.0.0" } @@ -6288,7 +6301,6 @@ "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", "dev": true, "optional": true, - "peer": true, "dependencies": { "amdefine": ">=0.0.4" }, @@ -6320,7 +6332,7 @@ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", "dev": true, - "peer": true + "license": "BSD-3-Clause" }, "node_modules/stacktrace-parser": { "version": "0.1.11", @@ -6366,7 +6378,7 @@ "resolved": "https://registry.npmjs.org/string-format/-/string-format-2.0.0.tgz", "integrity": "sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA==", "dev": true, - "peer": true + "license": "WTFPL OR MIT" }, "node_modules/string-width": { "version": "4.2.3", @@ -6399,7 +6411,7 @@ "resolved": "https://registry.npmjs.org/strip-hex-prefix/-/strip-hex-prefix-1.0.0.tgz", "integrity": "sha512-q8d4ue7JGEiVcypji1bALTos+0pWtyGlivAWyPuTkHzuTCJqrK9sWxYQZUq6Nq3cuyv3bm734IhHvHtGGURU6A==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "is-hex-prefixed": "1.0.0" }, @@ -6437,7 +6449,7 @@ "resolved": "https://registry.npmjs.org/sync-request/-/sync-request-6.1.0.tgz", "integrity": "sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "http-response-object": "^3.0.1", "sync-rpc": "^1.2.1", @@ -6452,7 +6464,7 @@ "resolved": "https://registry.npmjs.org/sync-rpc/-/sync-rpc-1.3.6.tgz", "integrity": "sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "get-port": "^3.1.0" } @@ -6462,7 +6474,7 @@ "resolved": "https://registry.npmjs.org/table/-/table-6.9.0.tgz", "integrity": "sha512-9kY+CygyYM6j02t5YFHbNz2FN5QmYGv9zAjVp4lCDjlCw7amdckXlEt/bjMhUIfj4ThGRE4gCUH5+yGnNuPo5A==", "dev": true, - "peer": true, + "license": "BSD-3-Clause", "dependencies": { "ajv": "^8.0.1", "lodash.truncate": "^4.4.2", @@ -6479,7 +6491,7 @@ "resolved": "https://registry.npmjs.org/table-layout/-/table-layout-1.0.2.tgz", "integrity": "sha512-qd/R7n5rQTRFi+Zf2sk5XVVd9UQl6ZkduPFC3S7WEGJAmetDTjY3qPN50eSKzwuzEyQKy5TN2TiZdkIjos2L6A==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "array-back": "^4.0.1", "deep-extend": "~0.6.0", @@ -6495,7 +6507,7 @@ "resolved": "https://registry.npmjs.org/array-back/-/array-back-4.0.2.tgz", "integrity": "sha512-NbdMezxqf94cnNfWLL7V/im0Ub+Anbb0IoZhvzie8+4HJ4nMQuzHuy49FkGYCJK2yAloZ3meiB6AVMClbrI1vg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6505,7 +6517,7 @@ "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6515,7 +6527,7 @@ "resolved": "https://registry.npmjs.org/then-request/-/then-request-6.0.2.tgz", "integrity": "sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/concat-stream": "^1.6.0", "@types/form-data": "0.0.33", @@ -6538,14 +6550,14 @@ "resolved": "https://registry.npmjs.org/@types/node/-/node-8.10.66.tgz", "integrity": "sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/then-request/node_modules/form-data": { "version": "2.5.5", "resolved": "https://registry.npmjs.org/form-data/-/form-data-2.5.5.tgz", "integrity": "sha512-jqdObeR2rxZZbPSGL+3VckHMYtu+f9//KXBsVny6JSX/pa38Fy+bGjuG8eW/H6USNQWhLi8Num++cU2yOCNz4A==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "asynckit": "^0.4.0", "combined-stream": "^1.0.8", @@ -6620,7 +6632,7 @@ "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "isarray": "^2.0.5", "safe-buffer": "^5.2.1", @@ -6656,7 +6668,7 @@ "resolved": "https://registry.npmjs.org/ts-command-line-args/-/ts-command-line-args-2.5.1.tgz", "integrity": "sha512-H69ZwTw3rFHb5WYpQya40YAX2/w7Ut75uUECbgBIsLmM+BNuYnxsltfyyLMxy6sEeKxgijLTnQtLd0nKd6+IYw==", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "chalk": "^4.1.0", "command-line-args": "^5.1.1", @@ -6672,7 +6684,7 @@ "resolved": "https://registry.npmjs.org/ts-essentials/-/ts-essentials-7.0.3.tgz", "integrity": "sha512-8+gr5+lqO3G84KdiTSMRLtuyJ+nTBVRKuCrK4lidMPdVeEp0uqC875uE5NMcaA7YYMN7XsNiFQuMvasF8HT/xQ==", "dev": true, - "peer": true, + "license": "MIT", "peerDependencies": { "typescript": ">=3.7.0" } @@ -6734,7 +6746,7 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.7.0.tgz", "integrity": "sha512-gLXCKdN1/j47AiHiOkJN69hJmcbGTHI0ImLmbYLHykhgeN0jVGola9yVjFgzCUklsZQMW55o+dW7IXv3RCXDzA==", "dev": true, - "peer": true + "license": "0BSD" }, "node_modules/tsort": { "version": "0.0.1", @@ -6747,7 +6759,7 @@ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.3.2.tgz", "integrity": "sha512-ZCmOJdvOWDBYJlzAoFkC+Q0+bUyEOS1ltgp1MGU03fqHG+dbi9tBFU2Rd9QKiDZFAYrhPh2JUf7rZRIuHRKtOg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "prelude-ls": "~1.1.2" }, @@ -6760,7 +6772,7 @@ "resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.1.0.tgz", "integrity": "sha512-Acylog8/luQ8L7il+geoSxhEkazvkslg7PSNKOX59mbB9cOveP5aq9h74Y7YU8yDpJwetzQQrfIwtf4Wp4LKcw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=4" } @@ -6782,7 +6794,7 @@ "resolved": "https://registry.npmjs.org/typechain/-/typechain-8.3.2.tgz", "integrity": "sha512-x/sQYr5w9K7yv3es7jo4KTX05CLxOf7TRWwoHlrjRh8H82G64g+k7VuWPJlgMo6qrjfCulOdfBjiaDtmhFYD/Q==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@types/prettier": "^2.1.1", "debug": "^4.3.1", @@ -6807,7 +6819,7 @@ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" @@ -6818,7 +6830,7 @@ "resolved": "https://registry.npmjs.org/fs-extra/-/fs-extra-7.0.1.tgz", "integrity": "sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "graceful-fs": "^4.1.2", "jsonfile": "^4.0.0", @@ -6834,7 +6846,7 @@ "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==", "deprecated": "Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -6855,7 +6867,7 @@ "resolved": "https://registry.npmjs.org/jsonfile/-/jsonfile-4.0.0.tgz", "integrity": "sha512-m6F1R3z8jjlf2imQHS2Qez5sjKWQzbuuhuJ/FKYFRZvPE3PuHcSMVZzfsLhGVOkfd20obL5SWEBew5ShlquNxg==", "dev": true, - "peer": true, + "license": "MIT", "optionalDependencies": { "graceful-fs": "^4.1.6" } @@ -6865,7 +6877,7 @@ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "brace-expansion": "^1.1.7" }, @@ -6878,7 +6890,7 @@ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz", "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==", "dev": true, - "peer": true, + "license": "MIT", "bin": { "mkdirp": "bin/cmd.js" }, @@ -6891,7 +6903,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-0.1.2.tgz", "integrity": "sha512-rBJeI5CXAlmy1pV+617WB9J63U6XcazHHF2f2dbJix4XzpUF0RS3Zbj0FGIOCAva5P/d/GBOYaACQ1w+0azUkg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 4.0.0" } @@ -6901,7 +6913,7 @@ "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -6916,7 +6928,7 @@ "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/typescript": { "version": "5.9.3", @@ -6936,7 +6948,7 @@ "resolved": "https://registry.npmjs.org/typical/-/typical-4.0.0.tgz", "integrity": "sha512-VAH4IvQ7BDFYglMd7BPRDfLgxZZX4O4TFcRDA6EN5X7erNJJq+McIEp8np9aVtxrCJ6qx4GTYVfOWNjcqwZgRw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -6946,8 +6958,8 @@ "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.19.3.tgz", "integrity": "sha512-v3Xu+yuwBXisp6QYTcH4UbH+xYJXqnq2m/LtQVWKWzYc1iehYnLixoQDN9FH6/j9/oybfd6W9Ghwkl8+UMKTKQ==", "dev": true, + "license": "BSD-2-Clause", "optional": true, - "peer": true, "bin": { "uglifyjs": "bin/uglifyjs" }, @@ -6978,7 +6990,7 @@ "resolved": "https://registry.npmjs.org/universalify/-/universalify-2.0.1.tgz", "integrity": "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 10.0.0" } @@ -6997,7 +7009,7 @@ "resolved": "https://registry.npmjs.org/utf8/-/utf8-3.0.0.tgz", "integrity": "sha512-E8VjFIQ/TyQgp+TZfS6l8yp/xWppSAHzidGiRrqe4bK4XP9pTRyKFgGJpO3SN7zdX4DeomTrwaseCHovfpFcqQ==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/util-deprecate": { "version": "1.0.2", @@ -7025,7 +7037,7 @@ "resolved": "https://registry.npmjs.org/web3-utils/-/web3-utils-1.10.4.tgz", "integrity": "sha512-tsu8FiKJLk2PzhDl9fXbGUWTkkVXYhtTA+SmEFkKft+9BgwLxfCRpU96sWv7ICC8zixBNd3JURVoiR3dUXgP8A==", "dev": true, - "peer": true, + "license": "LGPL-3.0", "dependencies": { "@ethereumjs/util": "^8.1.0", "bn.js": "^5.2.1", @@ -7045,7 +7057,7 @@ "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-4.0.1.tgz", "integrity": "sha512-tqsQiBQDQdmPWE1xkkBq4rlSW5QZpLOUJ5RJh2/9fug+q9tnUhuZoVLk7s0scUIKTOzEtR72DFBXI4WiZcMpvw==", "dev": true, - "peer": true, + "license": "MPL-2.0", "bin": { "rlp": "bin/rlp" }, @@ -7058,7 +7070,7 @@ "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-8.1.0.tgz", "integrity": "sha512-zQ0IqbdX8FZ9aw11vP+dZkKDkS+kgIvQPHnSAXzP9pLu+Rfu3D3XEeLbicvoXJTYnhZiPmsZUxgdzXwNKxRPbA==", "dev": true, - "peer": true, + "license": "MPL-2.0", "dependencies": { "@ethereumjs/rlp": "^4.0.1", "ethereum-cryptography": "^2.0.0", @@ -7073,7 +7085,7 @@ "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.4.2.tgz", "integrity": "sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@noble/hashes": "1.4.0" }, @@ -7086,7 +7098,7 @@ "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.4.0.tgz", "integrity": "sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">= 16" }, @@ -7099,7 +7111,7 @@ "resolved": "https://registry.npmjs.org/ethereum-cryptography/-/ethereum-cryptography-2.2.1.tgz", "integrity": "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", @@ -7112,7 +7124,7 @@ "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "dev": true, - "peer": true, + "license": "ISC", "dependencies": { "isexe": "^2.0.0" }, @@ -7125,7 +7137,7 @@ "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", @@ -7159,7 +7171,7 @@ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", "integrity": "sha512-BN22B5eaMMI9UMtjrGd5g5eCYPpCPDUy0FJXbYsaT5zYxjFOckS53SQDE3pWkVoWpHXVb3BrYcEN4Twa55B5cA==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=0.10.0" } @@ -7169,14 +7181,14 @@ "resolved": "https://registry.npmjs.org/wordwrap/-/wordwrap-1.0.0.tgz", "integrity": "sha512-gvVzJFlPycKc5dZN4yPkP8w7Dc37BtP1yczEneOb4uq34pXZcvrtRTmWV8W+Ume+XCxKgbjM+nevkyFPMybd4Q==", "dev": true, - "peer": true + "license": "MIT" }, "node_modules/wordwrapjs": { "version": "4.0.1", "resolved": "https://registry.npmjs.org/wordwrapjs/-/wordwrapjs-4.0.1.tgz", "integrity": "sha512-kKlNACbvHrkpIw6oPeYDSmdCTu2hdMHoyXLTcUKala++lx5Y+wjJ/e474Jqv5abnVmwxw08DiTuHmw69lJGksA==", "dev": true, - "peer": true, + "license": "MIT", "dependencies": { "reduce-flatten": "^2.0.0", "typical": "^5.2.0" @@ -7190,7 +7202,7 @@ "resolved": "https://registry.npmjs.org/typical/-/typical-5.2.0.tgz", "integrity": "sha512-dvdQgNDNJo+8B2uBQoqdb11eUCE1JQXhvjC/CZtgvZseVd5TYMXnq0+vuUemXbd/Se29cTaUuPX3YIc2xgbvIg==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=8" } @@ -7229,7 +7241,7 @@ "resolved": "https://registry.npmjs.org/ws/-/ws-8.17.1.tgz", "integrity": "sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ==", "dev": true, - "peer": true, + "license": "MIT", "engines": { "node": ">=10.0.0" }, diff --git a/contracts/package.json b/contracts/package.json index ac10e05..252f966 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -4,19 +4,35 @@ "scripts": { "compile": "hardhat compile", "test": "hardhat test", - "deploy:flare": "hardhat run scripts/deploy-flare.ts --network coston2", - "deploy:plasma": "hardhat run scripts/deploy-plasma.ts --network plasma" + "deploy:pool": "hardhat run scripts/deploy-pool.ts --network coston2", + "deploy:pool:local": "hardhat run scripts/deploy-pool.ts --network localhost", + "deploy:flare": "hardhat run scripts/deploy-pool.ts --network coston2", + "deploy:plasma": "hardhat run scripts/deploy-escrow.ts --network plasma", + "e2e": "hardhat run scripts/e2e-test.ts --network coston2", + "e2e:local": "hardhat run scripts/e2e-test.ts --network localhost" }, "devDependencies": { + "@nomicfoundation/hardhat-chai-matchers": "^2.1.0", + "@nomicfoundation/hardhat-ethers": "^3.1.3", + "@nomicfoundation/hardhat-network-helpers": "^1.1.2", "@nomicfoundation/hardhat-toolbox": "^4.0.0", + "@nomicfoundation/hardhat-verify": "^2.1.3", + "@typechain/ethers-v6": "^0.5.1", + "@typechain/hardhat": "^9.1.0", "@types/chai": "^5.2.3", "@types/mocha": "^10.0.10", "@types/node": "^20.0.0", + "chai": "^4.5.0", + "ethers": "^6.16.0", "hardhat": "^2.19.0", + "hardhat-gas-reporter": "^1.0.10", + "solidity-coverage": "^0.8.17", "ts-node": "^10.9.0", + "typechain": "^8.3.2", "typescript": "^5.3.0" }, "dependencies": { - "@openzeppelin/contracts": "^5.0.0" + "@openzeppelin/contracts": "^5.0.0", + "dotenv": "^17.2.4" } } diff --git a/contracts/scripts/deploy-escrow.ts b/contracts/scripts/deploy-escrow.ts new file mode 100644 index 0000000..4a3f286 --- /dev/null +++ b/contracts/scripts/deploy-escrow.ts @@ -0,0 +1,56 @@ +import { ethers } from "hardhat"; + +/** + * Deploy LottoEscrow to Plasma network + * + * The escrow handles: + * - Ticket purchases (player deposits USDT) + * - Payouts to winners (owner-only, triggered by backend) + * - Charity distributions + * + * Usage: npx hardhat run scripts/deploy-escrow.ts --network plasma + */ + +// Plasma mainnet USDT +const PLASMA_USDT = process.env.PLASMA_USDT_ADDRESS || "0xB8CE59FC3717ada4C02eaDF9682A9e934F625ebb"; + +async function main() { + const [deployer] = await ethers.getSigners(); + console.log("Deploying LottoEscrow with account:", deployer.address); + + const balance = await ethers.provider.getBalance(deployer.address); + console.log("Account balance:", ethers.formatEther(balance)); + + if (balance === 0n) { + console.warn("\n⚠️ WARNING: Account has 0 balance. You need native tokens for gas.\n"); + } + + console.log(`\nUsing USDT address: ${PLASMA_USDT}`); + + // Deploy LottoEscrow + console.log("\nDeploying LottoEscrow..."); + const LottoEscrow = await ethers.getContractFactory("LottoEscrow"); + const escrow = await LottoEscrow.deploy(PLASMA_USDT); + await escrow.waitForDeployment(); + const escrowAddress = await escrow.getAddress(); + + console.log("\n" + "=".repeat(60)); + console.log("DEPLOYMENT COMPLETE"); + console.log("=".repeat(60)); + console.log(`\n ESCROW_ADDRESS=${escrowAddress}`); + console.log(` Owner (payout signer): ${deployer.address}`); + console.log(` USDT: ${PLASMA_USDT}`); + + console.log("\n\nAdd to backend/.env:"); + console.log("─".repeat(60)); + console.log(`ESCROW_ADDRESS=${escrowAddress}`); + console.log(`ESCROW_PRIVATE_KEY=${process.env.PRIVATE_KEY || ""}`); + console.log("─".repeat(60)); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error("Deployment failed:", error); + process.exit(1); + }); diff --git a/contracts/scripts/deploy-pool.ts b/contracts/scripts/deploy-pool.ts new file mode 100644 index 0000000..65acb00 --- /dev/null +++ b/contracts/scripts/deploy-pool.ts @@ -0,0 +1,287 @@ +import { ethers } from "hardhat"; +import * as fs from "fs"; +import * as path from "path"; + +/** + * Deploy script for LottoLink CaaS contracts on Coston2 testnet + * + * Deploys: + * 1. MockUSDT (or use existing testnet USDT) + * 2. SharedLiquidityPool + * 3. LotteryRegistry + * 4. DrawManager + * 5. PayoutCalculator + * + * Features: + * - Lock file prevents concurrent deployments + * - Manifest tracks deployed addresses per network to avoid overlap + * - Use --force flag to redeploy even if manifest exists + * + * Usage: npx hardhat run scripts/deploy-pool.ts --network coston2 + */ + +// Coston2 addresses (update with actual deployed addresses if available) +const COSTON2_RNG_ADDRESS = "0x5Cdf9eaF3eB8B44Fb696F8D6a062Ef6b1E1fd909"; // RandomNumberV2 on Coston2 +const COSTON2_FTSO_ADDRESS = "0x3d893C53D9e8056135C26C8c638B76C8b60Df726"; // FtsoV2 on Coston2 + +const LOCK_FILE = path.resolve(__dirname, "../.deployment-lock"); +const MANIFEST_FILE = path.resolve(__dirname, "../deployment-manifest.json"); + +interface DeploymentManifest { + [network: string]: { + deployer: string; + timestamp: string; + contracts: { + USDT_ADDRESS: string; + POOL_ADDRESS: string; + REGISTRY_ADDRESS: string; + DRAW_MANAGER_ADDRESS: string; + PAYOUT_CALC_ADDRESS: string; + }; + }; +} + +function acquireLock(): void { + if (fs.existsSync(LOCK_FILE)) { + const lockData = fs.readFileSync(LOCK_FILE, "utf-8"); + const lock = JSON.parse(lockData); + const ageMs = Date.now() - lock.timestamp; + const STALE_THRESHOLD = 10 * 60 * 1000; // 10 minutes + + if (ageMs < STALE_THRESHOLD) { + throw new Error( + `Deployment already in progress (started ${Math.round(ageMs / 1000)}s ago by ${lock.deployer}). ` + + `If this is stale, delete ${LOCK_FILE}` + ); + } + console.warn(`⚠️ Stale lock detected (${Math.round(ageMs / 60000)}min old). Overriding...`); + } + fs.writeFileSync(LOCK_FILE, JSON.stringify({ + deployer: process.env.USER || "unknown", + timestamp: Date.now(), + pid: process.pid + })); +} + +function releaseLock(): void { + if (fs.existsSync(LOCK_FILE)) { + fs.unlinkSync(LOCK_FILE); + } +} + +function loadManifest(): DeploymentManifest { + if (fs.existsSync(MANIFEST_FILE)) { + return JSON.parse(fs.readFileSync(MANIFEST_FILE, "utf-8")); + } + return {}; +} + +function saveManifest(manifest: DeploymentManifest): void { + fs.writeFileSync(MANIFEST_FILE, JSON.stringify(manifest, null, 2)); +} + +async function checkExistingDeployment(network: string): Promise { + const manifest = loadManifest(); + const existing = manifest[network]; + if (!existing) return false; + + console.log(`\n⚠️ Found existing deployment on "${network}":`); + console.log(` Deployer: ${existing.deployer}`); + console.log(` Timestamp: ${existing.timestamp}`); + for (const [key, addr] of Object.entries(existing.contracts)) { + // Verify contract still has code at the address + const code = await ethers.provider.getCode(addr); + const alive = code !== "0x"; + console.log(` ${key}: ${addr} ${alive ? "✅ active" : "❌ no code"}`); + } + + const force = process.env.FORCE_DEPLOY === "true"; + if (!force) { + console.log("\n To redeploy, set FORCE_DEPLOY=true"); + return true; // deployment exists, skip + } + console.log("\n FORCE_DEPLOY=true — proceeding with fresh deployment..."); + return false; +} + +async function main() { + const network = (await ethers.provider.getNetwork()).name || "unknown"; + const chainId = Number((await ethers.provider.getNetwork()).chainId); + const networkKey = chainId === 114 ? "coston2" : chainId === 31337 ? "localhost" : `chain-${chainId}`; + + acquireLock(); + + try { + // Check for existing deployment + if (await checkExistingDeployment(networkKey)) { + console.log("\n✅ Existing deployment is active. Skipping. Use FORCE_DEPLOY=true to override."); + return; + } + + const signers = await ethers.getSigners(); + console.log("Got signers:", signers.length); + + if (signers.length === 0) { + throw new Error("No signers available. Check PRIVATE_KEY in .env"); + } + + const deployer = signers[0]; + console.log("Deploying contracts with account:", deployer.address); + + const balance = await ethers.provider.getBalance(deployer.address); + console.log("Account balance:", ethers.formatEther(balance), "C2FLR"); + + if (balance === 0n) { + console.warn("\n⚠️ WARNING: Account has 0 C2FLR balance. Get testnet tokens from:"); + console.warn(" https://faucet.flare.network/coston2\n"); + } + + // ============ Step 1: Deploy MockUSDT ============ + console.log("\n1. Deploying MockUSDT..."); + const MockUSDT = await ethers.getContractFactory("MockUSDT"); + const usdt = await MockUSDT.deploy(); + await usdt.waitForDeployment(); + const usdtAddress = await usdt.getAddress(); + console.log(" MockUSDT deployed to:", usdtAddress); + + // Mint initial USDT to deployer for testing + const mintAmount = ethers.parseUnits("1000000", 6); // 1M USDT + await usdt.mint(deployer.address, mintAmount); + console.log(" Minted 1,000,000 USDT to deployer"); + + // ============ Step 2: Deploy SharedLiquidityPool ============ + console.log("\n2. Deploying SharedLiquidityPool..."); + const SharedLiquidityPool = await ethers.getContractFactory("SharedLiquidityPool"); + const pool = await SharedLiquidityPool.deploy(usdtAddress); + await pool.waitForDeployment(); + const poolAddress = await pool.getAddress(); + console.log(" SharedLiquidityPool deployed to:", poolAddress); + + // ============ Step 3: Deploy LotteryRegistry ============ + console.log("\n3. Deploying LotteryRegistry..."); + const LotteryRegistry = await ethers.getContractFactory("LotteryRegistry"); + const registry = await LotteryRegistry.deploy(); + await registry.waitForDeployment(); + const registryAddress = await registry.getAddress(); + console.log(" LotteryRegistry deployed to:", registryAddress); + + // ============ Step 4: Deploy DrawManager ============ + console.log("\n4. Deploying DrawManager..."); + const DrawManager = await ethers.getContractFactory("DrawManager"); + const drawManager = await DrawManager.deploy(COSTON2_RNG_ADDRESS, registryAddress); + await drawManager.waitForDeployment(); + const drawManagerAddress = await drawManager.getAddress(); + console.log(" DrawManager deployed to:", drawManagerAddress); + + // ============ Step 5: Deploy PayoutCalculator ============ + console.log("\n5. Deploying PayoutCalculator..."); + const PayoutCalculator = await ethers.getContractFactory("PayoutCalculator"); + const payoutCalc = await PayoutCalculator.deploy( + COSTON2_FTSO_ADDRESS, + drawManagerAddress, + registryAddress, + poolAddress + ); + await payoutCalc.waitForDeployment(); + const payoutCalcAddress = await payoutCalc.getAddress(); + console.log(" PayoutCalculator deployed to:", payoutCalcAddress); + + // ============ Step 6: Authorize PayoutCalculator to call pool.executePayout() ============ + console.log("\n6. Authorizing PayoutCalculator on pool..."); + await pool.setPayoutAuthorized(payoutCalcAddress, true); + console.log(" PayoutCalculator authorized for payouts"); + + // ============ Summary ============ + console.log("\n" + "=".repeat(60)); + console.log("DEPLOYMENT COMPLETE"); + console.log("=".repeat(60)); + console.log("\nContract Addresses:"); + console.log(` USDT_ADDRESS=${usdtAddress}`); + console.log(` POOL_ADDRESS=${poolAddress}`); + console.log(` REGISTRY_ADDRESS=${registryAddress}`); + console.log(` DRAW_MANAGER_ADDRESS=${drawManagerAddress}`); + console.log(` PAYOUT_CALC_ADDRESS=${payoutCalcAddress}`); + + console.log("\n\nCopy to backend/.env:"); + console.log("─".repeat(60)); + console.log(`USDT_ADDRESS=${usdtAddress}`); + console.log(`POOL_ADDRESS=${poolAddress}`); + console.log(`REGISTRY_ADDRESS=${registryAddress}`); + console.log(`DRAW_MANAGER_ADDRESS=${drawManagerAddress}`); + console.log(`PAYOUT_CALC_ADDRESS=${payoutCalcAddress}`); + console.log("─".repeat(60)); + + // ============ Optional: Seed initial data ============ + console.log("\n\n7. Seeding initial data..."); + + // Register deployer as operator on pool + await pool.registerOperator(deployer.address); + console.log(" Registered deployer as operator"); + + // Approve pool to spend deployer's USDT + await usdt.approve(poolAddress, ethers.MaxUint256); + console.log(" Approved pool to spend USDT"); + + // Deposit initial liquidity + const depositAmount = ethers.parseUnits("100000", 6); // 100K USDT + await pool.depositLiquidity(depositAmount); + console.log(" Deposited 100,000 USDT initial liquidity"); + + // Pay initial premium + const premiumAmount = ethers.parseUnits("5000", 6); // 5K USDT + await pool.collectPremium(premiumAmount); + console.log(" Paid 5,000 USDT initial premium"); + + // Register a demo lottery + await registry.registerLottery( + "Flare Jackpot Demo", // name + ethers.parseUnits("5", 6), // ticketPriceUSD (5 USDT) + 3600, // drawIntervalSec (1 hour) + 5, // charityPercent + deployer.address, // charityAddress (deployer for demo) + 49, // numberRange (1-49) + 6, // numbersPerTicket + true, // usesFlareRNG + "", // externalApiUrl + "", // jqFilter + 500 // premiumRate (5% = 500 basis points) + ); + console.log(" Registered demo lottery: Flare Jackpot Demo"); + + // Final pool stats + const [tvl, lpCount, opCount, premiums, payouts] = await pool.getPoolStats(); + console.log("\n\nPool Stats:"); + console.log(` TVL: ${ethers.formatUnits(tvl, 6)} USDT`); + console.log(` Operators: ${opCount}`); + console.log(` Premiums collected: ${ethers.formatUnits(premiums, 6)} USDT`); + + // ============ Save deployment manifest ============ + const manifest = loadManifest(); + manifest[networkKey] = { + deployer: deployer.address, + timestamp: new Date().toISOString(), + contracts: { + USDT_ADDRESS: usdtAddress, + POOL_ADDRESS: poolAddress, + REGISTRY_ADDRESS: registryAddress, + DRAW_MANAGER_ADDRESS: drawManagerAddress, + PAYOUT_CALC_ADDRESS: payoutCalcAddress + } + }; + saveManifest(manifest); + console.log(`\n📋 Deployment manifest saved to ${MANIFEST_FILE}`); + + console.log("\n✅ Deployment and seeding complete!"); + + } finally { + releaseLock(); + } +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + releaseLock(); + console.error("Deployment failed:", error); + process.exit(1); + }); diff --git a/contracts/scripts/e2e-test.ts b/contracts/scripts/e2e-test.ts new file mode 100644 index 0000000..b844de2 --- /dev/null +++ b/contracts/scripts/e2e-test.ts @@ -0,0 +1,205 @@ +import { ethers } from "hardhat"; +import * as fs from "fs"; +import * as path from "path"; + +/** + * End-to-end test script for LottoLink on Coston2 (or localhost). + * + * Reads deployed addresses from deployment-manifest.json and runs the full flow: + * 1. LP deposits USDT into pool + * 2. Operator registers lottery with premium rate + * 3. Operator pays premium + * 4. Register tickets for a draw + * 5. Execute draw via DrawManager (Flare Secure RNG) + * 6. Calculate payouts (from pool) + * 7. Verify pool stats reflect all operations + * + * Usage: npx hardhat run scripts/e2e-test.ts --network coston2 + * npx hardhat run scripts/e2e-test.ts --network localhost + */ + +const MANIFEST_FILE = path.resolve(__dirname, "../deployment-manifest.json"); + +interface Contracts { + USDT_ADDRESS: string; + POOL_ADDRESS: string; + REGISTRY_ADDRESS: string; + DRAW_MANAGER_ADDRESS: string; + PAYOUT_CALC_ADDRESS: string; +} + +function loadAddresses(): Contracts { + if (!fs.existsSync(MANIFEST_FILE)) { + throw new Error(`No deployment manifest found at ${MANIFEST_FILE}. Run deploy-pool.ts first.`); + } + const manifest = JSON.parse(fs.readFileSync(MANIFEST_FILE, "utf-8")); + const chainId = 31337; // will be overridden below + return manifest; +} + +async function main() { + const network = await ethers.provider.getNetwork(); + const chainId = Number(network.chainId); + const networkKey = chainId === 114 ? "coston2" : chainId === 31337 ? "localhost" : `chain-${chainId}`; + + console.log("=".repeat(60)); + console.log(`LottoLink E2E Test — ${networkKey} (chainId: ${chainId})`); + console.log("=".repeat(60)); + + // Load manifest + if (!fs.existsSync(MANIFEST_FILE)) { + throw new Error(`No deployment manifest at ${MANIFEST_FILE}. Run deploy-pool.ts first.`); + } + const manifest = JSON.parse(fs.readFileSync(MANIFEST_FILE, "utf-8")); + const deployment = manifest[networkKey]; + if (!deployment) { + throw new Error(`No deployment found for network "${networkKey}" in manifest. Available: ${Object.keys(manifest).join(", ")}`); + } + const addrs: Contracts = deployment.contracts; + + const [signer] = await ethers.getSigners(); + console.log(`\nSigner: ${signer.address}`); + const balance = await ethers.provider.getBalance(signer.address); + console.log(`Balance: ${ethers.formatEther(balance)} native tokens\n`); + + // Get contract instances + const usdt = await ethers.getContractAt("MockUSDT", addrs.USDT_ADDRESS); + const pool = await ethers.getContractAt("SharedLiquidityPool", addrs.POOL_ADDRESS); + const registry = await ethers.getContractAt("LotteryRegistry", addrs.REGISTRY_ADDRESS); + const drawManager = await ethers.getContractAt("DrawManager", addrs.DRAW_MANAGER_ADDRESS); + const payoutCalc = await ethers.getContractAt("PayoutCalculator", addrs.PAYOUT_CALC_ADDRESS); + + let step = 0; + const pass = (msg: string) => console.log(` ✅ ${msg}`); + const info = (msg: string) => console.log(` ℹ️ ${msg}`); + + // ============ Step 1: LP Deposit ============ + console.log(`\n${++step}. LP Deposits USDT`); + const depositAmount = ethers.parseUnits("10000", 6); + const usdtBal = await usdt.balanceOf(signer.address); + info(`USDT balance: ${ethers.formatUnits(usdtBal, 6)}`); + + if (usdtBal < depositAmount) { + info("Minting USDT for test..."); + await (await usdt.mint(signer.address, ethers.parseUnits("100000", 6))).wait(); + } + + await (await usdt.approve(addrs.POOL_ADDRESS, ethers.MaxUint256)).wait(); + const depositTx = await pool.depositLiquidity(depositAmount); + const depositReceipt = await depositTx.wait(); + const sharesAfterDeposit = await pool.balanceOf(signer.address); + pass(`Deposited 10,000 USDT → received ${ethers.formatUnits(sharesAfterDeposit, 6)} shares (tx: ${depositReceipt!.hash.slice(0, 10)}...)`); + + // ============ Step 2: Register Lottery ============ + console.log(`\n${++step}. Register Lottery`); + const regTx = await registry.registerLottery( + "E2E Test Lottery", // name + ethers.parseUnits("2", 6), // ticketPriceUSD + 3600, // drawIntervalSec + 10, // charityPercent + signer.address, // charityAddress + 49, // numberRange + 6, // numbersPerTicket + true, // usesFlareRNG + "", // externalApiUrl + "", // jqFilter + 500 // premiumRate (5%) + ); + await regTx.wait(); + const nextLottery = await registry.nextLotteryId(); + const lotteryId = Number(nextLottery) - 1; + pass(`Registered lottery id=${lotteryId} with 5% premium rate`); + + // ============ Step 3: Operator Pays Premium ============ + console.log(`\n${++step}. Operator Pays Premium`); + const isOp = await pool.isOperator(signer.address); + if (!isOp) { + info("Registering signer as operator..."); + await (await pool.registerOperator(signer.address)).wait(); + } + const premiumAmount = ethers.parseUnits("500", 6); + await (await usdt.approve(addrs.POOL_ADDRESS, ethers.MaxUint256)).wait(); + const premTx = await pool.collectPremium(premiumAmount); + await premTx.wait(); + pass(`Paid 500 USDT premium`); + + // ============ Step 4: Register Tickets ============ + console.log(`\n${++step}. Register Tickets for Draw`); + const drawId = Number(await drawManager.nextDrawId()); + + // Register 3 test tickets + const ticketNumbers = [ + [1, 7, 14, 22, 35, 49], + [3, 11, 17, 28, 33, 44], + [5, 12, 19, 25, 38, 46] + ]; + for (const nums of ticketNumbers) { + await (await registry.registerTicket(lotteryId, signer.address, nums, drawId)).wait(); + } + pass(`Registered ${ticketNumbers.length} tickets for drawId=${drawId}`); + + // ============ Step 5: Execute Draw ============ + console.log(`\n${++step}. Execute Draw via DrawManager`); + try { + const drawTx = await drawManager.executeDraw(lotteryId); + const drawReceipt = await drawTx.wait(); + const winningNumbers = await drawManager.getWinningNumbers(drawId); + pass(`Draw executed! Winning numbers: [${winningNumbers.join(", ")}] (tx: ${drawReceipt!.hash.slice(0, 10)}...)`); + } catch (err: any) { + info(`Draw execution failed (expected on localhost without RNG mock): ${err.message.slice(0, 100)}`); + info("Skipping payout step..."); + + // Still show pool stats + console.log(`\n${++step}. Pool Stats (final)`); + const [tvl, lpCount, opCount, premiums, payouts] = await pool.getPoolStats(); + const sharePrice = await pool.getSharePrice(); + info(`TVL: ${ethers.formatUnits(tvl, 6)} USDT`); + info(`Operators: ${opCount}`); + info(`Premiums: ${ethers.formatUnits(premiums, 6)} USDT`); + info(`Payouts: ${ethers.formatUnits(payouts, 6)} USDT`); + info(`Share price: ${ethers.formatUnits(sharePrice, 18)} USDT/share`); + + console.log("\n" + "=".repeat(60)); + console.log("E2E Test Complete (draw skipped — no RNG on this network)"); + console.log("=".repeat(60)); + return; + } + + // ============ Step 6: Calculate Payouts ============ + console.log(`\n${++step}. Calculate Payouts from Pool`); + const prizePool = ethers.parseUnits("5000", 6); + try { + const payoutTx = await payoutCalc.calculatePayouts(lotteryId, drawId, prizePool); + const payoutReceipt = await payoutTx.wait(); + pass(`Payouts calculated and executed from pool (tx: ${payoutReceipt!.hash.slice(0, 10)}...)`); + } catch (err: any) { + info(`Payout calculation note: ${err.message.slice(0, 120)}`); + } + + // ============ Step 7: Final Pool Stats ============ + console.log(`\n${++step}. Pool Stats (final)`); + const [tvl, lpCount, opCount, premiums, payouts] = await pool.getPoolStats(); + const utilization = await pool.getUtilization(); + const sharePrice = await pool.getSharePrice(); + const lpPos = await pool.getLPPosition(signer.address); + + info(`TVL: ${ethers.formatUnits(tvl, 6)} USDT`); + info(`Operators: ${opCount}`); + info(`Premiums: ${ethers.formatUnits(premiums, 6)} USDT`); + info(`Payouts: ${ethers.formatUnits(payouts, 6)} USDT`); + info(`Utilization: ${Number(utilization) / 100}%`); + info(`Share price: ${ethers.formatUnits(sharePrice, 18)} USDT/share`); + info(`LP shares: ${ethers.formatUnits(lpPos[0], 6)}`); + info(`LP value: ${ethers.formatUnits(lpPos[1], 6)} USDT`); + + console.log("\n" + "=".repeat(60)); + console.log("E2E Test Complete — All steps passed!"); + console.log("=".repeat(60)); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error("\nE2E Test Failed:", error); + process.exit(1); + }); diff --git a/contracts/test/LotteryRegistry.test.ts b/contracts/test/LotteryRegistry.test.ts new file mode 100644 index 0000000..f83ca64 --- /dev/null +++ b/contracts/test/LotteryRegistry.test.ts @@ -0,0 +1,215 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { LotteryRegistry } from "../typechain-types"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + +describe("LotteryRegistry", function () { + let registry: LotteryRegistry; + let owner: SignerWithAddress; + let operator1: SignerWithAddress; + let operator2: SignerWithAddress; + let player1: SignerWithAddress; + let charity: SignerWithAddress; + + beforeEach(async function () { + [owner, operator1, operator2, player1, charity] = await ethers.getSigners(); + + const LotteryRegistry = await ethers.getContractFactory("LotteryRegistry"); + registry = await LotteryRegistry.deploy(); + await registry.waitForDeployment(); + }); + + describe("Lottery Registration", function () { + it("should register a lottery with premium rate", async function () { + const tx = await registry.connect(operator1).registerLottery( + "Test Lottery", + ethers.parseUnits("5", 6), // 5 USDT + 3600, // 1 hour + 10, // 10% charity + charity.address, + 49, // 1-49 + 6, // pick 6 + true, // uses Flare RNG + "", + "", + 500 // 5% premium rate + ); + + await expect(tx) + .to.emit(registry, "LotteryRegistered") + .withArgs(0, operator1.address, "Test Lottery", 500); + + const lottery = await registry.lotteries(0); + expect(lottery.operator).to.equal(operator1.address); + expect(lottery.name).to.equal("Test Lottery"); + expect(lottery.premiumRate).to.equal(500); + expect(lottery.active).to.be.true; + }); + + it("should reject premium rate exceeding 20%", async function () { + await expect( + registry.connect(operator1).registerLottery( + "Bad Lottery", + ethers.parseUnits("5", 6), + 3600, + 10, + charity.address, + 49, + 6, + true, + "", + "", + 2001 // 20.01% - should fail + ) + ).to.be.revertedWith("Premium rate cannot exceed 20%"); + }); + + it("should reject charity percent exceeding 50%", async function () { + await expect( + registry.connect(operator1).registerLottery( + "Bad Lottery", + ethers.parseUnits("5", 6), + 3600, + 51, // 51% - should fail + charity.address, + 49, + 6, + true, + "", + "", + 500 + ) + ).to.be.revertedWith("Charity percent cannot exceed 50%"); + }); + + it("should increment lottery ID for each registration", async function () { + await registry.connect(operator1).registerLottery( + "Lottery 1", ethers.parseUnits("5", 6), 3600, 5, charity.address, 49, 6, true, "", "", 500 + ); + await registry.connect(operator2).registerLottery( + "Lottery 2", ethers.parseUnits("10", 6), 7200, 10, charity.address, 59, 6, false, "https://api.example.com", ".numbers", 300 + ); + + expect(await registry.nextLotteryId()).to.equal(2); + + const lottery1 = await registry.lotteries(0); + const lottery2 = await registry.lotteries(1); + + expect(lottery1.name).to.equal("Lottery 1"); + expect(lottery2.name).to.equal("Lottery 2"); + expect(lottery2.premiumRate).to.equal(300); + }); + }); + + describe("Premium Rate Management", function () { + beforeEach(async function () { + await registry.connect(operator1).registerLottery( + "Test Lottery", ethers.parseUnits("5", 6), 3600, 5, charity.address, 49, 6, true, "", "", 500 + ); + }); + + it("should allow operator to update premium rate", async function () { + const tx = await registry.connect(operator1).updatePremiumRate(0, 750); + + await expect(tx) + .to.emit(registry, "PremiumRateUpdated") + .withArgs(0, 500, 750); + + expect(await registry.getPremiumRate(0)).to.equal(750); + }); + + it("should reject premium rate update from non-operator", async function () { + await expect( + registry.connect(operator2).updatePremiumRate(0, 750) + ).to.be.revertedWith("Not operator"); + }); + + it("should reject premium rate update exceeding 20%", async function () { + await expect( + registry.connect(operator1).updatePremiumRate(0, 2001) + ).to.be.revertedWith("Premium rate cannot exceed 20%"); + }); + + it("should calculate premium correctly", async function () { + const ticketRevenue = ethers.parseUnits("10000", 6); // 10K USDT + const premium = await registry.calculatePremium(0, ticketRevenue); + + // 5% of 10000 = 500 + expect(premium).to.equal(ethers.parseUnits("500", 6)); + }); + }); + + describe("Ticket Registration", function () { + beforeEach(async function () { + await registry.connect(operator1).registerLottery( + "Test Lottery", ethers.parseUnits("5", 6), 3600, 5, charity.address, 49, 6, true, "", "", 500 + ); + }); + + it("should register a ticket with correct numbers", async function () { + const numbers = [1, 7, 15, 23, 38, 42]; + const tx = await registry.registerTicket(0, player1.address, numbers, 1); + + await expect(tx) + .to.emit(registry, "TicketPurchased") + .withArgs(0, 0, player1.address, numbers); + + const ticketNumbers = await registry.getTicketNumbers(0); + expect(ticketNumbers.map(n => Number(n))).to.deep.equal(numbers); + expect(await registry.getTicketPlayer(0)).to.equal(player1.address); + }); + + it("should reject ticket with wrong number count", async function () { + const wrongNumbers = [1, 2, 3, 4, 5]; // Only 5 numbers, should be 6 + + await expect( + registry.registerTicket(0, player1.address, wrongNumbers, 1) + ).to.be.revertedWith("Invalid number count"); + }); + + it("should reject ticket for inactive lottery", async function () { + await registry.connect(operator1).deactivateLottery(0); + + await expect( + registry.registerTicket(0, player1.address, [1, 2, 3, 4, 5, 6], 1) + ).to.be.revertedWith("Lottery not active"); + }); + + it("should associate tickets with draws correctly", async function () { + await registry.registerTicket(0, player1.address, [1, 2, 3, 4, 5, 6], 1); + await registry.registerTicket(0, player1.address, [7, 8, 9, 10, 11, 12], 1); + await registry.registerTicket(0, player1.address, [13, 14, 15, 16, 17, 18], 2); + + const draw1Tickets = await registry.getDrawTickets(1); + const draw2Tickets = await registry.getDrawTickets(2); + + expect(draw1Tickets.length).to.equal(2); + expect(draw2Tickets.length).to.equal(1); + }); + }); + + describe("Lottery Deactivation", function () { + beforeEach(async function () { + await registry.connect(operator1).registerLottery( + "Test Lottery", ethers.parseUnits("5", 6), 3600, 5, charity.address, 49, 6, true, "", "", 500 + ); + }); + + it("should allow operator to deactivate lottery", async function () { + const tx = await registry.connect(operator1).deactivateLottery(0); + + await expect(tx) + .to.emit(registry, "LotteryDeactivated") + .withArgs(0); + + const lottery = await registry.lotteries(0); + expect(lottery.active).to.be.false; + }); + + it("should reject deactivation from non-operator", async function () { + await expect( + registry.connect(operator2).deactivateLottery(0) + ).to.be.revertedWith("Not operator"); + }); + }); +}); diff --git a/contracts/test/PayoutCalculator.test.ts b/contracts/test/PayoutCalculator.test.ts new file mode 100644 index 0000000..168bd0d --- /dev/null +++ b/contracts/test/PayoutCalculator.test.ts @@ -0,0 +1,188 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { + PayoutCalculator, + SharedLiquidityPool, + LotteryRegistry, + DrawManager, + MockUSDT +} from "../typechain-types"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + +/** + * Integration tests for PayoutCalculator with SharedLiquidityPool + * Tests the full flow: operator pays premium -> draw happens -> payout from pool + */ +describe("PayoutCalculator Integration", function () { + let payoutCalc: PayoutCalculator; + let pool: SharedLiquidityPool; + let registry: LotteryRegistry; + let drawManager: DrawManager; + let usdt: MockUSDT; + + let owner: SignerWithAddress; + let lp1: SignerWithAddress; + let operator: SignerWithAddress; + let player1: SignerWithAddress; + let player2: SignerWithAddress; + + const INITIAL_LIQUIDITY = ethers.parseUnits("100000", 6); // 100K USDT + const PREMIUM_AMOUNT = ethers.parseUnits("5000", 6); // 5K USDT + const PRIZE_POOL = ethers.parseUnits("10000", 6); // 10K USDT + + // Mock addresses for RNG and FTSO (not used in these tests) + const MOCK_RNG = "0x0000000000000000000000000000000000000001"; + const MOCK_FTSO = "0x0000000000000000000000000000000000000002"; + + beforeEach(async function () { + [owner, lp1, operator, player1, player2] = await ethers.getSigners(); + + // Deploy MockUSDT + const MockUSDT = await ethers.getContractFactory("MockUSDT"); + usdt = await MockUSDT.deploy(); + await usdt.waitForDeployment(); + + // Deploy SharedLiquidityPool + const SharedLiquidityPool = await ethers.getContractFactory("SharedLiquidityPool"); + pool = await SharedLiquidityPool.deploy(await usdt.getAddress()); + await pool.waitForDeployment(); + + // Deploy LotteryRegistry + const LotteryRegistry = await ethers.getContractFactory("LotteryRegistry"); + registry = await LotteryRegistry.deploy(); + await registry.waitForDeployment(); + + // Deploy DrawManager (with mock RNG) + const DrawManager = await ethers.getContractFactory("DrawManager"); + drawManager = await DrawManager.deploy(MOCK_RNG, await registry.getAddress()); + await drawManager.waitForDeployment(); + + // Deploy PayoutCalculator with pool integration + const PayoutCalculator = await ethers.getContractFactory("PayoutCalculator"); + payoutCalc = await PayoutCalculator.deploy( + MOCK_FTSO, + await drawManager.getAddress(), + await registry.getAddress(), + await pool.getAddress() + ); + await payoutCalc.waitForDeployment(); + + // Setup: Mint USDT to LP and operator + await usdt.mint(lp1.address, INITIAL_LIQUIDITY * 2n); + await usdt.mint(operator.address, PREMIUM_AMOUNT * 10n); + + // Setup: LP deposits liquidity + await usdt.connect(lp1).approve(await pool.getAddress(), ethers.MaxUint256); + await pool.connect(lp1).depositLiquidity(INITIAL_LIQUIDITY); + + // Setup: Register operator + await pool.registerOperator(operator.address); + + // Setup: Operator pays premium + await usdt.connect(operator).approve(await pool.getAddress(), ethers.MaxUint256); + await pool.connect(operator).collectPremium(PREMIUM_AMOUNT); + }); + + describe("Contract Setup", function () { + it("should have pool reference set correctly", async function () { + expect(await payoutCalc.pool()).to.equal(await pool.getAddress()); + }); + + it("should have registry reference set correctly", async function () { + expect(await payoutCalc.registry()).to.equal(await registry.getAddress()); + }); + + it("should have drawManager reference set correctly", async function () { + expect(await payoutCalc.drawManager()).to.equal(await drawManager.getAddress()); + }); + }); + + describe("Pool State Before Payouts", function () { + it("should have correct TVL after LP deposit and premium", async function () { + const tvl = await pool.getTVL(); + expect(tvl).to.equal(INITIAL_LIQUIDITY + PREMIUM_AMOUNT); + }); + + it("should have operator registered", async function () { + expect(await pool.isOperator(operator.address)).to.be.true; + }); + + it("should track premiums collected", async function () { + expect(await pool.totalPremiumsCollected()).to.equal(PREMIUM_AMOUNT); + }); + }); + + describe("Payout Execution", function () { + it("should execute payout from pool to winner", async function () { + const payoutAmount = ethers.parseUnits("1000", 6); + const winnerBalanceBefore = await usdt.balanceOf(player1.address); + + // Only owner can call executePayout directly + await pool.executePayout(player1.address, payoutAmount, 0, 0); + + const winnerBalanceAfter = await usdt.balanceOf(player1.address); + expect(winnerBalanceAfter - winnerBalanceBefore).to.equal(payoutAmount); + }); + + it("should track total payouts distributed", async function () { + const payoutAmount = ethers.parseUnits("1000", 6); + + await pool.executePayout(player1.address, payoutAmount, 0, 0); + await pool.executePayout(player2.address, payoutAmount, 0, 0); + + expect(await pool.totalPayoutsDistributed()).to.equal(payoutAmount * 2n); + }); + + it("should emit PayoutExecuted event", async function () { + const payoutAmount = ethers.parseUnits("1000", 6); + + await expect(pool.executePayout(player1.address, payoutAmount, 1, 5)) + .to.emit(pool, "PayoutExecuted") + .withArgs(player1.address, payoutAmount, 1, 5); + }); + + it("should reject payout exceeding TVL", async function () { + const excessiveAmount = INITIAL_LIQUIDITY + PREMIUM_AMOUNT + 1n; + + await expect(pool.executePayout(player1.address, excessiveAmount, 0, 0)) + .to.be.revertedWith("Insufficient pool liquidity"); + }); + + it("should update TVL after payouts", async function () { + const payoutAmount = ethers.parseUnits("5000", 6); + const tvlBefore = await pool.getTVL(); + + await pool.executePayout(player1.address, payoutAmount, 0, 0); + + const tvlAfter = await pool.getTVL(); + expect(tvlAfter).to.equal(tvlBefore - payoutAmount); + }); + }); + + describe("LP Returns After Payouts", function () { + it("should allow LP to withdraw remaining value after payouts", async function () { + // Execute some payouts + const payoutAmount = ethers.parseUnits("10000", 6); + await pool.executePayout(player1.address, payoutAmount, 0, 0); + + // LP withdraws + const lpShares = await pool.balanceOf(lp1.address); + const lpBalanceBefore = await usdt.balanceOf(lp1.address); + + await pool.connect(lp1).withdrawLiquidity(lpShares); + + const lpBalanceAfter = await usdt.balanceOf(lp1.address); + const withdrawn = lpBalanceAfter - lpBalanceBefore; + + // LP should get: initial deposit + premium - payouts = 100K + 5K - 10K = 95K + expect(withdrawn).to.equal(ethers.parseUnits("95000", 6)); + }); + + it("should calculate LP yield from premiums", async function () { + const lpYield = await pool.getLPYield(lp1.address); + + // LP gets 100% of premiums since they're the only LP + expect(lpYield).to.equal(PREMIUM_AMOUNT); + }); + }); +}); diff --git a/contracts/test/SharedLiquidityPool.test.ts b/contracts/test/SharedLiquidityPool.test.ts new file mode 100644 index 0000000..e6da3ac --- /dev/null +++ b/contracts/test/SharedLiquidityPool.test.ts @@ -0,0 +1,225 @@ +import { expect } from "chai"; +import { ethers } from "hardhat"; +import { SharedLiquidityPool, MockUSDT } from "../typechain-types"; +import { SignerWithAddress } from "@nomicfoundation/hardhat-ethers/signers"; + +describe("SharedLiquidityPool", function () { + let pool: SharedLiquidityPool; + let usdt: MockUSDT; + let owner: SignerWithAddress; + let lp1: SignerWithAddress; + let lp2: SignerWithAddress; + let operator: SignerWithAddress; + let winner: SignerWithAddress; + + const INITIAL_SUPPLY = ethers.parseUnits("1000000", 6); // 1M USDT + const DEPOSIT_AMOUNT = ethers.parseUnits("10000", 6); // 10K USDT + const PREMIUM_AMOUNT = ethers.parseUnits("500", 6); // 500 USDT + const PAYOUT_AMOUNT = ethers.parseUnits("5000", 6); // 5K USDT + + beforeEach(async function () { + [owner, lp1, lp2, operator, winner] = await ethers.getSigners(); + + // Deploy mock USDT + const MockUSDT = await ethers.getContractFactory("MockUSDT"); + usdt = await MockUSDT.deploy(); + await usdt.waitForDeployment(); + + // Deploy SharedLiquidityPool + const SharedLiquidityPool = await ethers.getContractFactory("SharedLiquidityPool"); + pool = await SharedLiquidityPool.deploy(await usdt.getAddress()); + await pool.waitForDeployment(); + + // Distribute USDT to test accounts + await usdt.mint(lp1.address, INITIAL_SUPPLY); + await usdt.mint(lp2.address, INITIAL_SUPPLY); + await usdt.mint(operator.address, INITIAL_SUPPLY); + + // Approve pool to spend USDT + await usdt.connect(lp1).approve(await pool.getAddress(), ethers.MaxUint256); + await usdt.connect(lp2).approve(await pool.getAddress(), ethers.MaxUint256); + await usdt.connect(operator).approve(await pool.getAddress(), ethers.MaxUint256); + }); + + describe("LP Functions", function () { + it("should allow LP to deposit and receive shares", async function () { + await pool.connect(lp1).depositLiquidity(DEPOSIT_AMOUNT); + + expect(await pool.balanceOf(lp1.address)).to.equal(DEPOSIT_AMOUNT); + expect(await pool.getTVL()).to.equal(DEPOSIT_AMOUNT); + }); + + it("should mint proportional shares for subsequent deposits", async function () { + // First LP deposits + await pool.connect(lp1).depositLiquidity(DEPOSIT_AMOUNT); + + // Second LP deposits same amount - should get same shares + await pool.connect(lp2).depositLiquidity(DEPOSIT_AMOUNT); + + expect(await pool.balanceOf(lp1.address)).to.equal(DEPOSIT_AMOUNT); + expect(await pool.balanceOf(lp2.address)).to.equal(DEPOSIT_AMOUNT); + expect(await pool.getTVL()).to.equal(DEPOSIT_AMOUNT * 2n); + }); + + it("should allow LP to withdraw with yield from premiums", async function () { + // LP1 deposits + await pool.connect(lp1).depositLiquidity(DEPOSIT_AMOUNT); + + // Register operator and collect premium + await pool.registerOperator(operator.address); + await pool.connect(operator).collectPremium(PREMIUM_AMOUNT); + + // LP1 withdraws all shares - should get deposit + premium + const shares = await pool.balanceOf(lp1.address); + await pool.connect(lp1).withdrawLiquidity(shares); + + const finalBalance = await usdt.balanceOf(lp1.address); + // LP1 should have initial supply - deposit + (deposit + premium) + expect(finalBalance).to.equal(INITIAL_SUPPLY + PREMIUM_AMOUNT); + }); + + it("should return correct share price", async function () { + // Empty pool - 1:1 ratio (scaled by 1e18) + expect(await pool.getSharePrice()).to.equal(ethers.parseUnits("1", 18)); + + // After deposit + await pool.connect(lp1).depositLiquidity(DEPOSIT_AMOUNT); + expect(await pool.getSharePrice()).to.equal(ethers.parseUnits("1", 18)); // 1e18 since we multiply by 1e18 + + // After premium collected (price should increase) + await pool.registerOperator(operator.address); + await pool.connect(operator).collectPremium(PREMIUM_AMOUNT); + + const priceAfterPremium = await pool.getSharePrice(); + expect(priceAfterPremium).to.be.gt(ethers.parseUnits("1", 18)); + }); + + it("should reject zero deposit", async function () { + await expect(pool.connect(lp1).depositLiquidity(0)) + .to.be.revertedWith("Amount must be > 0"); + }); + + it("should reject withdrawal exceeding balance", async function () { + await pool.connect(lp1).depositLiquidity(DEPOSIT_AMOUNT); + await expect(pool.connect(lp1).withdrawLiquidity(DEPOSIT_AMOUNT + 1n)) + .to.be.revertedWith("Insufficient shares"); + }); + }); + + describe("Operator Functions", function () { + it("should allow owner to register operator", async function () { + await pool.registerOperator(operator.address); + + expect(await pool.isOperator(operator.address)).to.be.true; + expect(await pool.operatorCount()).to.equal(1); + }); + + it("should reject duplicate operator registration", async function () { + await pool.registerOperator(operator.address); + await expect(pool.registerOperator(operator.address)) + .to.be.revertedWith("Already registered"); + }); + + it("should allow operator to pay premium", async function () { + await pool.registerOperator(operator.address); + await pool.connect(operator).collectPremium(PREMIUM_AMOUNT); + + expect(await pool.operatorPremiumsPaid(operator.address)).to.equal(PREMIUM_AMOUNT); + expect(await pool.totalPremiumsCollected()).to.equal(PREMIUM_AMOUNT); + expect(await pool.getTVL()).to.equal(PREMIUM_AMOUNT); + }); + + it("should reject premium from non-operator", async function () { + await expect(pool.connect(operator).collectPremium(PREMIUM_AMOUNT)) + .to.be.revertedWith("Not a registered operator"); + }); + }); + + describe("Payout Functions", function () { + beforeEach(async function () { + // Setup: LP deposits, operator registered + await pool.connect(lp1).depositLiquidity(DEPOSIT_AMOUNT); + await pool.registerOperator(operator.address); + }); + + it("should execute payout from pool", async function () { + const winnerBalanceBefore = await usdt.balanceOf(winner.address); + + await pool.executePayout(winner.address, PAYOUT_AMOUNT, 1, 1); + + const winnerBalanceAfter = await usdt.balanceOf(winner.address); + expect(winnerBalanceAfter - winnerBalanceBefore).to.equal(PAYOUT_AMOUNT); + expect(await pool.totalPayoutsDistributed()).to.equal(PAYOUT_AMOUNT); + }); + + it("should reject payout exceeding pool balance", async function () { + const excessAmount = DEPOSIT_AMOUNT + 1n; + await expect(pool.executePayout(winner.address, excessAmount, 1, 1)) + .to.be.revertedWith("Insufficient pool liquidity"); + }); + + it("should reject payout from non-authorized caller", async function () { + await expect(pool.connect(lp1).executePayout(winner.address, PAYOUT_AMOUNT, 1, 1)) + .to.be.revertedWith("Not authorized for payouts"); + }); + }); + + describe("View Functions", function () { + beforeEach(async function () { + await pool.connect(lp1).depositLiquidity(DEPOSIT_AMOUNT); + await pool.registerOperator(operator.address); + await pool.connect(operator).collectPremium(PREMIUM_AMOUNT); + }); + + it("should return correct pool stats", async function () { + const [tvl, lpCount, opCount, premiums, payouts] = await pool.getPoolStats(); + + expect(tvl).to.equal(DEPOSIT_AMOUNT + PREMIUM_AMOUNT); + expect(opCount).to.equal(1); + expect(premiums).to.equal(PREMIUM_AMOUNT); + expect(payouts).to.equal(0); + }); + + it("should return correct LP position", async function () { + const [shares, usdtValue, depositTime] = await pool.getLPPosition(lp1.address); + + expect(shares).to.equal(DEPOSIT_AMOUNT); + expect(usdtValue).to.equal(DEPOSIT_AMOUNT + PREMIUM_AMOUNT); // Share of total pool + expect(depositTime).to.be.gt(0); + }); + + it("should return correct utilization", async function () { + // No payouts yet - 0% utilization + expect(await pool.getUtilization()).to.equal(0); + + // Execute payout + await pool.executePayout(winner.address, PAYOUT_AMOUNT, 1, 1); + + // Utilization = payouts / remaining TVL + const utilization = await pool.getUtilization(); + expect(utilization).to.be.gt(0); + }); + }); + + describe("Events", function () { + it("should emit LiquidityDeposited on deposit", async function () { + await expect(pool.connect(lp1).depositLiquidity(DEPOSIT_AMOUNT)) + .to.emit(pool, "LiquidityDeposited") + .withArgs(lp1.address, DEPOSIT_AMOUNT, DEPOSIT_AMOUNT); + }); + + it("should emit PremiumCollected on premium payment", async function () { + await pool.registerOperator(operator.address); + await expect(pool.connect(operator).collectPremium(PREMIUM_AMOUNT)) + .to.emit(pool, "PremiumCollected") + .withArgs(operator.address, PREMIUM_AMOUNT); + }); + + it("should emit PayoutExecuted on payout", async function () { + await pool.connect(lp1).depositLiquidity(DEPOSIT_AMOUNT); + await expect(pool.executePayout(winner.address, PAYOUT_AMOUNT, 1, 1)) + .to.emit(pool, "PayoutExecuted") + .withArgs(winner.address, PAYOUT_AMOUNT, 1, 1); + }); + }); +}); diff --git a/docker-compose.yml b/docker-compose.yml index 15e14ba..4aebdba 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -1,16 +1,18 @@ -services: - frontend: - build: ./frontend - ports: - - "3000:3000" - environment: - - NODE_ENV=production - depends_on: - - backend - - backend: - build: ./backend - ports: - - "4000:4000" - env_file: - - ./backend/.env +services: + frontend: + build: ./frontend + ports: + - "${FRONTEND_PORT:-3001}:${FRONTEND_PORT:-3001}" + environment: + - NODE_ENV=production + - PORT=${FRONTEND_PORT:-3001} + - BACKEND_URL=http://backend:4000 + depends_on: + - backend + + backend: + build: ./backend + ports: + - "${BACKEND_PORT:-4001}:4000" + env_file: + - ./backend/.env diff --git a/frontend/.dockerignore b/frontend/.dockerignore new file mode 100644 index 0000000..8b88447 --- /dev/null +++ b/frontend/.dockerignore @@ -0,0 +1,3 @@ +node_modules +.next +.git diff --git a/frontend/Dockerfile b/frontend/Dockerfile index 45b2b2f..a66e639 100644 --- a/frontend/Dockerfile +++ b/frontend/Dockerfile @@ -1,20 +1,22 @@ -FROM node:20-alpine -RUN apk add --no-cache python3 make g++ -WORKDIR /app -COPY package.json ./ -RUN npm i -COPY . . -RUN npm run build - -# Copy static assets for standalone mode -RUN cp -r .next/static .next/standalone/.next/ - -EXPOSE 3000 -ENV HOSTNAME="0.0.0.0" -ENV PORT=3000 - -# Set working directory to standalone folder -WORKDIR /app/.next/standalone - -USER 5000 -ENTRYPOINT ["node", "server.js"] +FROM node:20-alpine +RUN apk add --no-cache python3 make g++ +WORKDIR /app +COPY package.json ./ +RUN npm i +COPY . . +ARG BACKEND_URL=http://backend:4000 +ENV BACKEND_URL=${BACKEND_URL} +RUN npm run build + +# Copy static assets for standalone mode +RUN cp -r .next/static .next/standalone/.next/ + +EXPOSE 3000 +ENV HOSTNAME="0.0.0.0" +ENV PORT=3000 + +# Set working directory to standalone folder +WORKDIR /app/.next/standalone + +USER 5000 +ENTRYPOINT ["node", "server.js"] diff --git a/frontend/app/contexts/AuthContext.tsx b/frontend/app/contexts/AuthContext.tsx new file mode 100644 index 0000000..2d47d50 --- /dev/null +++ b/frontend/app/contexts/AuthContext.tsx @@ -0,0 +1,101 @@ +"use client"; +import { createContext, useContext, useState, useEffect, ReactNode } from "react"; + +type UserRole = "organiser" | "player"; + +interface User { + username: string; + role: UserRole; + displayName?: string; +} + +interface AuthContextType { + user: User | null; + login: (username: string, password: string, role: UserRole) => boolean; + logout: () => void; + isAuthenticated: boolean; +} + +const AuthContext = createContext(undefined); + +export { AuthContext }; + +// Registered charitable lottery organisations +const ORGANISER_CREDENTIALS = [ + { username: "omaze", password: "demo123", displayName: "Omaze" }, + { username: "postcode_lottery", password: "secure456", displayName: "People's Postcode Lottery" }, + { username: "comic_relief", password: "pass789", displayName: "Comic Relief" } +]; + +// Player accounts +const PLAYER_CREDENTIALS = [ + { username: "alice", password: "demo123", displayName: "Alice" }, + { username: "bob", password: "demo123", displayName: "Bob" }, + { username: "charlie", password: "demo123", displayName: "Charlie" } +]; + +export function AuthProvider({ children }: { children: ReactNode }) { + const [user, setUser] = useState(null); + const [isLoading, setIsLoading] = useState(true); + + // Check for existing session on mount + useEffect(() => { + const storedUser = localStorage.getItem("lottolink_auth"); + if (storedUser) { + try { + setUser(JSON.parse(storedUser)); + } catch (e) { + localStorage.removeItem("lottolink_auth"); + } + } + setIsLoading(false); + }, []); + + const login = (username: string, password: string, role: UserRole): boolean => { + const credentials = role === "organiser" ? ORGANISER_CREDENTIALS : PLAYER_CREDENTIALS; + const validUser = credentials.find( + (cred) => cred.username === username && cred.password === password + ); + + if (validUser) { + const userData: User = { username: validUser.username, role, displayName: validUser.displayName }; + setUser(userData); + localStorage.setItem("lottolink_auth", JSON.stringify(userData)); + return true; + } + return false; + }; + + const logout = () => { + setUser(null); + localStorage.removeItem("lottolink_auth"); + }; + + if (isLoading) { + return ( +
+
Loading...
+
+ ); + } + + return ( + + {children} + + ); +} + +export function useAuth() { + const context = useContext(AuthContext); + // Return safe defaults during SSR + if (context === undefined) { + return { + user: null, + login: () => false, + logout: () => {}, + isAuthenticated: false + }; + } + return context; +} diff --git a/frontend/app/contexts/ModeContext.tsx b/frontend/app/contexts/ModeContext.tsx new file mode 100644 index 0000000..29cf266 --- /dev/null +++ b/frontend/app/contexts/ModeContext.tsx @@ -0,0 +1,42 @@ +"use client"; +import { createContext, useContext, useState, useEffect, ReactNode } from "react"; + +interface ModeContextType { + isOnChain: boolean; + toggleMode: () => void; +} + +const ModeContext = createContext({ + isOnChain: false, + toggleMode: () => {}, +}); + +export function useMode() { + return useContext(ModeContext); +} + +export function ModeProvider({ children }: { children: ReactNode }) { + const [isOnChain, setIsOnChain] = useState(false); + + useEffect(() => { + if (typeof window === "undefined") return; + const stored = localStorage.getItem("lottolink_mode"); + if (stored === "live") { + setIsOnChain(true); + } + }, []); + + const toggleMode = () => { + setIsOnChain((prev) => { + const next = !prev; + localStorage.setItem("lottolink_mode", next ? "live" : "demo"); + return next; + }); + }; + + return ( + + {children} + + ); +} diff --git a/frontend/app/contexts/WalletContext.tsx b/frontend/app/contexts/WalletContext.tsx new file mode 100644 index 0000000..d3027a5 --- /dev/null +++ b/frontend/app/contexts/WalletContext.tsx @@ -0,0 +1,274 @@ +"use client"; +import { createContext, useContext, useState, useEffect, useCallback, ReactNode } from "react"; +import { usePathname } from "next/navigation"; +import { AuthContext } from "./AuthContext"; + +// Wallet addresses for each user account +const USER_WALLETS: Record = { + // Organisers + omaze: "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb2", + postcode_lottery: "0x8B7d2C9f5E3a1D4c6F0e8A2b5D1c4E7a9F3b6C8d", + comic_relief: "0x3F9e4A1b8C6d2E5f7A0c3B9e6D4f1E8a2C5b7D9f", + // Players + alice: "0x5A0b54D5dc17e0AadC383d2db43B0a0D3E029c4c", + bob: "0x1C8e9F2a4B7d5E3c6A0f8D1b4E7a9C2f5B8d1E4a", + charlie: "0x6D2f5A8c1E4b7C9e2F5a8D1c4E7b0A3f6C9e2D5a", + // Liquidity Provider (generic) + "liquidity-provider": "0x8f3Cf7ad23Cd3CaDbD9735AFf958023239c6A063", + // Default fallback + default: "0x1a9C8182C09F50C8318d769245beA52c32BE35BC", +}; + +type UserType = "organiser" | "user" | "liquidity-provider" | null; + +interface WalletContextType { + address: string | null; + isConnected: boolean; + isConnecting: boolean; + isDemo: boolean; + error: string | null; + connect: () => Promise; + connectDemo: (username?: string) => void; + disconnect: () => void; + switchToPlasma: () => Promise; +} + +const WalletContext = createContext({ + address: null, + isConnected: false, + isConnecting: false, + isDemo: false, + error: null, + connect: async () => { }, + connectDemo: () => { }, + disconnect: () => { }, + switchToPlasma: async () => { }, +}); + +export function useWallet() { + return useContext(WalletContext); +} + +export function WalletProvider({ children }: { children: ReactNode }) { + const pathname = usePathname(); + const auth = useContext(AuthContext); + const [address, setAddress] = useState(null); + const [isConnecting, setIsConnecting] = useState(false); + const [isDemo, setIsDemo] = useState(false); + const [error, setError] = useState(null); + const [currentRole, setCurrentRole] = useState(null); + const [currentUsername, setCurrentUsername] = useState(null); + + const isConnected = !!address; + + // Detect user role from pathname + const getRoleFromPath = (path: string): UserType => { + if (path.includes('/organiser')) return 'organiser'; + if (path.includes('/user')) return 'user'; + if (path.includes('/liquidity-provider')) return 'liquidity-provider'; + return null; + }; + + // Load wallet state for current user from localStorage + const loadWalletForUser = useCallback((username: string) => { + if (typeof window === 'undefined' || !username) return; + + const key = `wallet_${username}`; + const stored = localStorage.getItem(key); + + if (stored) { + try { + const { address, isDemo } = JSON.parse(stored); + setAddress(address); + setIsDemo(isDemo); + } catch { + localStorage.removeItem(key); + const walletAddr = USER_WALLETS[username] || USER_WALLETS.default; + setAddress(walletAddr); + setIsDemo(true); + } + } else { + const walletAddr = USER_WALLETS[username] || USER_WALLETS.default; + // Auto-assign wallet for new user + // setAddress(walletAddr); + // setIsDemo(true); + + // Require explicit wallet connection + setAddress(null); + setIsDemo(false); + } + }, []); + + // Save wallet state for current user to localStorage + const saveWalletForUser = useCallback((username: string, addr: string | null, demo: boolean) => { + if (typeof window === 'undefined' || !username) return; + + const key = `wallet_${username}`; + if (addr) { + localStorage.setItem(key, JSON.stringify({ address: addr, isDemo: demo })); + } else { + localStorage.removeItem(key); + } + }, []); + + // Detect user/role changes and load appropriate wallet + useEffect(() => { + const role = getRoleFromPath(pathname); + const username = auth?.user?.username || null; + + // Check if user changed + if (username !== currentUsername) { + setCurrentUsername(username); + setCurrentRole(role); + if (username) { + loadWalletForUser(username); + } else { + // No user logged in, clear wallet + setAddress(null); + setIsDemo(false); + } + } else if (role !== currentRole) { + // Same user but different role page (shouldn't happen often) + setCurrentRole(role); + } + }, [pathname, currentRole, currentUsername, auth?.user?.username, loadWalletForUser]); + + // Save wallet state whenever it changes + useEffect(() => { + if (currentUsername) { + saveWalletForUser(currentUsername, address, isDemo); + } + }, [address, isDemo, currentUsername, saveWalletForUser]); + + // Listen for wallet events (MetaMask account/chain changes) + useEffect(() => { + if (typeof window === "undefined" || isDemo) return; + const ethereum = (window as any).ethereum; + if (!ethereum) return; + + const handleAccountsChanged = (accounts: string[]) => { + if (accounts.length === 0) { + setAddress(null); + setIsDemo(false); + } else if (!isDemo) { + // Only update if not using demo wallet + setAddress(accounts[0]); + } + }; + + const handleChainChanged = () => { + // Reload on chain change for simplicity + window.location.reload(); + }; + + ethereum.on("accountsChanged", handleAccountsChanged); + ethereum.on("chainChanged", handleChainChanged); + + return () => { + ethereum.removeListener("accountsChanged", handleAccountsChanged); + ethereum.removeListener("chainChanged", handleChainChanged); + }; + }, [isDemo]); + + const connect = useCallback(async () => { + setError(null); + setIsConnecting(true); + + try { + const ethereum = (window as any).ethereum; + if (!ethereum) { + setError("No wallet detected. Install MetaMask or another browser wallet."); + setIsConnecting(false); + return; + } + + // Request accounts + const accounts: string[] = await ethereum.request({ + method: "eth_requestAccounts", + }); + + if (accounts.length > 0) { + setAddress(accounts[0]); + setIsDemo(false); // Real wallet connected, not demo + + // Try to switch to Coston2 + try { + await ethereum.request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: "0x72" }], // 114 in hex + }); + } catch (switchError: any) { + // Chain not added — add it + if (switchError.code === 4902) { + await ethereum.request({ + method: "wallet_addEthereumChain", + params: [{ + chainId: "0x72", + chainName: "Flare Testnet Coston2", + nativeCurrency: { name: "Coston2 Flare", symbol: "C2FLR", decimals: 18 }, + rpcUrls: ["https://coston2-api.flare.network/ext/C/rpc"], + blockExplorerUrls: ["https://coston2-explorer.flare.network"], + }], + }); + } + } + } + } catch (err: any) { + if (err.code === 4001) { + setError("Connection rejected by user"); + } else { + setError(err.message || "Failed to connect wallet"); + } + } finally { + setIsConnecting(false); + } + }, []); + + const connectDemo = useCallback((username?: string) => { + const user = username || currentUsername || 'default'; + const demoAddress = USER_WALLETS[user] || USER_WALLETS.default; + setAddress(demoAddress); + setIsDemo(true); + setError(null); + }, [currentUsername]); + + const disconnect = useCallback(() => { + setAddress(null); + setIsDemo(false); + setError(null); + }, []); + + const switchToPlasma = useCallback(async () => { + const ethereum = (window as any).ethereum; + if (!ethereum) throw new Error("No wallet detected"); + + const plasmaChainId = "0x2612"; // 9746 in hex + + try { + await ethereum.request({ + method: "wallet_switchEthereumChain", + params: [{ chainId: plasmaChainId }], + }); + } catch (switchError: any) { + if (switchError.code === 4902) { + await ethereum.request({ + method: "wallet_addEthereumChain", + params: [{ + chainId: plasmaChainId, + chainName: "Plasma Testnet", + nativeCurrency: { name: "ETH", symbol: "ETH", decimals: 18 }, + rpcUrls: ["https://testnet-rpc.plasma.to"], + }], + }); + } else { + throw switchError; + } + } + }, []); + + return ( + + {children} + + ); +} diff --git a/frontend/app/globals.css b/frontend/app/globals.css index 1068e61..e457485 100644 --- a/frontend/app/globals.css +++ b/frontend/app/globals.css @@ -1,5 +1,60 @@ +@tailwind base; +@tailwind components; +@tailwind utilities; + +:root { + /* Brand Colors */ + --flare-primary: #E62058; + --flare-secondary: #C41A4A; + --plasma-primary: #6366F1; + --plasma-secondary: #4F46E5; + + /* Background Colors */ + --bg-dark-start: #0f0f1a; + --bg-dark-end: #1a1a2e; + --card-bg: #1a1a2e; + --card-border: #374151; + + /* Text Colors */ + --text-primary: #ffffff; + --text-secondary: #9ca3af; + --text-accent: #4ade80; +} + * { margin: 0; padding: 0; box-sizing: border-box; -} \ No newline at end of file +} + +body { + font-family: system-ui, -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif; + -webkit-font-smoothing: antialiased; + -moz-osx-font-smoothing: grayscale; +} + +/* Custom scrollbar */ +::-webkit-scrollbar { + width: 8px; + height: 8px; +} + +::-webkit-scrollbar-track { + background: var(--bg-dark-start); +} + +::-webkit-scrollbar-thumb { + background: var(--card-border); + border-radius: 4px; +} + +::-webkit-scrollbar-thumb:hover { + background: #4b5563; +} + +/* Smooth transitions */ +@layer utilities { + .transition-smooth { + transition: all 0.3s cubic-bezier(0.4, 0, 0.2, 1); + } +} diff --git a/frontend/app/layout.tsx b/frontend/app/layout.tsx index 9bedd97..0af0387 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -1,5 +1,8 @@ import "./globals.css"; import type { Metadata } from "next"; +import { AuthProvider } from "./contexts/AuthContext"; +import { WalletProvider } from "./contexts/WalletContext"; +import { ModeProvider } from "./contexts/ModeContext"; export const metadata: Metadata = { title: "LottoLink | Provably Fair Lottery on Flare", @@ -9,7 +12,13 @@ export const metadata: Metadata = { export default function RootLayout({ children }: { children: React.ReactNode }) { return ( - {children} + + + + {children} + + + ); } diff --git a/frontend/app/liquidity-provider/page.tsx b/frontend/app/liquidity-provider/page.tsx new file mode 100644 index 0000000..8dfc817 --- /dev/null +++ b/frontend/app/liquidity-provider/page.tsx @@ -0,0 +1,516 @@ +"use client"; +import { useState, useEffect, useCallback } from "react"; +import { useWallet } from "@/app/contexts/WalletContext"; +import Navigation from "@/components/Navigation"; + +interface PoolStats { + balance: number; + tvl: string; + exposure: number; + exposureLotteries: { id: number; name: string; jackpot: number; nextDraw: number }[]; + withdrawable: number; + apy: number; + totalPremiums?: string; + totalPayouts?: string; +} + +interface Transaction { + id: number; + type: "Deposit" | "Withdraw"; + amount: number; + provider: string; + timestamp: string; + txHash: string; +} + +interface Activity { + id: number; + type: string; + actor: string; + amount: number; + description: string; + timestamp: string; +} + +export default function LiquidityProviderPage() { + const { address, isConnected } = useWallet(); + const [stats, setStats] = useState(null); + const [transactions, setTransactions] = useState([]); + const [activity, setActivity] = useState([]); + const [depositAmount, setDepositAmount] = useState(""); + const [withdrawAmount, setWithdrawAmount] = useState(""); + const [premiumAmount, setPremiumAmount] = useState(""); + const [isDepositing, setIsDepositing] = useState(false); + const [isWithdrawing, setIsWithdrawing] = useState(false); + const [isPaying, setIsPaying] = useState(false); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(""); + const [activeTab, setActiveTab] = useState<"overview" | "operator">("overview"); + + const lpAddress = isConnected && address ? address : ""; + + const fetchData = useCallback(async () => { + try { + const [statsRes, txRes, activityRes] = await Promise.all([ + fetch("/api/pool/stats"), + fetch(`/api/pool/transactions?provider=${lpAddress}`), + fetch("/api/pool/activity") + ]); + if (statsRes.ok) setStats(await statsRes.json()); + if (txRes.ok) setTransactions(await txRes.json()); + if (activityRes.ok) setActivity(await activityRes.json()); + } catch (err) { + console.error("Failed to fetch pool data", err); + } + }, [lpAddress]); + + useEffect(() => { + fetchData(); + }, [fetchData]); + + const handleDeposit = async (e: React.FormEvent) => { + e.preventDefault(); + const amount = parseFloat(depositAmount); + if (!(amount > 0)) return; + + setIsDepositing(true); + setError(""); + setSuccess(""); + try { + const res = await fetch("/api/pool/deposit", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ amount, provider: lpAddress }) + }); + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error || "Deposit failed"); + } + setDepositAmount(""); + setSuccess(`Successfully deposited $${amount.toLocaleString()}`); + await fetchData(); + } catch (err: any) { + setError(err.message); + } finally { + setIsDepositing(false); + } + }; + + const handleWithdraw = async (e: React.FormEvent) => { + e.preventDefault(); + const amount = parseFloat(withdrawAmount); + if (!(amount > 0)) return; + + setIsWithdrawing(true); + setError(""); + setSuccess(""); + try { + const res = await fetch("/api/pool/withdraw", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ amount, provider: lpAddress }) + }); + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error || "Withdrawal failed"); + } + setWithdrawAmount(""); + setSuccess(`Successfully withdrew $${amount.toLocaleString()}`); + await fetchData(); + } catch (err: any) { + setError(err.message); + } finally { + setIsWithdrawing(false); + } + }; + + const handlePremium = async (e: React.FormEvent) => { + e.preventDefault(); + const amount = parseFloat(premiumAmount); + if (!(amount > 0)) return; + + setIsPaying(true); + setError(""); + setSuccess(""); + try { + const res = await fetch("/api/pool/premium", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ amount, operator: lpAddress || "anonymous" }) + }); + if (!res.ok) { + const data = await res.json(); + throw new Error(data.error || "Premium payment failed"); + } + setPremiumAmount(""); + setSuccess(`Successfully paid $${amount.toLocaleString()} premium`); + await fetchData(); + } catch (err: any) { + setError(err.message); + } finally { + setIsPaying(false); + } + }; + + const formatUSDT = (value: string | undefined): string => { + if (!value) return "$0"; + const num = Number(value) / 1e6; + return `$${num.toLocaleString(undefined, { minimumFractionDigits: 0, maximumFractionDigits: 2 })}`; + }; + + return ( +
+
+ + + {/* Status Messages */} + {error && ( +
+ {error} +
+ )} + {success && ( +
+ {success} +
+ )} + + {/* Tab Navigation */} +
+
+ + +
+
+ + {activeTab === "overview" && ( + <> + {/* Pool Overview Stats */} +
+
+

TVL

+

+ {stats?.tvl ? formatUSDT(stats.tvl) : `$${(stats?.balance ?? 0).toLocaleString()}`} +

+
+
+

APY

+

{stats?.apy ?? 0}%

+
+
+ + {/* Financial Summary */} +
+
+

Pool Balance

+

+ ${(stats?.balance ?? 0).toLocaleString()} +

+

Total staked USDT

+
+
+

Exposure (14d)

+

+ ${(stats?.exposure ?? 0).toLocaleString()} +

+

+ {stats?.exposureLotteries?.length ?? 0} upcoming draws +

+
+
+

Withdrawable

+

+ ${(stats?.withdrawable ?? 0).toLocaleString()} +

+

Balance minus exposure

+
+
+

Premiums Collected

+

+ {stats?.totalPremiums ? formatUSDT(stats.totalPremiums) : "$0"} +

+

+ Payouts: {stats?.totalPayouts ? formatUSDT(stats.totalPayouts) : "$0"} +

+
+
+ + {/* Exposure Breakdown */} + {stats?.exposureLotteries && stats.exposureLotteries.length > 0 && ( +
+

Exposure Breakdown — Lotteries Drawing Within 14 Days

+
+ {stats.exposureLotteries.map(l => ( +
+ {l.name} +
+ + Draw: {new Date(l.nextDraw).toLocaleDateString()} + + + ${l.jackpot.toLocaleString()} + +
+
+ ))} +
+
+ )} + + {/* Deposit & Withdraw Forms */} +
+
+

Deposit Funds

+
+
+ + setDepositAmount(e.target.value)} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-3 text-white placeholder-gray-500 focus:border-plasma-500 focus:outline-none focus:ring-2 focus:ring-plasma-500/20" + placeholder="0.00" + step="0.01" + min="0" + /> +
+
+
+ No deposit limit + Unlimited +
+
+ +
+
+ +
+

Withdraw Funds

+
+
+ + setWithdrawAmount(e.target.value)} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-3 text-white placeholder-gray-500 focus:border-flare-500 focus:outline-none focus:ring-2 focus:ring-flare-500/20" + placeholder="0.00" + step="0.01" + min="0" + max={stats?.withdrawable ?? 0} + /> +
+
+
+ Pool Balance: + + ${(stats?.balance ?? 0).toLocaleString()} + +
+
+ Locked (exposure): + + -${(stats?.exposure ?? 0).toLocaleString()} + +
+
+ Max Withdrawable: + + ${(stats?.withdrawable ?? 0).toLocaleString()} + +
+
+ +
+
+
+ + {/* Activity Feed & Transaction History side-by-side */} +
+ {/* Activity Feed */} +
+

Activity Feed

+ {activity.length === 0 ? ( +

No activity yet.

+ ) : ( +
+ {activity.slice(0, 20).map((a) => ( +
+
+ +
+

{a.description}

+

+ {new Date(a.timestamp).toLocaleString()} +

+
+
+ + {a.type === "Deposit" ? "+" : "-"}${a.amount.toLocaleString()} + +
+ ))} +
+ )} +
+ + {/* Transaction History */} +
+

Your Transactions

+ {transactions.length === 0 ? ( +

No transactions yet.

+ ) : ( +
+ {transactions.map((tx) => ( +
+
+
+ + {tx.type} + + + {tx.type === "Withdraw" ? "-" : "+"}${tx.amount.toLocaleString()} + +
+

+ {new Date(tx.timestamp).toLocaleString()} +

+
+ + {tx.txHash} + +
+ ))} +
+ )} +
+
+ + )} + + {activeTab === "operator" && ( + <> + {/* Operator Premium Payment */} +
+
+

Pay Premium

+

+ Lottery operators pay premiums to the pool in exchange for coverage of potential payouts. + This is the CaaS model — operators transfer jackpot risk to LPs. +

+
+
+ + setPremiumAmount(e.target.value)} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-3 text-white placeholder-gray-500 focus:border-flare-500 focus:outline-none focus:ring-2 focus:ring-flare-500/20" + placeholder="0.00" + step="0.01" + min="0" + /> +
+ +
+
+ +
+

CaaS Model

+
+
+

How It Works

+
    +
  1. Lottery operators register with the pool
  2. +
  3. Operators pay premiums based on their lottery risk profile
  4. +
  5. The pool covers lottery payouts using LP capital
  6. +
  7. LPs earn yield from operator premiums
  8. +
+
+
+

+ {stats?.totalPremiums ? formatUSDT(stats.totalPremiums) : "$0"} +

+

Total Premiums

+
+
+
+
+ + )} + + {/* Info Box */} +
+

How Liquidity Providing Works

+
    +
  • + + Your USDT is staked in the SharedLiquidityPool to provide coverage for lottery payouts +
  • +
  • + + Earn yields from operator premium payments across all active lotteries +
  • +
  • + + Withdrawals are limited by exposure: the pool must retain enough to cover jackpots of lotteries drawing within 14 days +
  • +
  • + + APY is variable based on lottery activity and total liquidity pool size +
  • +
  • + + All transactions are recorded on the Flare Network blockchain +
  • +
+
+
+
+ ); +} diff --git a/frontend/app/organiser/dashboard/layout.tsx b/frontend/app/organiser/dashboard/layout.tsx new file mode 100644 index 0000000..1853d39 --- /dev/null +++ b/frontend/app/organiser/dashboard/layout.tsx @@ -0,0 +1,10 @@ +"use client"; +import ProtectedRoute from "@/components/ProtectedRoute"; + +export default function OrganiserDashboardLayout({ + children, +}: { + children: React.ReactNode; +}) { + return {children}; +} diff --git a/frontend/app/organiser/dashboard/page.tsx b/frontend/app/organiser/dashboard/page.tsx new file mode 100644 index 0000000..a3ad67f --- /dev/null +++ b/frontend/app/organiser/dashboard/page.tsx @@ -0,0 +1,587 @@ +"use client"; +import { useState, useEffect, useCallback } from "react"; +import { useAuth } from "@/app/contexts/AuthContext"; +import { useRouter } from "next/navigation"; +import Navigation from "@/components/Navigation"; + +interface LotteryData { + id: number; + name: string; + ticketPriceUSD: number; + jackpot: number; + nextDraw: number; + charityPercent: number; + usesFlareRNG: boolean; + status: string; + ticketsSold?: number; + premiumRate?: number; + ticketRevenue?: number; + premiumOwed?: number; +} + +interface DrawResult { + id: number; + lotteryId: number; + lotteryName: string; + winningNumbers: number[]; + bonusNumber: number; + totalTickets: number; + winners: { + ticketNumber: string; + player: string; + numbers: number[]; + matchedNumbers: number[]; + matches: number; + bonusMatch: boolean; + tier: string; + prize: number; + }[]; + totalPaidOut: number; +} + +export default function OrganiserDashboard() { + const { user, logout } = useAuth(); + const router = useRouter(); + const [lotteries, setLotteries] = useState([]); + const [showCreateForm, setShowCreateForm] = useState(false); + const [isCreating, setIsCreating] = useState(false); + const [createError, setCreateError] = useState(""); + const [isDrawing, setIsDrawing] = useState(null); + const [drawResult, setDrawResult] = useState(null); + const [drawError, setDrawError] = useState(""); + const [newLottery, setNewLottery] = useState({ + name: "", + ticketPriceUSD: 5, + jackpot: 10000, + drawTime: "", + charityPercent: 5, + premiumRate: 500, + usesFlareRNG: true, + organiserDisplayName: "", + description: "", + website: "" + }); + + const fetchLotteries = useCallback(async () => { + if (!user) return; + try { + const res = await fetch(`/api/lotteries?organiser=${encodeURIComponent(user.username)}`); + if (res.ok) { + const data = await res.json(); + setLotteries(data); + } + } catch (err) { + console.error("Failed to fetch lotteries", err); + } + }, [user]); + + useEffect(() => { + if (!user) { + router.push("/organiser/login"); + return; + } + fetchLotteries(); + }, [user, router, fetchLotteries]); + + // Safe JSON parse helper to handle non-JSON responses + const safeJsonParse = async (res: Response) => { + const text = await res.text(); + try { + return JSON.parse(text); + } catch { + return { error: text || `Server error (${res.status})` }; + } + }; + + const handleCreateLottery = async (e: React.FormEvent) => { + e.preventDefault(); + setIsCreating(true); + setCreateError(""); + try { + const res = await fetch("/api/lotteries", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + name: newLottery.name, + ticketPriceUSD: newLottery.ticketPriceUSD, + jackpot: newLottery.jackpot, + nextDraw: newLottery.drawTime, + charityPercent: newLottery.charityPercent, + usesFlareRNG: newLottery.usesFlareRNG, + organiser: user?.username, + organiserDisplayName: newLottery.organiserDisplayName || user?.username, + description: newLottery.description, + website: newLottery.website, + premiumRate: newLottery.premiumRate + }) + }); + if (!res.ok) { + const err = await safeJsonParse(res); + throw new Error(err.error || "Failed to create lottery"); + } + await fetchLotteries(); + setShowCreateForm(false); + setNewLottery({ + name: "", + ticketPriceUSD: 5, + jackpot: 10000, + drawTime: "", + charityPercent: 5, + premiumRate: 500, + usesFlareRNG: true, + organiserDisplayName: "", + description: "", + website: "" + }); + } catch (err: any) { + setCreateError(err.message || "Failed to connect to server. Is the backend running?"); + } finally { + setIsCreating(false); + } + }; + + const handleTriggerDraw = async (id: number) => { + setIsDrawing(id); + setDrawError(""); + try { + const res = await fetch(`/api/lotteries/${id}/draw`, { + method: "POST", + headers: { "Content-Type": "application/json" } + }); + const data = await safeJsonParse(res); + if (!res.ok) { + throw new Error(data.error || "Draw failed"); + } + setDrawResult(data); + await fetchLotteries(); + } catch (err: any) { + setDrawError(err.message || "Failed to connect to server"); + } finally { + setIsDrawing(null); + } + }; + + const handleLogout = () => { + logout(); + router.push("/organiser/login"); + }; + + if (!user) { + return null; + } + + return ( +
+
+
+ +
+ + Logged in as {user.username} + + +
+
+ + {/* Create Lottery Section */} +
+ + + {showCreateForm && ( +
+

Create New Lottery

+
+
+ + setNewLottery({ ...newLottery, name: e.target.value })} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-2 text-white focus:border-flare-500 focus:outline-none" + required + /> +
+ +
+ + setNewLottery({ ...newLottery, ticketPriceUSD: Number(e.target.value) })} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-2 text-white focus:border-flare-500 focus:outline-none" + required + /> +
+ +
+ + setNewLottery({ ...newLottery, jackpot: Number(e.target.value) })} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-2 text-white focus:border-flare-500 focus:outline-none" + required + /> +
+ +
+ + setNewLottery({ ...newLottery, drawTime: e.target.value })} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-2 text-white focus:border-flare-500 focus:outline-none" + required + /> +
+ +
+ + setNewLottery({ ...newLottery, charityPercent: Number(e.target.value) })} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-2 text-white focus:border-flare-500 focus:outline-none" + min="0" + max="50" + required + /> +
+ +
+ + setNewLottery({ ...newLottery, premiumRate: Number(e.target.value) })} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-2 text-white focus:border-flare-500 focus:outline-none" + min="0" + max="2000" + required + /> +

+ {(newLottery.premiumRate / 100).toFixed(1)}% of ticket revenue paid to liquidity pool +

+
+ +
+ + +
+ +
+ + setNewLottery({ ...newLottery, organiserDisplayName: e.target.value })} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-2 text-white placeholder-gray-500 focus:border-flare-500 focus:outline-none" + placeholder="e.g. My Charity Foundation" + /> +
+ +
+ + setNewLottery({ ...newLottery, website: e.target.value })} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-2 text-white placeholder-gray-500 focus:border-flare-500 focus:outline-none" + placeholder="https://www.example.com" + /> +
+ +
+ +