From 6d2b8dfa96fcba3b5b949bf0f85bd365c1f5ae91 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Sat, 7 Feb 2026 18:42:47 +0000 Subject: [PATCH 01/31] feat: Get separate dashboards --- frontend/app/contexts/AuthContext.tsx | 81 ++++++ frontend/app/globals.css | 57 +++- frontend/app/layout.tsx | 5 +- frontend/app/liquidity-provider/page.tsx | 249 ++++++++++++++++ frontend/app/organiser/dashboard/layout.tsx | 10 + frontend/app/organiser/dashboard/page.tsx | 290 +++++++++++++++++++ frontend/app/organiser/login/page.tsx | 108 +++++++ frontend/app/page.tsx | 137 ++++----- frontend/app/user/page.tsx | 300 ++++++++++++++++++++ frontend/components/LotteryCard.tsx | 90 ++++++ frontend/components/Navigation.tsx | 57 ++++ frontend/components/ProtectedRoute.tsx | 29 ++ frontend/components/UserTypeCard.tsx | 52 ++++ 13 files changed, 1387 insertions(+), 78 deletions(-) create mode 100644 frontend/app/contexts/AuthContext.tsx create mode 100644 frontend/app/liquidity-provider/page.tsx create mode 100644 frontend/app/organiser/dashboard/layout.tsx create mode 100644 frontend/app/organiser/dashboard/page.tsx create mode 100644 frontend/app/organiser/login/page.tsx create mode 100644 frontend/app/user/page.tsx create mode 100644 frontend/components/LotteryCard.tsx create mode 100644 frontend/components/Navigation.tsx create mode 100644 frontend/components/ProtectedRoute.tsx create mode 100644 frontend/components/UserTypeCard.tsx diff --git a/frontend/app/contexts/AuthContext.tsx b/frontend/app/contexts/AuthContext.tsx new file mode 100644 index 0000000..a9d302b --- /dev/null +++ b/frontend/app/contexts/AuthContext.tsx @@ -0,0 +1,81 @@ +"use client"; +import { createContext, useContext, useState, useEffect, ReactNode } from "react"; + +interface User { + username: string; +} + +interface AuthContextType { + user: User | null; + login: (username: string, password: string) => boolean; + logout: () => void; + isAuthenticated: boolean; +} + +const AuthContext = createContext(undefined); + +// Hardcoded credentials for demo +const VALID_CREDENTIALS = [ + { username: "admin1", password: "demo123" }, + { username: "lottery_ops", password: "secure456" }, + { username: "organiser", password: "pass789" } +]; + +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): boolean => { + const validUser = VALID_CREDENTIALS.find( + (cred) => cred.username === username && cred.password === password + ); + + if (validUser) { + const userData = { username: validUser.username }; + 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); + if (context === undefined) { + throw new Error("useAuth must be used within an AuthProvider"); + } + return context; +} 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..e01afbf 100644 --- a/frontend/app/layout.tsx +++ b/frontend/app/layout.tsx @@ -1,5 +1,6 @@ import "./globals.css"; import type { Metadata } from "next"; +import { AuthProvider } from "./contexts/AuthContext"; export const metadata: Metadata = { title: "LottoLink | Provably Fair Lottery on Flare", @@ -9,7 +10,9 @@ 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..8c22e41 --- /dev/null +++ b/frontend/app/liquidity-provider/page.tsx @@ -0,0 +1,249 @@ +"use client"; +import { useState, useEffect } from "react"; +import Navigation from "@/components/Navigation"; + +interface Transaction { + id: number; + type: "Deposit" | "Withdraw" | "Yield"; + amount: number; + timestamp: string; + txHash: string; +} + +export default function LiquidityProviderPage() { + const [balance, setBalance] = useState({ + staked: 25000, + available: 5000, + totalReturns: 1250 + }); + const [depositAmount, setDepositAmount] = useState(""); + const [withdrawAmount, setWithdrawAmount] = useState(""); + const [transactions, setTransactions] = useState([]); + + useEffect(() => { + // Mock transaction data + setTransactions([ + { + id: 1, + type: "Deposit", + amount: 10000, + timestamp: new Date(Date.now() - 86400000 * 7).toISOString(), + txHash: "0x1234...5678" + }, + { + id: 2, + type: "Yield", + amount: 450, + timestamp: new Date(Date.now() - 86400000 * 3).toISOString(), + txHash: "0xabcd...efgh" + }, + { + id: 3, + type: "Deposit", + amount: 15000, + timestamp: new Date(Date.now() - 86400000 * 2).toISOString(), + txHash: "0x9876...5432" + }, + { + id: 4, + type: "Yield", + amount: 800, + timestamp: new Date(Date.now() - 86400000).toISOString(), + txHash: "0xfedc...ba98" + } + ]); + }, []); + + const handleDeposit = (e: React.FormEvent) => { + e.preventDefault(); + const amount = parseFloat(depositAmount); + if (amount > 0) { + console.log(`Depositing ${amount} USDT`); + // TODO: Integrate with Plasma Network + setDepositAmount(""); + } + }; + + const handleWithdraw = (e: React.FormEvent) => { + e.preventDefault(); + const amount = parseFloat(withdrawAmount); + if (amount > 0 && amount <= balance.staked) { + console.log(`Withdrawing ${amount} USDT`); + // TODO: Integrate with Plasma Network + setWithdrawAmount(""); + } + }; + + const apy = 18.5; // Mock APY + + return ( +
+
+ + + {/* Balance Overview */} +
+
+

Total Staked

+

+ ${balance.staked.toLocaleString()} +

+

USDT on Plasma Network

+
+ +
+

Total Returns

+

+ ${balance.totalReturns.toLocaleString()} +

+

Lifetime earnings

+
+ +
+

Current APY

+

{apy}%

+

Variable rate

+
+
+ + {/* 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" + /> +
+
+
+ Available Balance: + + ${balance.available.toLocaleString()} USDT + +
+
+ +
+
+ +
+

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={balance.staked} + /> +
+
+
+ Staked Balance: + + ${balance.staked.toLocaleString()} USDT + +
+
+ +
+
+
+ + {/* Transaction History */} +
+

Transaction History

+
+ + + + + + + + + + + {transactions.map((tx) => ( + + + + + + + ))} + +
TypeAmountDateTransaction Hash
+ + {tx.type} + + + {tx.type === "Withdraw" ? "-" : "+"}${tx.amount.toLocaleString()} + + {new Date(tx.timestamp).toLocaleDateString()} + + {tx.txHash} +
+
+
+ + {/* Info Box */} +
+

How Liquidity Providing Works

+
    +
  • + + Your USDT is staked on the Plasma Network to provide liquidity for instant lottery payouts +
  • +
  • + + Earn yields from a percentage of ticket sales across all active lotteries +
  • +
  • + + Withdraw your funds at any time (subject to liquidity availability) +
  • +
  • + + APY is variable based on lottery activity and total liquidity pool size +
  • +
+
+
+
+ ); +} 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..da8e205 --- /dev/null +++ b/frontend/app/organiser/dashboard/page.tsx @@ -0,0 +1,290 @@ +"use client"; +import { useState, useEffect } from "react"; +import { useAuth } from "@/app/contexts/AuthContext"; +import { useRouter } from "next/navigation"; +import Navigation from "@/components/Navigation"; + +interface LotteryData { + id: number; + name: string; + ticketPrice: number; + jackpot: number; + drawTime: string; + charityPercent: number; + rngType: "Flare RNG" | "FDC Attested"; + status: "Active" | "Pending" | "Completed"; + ticketsSold: number; +} + +export default function OrganiserDashboard() { + const { user, logout } = useAuth(); + const router = useRouter(); + const [lotteries, setLotteries] = useState([]); + const [showCreateForm, setShowCreateForm] = useState(false); + const [newLottery, setNewLottery] = useState<{ + name: string; + ticketPrice: number; + jackpot: number; + drawTime: string; + charityPercent: number; + rngType: "Flare RNG" | "FDC Attested"; + }>({ + name: "", + ticketPrice: 5, + jackpot: 10000, + drawTime: "", + charityPercent: 5, + rngType: "Flare RNG" + }); + + useEffect(() => { + if (!user) { + router.push("/organiser/login"); + return; + } + + // Mock data + setLotteries([ + { + id: 1, + name: "Flare Jackpot", + ticketPrice: 5, + jackpot: 50000, + drawTime: new Date(Date.now() + 3600000).toISOString(), + charityPercent: 5, + rngType: "Flare RNG", + status: "Active", + ticketsSold: 1234 + }, + { + id: 2, + name: "Plasma Daily", + ticketPrice: 2, + jackpot: 10000, + drawTime: new Date(Date.now() + 7200000).toISOString(), + charityPercent: 10, + rngType: "Flare RNG", + status: "Active", + ticketsSold: 567 + } + ]); + }, [user, router]); + + const handleCreateLottery = (e: React.FormEvent) => { + e.preventDefault(); + const lottery: LotteryData = { + id: Date.now(), + name: newLottery.name, + ticketPrice: newLottery.ticketPrice, + jackpot: newLottery.jackpot, + drawTime: newLottery.drawTime, + charityPercent: newLottery.charityPercent, + rngType: newLottery.rngType, + status: "Pending", + ticketsSold: 0 + }; + setLotteries([...lotteries, lottery]); + setShowCreateForm(false); + setNewLottery({ + name: "", + ticketPrice: 5, + jackpot: 10000, + drawTime: "", + charityPercent: 5, + rngType: "Flare RNG" + }); + }; + + const handleTriggerDraw = (id: number) => { + console.log(`Triggering draw for lottery ${id}`); + // TODO: Integrate with smart contract + }; + + 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, ticketPrice: 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 + /> +
+ +
+ + +
+ +
+ +
+
+
+ )} +
+ + {/* Active Lotteries Table */} +
+

Manage Lotteries

+
+ + + + + + + + + + + + + + {lotteries.map((lottery) => ( + + + + + + + + + + ))} + +
NamePriceJackpotTickets SoldDraw TimeStatusActions
{lottery.name}${lottery.ticketPrice}${lottery.jackpot.toLocaleString()}{lottery.ticketsSold} + {new Date(lottery.drawTime).toLocaleString()} + + + {lottery.status} + + + +
+
+
+
+
+ ); +} diff --git a/frontend/app/organiser/login/page.tsx b/frontend/app/organiser/login/page.tsx new file mode 100644 index 0000000..705db8a --- /dev/null +++ b/frontend/app/organiser/login/page.tsx @@ -0,0 +1,108 @@ +"use client"; +import { useState, FormEvent } from "react"; +import { useRouter } from "next/navigation"; +import { useAuth } from "@/app/contexts/AuthContext"; +import Link from "next/link"; + +export default function OrganiserLoginPage() { + const [username, setUsername] = useState(""); + const [password, setPassword] = useState(""); + const [error, setError] = useState(""); + const router = useRouter(); + const { login } = useAuth(); + + const handleSubmit = (e: FormEvent) => { + e.preventDefault(); + setError(""); + + const success = login(username, password); + if (success) { + router.push("/organiser/dashboard"); + } else { + setError("Invalid username or password"); + } + }; + + return ( +
+ + ← Back to Home + + +
+
+

+ LottoLink +

+

+ Organiser Login +

+

+ Access your lottery management dashboard +

+
+ +
+
+
+ + setUsername(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="Enter your username" + required + /> +
+ +
+ + setPassword(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="Enter your password" + required + /> +
+ + {error && ( +
+ {error} +
+ )} + + +
+ +
+

+ Demo Credentials: +

+
+

admin1 / demo123

+

lottery_ops / secure456

+

organiser / pass789

+
+
+
+
+
+ ); +} diff --git a/frontend/app/page.tsx b/frontend/app/page.tsx index b08ee95..6c9aaae 100644 --- a/frontend/app/page.tsx +++ b/frontend/app/page.tsx @@ -1,85 +1,70 @@ -"use client"; -import { useState, useEffect } from "react"; - -interface Lottery { - id: number; - name: string; - ticketPriceUSD: number; - jackpot: number; - nextDraw: number; - charityPercent: number; - usesFlareRNG: boolean; -} - -const styles = { - page: { minHeight: "100vh", padding: "2rem", background: "linear-gradient(135deg, #0f0f1a 0%, #1a1a2e 100%)", color: "white", fontFamily: "system-ui, -apple-system, sans-serif" }, - header: { marginBottom: "3rem", textAlign: "center" as const }, - title: { fontSize: "3.5rem", fontWeight: "bold", background: "linear-gradient(90deg, #E62058, #6366F1)", WebkitBackgroundClip: "text", WebkitTextFillColor: "transparent", marginBottom: "1rem" }, - subtitle: { color: "#9ca3af", fontSize: "1.125rem" }, - section: { maxWidth: "1200px", margin: "0 auto" }, - sectionTitle: { fontSize: "1.5rem", fontWeight: "600", marginBottom: "1.5rem" }, - grid: { display: "grid", gridTemplateColumns: "repeat(auto-fit, minmax(320px, 1fr))", gap: "1.5rem" }, - card: { background: "#1a1a2e", borderRadius: "1rem", padding: "1.5rem", border: "1px solid #374151", transition: "all 0.3s", cursor: "pointer" }, - cardHeader: { display: "flex", justifyContent: "space-between", alignItems: "flex-start", marginBottom: "1rem" }, - cardTitle: { fontSize: "1.25rem", fontWeight: "600" }, - badge: { padding: "0.25rem 0.5rem", borderRadius: "0.25rem", fontSize: "0.75rem", fontWeight: "500" }, - badgeFlare: { background: "rgba(230, 32, 88, 0.2)", color: "#E62058" }, - badgeFdc: { background: "rgba(99, 102, 241, 0.2)", color: "#6366F1" }, - row: { display: "flex", justifyContent: "space-between", marginBottom: "0.75rem" }, - label: { color: "#9ca3af" }, - jackpot: { fontSize: "1.5rem", fontWeight: "bold", color: "#4ade80" }, - timer: { color: "#fbbf24" }, - button: { width: "100%", marginTop: "1.5rem", padding: "0.75rem", background: "linear-gradient(90deg, #E62058, #6366F1)", border: "none", borderRadius: "0.75rem", fontWeight: "600", color: "white", fontSize: "1rem", cursor: "pointer" } -}; +import UserTypeCard from "@/components/UserTypeCard"; export default function HomePage() { - const [lotteries, setLotteries] = useState([]); - const [time, setTime] = useState(Date.now()); - - useEffect(() => { - setLotteries([ - { id: 1, name: "Flare Jackpot", ticketPriceUSD: 5, jackpot: 50000, nextDraw: Date.now() + 3600000, charityPercent: 5, usesFlareRNG: true }, - { id: 2, name: "Plasma Daily", ticketPriceUSD: 2, jackpot: 10000, nextDraw: Date.now() + 7200000, charityPercent: 10, usesFlareRNG: true }, - { id: 3, name: "Global Mega", ticketPriceUSD: 10, jackpot: 100000, nextDraw: Date.now() + 86400000, charityPercent: 8, usesFlareRNG: false } - ]); - const interval = setInterval(() => setTime(Date.now()), 60000); - return () => clearInterval(interval); - }, []); - return ( -
-
-

LottoLink

-

Provably fair draws • Instant zero-fee payouts • Powered by Flare & Plasma

+
+
+

+ LottoLink +

+

+ Provably fair draws • Instant zero-fee payouts • Powered by Flare & Plasma +

-
-

Active Lotteries

-
- {lotteries.map((lottery) => )} + +
+

+ Choose Your Role +

+ +
+ + + + +
-
- ); -} -function LotteryCard({ lottery, now }: { lottery: Lottery; now: number }) { - const timeLeft = Math.max(0, Math.floor((lottery.nextDraw - now) / 1000)); - const hours = Math.floor(timeLeft / 3600); - const minutes = Math.floor((timeLeft % 3600) / 60); - - return ( -
(e.currentTarget.style.borderColor = "#E62058")} onMouseLeave={e => (e.currentTarget.style.borderColor = "#374151")}> -
-

{lottery.name}

- - {lottery.usesFlareRNG ? "Flare RNG" : "FDC Attested"} - -
-
Jackpot${lottery.jackpot.toLocaleString()}
-
Ticket Price${lottery.ticketPriceUSD} USDT
-
Charity{lottery.charityPercent}%
-
Next Draw{hours}h {minutes}m
- -
+
+

Built on Flare Network (Coston2) & Plasma Network

+
+
); } diff --git a/frontend/app/user/page.tsx b/frontend/app/user/page.tsx new file mode 100644 index 0000000..9d488a8 --- /dev/null +++ b/frontend/app/user/page.tsx @@ -0,0 +1,300 @@ +"use client"; +import { useState, useEffect } from "react"; +import Navigation from "@/components/Navigation"; +import LotteryCard, { Lottery } from "@/components/LotteryCard"; + +interface Ticket { + id: number; + lotteryId: number; + lotteryName: string; + ticketNumber: string; + purchaseDate: string; + drawDate: string; + status: "Pending" | "Won" | "Lost"; + prize?: number; +} + +export default function UserPage() { + const [lotteries, setLotteries] = useState([]); + const [time, setTime] = useState(Date.now()); + const [myTickets, setMyTickets] = useState([]); + const [showModal, setShowModal] = useState(false); + const [selectedLottery, setSelectedLottery] = useState(null); + const [numberOfTickets, setNumberOfTickets] = useState(1); + + useEffect(() => { + // Mock lottery data + setLotteries([ + { + id: 1, + name: "Flare Jackpot", + ticketPriceUSD: 5, + jackpot: 50000, + nextDraw: Date.now() + 3600000, + charityPercent: 5, + usesFlareRNG: true + }, + { + id: 2, + name: "Plasma Daily", + ticketPriceUSD: 2, + jackpot: 10000, + nextDraw: Date.now() + 7200000, + charityPercent: 10, + usesFlareRNG: true + }, + { + id: 3, + name: "Global Mega", + ticketPriceUSD: 10, + jackpot: 100000, + nextDraw: Date.now() + 86400000, + charityPercent: 8, + usesFlareRNG: false + } + ]); + + // Mock ticket data + setMyTickets([ + { + id: 1, + lotteryId: 1, + lotteryName: "Flare Jackpot", + ticketNumber: "FJ-2024-00123", + purchaseDate: new Date(Date.now() - 86400000 * 2).toISOString(), + drawDate: new Date(Date.now() + 3600000).toISOString(), + status: "Pending" + }, + { + id: 2, + lotteryId: 2, + lotteryName: "Plasma Daily", + ticketNumber: "PD-2024-00456", + purchaseDate: new Date(Date.now() - 86400000 * 1).toISOString(), + drawDate: new Date(Date.now() + 7200000).toISOString(), + status: "Pending" + }, + { + id: 3, + lotteryId: 1, + lotteryName: "Flare Jackpot", + ticketNumber: "FJ-2024-00089", + purchaseDate: new Date(Date.now() - 86400000 * 8).toISOString(), + drawDate: new Date(Date.now() - 86400000 * 1).toISOString(), + status: "Lost" + }, + { + id: 4, + lotteryId: 3, + lotteryName: "Global Mega", + ticketNumber: "GM-2024-00234", + purchaseDate: new Date(Date.now() - 86400000 * 15).toISOString(), + drawDate: new Date(Date.now() - 86400000 * 7).toISOString(), + status: "Won", + prize: 150 + } + ]); + + const interval = setInterval(() => setTime(Date.now()), 60000); + return () => clearInterval(interval); + }, []); + + const handleBuyTicket = (lotteryId: number) => { + const lottery = lotteries.find((l) => l.id === lotteryId); + if (lottery) { + setSelectedLottery(lottery); + setShowModal(true); + } + }; + + const handlePurchase = () => { + if (selectedLottery) { + console.log(`Purchasing ${numberOfTickets} ticket(s) for ${selectedLottery.name}`); + // TODO: Integrate with smart contract + setShowModal(false); + setNumberOfTickets(1); + setSelectedLottery(null); + } + }; + + const totalSpent = myTickets.reduce((sum, ticket) => { + const lottery = lotteries.find((l) => l.id === ticket.lotteryId); + return sum + (lottery?.ticketPriceUSD || 0); + }, 0); + + const totalWinnings = myTickets + .filter((t) => t.status === "Won") + .reduce((sum, ticket) => sum + (ticket.prize || 0), 0); + + return ( +
+
+ + + {/* User Stats */} +
+
+

Total Tickets

+

{myTickets.length}

+
+ +
+

Total Spent

+

+ ${totalSpent.toFixed(2)} +

+
+ +
+

Total Winnings

+

+ ${totalWinnings.toFixed(2)} +

+
+
+ + {/* Available Lotteries */} +
+

Available Lotteries

+
+ {lotteries.map((lottery) => ( + + ))} +
+
+ + {/* My Tickets */} +
+

My Tickets

+
+
+ + + + + + + + + + + + + {myTickets.length === 0 ? ( + + + + ) : ( + myTickets.map((ticket) => ( + + + + + + + + + )) + )} + +
Ticket NumberLotteryPurchase DateDraw DateStatusPrize
+ No tickets yet. Purchase a ticket to get started! +
{ticket.ticketNumber}{ticket.lotteryName} + {new Date(ticket.purchaseDate).toLocaleDateString()} + + {new Date(ticket.drawDate).toLocaleDateString()} + + + {ticket.status} + + + {ticket.prize ? ( + ${ticket.prize} + ) : ( + - + )} +
+
+
+
+
+ + {/* Purchase Modal */} + {showModal && selectedLottery && ( +
+
+

+ Purchase Tickets +

+

+ {selectedLottery.name} +

+ +
+
+ Ticket Price: + ${selectedLottery.ticketPriceUSD} USDT +
+
+ Jackpot: + + ${selectedLottery.jackpot.toLocaleString()} + +
+
+ +
+ + setNumberOfTickets(Math.max(1, parseInt(e.target.value) || 1))} + className="w-full rounded-lg border border-gray-700 bg-gray-800 px-4 py-3 text-white focus:border-green-500 focus:outline-none focus:ring-2 focus:ring-green-500/20" + min="1" + /> +
+ +
+
+ Total Cost: + + ${(selectedLottery.ticketPriceUSD * numberOfTickets).toFixed(2)} USDT + +
+
+ +
+ + +
+
+
+ )} +
+ ); +} diff --git a/frontend/components/LotteryCard.tsx b/frontend/components/LotteryCard.tsx new file mode 100644 index 0000000..25d89c6 --- /dev/null +++ b/frontend/components/LotteryCard.tsx @@ -0,0 +1,90 @@ +"use client"; +import { useState } from "react"; + +export interface Lottery { + id: number; + name: string; + ticketPriceUSD: number; + jackpot: number; + nextDraw: number; + charityPercent: number; + usesFlareRNG: boolean; +} + +interface LotteryCardProps { + lottery: Lottery; + now: number; + onBuyTicket?: (lotteryId: number) => void; +} + +export default function LotteryCard({ lottery, now, onBuyTicket }: LotteryCardProps) { + const [isHovered, setIsHovered] = useState(false); + + const timeLeft = Math.max(0, Math.floor((lottery.nextDraw - now) / 1000)); + const hours = Math.floor(timeLeft / 3600); + const minutes = Math.floor((timeLeft % 3600) / 60); + + const handleBuyClick = () => { + if (onBuyTicket) { + onBuyTicket(lottery.id); + } else { + console.log(`Buy ticket for lottery ${lottery.id}`); + } + }; + + return ( +
setIsHovered(true)} + onMouseLeave={() => setIsHovered(false)} + > +
+

{lottery.name}

+ + {lottery.usesFlareRNG ? "Flare RNG" : "FDC Attested"} + +
+ +
+
+ Jackpot + + ${lottery.jackpot.toLocaleString()} + +
+ +
+ Ticket Price + ${lottery.ticketPriceUSD} USDT +
+ +
+ Charity + {lottery.charityPercent}% +
+ +
+ Next Draw + + {hours}h {minutes}m + +
+
+ + +
+ ); +} diff --git a/frontend/components/Navigation.tsx b/frontend/components/Navigation.tsx new file mode 100644 index 0000000..c0ee4af --- /dev/null +++ b/frontend/components/Navigation.tsx @@ -0,0 +1,57 @@ +"use client"; +import Link from "next/link"; +import { usePathname } from "next/navigation"; + +interface NavigationProps { + userType?: "organiser" | "liquidity-provider" | "user"; + showBackToHome?: boolean; +} + +export default function Navigation({ userType, showBackToHome = true }: NavigationProps) { + const pathname = usePathname(); + const isHome = pathname === "/"; + + const getUserTypeLabel = () => { + switch (userType) { + case "organiser": + return "Organiser Dashboard"; + case "liquidity-provider": + return "Liquidity Provider Dashboard"; + case "user": + return "User Dashboard"; + default: + return "LottoLink"; + } + }; + + const getUserTypeColor = () => { + switch (userType) { + case "organiser": + return "text-flare-500"; + case "liquidity-provider": + return "text-plasma-500"; + case "user": + return "text-green-400"; + default: + return "text-white"; + } + }; + + return ( + + ); +} diff --git a/frontend/components/ProtectedRoute.tsx b/frontend/components/ProtectedRoute.tsx new file mode 100644 index 0000000..1e8dad7 --- /dev/null +++ b/frontend/components/ProtectedRoute.tsx @@ -0,0 +1,29 @@ +"use client"; +import { useEffect } from "react"; +import { useRouter } from "next/navigation"; +import { useAuth } from "@/app/contexts/AuthContext"; + +interface ProtectedRouteProps { + children: React.ReactNode; +} + +export default function ProtectedRoute({ children }: ProtectedRouteProps) { + const { isAuthenticated } = useAuth(); + const router = useRouter(); + + useEffect(() => { + if (!isAuthenticated) { + router.push("/organiser/login"); + } + }, [isAuthenticated, router]); + + if (!isAuthenticated) { + return ( +
+
Redirecting to login...
+
+ ); + } + + return <>{children}; +} diff --git a/frontend/components/UserTypeCard.tsx b/frontend/components/UserTypeCard.tsx new file mode 100644 index 0000000..fcd0a74 --- /dev/null +++ b/frontend/components/UserTypeCard.tsx @@ -0,0 +1,52 @@ +"use client"; +import Link from "next/link"; + +interface UserTypeCardProps { + title: string; + description: string; + icon: string; + href: string; + colorClass: string; + features: string[]; +} + +export default function UserTypeCard({ + title, + description, + icon, + href, + colorClass, + features +}: UserTypeCardProps) { + return ( + +
+
+
+ {icon} +
+
+ → +
+
+ +

+ {title} +

+ +

+ {description} +

+ +
    + {features.map((feature, index) => ( +
  • + + {feature} +
  • + ))} +
+
+ + ); +} From 59b1776975f0c8264acbd5e9c4a5f1e3e8c8969e Mon Sep 17 00:00:00 2001 From: Jay Ambadkar Date: Sat, 7 Feb 2026 21:14:48 +0000 Subject: [PATCH 02/31] start liquidity pool bro --- contracts/flare/SharedLiquidityPool.sol | 237 +++++++++++++++++++++ contracts/test/MockUSDT.sol | 20 ++ contracts/test/SharedLiquidityPool.test.ts | 225 +++++++++++++++++++ 3 files changed, 482 insertions(+) create mode 100644 contracts/flare/SharedLiquidityPool.sol create mode 100644 contracts/test/MockUSDT.sol create mode 100644 contracts/test/SharedLiquidityPool.test.ts diff --git a/contracts/flare/SharedLiquidityPool.sol b/contracts/flare/SharedLiquidityPool.sol new file mode 100644 index 0000000..c7b6531 --- /dev/null +++ b/contracts/flare/SharedLiquidityPool.sol @@ -0,0 +1,237 @@ +// 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 iBankroll-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; + + // 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); + } + + // ============ 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"); + require(usdt.transferFrom(msg.sender, address(this), usdtAmount), "Transfer failed"); + + // Calculate shares: if pool is empty, 1:1 ratio; otherwise proportional + uint256 totalUSDT = getTVL(); + uint256 totalShares = totalSupply(); + + if (totalShares == 0 || totalUSDT == 0) { + shares = usdtAmount; // 1:1 for first deposit + } else { + shares = (usdtAmount * totalShares) / totalUSDT; + } + + _mint(msg.sender, shares); + 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 (18 decimals) + * @return price Price of 1 share in USDT wei + */ + function getSharePrice() external view returns (uint256 price) { + uint256 totalShares = totalSupply(); + if (totalShares == 0) { + return 1e6; // 1 USDT (6 decimals) if no shares + } + 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 onlyOwner nonReentrant { + 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/test/MockUSDT.sol b/contracts/test/MockUSDT.sol new file mode 100644 index 0000000..88927e4 --- /dev/null +++ b/contracts/test/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/test/SharedLiquidityPool.test.ts b/contracts/test/SharedLiquidityPool.test.ts new file mode 100644 index 0000000..f2993e6 --- /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 + expect(await pool.getSharePrice()).to.equal(ethers.parseUnits("1", 6)); + + // 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-owner", async function () { + await expect(pool.connect(lp1).executePayout(winner.address, PAYOUT_AMOUNT, 1, 1)) + .to.be.revertedWithCustomError(pool, "OwnableUnauthorizedAccount"); + }); + }); + + 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); + }); + }); +}); From ef0e76d51be92e0e2827f8e949d9ab2b58a5dcb3 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Sat, 7 Feb 2026 21:55:24 +0000 Subject: [PATCH 03/31] feat: Get buying lottery tickets and setting up lotteries connected to the backend --- backend/package-lock.json | 35 +++++ backend/package.json | 12 +- backend/src/index.ts | 111 +++++++++++++-- frontend/.dockerignore | 3 + frontend/app/organiser/dashboard/page.tsx | 160 +++++++++++----------- frontend/app/user/page.tsx | 149 ++++++++++---------- frontend/next.config.js | 8 ++ 7 files changed, 307 insertions(+), 171 deletions(-) create mode 100644 frontend/.dockerignore diff --git a/backend/package-lock.json b/backend/package-lock.json index 9db8e07..f86152b 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -8,11 +8,13 @@ "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", @@ -126,6 +128,15 @@ "@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/express": { "version": "4.17.25", "resolved": "https://registry.npmjs.org/@types/express/-/express-4.17.25.tgz", @@ -355,6 +366,22 @@ "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", @@ -790,6 +817,14 @@ "node": ">= 0.6" } }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "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", diff --git a/backend/package.json b/backend/package.json index 445f506..0933c44 100644 --- a/backend/package.json +++ b/backend/package.json @@ -8,14 +8,16 @@ "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", - "typescript": "^5.3.0", - "ts-node": "^10.9.0" + "ts-node": "^10.9.0", + "typescript": "^5.3.0" } -} \ No newline at end of file +} diff --git a/backend/src/index.ts b/backend/src/index.ts index 52a648a..33ab8ed 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,9 +1,11 @@ import express from "express"; +import cors from "cors"; import { startFlareListener, startPlasmaListener } from "./listeners"; import { executeGaslessPayout } from "./services/plasma"; const app = express(); app.use(express.json()); +app.use(cors()); // Config (set via env in production) const DRAW_MANAGER = process.env.DRAW_MANAGER_ADDRESS || ""; @@ -16,32 +18,123 @@ const ESCROW_KEY = process.env.ESCROW_PRIVATE_KEY || ""; const lotteries: any[] = []; const tickets: any[] = []; const draws: any[] = []; +let ticketIdCounter = 1; +let lotteryIdCounter = 4; + +// 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}`; +} // 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))); + +// 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 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" }); + } + + const purchased: any[] = []; + for (let i = 0; i < quantity; i++) { + const ticket = { + id: ticketIdCounter++, + lotteryId: lottery.id, + lotteryName: lottery.name, + ticketNumber: generateTicketNumber(lottery.name), + 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); +}); + 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, + name: "Flare Jackpot", + ticketPriceUSD: 5, + jackpot: 50000, nextDraw: Date.now() + 3600000, + charityPercent: 5, + usesFlareRNG: true, status: "Open" }); lotteries.push({ id: 2, - name: "Powerball", - price: "5", - prizePool: "50000000", - drawInterval: 86400, + 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 data"); @@ -76,7 +169,7 @@ async function main() { }); } - const PORT = process.env.PORT || 3001; + const PORT = process.env.PORT || 4000; app.listen(PORT, () => console.log(`LottoLink API running on port ${PORT}`)); } 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/app/organiser/dashboard/page.tsx b/frontend/app/organiser/dashboard/page.tsx index da8e205..2d6714e 100644 --- a/frontend/app/organiser/dashboard/page.tsx +++ b/frontend/app/organiser/dashboard/page.tsx @@ -1,5 +1,5 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import { useAuth } from "@/app/contexts/AuthContext"; import { useRouter } from "next/navigation"; import Navigation from "@/components/Navigation"; @@ -7,13 +7,12 @@ import Navigation from "@/components/Navigation"; interface LotteryData { id: number; name: string; - ticketPrice: number; + ticketPriceUSD: number; jackpot: number; - drawTime: string; + nextDraw: number; charityPercent: number; - rngType: "Flare RNG" | "FDC Attested"; - status: "Active" | "Pending" | "Completed"; - ticketsSold: number; + usesFlareRNG: boolean; + status: string; } export default function OrganiserDashboard() { @@ -21,78 +20,73 @@ export default function OrganiserDashboard() { const router = useRouter(); const [lotteries, setLotteries] = useState([]); const [showCreateForm, setShowCreateForm] = useState(false); - const [newLottery, setNewLottery] = useState<{ - name: string; - ticketPrice: number; - jackpot: number; - drawTime: string; - charityPercent: number; - rngType: "Flare RNG" | "FDC Attested"; - }>({ + const [isCreating, setIsCreating] = useState(false); + const [createError, setCreateError] = useState(""); + const [newLottery, setNewLottery] = useState({ name: "", - ticketPrice: 5, + ticketPriceUSD: 5, jackpot: 10000, drawTime: "", charityPercent: 5, - rngType: "Flare RNG" + usesFlareRNG: true }); + const fetchLotteries = useCallback(async () => { + try { + const res = await fetch("/api/lotteries"); + if (res.ok) { + const data = await res.json(); + setLotteries(data); + } + } catch (err) { + console.error("Failed to fetch lotteries", err); + } + }, []); + useEffect(() => { if (!user) { router.push("/organiser/login"); return; } + fetchLotteries(); + }, [user, router, fetchLotteries]); - // Mock data - setLotteries([ - { - id: 1, - name: "Flare Jackpot", - ticketPrice: 5, - jackpot: 50000, - drawTime: new Date(Date.now() + 3600000).toISOString(), - charityPercent: 5, - rngType: "Flare RNG", - status: "Active", - ticketsSold: 1234 - }, - { - id: 2, - name: "Plasma Daily", - ticketPrice: 2, - jackpot: 10000, - drawTime: new Date(Date.now() + 7200000).toISOString(), - charityPercent: 10, - rngType: "Flare RNG", - status: "Active", - ticketsSold: 567 - } - ]); - }, [user, router]); - - const handleCreateLottery = (e: React.FormEvent) => { + const handleCreateLottery = async (e: React.FormEvent) => { e.preventDefault(); - const lottery: LotteryData = { - id: Date.now(), - name: newLottery.name, - ticketPrice: newLottery.ticketPrice, - jackpot: newLottery.jackpot, - drawTime: newLottery.drawTime, - charityPercent: newLottery.charityPercent, - rngType: newLottery.rngType, - status: "Pending", - ticketsSold: 0 - }; - setLotteries([...lotteries, lottery]); - setShowCreateForm(false); - setNewLottery({ - name: "", - ticketPrice: 5, - jackpot: 10000, - drawTime: "", - charityPercent: 5, - rngType: "Flare RNG" - }); + 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 + }) + }); + if (!res.ok) { + const err = await res.json(); + throw new Error(err.error || "Failed to create lottery"); + } + await fetchLotteries(); + setShowCreateForm(false); + setNewLottery({ + name: "", + ticketPriceUSD: 5, + jackpot: 10000, + drawTime: "", + charityPercent: 5, + usesFlareRNG: true + }); + } catch (err: any) { + setCreateError(err.message); + } finally { + setIsCreating(false); + } }; const handleTriggerDraw = (id: number) => { @@ -159,8 +153,8 @@ export default function OrganiserDashboard() { setNewLottery({ ...newLottery, ticketPrice: Number(e.target.value) })} + value={newLottery.ticketPriceUSD} + onChange={(e) => 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 /> @@ -212,21 +206,28 @@ export default function OrganiserDashboard() { RNG Type + {createError && ( +
+ {createError} +
+ )} +
@@ -244,7 +245,7 @@ export default function OrganiserDashboard() { Name Price Jackpot - Tickets Sold + RNG Draw Time Status Actions @@ -254,16 +255,15 @@ export default function OrganiserDashboard() { {lotteries.map((lottery) => ( {lottery.name} - ${lottery.ticketPrice} + ${lottery.ticketPriceUSD} ${lottery.jackpot.toLocaleString()} - {lottery.ticketsSold} + {lottery.usesFlareRNG ? "Flare RNG" : "FDC"} - {new Date(lottery.drawTime).toLocaleString()} + {new Date(lottery.nextDraw).toLocaleString()} {lottery.status} @@ -273,7 +273,7 @@ export default function OrganiserDashboard() { diff --git a/frontend/app/user/page.tsx b/frontend/app/user/page.tsx index 9d488a8..56f7cf8 100644 --- a/frontend/app/user/page.tsx +++ b/frontend/app/user/page.tsx @@ -1,5 +1,5 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import Navigation from "@/components/Navigation"; import LotteryCard, { Lottery } from "@/components/LotteryCard"; @@ -14,6 +14,9 @@ interface Ticket { prize?: number; } +// Demo user ID - will be replaced with wallet address when Web3 is integrated +const DEMO_PLAYER = "demo-user-1"; + export default function UserPage() { const [lotteries, setLotteries] = useState([]); const [time, setTime] = useState(Date.now()); @@ -21,99 +24,82 @@ export default function UserPage() { const [showModal, setShowModal] = useState(false); const [selectedLottery, setSelectedLottery] = useState(null); const [numberOfTickets, setNumberOfTickets] = useState(1); + const [isPurchasing, setIsPurchasing] = useState(false); + const [purchaseError, setPurchaseError] = useState(""); - useEffect(() => { - // Mock lottery data - setLotteries([ - { - id: 1, - name: "Flare Jackpot", - ticketPriceUSD: 5, - jackpot: 50000, - nextDraw: Date.now() + 3600000, - charityPercent: 5, - usesFlareRNG: true - }, - { - id: 2, - name: "Plasma Daily", - ticketPriceUSD: 2, - jackpot: 10000, - nextDraw: Date.now() + 7200000, - charityPercent: 10, - usesFlareRNG: true - }, - { - id: 3, - name: "Global Mega", - ticketPriceUSD: 10, - jackpot: 100000, - nextDraw: Date.now() + 86400000, - charityPercent: 8, - usesFlareRNG: false + const fetchTickets = useCallback(async () => { + try { + const res = await fetch(`/api/tickets?player=${DEMO_PLAYER}`); + if (res.ok) { + const data = await res.json(); + setMyTickets(data); } - ]); + } catch (err) { + console.error("Failed to fetch tickets:", err); + } + }, []); - // Mock ticket data - setMyTickets([ - { - id: 1, - lotteryId: 1, - lotteryName: "Flare Jackpot", - ticketNumber: "FJ-2024-00123", - purchaseDate: new Date(Date.now() - 86400000 * 2).toISOString(), - drawDate: new Date(Date.now() + 3600000).toISOString(), - status: "Pending" - }, - { - id: 2, - lotteryId: 2, - lotteryName: "Plasma Daily", - ticketNumber: "PD-2024-00456", - purchaseDate: new Date(Date.now() - 86400000 * 1).toISOString(), - drawDate: new Date(Date.now() + 7200000).toISOString(), - status: "Pending" - }, - { - id: 3, - lotteryId: 1, - lotteryName: "Flare Jackpot", - ticketNumber: "FJ-2024-00089", - purchaseDate: new Date(Date.now() - 86400000 * 8).toISOString(), - drawDate: new Date(Date.now() - 86400000 * 1).toISOString(), - status: "Lost" - }, - { - id: 4, - lotteryId: 3, - lotteryName: "Global Mega", - ticketNumber: "GM-2024-00234", - purchaseDate: new Date(Date.now() - 86400000 * 15).toISOString(), - drawDate: new Date(Date.now() - 86400000 * 7).toISOString(), - status: "Won", - prize: 150 + useEffect(() => { + async function fetchLotteries() { + try { + const res = await fetch("/api/lotteries"); + if (res.ok) { + const data = await res.json(); + setLotteries(data); + } + } catch (err) { + console.error("Failed to fetch lotteries:", err); } - ]); + } + + fetchLotteries(); + fetchTickets(); const interval = setInterval(() => setTime(Date.now()), 60000); return () => clearInterval(interval); - }, []); + }, [fetchTickets]); const handleBuyTicket = (lotteryId: number) => { const lottery = lotteries.find((l) => l.id === lotteryId); if (lottery) { setSelectedLottery(lottery); + setPurchaseError(""); setShowModal(true); } }; - const handlePurchase = () => { - if (selectedLottery) { - console.log(`Purchasing ${numberOfTickets} ticket(s) for ${selectedLottery.name}`); - // TODO: Integrate with smart contract + const handlePurchase = async () => { + if (!selectedLottery) return; + + setIsPurchasing(true); + setPurchaseError(""); + + try { + const res = await fetch("/api/tickets", { + method: "POST", + headers: { "Content-Type": "application/json" }, + body: JSON.stringify({ + lotteryId: selectedLottery.id, + quantity: numberOfTickets, + player: DEMO_PLAYER + }) + }); + + if (!res.ok) { + const err = await res.json(); + throw new Error(err.error || "Purchase failed"); + } + + // Refresh tickets list from backend + await fetchTickets(); + setShowModal(false); setNumberOfTickets(1); setSelectedLottery(null); + } catch (err: any) { + setPurchaseError(err.message || "Failed to purchase tickets"); + } finally { + setIsPurchasing(false); } }; @@ -265,6 +251,12 @@ export default function UserPage() { /> + {purchaseError && ( +
+ {purchaseError} +
+ )} +
Total Cost: @@ -280,16 +272,19 @@ export default function UserPage() { setShowModal(false); setNumberOfTickets(1); setSelectedLottery(null); + setPurchaseError(""); }} className="flex-1 rounded-xl border border-gray-700 bg-gray-800 px-6 py-3 font-semibold text-white transition-colors hover:bg-gray-700" + disabled={isPurchasing} > Cancel
diff --git a/frontend/next.config.js b/frontend/next.config.js index b6b4bc3..981227d 100644 --- a/frontend/next.config.js +++ b/frontend/next.config.js @@ -1,4 +1,12 @@ /** @type {import('next').NextConfig} */ module.exports = { output: 'standalone', + async rewrites() { + return [ + { + source: '/api/:path*', + destination: 'http://backend:4000/api/:path*', + }, + ]; + }, } From 36d822f321a6c1b622c763c9f6a6d06f8ac7bc3b Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Sat, 7 Feb 2026 22:24:12 +0000 Subject: [PATCH 04/31] feat: Fix the liquidity provider dashboard --- backend/src/index.ts | 122 +++++++++++- frontend/app/liquidity-provider/page.tsx | 233 ++++++++++++++++------- 2 files changed, 280 insertions(+), 75 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 33ab8ed..8ea3f11 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -20,6 +20,10 @@ const tickets: any[] = []; const draws: any[] = []; let ticketIdCounter = 1; let lotteryIdCounter = 4; +let poolTxCounter = 1; + +// LP pool store +const poolTransactions: any[] = []; // Helper to generate ticket number function generateTicketNumber(lotteryName: string): string { @@ -103,7 +107,100 @@ app.post("/api/tickets", (req, res) => { return res.status(201).json(purchased); }); -app.get("/api/pool/stats", (_, res) => res.json({ tvl: 0, yield: 0, charityTotal: 0 })); +// ── LP Pool ───────────────────────────────────────────────── + +function getPoolBalance(): number { + return poolTransactions.reduce((sum: number, tx: any) => { + if (tx.type === "Deposit") 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", (_, res) => { + const balance = getPoolBalance(); + const exposure = getExposure(); + const withdrawable = Math.max(0, balance - exposure.total); + res.json({ + balance, + exposure: exposure.total, + exposureLotteries: exposure.lotteries, + withdrawable, + apy: lotteries.length > 0 ? 18.5 : 0 + }); +}); + +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", (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" as const, + 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" as const, + 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) { @@ -137,7 +234,28 @@ function seedData() { usesFlareRNG: false, status: "Open" }); - console.log("Seeded initial data"); + 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"); } } diff --git a/frontend/app/liquidity-provider/page.tsx b/frontend/app/liquidity-provider/page.tsx index 8c22e41..a284525 100644 --- a/frontend/app/liquidity-provider/page.tsx +++ b/frontend/app/liquidity-provider/page.tsx @@ -1,111 +1,183 @@ "use client"; -import { useState, useEffect } from "react"; +import { useState, useEffect, useCallback } from "react"; import Navigation from "@/components/Navigation"; +const DEMO_LP = "demo-lp-1"; + +interface PoolStats { + balance: number; + exposure: number; + exposureLotteries: { id: number; name: string; jackpot: number; nextDraw: number }[]; + withdrawable: number; + apy: number; +} + interface Transaction { id: number; - type: "Deposit" | "Withdraw" | "Yield"; + type: "Deposit" | "Withdraw"; amount: number; + provider: string; timestamp: string; txHash: string; } export default function LiquidityProviderPage() { - const [balance, setBalance] = useState({ - staked: 25000, - available: 5000, - totalReturns: 1250 - }); + const [stats, setStats] = useState(null); + const [transactions, setTransactions] = useState([]); const [depositAmount, setDepositAmount] = useState(""); const [withdrawAmount, setWithdrawAmount] = useState(""); - const [transactions, setTransactions] = useState([]); + const [isDepositing, setIsDepositing] = useState(false); + const [isWithdrawing, setIsWithdrawing] = useState(false); + const [error, setError] = useState(""); + const [success, setSuccess] = useState(""); - useEffect(() => { - // Mock transaction data - setTransactions([ - { - id: 1, - type: "Deposit", - amount: 10000, - timestamp: new Date(Date.now() - 86400000 * 7).toISOString(), - txHash: "0x1234...5678" - }, - { - id: 2, - type: "Yield", - amount: 450, - timestamp: new Date(Date.now() - 86400000 * 3).toISOString(), - txHash: "0xabcd...efgh" - }, - { - id: 3, - type: "Deposit", - amount: 15000, - timestamp: new Date(Date.now() - 86400000 * 2).toISOString(), - txHash: "0x9876...5432" - }, - { - id: 4, - type: "Yield", - amount: 800, - timestamp: new Date(Date.now() - 86400000).toISOString(), - txHash: "0xfedc...ba98" - } - ]); + const fetchData = useCallback(async () => { + try { + const [statsRes, txRes] = await Promise.all([ + fetch("/api/pool/stats"), + fetch(`/api/pool/transactions?provider=${DEMO_LP}`) + ]); + if (statsRes.ok) setStats(await statsRes.json()); + if (txRes.ok) setTransactions(await txRes.json()); + } catch (err) { + console.error("Failed to fetch pool data", err); + } }, []); - const handleDeposit = (e: React.FormEvent) => { + useEffect(() => { + fetchData(); + }, [fetchData]); + + const handleDeposit = async (e: React.FormEvent) => { e.preventDefault(); const amount = parseFloat(depositAmount); - if (amount > 0) { - console.log(`Depositing ${amount} USDT`); - // TODO: Integrate with Plasma Network + 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: DEMO_LP }) + }); + 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 = (e: React.FormEvent) => { + const handleWithdraw = async (e: React.FormEvent) => { e.preventDefault(); const amount = parseFloat(withdrawAmount); - if (amount > 0 && amount <= balance.staked) { - console.log(`Withdrawing ${amount} USDT`); - // TODO: Integrate with Plasma Network + 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: DEMO_LP }) + }); + 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 apy = 18.5; // Mock APY - return (
+ {/* Status Messages */} + {error && ( +
+ {error} +
+ )} + {success && ( +
+ {success} +
+ )} + {/* Balance Overview */} -
+
-

Total Staked

+

Pool Balance

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

+

Total staked USDT

+
+ +
+

Exposure (14d)

+

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

+

+ {stats?.exposureLotteries?.length ?? 0} lottery/lotteries drawing soon

-

USDT on Plasma Network

-

Total Returns

+

Withdrawable

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

-

Lifetime earnings

+

Balance minus exposure

Current APY

-

{apy}%

+

{stats?.apy ?? 0}%

Variable rate

+ {/* 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 */}
@@ -127,17 +199,16 @@ export default function LiquidityProviderPage() {
- Available Balance: - - ${balance.available.toLocaleString()} USDT - + No deposit limit + Unlimited
@@ -157,22 +228,35 @@ export default function LiquidityProviderPage() { placeholder="0.00" step="0.01" min="0" - max={balance.staked} + max={stats?.withdrawable ?? 0} />
-
+
- Staked Balance: + Pool Balance: - ${balance.staked.toLocaleString()} USDT + ${(stats?.balance ?? 0).toLocaleString()} + +
+
+ Locked (exposure): + + -${(stats?.exposure ?? 0).toLocaleString()} + +
+
+ Max Withdrawable: + + ${(stats?.withdrawable ?? 0).toLocaleString()}
@@ -181,6 +265,9 @@ export default function LiquidityProviderPage() { {/* Transaction History */}

Transaction History

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

No transactions yet.

+ ) : (
@@ -197,8 +284,7 @@ export default function LiquidityProviderPage() {
{tx.type} @@ -219,6 +305,7 @@ export default function LiquidityProviderPage() {
+ )}
{/* Info Box */} @@ -235,7 +322,7 @@ export default function LiquidityProviderPage() {
  • - Withdraw your funds at any time (subject to liquidity availability) + Withdrawals are limited by exposure: the pool must retain enough to cover jackpots of lotteries drawing within 14 days
  • From 722d174a70de4da1fdc7ef6a4dc9ca5e092ee6a7 Mon Sep 17 00:00:00 2001 From: Sean Robinson Date: Sat, 7 Feb 2026 22:42:39 +0000 Subject: [PATCH 05/31] feat: Make it so that you can win lotteries --- backend/src/index.ts | 120 ++++++++++++++- frontend/app/organiser/dashboard/page.tsx | 180 ++++++++++++++++++++-- frontend/app/user/page.tsx | 29 ++-- frontend/components/LotteryCard.tsx | 14 +- 4 files changed, 318 insertions(+), 25 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 8ea3f11..7641f6c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -33,8 +33,43 @@ function generateTicketNumber(lotteryName: string): string { 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", (_, 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))); @@ -84,6 +119,9 @@ app.post("/api/tickets", (req, res) => { 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++) { @@ -92,6 +130,7 @@ app.post("/api/tickets", (req, res) => { lotteryId: lottery.id, lotteryName: lottery.name, ticketNumber: generateTicketNumber(lottery.name), + numbers: pickNumbers(6, 49), player, price: lottery.ticketPriceUSD, purchaseDate: new Date().toISOString(), @@ -107,6 +146,85 @@ app.post("/api/tickets", (req, res) => { 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 { diff --git a/frontend/app/organiser/dashboard/page.tsx b/frontend/app/organiser/dashboard/page.tsx index 2d6714e..95efd46 100644 --- a/frontend/app/organiser/dashboard/page.tsx +++ b/frontend/app/organiser/dashboard/page.tsx @@ -13,6 +13,27 @@ interface LotteryData { charityPercent: number; usesFlareRNG: boolean; status: string; + ticketsSold?: 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() { @@ -22,6 +43,9 @@ export default function OrganiserDashboard() { 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, @@ -89,9 +113,26 @@ export default function OrganiserDashboard() { } }; - const handleTriggerDraw = (id: number) => { - console.log(`Triggering draw for lottery ${id}`); - // TODO: Integrate with smart contract + const handleTriggerDraw = async (id: number) => { + setIsDrawing(id); + setDrawError(""); + try { + const res = await fetch(`/api/lotteries/${id}/draw`, { + method: "POST", + headers: { "Content-Type": "application/json" } + }); + if (!res.ok) { + const err = await res.json(); + throw new Error(err.error || "Draw failed"); + } + const result: DrawResult = await res.json(); + setDrawResult(result); + await fetchLotteries(); + } catch (err: any) { + setDrawError(err.message); + } finally { + setIsDrawing(null); + } }; const handleLogout = () => { @@ -245,6 +286,7 @@ export default function OrganiserDashboard() { Name Price Jackpot + Tickets RNG Draw Time Status @@ -257,6 +299,7 @@ export default function OrganiserDashboard() { {lottery.name} ${lottery.ticketPriceUSD} ${lottery.jackpot.toLocaleString()} + {lottery.ticketsSold ?? 0} {lottery.usesFlareRNG ? "Flare RNG" : "FDC"} {new Date(lottery.nextDraw).toLocaleString()} @@ -264,19 +307,35 @@ export default function OrganiserDashboard() { {lottery.status} - + {lottery.status === "Open" ? ( + + ) : ( + + )} ))} @@ -284,7 +343,108 @@ export default function OrganiserDashboard() {
  • + + {/* Draw Error */} + {drawError && ( +
    + {drawError} +
    + )} + + {/* Draw Results Modal */} + {drawResult && ( +
    +
    +
    +

    Draw Results: {drawResult.lotteryName}

    + +
    + + {/* Winning Numbers */} +
    +

    Winning Numbers

    +
    + {drawResult.winningNumbers.map((n, i) => ( + + {n} + + ))} + + + + {drawResult.bonusNumber} + +
    +
    + + {/* Prize Tier Reference */} +
    +

    Prize Tiers (6/49 Model)

    +
    + 6/6 — Jackpot1 in 13,983,816 + 5 + Bonus — 2% of Jackpot1 in 2,330,636 + 5/6 — 0.5% of Jackpot1 in 55,492 + 4/6 — 20× ticket price1 in 1,033 + 3/6 — 5× ticket price1 in 57 + 2/6 — 1× ticket price1 in 7.6 +
    +
    + + {/* Stats */} +
    +
    +

    {drawResult.totalTickets}

    +

    Total Tickets

    +
    +
    +

    {drawResult.winners.length}

    +

    Winners

    +
    +
    +

    ${drawResult.totalPaidOut.toLocaleString()}

    +

    Total Paid Out

    +
    +
    + + {/* Winners List */} + {drawResult.winners.length > 0 ? ( +
    +

    Winners

    +
    + {drawResult.winners.map((w, i) => ( +
    +
    + {w.ticketNumber} + {w.player} +
    + {w.numbers.map((n: number, j: number) => ( + + {n} + + ))} +
    +
    +
    +

    ${w.prize.toLocaleString()}

    +

    {w.tier}

    +
    +
    + ))} +
    +
    + ) : ( +

    No winners this draw.

    + )} +
    +
    + )} ); } diff --git a/frontend/app/user/page.tsx b/frontend/app/user/page.tsx index 56f7cf8..f7c65af 100644 --- a/frontend/app/user/page.tsx +++ b/frontend/app/user/page.tsx @@ -8,10 +8,13 @@ interface Ticket { lotteryId: number; lotteryName: string; ticketNumber: string; + numbers?: number[]; + matchedNumbers?: number[]; purchaseDate: string; drawDate: string; status: "Pending" | "Won" | "Lost"; prize?: number; + prizeType?: string; } // Demo user ID - will be replaced with wallet address when Web3 is integrated @@ -164,8 +167,7 @@ export default function UserPage() { Ticket Number Lottery - Purchase Date - Draw Date + Your Numbers Status Prize @@ -173,7 +175,7 @@ export default function UserPage() { {myTickets.length === 0 ? ( - + No tickets yet. Purchase a ticket to get started! @@ -182,11 +184,18 @@ export default function UserPage() { {ticket.ticketNumber} {ticket.lotteryName} - - {new Date(ticket.purchaseDate).toLocaleDateString()} - - - {new Date(ticket.drawDate).toLocaleDateString()} + +
    + {(ticket.numbers || []).map((n, i) => ( + + {n} + + ))} +
    - {ticket.status} + {ticket.status}{ticket.prizeType ? ` (${ticket.prizeType})` : ""} {ticket.prize ? ( - ${ticket.prize} + ${ticket.prize.toLocaleString()} ) : ( - )} diff --git a/frontend/components/LotteryCard.tsx b/frontend/components/LotteryCard.tsx index 25d89c6..14ab746 100644 --- a/frontend/components/LotteryCard.tsx +++ b/frontend/components/LotteryCard.tsx @@ -9,6 +9,7 @@ export interface Lottery { nextDraw: number; charityPercent: number; usesFlareRNG: boolean; + status?: string; } interface LotteryCardProps { @@ -20,6 +21,7 @@ interface LotteryCardProps { export default function LotteryCard({ lottery, now, onBuyTicket }: LotteryCardProps) { const [isHovered, setIsHovered] = useState(false); + const isDrawn = lottery.status === "Drawn"; const timeLeft = Math.max(0, Math.floor((lottery.nextDraw - now) / 1000)); const hours = Math.floor(timeLeft / 3600); const minutes = Math.floor((timeLeft % 3600) / 60); @@ -41,7 +43,10 @@ export default function LotteryCard({ lottery, now, onBuyTicket }: LotteryCardPr onMouseLeave={() => setIsHovered(false)} >
    -

    {lottery.name}

    +
    +

    {lottery.name}

    + {isDrawn && Drawn} +
    Next Draw - {hours}h {minutes}m + {isDrawn ? "Completed" : `${hours}h ${minutes}m`}
    ); From b18b477dd78ad7282ec9b7989e32dbab1d4c5d34 Mon Sep 17 00:00:00 2001 From: Jay Ambadkar Date: Sat, 7 Feb 2026 23:32:00 +0000 Subject: [PATCH 06/31] more backend liquidity bro --- backend/dist/index.js | 418 +++++- backend/dist/listeners/index.js | 91 +- backend/dist/services/pool.js | 210 +++ backend/package-lock.json | 1791 ++++++++++++++++++++++- backend/package.json | 5 +- backend/src/__tests__/pool.test.ts | 136 ++ backend/src/index.ts | 108 +- backend/src/listeners/index.ts | 126 +- backend/src/services/pool.ts | 237 +++ backend/tsconfig.json | 4 + contracts/flare/DrawManager.sol | 54 +- contracts/flare/LotteryRegistry.sol | 66 +- contracts/{test => flare}/MockUSDT.sol | 0 contracts/flare/PayoutCalculator.sol | 135 +- contracts/flare/ResultAttestor.sol | 40 +- contracts/flare/SharedLiquidityPool.sol | 5 +- contracts/package.json | 4 +- contracts/scripts/deploy-pool.ts | 156 ++ contracts/test/LotteryRegistry.test.ts | 215 +++ contracts/test/PayoutCalculator.test.ts | 188 +++ 20 files changed, 3916 insertions(+), 73 deletions(-) create mode 100644 backend/dist/services/pool.js create mode 100644 backend/src/__tests__/pool.test.ts create mode 100644 backend/src/services/pool.ts rename contracts/{test => flare}/MockUSDT.sol (100%) create mode 100644 contracts/scripts/deploy-pool.ts create mode 100644 contracts/test/LotteryRegistry.test.ts create mode 100644 contracts/test/PayoutCalculator.test.ts 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..b002564 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 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/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 f86152b..3e29b06 100644 --- a/backend/package-lock.json +++ b/backend/package-lock.json @@ -18,7 +18,8 @@ "@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": { @@ -38,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", @@ -85,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", @@ -137,6 +899,13 @@ "@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", @@ -224,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", @@ -265,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", @@ -276,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", @@ -307,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", @@ -334,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", @@ -388,6 +1303,21 @@ "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", @@ -396,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", @@ -422,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", @@ -486,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", @@ -539,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", @@ -617,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", @@ -625,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", @@ -660,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", @@ -712,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", @@ -736,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", @@ -766,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", @@ -804,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", @@ -817,6 +2014,35 @@ "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", @@ -847,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", @@ -855,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", @@ -908,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", @@ -979,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", @@ -1047,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", @@ -1055,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", @@ -1111,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", @@ -1136,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", @@ -1172,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", @@ -1200,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 0933c44..391b8a3 100644 --- a/backend/package.json +++ b/backend/package.json @@ -18,6 +18,7 @@ "@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" } -} +} \ 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 7641f6c..2cb3e41 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -1,7 +1,8 @@ import express from "express"; import cors from "cors"; -import { startFlareListener, startPlasmaListener } from "./listeners"; +import { startFlareListener, startPlasmaListener, startPoolListener } from "./listeners"; import { executeGaslessPayout } from "./services/plasma"; +import { getPoolStatsFromContract, getLPPositionFromContract, isPoolConfigured, getTVL } from "./services/pool"; const app = express(); app.use(express.json()); @@ -13,6 +14,7 @@ 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[] = []; @@ -243,16 +245,84 @@ function getExposure(): { total: number; lotteries: any[] } { return { total, lotteries: upcoming.map((l: any) => ({ id: l.id, name: l.name, jackpot: l.jackpot, nextDraw: l.nextDraw })) }; } -app.get("/api/pool/stats", (_, res) => { +app.get("/api/pool/stats", async (_, res) => { + // Try contract data first if configured + if (isPoolConfigured()) { + const contractStats = await 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 + 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 (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" }); }); @@ -405,6 +475,38 @@ async function main() { }); } + // 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}`); + } + }); + } + const PORT = process.env.PORT || 4000; app.listen(PORT, () => console.log(`LottoLink API running on port ${PORT}`)); } diff --git a/backend/src/listeners/index.ts b/backend/src/listeners/index.ts index c978080..5f92399 100644 --- a/backend/src/listeners/index.ts +++ b/backend/src/listeners/index.ts @@ -1,5 +1,6 @@ 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"; @@ -8,24 +9,117 @@ const DRAW_MANAGER_ABI = ["event DrawCompleted(uint256 indexed lotteryId, uint25 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/pool.ts b/backend/src/services/pool.ts new file mode 100644 index 0000000..ebe4e57 --- /dev/null +++ b/backend/src/services/pool.ts @@ -0,0 +1,237 @@ +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)" +]; + +// 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: ethers.JsonRpcProvider | null = null; +let poolContract: ethers.Contract | null = null; +let registryContract: 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 + */ +export function isPoolConfigured(): boolean { + return !!POOL_ADDRESS; +} + +/** + * Check if registry service is configured and available + */ +export function isRegistryConfigured(): boolean { + return !!REGISTRY_ADDRESS; +} + +// Export ABIs for use in listeners +export { POOL_ABI, REGISTRY_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/flare/DrawManager.sol b/contracts/flare/DrawManager.sol index 3c9b0e4..86f765d 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,33 @@ 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"); (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 +74,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..6fd5e95 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 iBankroll 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,12 @@ 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%"); + lotteryId = nextLotteryId++; lotteries[lotteryId] = Lottery({ operator: msg.sender, @@ -61,22 +86,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/test/MockUSDT.sol b/contracts/flare/MockUSDT.sol similarity index 100% rename from contracts/test/MockUSDT.sol rename to contracts/flare/MockUSDT.sol diff --git a/contracts/flare/PayoutCalculator.sol b/contracts/flare/PayoutCalculator.sol index dac5665..f7514c0 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,29 +23,82 @@ 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 + * @param lotteryId ID of the lottery + * @param drawId ID of the draw + * @param prizePoolUSD Total prize pool in USD (USDT with 6 decimals) + * @return payouts Array of payout structures + */ + function calculatePayouts( + uint256 lotteryId, + uint256 drawId, + uint256 prizePoolUSD + ) external returns (Payout[] memory) { 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); // 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]++; @@ -51,33 +110,79 @@ contract PayoutCalculator { 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 = (prizePoolUSD * 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 + // 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) uint256 charityAmount = (prizePoolUSD * charityPercent) / 100; 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) { + 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 index c7b6531..0acc7bf 100644 --- a/contracts/flare/SharedLiquidityPool.sol +++ b/contracts/flare/SharedLiquidityPool.sol @@ -46,12 +46,13 @@ contract SharedLiquidityPool is ERC20, Ownable, ReentrancyGuard { */ function depositLiquidity(uint256 usdtAmount) external nonReentrant returns (uint256 shares) { require(usdtAmount > 0, "Amount must be > 0"); - require(usdt.transferFrom(msg.sender, address(this), usdtAmount), "Transfer failed"); - // Calculate shares: if pool is empty, 1:1 ratio; otherwise proportional + // 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 { diff --git a/contracts/package.json b/contracts/package.json index ac10e05..1b1d824 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -4,6 +4,8 @@ "scripts": { "compile": "hardhat compile", "test": "hardhat test", + "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-flare.ts --network coston2", "deploy:plasma": "hardhat run scripts/deploy-plasma.ts --network plasma" }, @@ -19,4 +21,4 @@ "dependencies": { "@openzeppelin/contracts": "^5.0.0" } -} +} \ No newline at end of file diff --git a/contracts/scripts/deploy-pool.ts b/contracts/scripts/deploy-pool.ts new file mode 100644 index 0000000..906c665 --- /dev/null +++ b/contracts/scripts/deploy-pool.ts @@ -0,0 +1,156 @@ +import { ethers } from "hardhat"; + +/** + * Deploy script for LottoLink iBankroll contracts on Coston2 testnet + * + * Deploys: + * 1. MockUSDT (or use existing testnet USDT) + * 2. SharedLiquidityPool + * 3. LotteryRegistry + * 4. DrawManager + * 5. PayoutCalculator + * + * 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 + +async function main() { + const [deployer] = await ethers.getSigners(); + console.log("Deploying contracts with account:", deployer.address); + console.log("Account balance:", ethers.formatEther(await ethers.provider.getBalance(deployer.address)), "C2FLR"); + + // ============ 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: Configure Pool (transfer ownership for payout execution) ============ + // Note: In production, you'd want a more sophisticated access control + // For POC, PayoutCalculator needs to be able to call pool.executePayout() + // Since executePayout is onlyOwner, we need to either: + // 1. Transfer ownership to PayoutCalculator (not ideal) + // 2. Add a role system (better for production) + // For now, we'll keep owner as deployer for manual payout execution in demo + console.log("\n6. Pool ownership remains with deployer for demo control"); + + // ============ 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`); + + console.log("\n✅ Deployment and seeding complete!"); +} + +main() + .then(() => process.exit(0)) + .catch((error) => { + console.error("Deployment 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); + }); + }); +}); From c4d71e20324b3a47c018da3edc680228f41e84b1 Mon Sep 17 00:00:00 2001 From: James Hughff Date: Sun, 8 Feb 2026 00:29:10 +0000 Subject: [PATCH 07/31] fix: Fix general bug and performance issues --- .claude/settings.local.json | 15 + contracts/flare/DrawManager.sol | 1 + contracts/flare/LotteryRegistry.sol | 2 + contracts/flare/PayoutCalculator.sol | 9 +- contracts/flare/SharedLiquidityPool.sol | 25 +- contracts/package-lock.json | 678 ++++++++++----------- contracts/package.json | 13 +- contracts/scripts/deploy-pool.ts | 12 +- contracts/test/SharedLiquidityPool.test.ts | 8 +- docker-compose.yml | 4 +- 10 files changed, 404 insertions(+), 363 deletions(-) create mode 100644 .claude/settings.local.json diff --git a/.claude/settings.local.json b/.claude/settings.local.json new file mode 100644 index 0000000..20b0263 --- /dev/null +++ b/.claude/settings.local.json @@ -0,0 +1,15 @@ +{ + "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:*)" + ] + } +} diff --git a/contracts/flare/DrawManager.sol b/contracts/flare/DrawManager.sol index 86f765d..1be36cc 100644 --- a/contracts/flare/DrawManager.sol +++ b/contracts/flare/DrawManager.sol @@ -52,6 +52,7 @@ contract DrawManager { ) = 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"); diff --git a/contracts/flare/LotteryRegistry.sol b/contracts/flare/LotteryRegistry.sol index 6fd5e95..81fa4a1 100644 --- a/contracts/flare/LotteryRegistry.sol +++ b/contracts/flare/LotteryRegistry.sol @@ -71,6 +71,8 @@ contract LotteryRegistry { ) 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({ diff --git a/contracts/flare/PayoutCalculator.sol b/contracts/flare/PayoutCalculator.sol index f7514c0..1b871c7 100644 --- a/contracts/flare/PayoutCalculator.sol +++ b/contracts/flare/PayoutCalculator.sol @@ -92,6 +92,10 @@ contract PayoutCalculator { ) = registry.lotteries(lotteryId); + // Deduct charity first, then apply tier percentages to the remainder + uint256 charityAmount = (prizePoolUSD * charityPercent) / 100; + uint256 tierPrizePool = prizePoolUSD - charityAmount; + // Count winners per tier uint256[4] memory tierWinners; for (uint256 i = 0; i < ticketIds.length; i++) { @@ -105,7 +109,7 @@ contract PayoutCalculator { } } - // 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++) { @@ -117,7 +121,7 @@ contract PayoutCalculator { if (matches >= 3) { uint8 tier = numbersPerTicket - matches; if (tier < 4 && tierWinners[tier] > 0) { - uint256 tierPool = (prizePoolUSD * tierPercents[tier]) / + uint256 tierPool = (tierPrizePool * tierPercents[tier]) / 100; uint256 amount = tierPool / tierWinners[tier]; payouts[payoutCount++] = Payout({ @@ -155,7 +159,6 @@ contract PayoutCalculator { } // Charity allocation (also from pool) - uint256 charityAmount = (prizePoolUSD * charityPercent) / 100; if (charityAddress != address(0) && charityAmount > 0) { pool.executePayout( charityAddress, diff --git a/contracts/flare/SharedLiquidityPool.sol b/contracts/flare/SharedLiquidityPool.sol index 0acc7bf..18a1b69 100644 --- a/contracts/flare/SharedLiquidityPool.sol +++ b/contracts/flare/SharedLiquidityPool.sol @@ -22,7 +22,10 @@ contract SharedLiquidityPool is ERC20, Ownable, ReentrancyGuard { // 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; @@ -37,6 +40,13 @@ contract SharedLiquidityPool is ERC20, Ownable, ReentrancyGuard { 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 ============ /** @@ -60,7 +70,9 @@ contract SharedLiquidityPool is ERC20, Ownable, ReentrancyGuard { } _mint(msg.sender, shares); - lpDepositTimestamp[msg.sender] = block.timestamp; + if (lpDepositTimestamp[msg.sender] == 0) { + lpDepositTimestamp[msg.sender] = block.timestamp; + } emit LiquidityDeposited(msg.sender, usdtAmount, shares); } @@ -89,13 +101,13 @@ contract SharedLiquidityPool is ERC20, Ownable, ReentrancyGuard { } /** - * @notice Get current price of 1 LP share in USDT (18 decimals) - * @return price Price of 1 share in USDT wei + * @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 1e6; // 1 USDT (6 decimals) if no shares + return 1e18; // 1:1 ratio when pool is empty (same scale as formula below) } return (getTVL() * 1e18) / totalShares; } @@ -140,7 +152,8 @@ contract SharedLiquidityPool is ERC20, Ownable, ReentrancyGuard { uint256 amount, uint256 lotteryId, uint256 drawId - ) external onlyOwner nonReentrant { + ) 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"); diff --git a/contracts/package-lock.json b/contracts/package-lock.json index 8c8c0dc..7c6635b 100644 --- a/contracts/package-lock.json +++ b/contracts/package-lock.json @@ -11,12 +11,23 @@ "@openzeppelin/contracts": "^5.0.0" }, "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 +36,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 +243,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.8.0", "@ethersproject/properties": "^5.8.0" @@ -312,7 +323,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 +379,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 +410,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 +432,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 +504,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.8.0", "@ethersproject/sha2": "^5.8.0" @@ -533,7 +544,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 +573,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 +605,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.8.0", "@ethersproject/logger": "^5.8.0" @@ -635,7 +646,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.8.0", "@ethersproject/logger": "^5.8.0", @@ -681,7 +692,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bignumber": "^5.8.0", "@ethersproject/bytes": "^5.8.0", @@ -754,7 +765,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bignumber": "^5.8.0", "@ethersproject/constants": "^5.8.0", @@ -776,7 +787,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 +844,7 @@ "url": "https://www.buymeacoffee.com/ricmoo" } ], - "peer": true, + "license": "MIT", "dependencies": { "@ethersproject/bytes": "^5.8.0", "@ethersproject/hash": "^5.8.0", @@ -881,7 +892,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 +905,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 +930,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 +944,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 +954,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 +1049,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 +1068,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 +1083,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 +1121,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 +1463,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 +1497,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 +1513,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 +1529,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 +1549,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 +1568,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 +1584,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 +1594,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 +1627,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 +1637,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 +1661,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 +1701,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 +1733,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 +1750,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 +1818,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 +1850,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 +1860,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 +1870,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 +1880,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 +1897,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 +1907,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 +1931,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 +1947,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 +1965,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 +1975,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 +1994,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 +2074,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 +2089,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 +2099,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 +2117,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 +2133,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 +2152,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 +2166,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 +2195,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 +2215,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 +2234,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 +2263,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 +2273,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 +2307,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 +2343,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 +2360,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 +2370,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 +2380,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 +2394,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 +2436,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 +2446,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 +2465,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 +2481,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 +2497,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 +2510,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 +2520,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 +2535,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 +2545,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 +2562,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 +2572,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 +2585,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 +2604,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 +2614,7 @@ "engines": [ "node >= 0.8" ], - "peer": true, + "license": "MIT", "dependencies": { "buffer-from": "^1.0.0", "inherits": "^2.0.3", @@ -2620,14 +2627,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 +2650,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 +2676,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 +2697,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 +2718,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 +2727,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 +2763,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 +2776,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 +2786,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 +2811,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 +2839,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 +2851,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" }, @@ -2859,7 +2864,7 @@ "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 +2928,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 +2938,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 +2948,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 +2961,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 +2998,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 +3021,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 +3035,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 +3044,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 +3054,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 +3090,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 +3113,7 @@ "url": "https://paulmillr.com/funding/" } ], - "peer": true, + "license": "MIT", "dependencies": { "@noble/hashes": "~1.2.0", "@noble/secp256k1": "~1.7.0", @@ -3127,7 +3131,7 @@ "url": "https://paulmillr.com/funding/" } ], - "peer": true, + "license": "MIT", "dependencies": { "@noble/hashes": "~1.2.0", "@scure/base": "~1.1.0" @@ -3138,7 +3142,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 +3165,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 +3204,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 +3214,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 +3227,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 +3251,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 +3278,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 +3297,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 +3307,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 +3329,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 +3347,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 +3371,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 +3388,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 +3417,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 +3475,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 +3491,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 +3514,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 +3530,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 +3557,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 +3576,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 +3586,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 +3611,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 +3621,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 +3635,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 +3649,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 +3662,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 +3677,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 +3687,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 +3704,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 +3714,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 +3759,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 +3772,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 +3787,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 +3807,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 +3819,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 +3840,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 +3853,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 +3872,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 +3894,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 +3966,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 +4109,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 +4122,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 +4135,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 +4151,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 +4167,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 +4190,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 +4207,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 +4224,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 +4246,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 +4264,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 +4300,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 +4310,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 +4342,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 +4384,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 +4422,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 +4465,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 +4494,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 +4522,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 +4554,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 +4570,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 +4583,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 +4608,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 +4618,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 +4653,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 +4668,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 +4698,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 +4720,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 +4737,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 +4758,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 +4806,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 +4825,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 +4839,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 +4849,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 +4886,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 +4896,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 +5010,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 +5023,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 +5044,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 +5054,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 +5076,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 +5091,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 +5108,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 +5136,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 +5154,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 +5214,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 +5230,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 +5246,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 +5256,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 +5266,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 +5302,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 +5312,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 +5322,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 +5331,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 +5347,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 +5364,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 +5401,7 @@ "url": "https://feross.org/support" } ], - "peer": true + "license": "MIT" }, "node_modules/randombytes": { "version": "2.1.0", @@ -5457,7 +5459,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 +5471,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 +5484,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 +5495,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 +5508,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 +5518,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 +5531,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 +5553,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 +5575,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 +5585,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 +5596,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 +5610,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 +5637,7 @@ "url": "https://feross.org/support" } ], - "peer": true, + "license": "MIT", "dependencies": { "queue-microtask": "^1.2.2" } @@ -5672,7 +5673,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 +5699,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 +5709,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 +5721,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 +5738,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 +5748,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 +5762,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 +5776,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 +5789,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 +5809,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 +5817,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 +5832,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 +5857,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 +5875,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 +5888,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 +5909,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 +5923,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 +5941,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 +5953,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 +5974,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 +5987,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 +6007,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 +6024,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 +6043,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 +6063,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 +6073,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 +6121,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 +6155,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 +6175,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 +6190,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 +6200,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 +6217,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 +6232,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 +6242,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 +6252,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 +6265,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 +6278,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 +6289,6 @@ "integrity": "sha512-CBdZ2oa/BHhS4xj5DlhjWNHcan57/5YuvfdLf17iVmIpd9KRm+DFLmC6nBNj+6Ua7Kt3TmOjDpQT1aTYOQtoUA==", "dev": true, "optional": true, - "peer": true, "dependencies": { "amdefine": ">=0.0.4" }, @@ -6320,7 +6320,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 +6366,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 +6399,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 +6437,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 +6452,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 +6462,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 +6479,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 +6495,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 +6505,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 +6515,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 +6538,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 +6620,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 +6656,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 +6672,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 +6734,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 +6747,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 +6760,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 +6782,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 +6807,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 +6818,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 +6834,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 +6855,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 +6865,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 +6878,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 +6891,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 +6901,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 +6916,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 +6936,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 +6946,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 +6978,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 +6997,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 +7025,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 +7045,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 +7058,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 +7073,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 +7086,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 +7099,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 +7112,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 +7125,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 +7159,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 +7169,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 +7190,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 +7229,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 1b1d824..5640501 100644 --- a/contracts/package.json +++ b/contracts/package.json @@ -10,15 +10,26 @@ "deploy:plasma": "hardhat run scripts/deploy-plasma.ts --network plasma" }, "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" } -} \ No newline at end of file +} diff --git a/contracts/scripts/deploy-pool.ts b/contracts/scripts/deploy-pool.ts index 906c665..af09685 100644 --- a/contracts/scripts/deploy-pool.ts +++ b/contracts/scripts/deploy-pool.ts @@ -72,14 +72,10 @@ async function main() { const payoutCalcAddress = await payoutCalc.getAddress(); console.log(" PayoutCalculator deployed to:", payoutCalcAddress); - // ============ Step 6: Configure Pool (transfer ownership for payout execution) ============ - // Note: In production, you'd want a more sophisticated access control - // For POC, PayoutCalculator needs to be able to call pool.executePayout() - // Since executePayout is onlyOwner, we need to either: - // 1. Transfer ownership to PayoutCalculator (not ideal) - // 2. Add a role system (better for production) - // For now, we'll keep owner as deployer for manual payout execution in demo - console.log("\n6. Pool ownership remains with deployer for demo control"); + // ============ 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)); diff --git a/contracts/test/SharedLiquidityPool.test.ts b/contracts/test/SharedLiquidityPool.test.ts index f2993e6..e6da3ac 100644 --- a/contracts/test/SharedLiquidityPool.test.ts +++ b/contracts/test/SharedLiquidityPool.test.ts @@ -79,8 +79,8 @@ describe("SharedLiquidityPool", function () { }); it("should return correct share price", async function () { - // Empty pool - 1:1 ratio - expect(await pool.getSharePrice()).to.equal(ethers.parseUnits("1", 6)); + // 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); @@ -158,9 +158,9 @@ describe("SharedLiquidityPool", function () { .to.be.revertedWith("Insufficient pool liquidity"); }); - it("should reject payout from non-owner", async function () { + it("should reject payout from non-authorized caller", async function () { await expect(pool.connect(lp1).executePayout(winner.address, PAYOUT_AMOUNT, 1, 1)) - .to.be.revertedWithCustomError(pool, "OwnableUnauthorizedAccount"); + .to.be.revertedWith("Not authorized for payouts"); }); }); diff --git a/docker-compose.yml b/docker-compose.yml index 15e14ba..5633c9a 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,7 +2,7 @@ services: frontend: build: ./frontend ports: - - "3000:3000" + - "${FRONTEND_PORT:-3001}:3000" environment: - NODE_ENV=production depends_on: @@ -11,6 +11,6 @@ services: backend: build: ./backend ports: - - "4000:4000" + - "${BACKEND_PORT:-4001}:4000" env_file: - ./backend/.env From 5f6c4354aec567504e0da5c19ada12ddfe814c9f Mon Sep 17 00:00:00 2001 From: James Hughff Date: Sun, 8 Feb 2026 00:40:42 +0000 Subject: [PATCH 08/31] feat: did some things --- .claude/settings.local.json | 3 ++- docker-compose.yml | 3 ++- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/.claude/settings.local.json b/.claude/settings.local.json index 20b0263..785ac6b 100644 --- a/.claude/settings.local.json +++ b/.claude/settings.local.json @@ -9,7 +9,8 @@ "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(MSYS_NO_PATHCONV=1 docker run:*)", + "Bash(git log:*)" ] } } diff --git a/docker-compose.yml b/docker-compose.yml index 5633c9a..8a0a79c 100644 --- a/docker-compose.yml +++ b/docker-compose.yml @@ -2,9 +2,10 @@ services: frontend: build: ./frontend ports: - - "${FRONTEND_PORT:-3001}:3000" + - "${FRONTEND_PORT:-3001}:${FRONTEND_PORT:-3001}" environment: - NODE_ENV=production + - PORT=${FRONTEND_PORT:-3001} depends_on: - backend From 903720c062548e26f6cf1ce5c1e0ba7524401e67 Mon Sep 17 00:00:00 2001 From: James Hughff Date: Sun, 8 Feb 2026 00:46:16 +0000 Subject: [PATCH 09/31] feat: make lotteries bound to a single organiser --- backend/src/index.ts | 12 +++++++++--- frontend/app/organiser/dashboard/page.tsx | 8 +++++--- 2 files changed, 14 insertions(+), 6 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index 2cb3e41..c0b0c70 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -65,8 +65,10 @@ function calculatePrize( let drawIdCounter = 1; // API Routes -app.get("/api/lotteries", (_, res) => { - const enriched = lotteries.map(l => ({ +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 => ({ ...l, ticketsSold: tickets.filter(t => t.lotteryId === l.id).length })); @@ -77,7 +79,7 @@ app.get("/api/lotteries/:id/results", (req, res) => res.json(draws.filter(d => d // Create lottery app.post("/api/lotteries", (req, res) => { - const { name, ticketPriceUSD, jackpot, nextDraw, charityPercent, usesFlareRNG } = req.body; + const { name, ticketPriceUSD, jackpot, nextDraw, charityPercent, usesFlareRNG, organiser } = req.body; if (!name || ticketPriceUSD == null || jackpot == null || !nextDraw) { return res.status(400).json({ error: "name, ticketPriceUSD, jackpot, and nextDraw are required" }); @@ -91,6 +93,7 @@ app.post("/api/lotteries", (req, res) => { nextDraw: new Date(nextDraw).getTime(), charityPercent: Number(charityPercent) || 0, usesFlareRNG: Boolean(usesFlareRNG), + organiser: organiser || "unknown", status: "Open" }; @@ -400,6 +403,7 @@ function seedData() { nextDraw: Date.now() + 3600000, charityPercent: 5, usesFlareRNG: true, + organiser: "admin1", status: "Open" }); lotteries.push({ @@ -410,6 +414,7 @@ function seedData() { nextDraw: Date.now() + 7200000, charityPercent: 10, usesFlareRNG: true, + organiser: "lottery_ops", status: "Open" }); lotteries.push({ @@ -420,6 +425,7 @@ function seedData() { nextDraw: Date.now() + 86400000, charityPercent: 8, usesFlareRNG: false, + organiser: "organiser", status: "Open" }); console.log("Seeded initial lottery data"); diff --git a/frontend/app/organiser/dashboard/page.tsx b/frontend/app/organiser/dashboard/page.tsx index 95efd46..8f5bcfc 100644 --- a/frontend/app/organiser/dashboard/page.tsx +++ b/frontend/app/organiser/dashboard/page.tsx @@ -56,8 +56,9 @@ export default function OrganiserDashboard() { }); const fetchLotteries = useCallback(async () => { + if (!user) return; try { - const res = await fetch("/api/lotteries"); + const res = await fetch(`/api/lotteries?organiser=${encodeURIComponent(user.username)}`); if (res.ok) { const data = await res.json(); setLotteries(data); @@ -65,7 +66,7 @@ export default function OrganiserDashboard() { } catch (err) { console.error("Failed to fetch lotteries", err); } - }, []); + }, [user]); useEffect(() => { if (!user) { @@ -89,7 +90,8 @@ export default function OrganiserDashboard() { jackpot: newLottery.jackpot, nextDraw: newLottery.drawTime, charityPercent: newLottery.charityPercent, - usesFlareRNG: newLottery.usesFlareRNG + usesFlareRNG: newLottery.usesFlareRNG, + organiser: user?.username }) }); if (!res.ok) { From 2a1d71f014bbb1cbe736f120292f55ec95b08d17 Mon Sep 17 00:00:00 2001 From: James Hughff Date: Sun, 8 Feb 2026 00:56:37 +0000 Subject: [PATCH 10/31] feat: added the capability for more info about charities and populated with real charities --- backend/src/index.ts | 32 +++++++++----- frontend/app/contexts/AuthContext.tsx | 8 ++-- frontend/app/organiser/login/page.tsx | 6 +-- frontend/app/user/page.tsx | 60 +++++++++++++++++++++++++++ frontend/components/LotteryCard.tsx | 50 ++++++++++++++++------ 5 files changed, 127 insertions(+), 29 deletions(-) diff --git a/backend/src/index.ts b/backend/src/index.ts index c0b0c70..fc04a88 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -79,7 +79,7 @@ app.get("/api/lotteries/:id/results", (req, res) => res.json(draws.filter(d => d // Create lottery app.post("/api/lotteries", (req, res) => { - const { name, ticketPriceUSD, jackpot, nextDraw, charityPercent, usesFlareRNG, organiser } = req.body; + const { name, ticketPriceUSD, jackpot, nextDraw, charityPercent, usesFlareRNG, organiser, organiserDisplayName, description, website } = req.body; if (!name || ticketPriceUSD == null || jackpot == null || !nextDraw) { return res.status(400).json({ error: "name, ticketPriceUSD, jackpot, and nextDraw are required" }); @@ -94,6 +94,9 @@ app.post("/api/lotteries", (req, res) => { charityPercent: Number(charityPercent) || 0, usesFlareRNG: Boolean(usesFlareRNG), organiser: organiser || "unknown", + organiserDisplayName: organiserDisplayName || organiser || "Unknown", + description: description || "", + website: website || "", status: "Open" }; @@ -397,35 +400,44 @@ function seedData() { if (lotteries.length === 0) { lotteries.push({ id: 1, - name: "Flare Jackpot", + name: "Omaze Dream House", ticketPriceUSD: 5, jackpot: 50000, nextDraw: Date.now() + 3600000, - charityPercent: 5, + charityPercent: 10, usesFlareRNG: true, - organiser: "admin1", + 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", status: "Open" }); lotteries.push({ id: 2, - name: "Plasma Daily", + name: "Postcode Neighbourhood Draw", ticketPriceUSD: 2, jackpot: 10000, nextDraw: Date.now() + 7200000, - charityPercent: 10, + charityPercent: 33, usesFlareRNG: true, - organiser: "lottery_ops", + 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", status: "Open" }); lotteries.push({ id: 3, - name: "Global Mega", + name: "Comic Relief Big Draw", ticketPriceUSD: 10, jackpot: 100000, nextDraw: Date.now() + 86400000, - charityPercent: 8, + charityPercent: 25, usesFlareRNG: false, - organiser: "organiser", + 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", status: "Open" }); console.log("Seeded initial lottery data"); diff --git a/frontend/app/contexts/AuthContext.tsx b/frontend/app/contexts/AuthContext.tsx index a9d302b..a13fc28 100644 --- a/frontend/app/contexts/AuthContext.tsx +++ b/frontend/app/contexts/AuthContext.tsx @@ -14,11 +14,11 @@ interface AuthContextType { const AuthContext = createContext(undefined); -// Hardcoded credentials for demo +// Hardcoded credentials for demo — real charitable lottery organisations const VALID_CREDENTIALS = [ - { username: "admin1", password: "demo123" }, - { username: "lottery_ops", password: "secure456" }, - { username: "organiser", password: "pass789" } + { username: "omaze", password: "demo123" }, + { username: "postcode_lottery", password: "secure456" }, + { username: "comic_relief", password: "pass789" } ]; export function AuthProvider({ children }: { children: ReactNode }) { diff --git a/frontend/app/organiser/login/page.tsx b/frontend/app/organiser/login/page.tsx index 705db8a..c9822e2 100644 --- a/frontend/app/organiser/login/page.tsx +++ b/frontend/app/organiser/login/page.tsx @@ -96,9 +96,9 @@ export default function OrganiserLoginPage() { Demo Credentials:

    -

    admin1 / demo123

    -

    lottery_ops / secure456

    -

    organiser / pass789

    +

    omaze / demo123

    +

    postcode_lottery / secure456

    +

    comic_relief / pass789

    diff --git a/frontend/app/user/page.tsx b/frontend/app/user/page.tsx index f7c65af..9ac8926 100644 --- a/frontend/app/user/page.tsx +++ b/frontend/app/user/page.tsx @@ -29,6 +29,7 @@ export default function UserPage() { const [numberOfTickets, setNumberOfTickets] = useState(1); const [isPurchasing, setIsPurchasing] = useState(false); const [purchaseError, setPurchaseError] = useState(""); + const [infoLottery, setInfoLottery] = useState(null); const fetchTickets = useCallback(async () => { try { @@ -152,6 +153,7 @@ export default function UserPage() { lottery={lottery} now={time} onBuyTicket={handleBuyTicket} + onShowInfo={setInfoLottery} /> ))} @@ -223,6 +225,64 @@ export default function UserPage() { + {/* Charity Info Modal */} + {infoLottery && ( +
    +
    +
    +
    +

    + {infoLottery.organiserDisplayName || infoLottery.name} +

    +

    {infoLottery.name}

    +
    + +
    + +

    + {infoLottery.description} +

    + +
    +
    +

    {infoLottery.charityPercent}%

    +

    Goes to charity

    +
    +
    +

    ${infoLottery.jackpot.toLocaleString()}

    +

    Current jackpot

    +
    +
    + + {infoLottery.website && ( + + Visit {infoLottery.organiserDisplayName || "charity"} website + + )} + + +
    +
    + )} + {/* Purchase Modal */} {showModal && selectedLottery && (
    diff --git a/frontend/components/LotteryCard.tsx b/frontend/components/LotteryCard.tsx index 14ab746..43a50f1 100644 --- a/frontend/components/LotteryCard.tsx +++ b/frontend/components/LotteryCard.tsx @@ -10,17 +10,21 @@ export interface Lottery { charityPercent: number; usesFlareRNG: boolean; status?: string; + organiserDisplayName?: string; + description?: string; + website?: string; } interface LotteryCardProps { lottery: Lottery; now: number; onBuyTicket?: (lotteryId: number) => void; + onShowInfo?: (lottery: Lottery) => void; } -export default function LotteryCard({ lottery, now, onBuyTicket }: LotteryCardProps) { +export default function LotteryCard({ lottery, now, onBuyTicket, onShowInfo }: LotteryCardProps) { const [isHovered, setIsHovered] = useState(false); - + const isDrawn = lottery.status === "Drawn"; const timeLeft = Math.max(0, Math.floor((lottery.nextDraw - now) / 1000)); const hours = Math.floor(timeLeft / 3600); @@ -58,6 +62,12 @@ export default function LotteryCard({ lottery, now, onBuyTicket }: LotteryCardPr
    + {lottery.organiserDisplayName && ( +

    + by {lottery.organiserDisplayName} +

    + )} +
    Jackpot @@ -65,17 +75,17 @@ export default function LotteryCard({ lottery, now, onBuyTicket }: LotteryCardPr ${lottery.jackpot.toLocaleString()}
    - +
    Ticket Price ${lottery.ticketPriceUSD} USDT
    - +
    Charity {lottery.charityPercent}%
    - +
    Next Draw @@ -84,13 +94,29 @@ export default function LotteryCard({ lottery, now, onBuyTicket }: LotteryCardPr
    - +
    + {onShowInfo && lottery.description && ( + + )} + +
    ); } From cd1ad234284f2148d814e5fd496484954b891d94 Mon Sep 17 00:00:00 2001 From: James Hughff Date: Sun, 8 Feb 2026 01:13:49 +0000 Subject: [PATCH 11/31] feat --- frontend/app/organiser/dashboard/page.tsx | 54 +++++++++++++++++++++-- 1 file changed, 51 insertions(+), 3 deletions(-) diff --git a/frontend/app/organiser/dashboard/page.tsx b/frontend/app/organiser/dashboard/page.tsx index 8f5bcfc..2ec53c9 100644 --- a/frontend/app/organiser/dashboard/page.tsx +++ b/frontend/app/organiser/dashboard/page.tsx @@ -52,7 +52,10 @@ export default function OrganiserDashboard() { jackpot: 10000, drawTime: "", charityPercent: 5, - usesFlareRNG: true + usesFlareRNG: true, + organiserDisplayName: "", + description: "", + website: "" }); const fetchLotteries = useCallback(async () => { @@ -91,7 +94,10 @@ export default function OrganiserDashboard() { nextDraw: newLottery.drawTime, charityPercent: newLottery.charityPercent, usesFlareRNG: newLottery.usesFlareRNG, - organiser: user?.username + organiser: user?.username, + organiserDisplayName: newLottery.organiserDisplayName || user?.username, + description: newLottery.description, + website: newLottery.website }) }); if (!res.ok) { @@ -106,7 +112,10 @@ export default function OrganiserDashboard() { jackpot: 10000, drawTime: "", charityPercent: 5, - usesFlareRNG: true + usesFlareRNG: true, + organiserDisplayName: "", + description: "", + website: "" }); } catch (err: any) { setCreateError(err.message); @@ -258,6 +267,45 @@ export default function OrganiserDashboard() { +
    + + 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" + /> +
    + +
    + +