diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 9adb7df..a544bf0 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -37,6 +37,39 @@ npm run build `npm run e2e:testnet` is an optional, read-only check against a running backend and live testnet; see the header of `e2e/testnet.e2e.test.ts`. +## Measuring realtime feeds + +Use separate terminals: + +```bash +node scripts/realtime-burst.mjs --manual --port 4310 +NEXT_PUBLIC_API_URL=http://localhost:4310/api/v1 npm run dev +``` + +Open `/markets?profile=1` or `/?profile=1` and keep that tab visible. +Development-only React Profiler logs `[realtime-profile]`, the component +name, commit count and total `actualDuration` every second. Let the page +settle, then trigger each sample from another terminal: + +```bash +curl 'http://localhost:4310/burst?count=500&rate=500' +# Wait for completion before starting the next sample. +curl 'http://localhost:4310/burst?count=500&rate=50' +``` + +Sum logged intervals covering each burst and its final settling interval. +Use the same browser, viewport and animation preference for both revisions. +For a baseline, instrument the original feed with the same profiler wrapper; +the original feeds need no functional changes. Profiler time measures React +render work, not socket validation, paint, or end-to-end latency. Slow +baselines may coalesce events under load, so fewer commits alone do not +prove better responsiveness. Production ignores `?profile=1`. + +The simulator serves an empty market API and emits synthetic public trades; +it never signs or submits transactions. `--count`, `--rate` and `--port` also +configure its automatic burst (omit `--manual` to emit after the first +connection). See [docs/STATUS.md](docs/STATUS.md) for measured results. + ## When the contracts change The contracts live in diff --git a/app/copy-trade/page.tsx b/app/copy-trade/page.tsx index 8b58598..ade7e66 100644 --- a/app/copy-trade/page.tsx +++ b/app/copy-trade/page.tsx @@ -1,5 +1,10 @@ "use client"; +import { TxReview, TxReviewPreferenceControl, shouldSkipTxReview } from "@/components/tx-review"; +import type { ReviewModel } from "@/lib/stellar/tx-decode"; +import { useSubmitQueueState } from "@/lib/stellar/submit-queue"; + + import React, { useState, useRef, useEffect, useCallback, Suspense } from "react"; import { useSearchParams, useRouter } from "next/navigation"; import { useWallet } from "@/lib/contexts/WalletContext"; @@ -7,12 +12,15 @@ import { getTrace, ReasoningTrace } from "@/lib/api/traces"; import { confirmCopyTrade, initiateCopyTrade } from "@/lib/api/trade"; import { ApiError } from "@/lib/api/client"; import { useMarket } from "@/lib/hooks/useMarkets"; -import { useBuy, useQuoteBuy, useUsdcBalance, useAccountReserve, usePrepareBuy } from "@/lib/hooks/useStellar"; +import { useBuy, useQuoteBuy, useUsdcBalance, useAccountReserve, usePrepareBuy, useOnChainMarket, fetchOnChainMarket, useUsdcTrustline, useEnableUsdc, useUsdcBalanceState, type PreparedWriteReview } from "@/lib/hooks/useStellar"; import { explorerTx } from "@/lib/stellar/config"; import { formatUsdc, formatXlm, minOut, shareOf } from "@/lib/stellar/units"; import { TxPhase } from "@/lib/stellar/tx-lifecycle"; /** Wait this long after the slider stops before asking for a new quote. */ +import { getLedgerNow, useLedgerNow } from "@/lib/stellar/ledger-clock"; +import { MIN_TX_TIMEOUT_S, SAFETY_MARGIN_S } from "@/lib/stellar/deadline"; +import { PendingValue } from "@/components/pending-value"; const QUOTE_DEBOUNCE_MS = 400; function renderTxPhase(phase: TxPhase): string | null { @@ -60,6 +68,13 @@ function CopyTradeContent() { const onChainMarket = useOnChainMarket(onChainMarketId); const balance = useUsdcBalance(address); + const balanceState = useUsdcBalanceState(address); + const ledger = useLedgerNow(); + const trustline = useUsdcTrustline(address); + const enableUsdc = useEnableUsdc(); + const trustlineMissing = trustline.data?.kind === "missing"; + const trustlineReady = trustline.data?.kind === "native" || (trustline.data?.kind === "present" && trustline.data.authorized); + const marketCanTrade = onChainMarket.data?.market.status.tag === "Open" && ledger.now !== null && Number(onChainMarket.data.market.close_time) - ledger.now > SAFETY_MARGIN_S + MIN_TX_TIMEOUT_S; const reserve = useAccountReserve(address); const buy = useBuy(); const submitQueue = useSubmitQueueState(address); @@ -241,6 +256,7 @@ function CopyTradeContent() { collateralIn: amountRaw, slippageBps, prepared: prepareBuy.data, + review: reviewPreparedBuy, }); if (result.txHash) await confirmCopyTrade(copyTradeId, result.txHash).catch(() => undefined); @@ -362,7 +378,7 @@ function CopyTradeContent() {
- {allocation.toFixed(2)}% ({amountRaw !== null ? `${formatUsdc(amountRaw, { maxDecimals: 2, minDecimals: 2, grouping: true })} USDC` : isConnected ? "loading balance…" : "connect a wallet"}) + {allocation.toFixed(2)}% ({amountRaw !== null ? `${formatUsdc(amountRaw, { maxDecimals: 2, minDecimals: 2, grouping: true })} USDC` : isConnected ? "loading balance…" : "connect a wallet"})
- - {children} -
- + Stellar Testnet
@@ -183,51 +184,36 @@ const MarketCard = ({ href, category, onChainMarketId, reasoningTraces, expiry, ); }; -interface FeedRow { - key: string; - time: string; - market: string; - size: string; - bet: string; - prob: string; - color: string; -} - -/** Payload of the backend's TRADE_EXECUTED socket event (docs/api.md). */ -interface TradeExecutedEvent { - eventId: string; - onChainMarketId: string; - marketQuestion: string | null; - direction: "YES" | "NO"; - isBuy: boolean; - collateralRaw: string; - priceYesBps: number; -} - const TradingFeed = () => { - const [feed, setFeed] = useState([]); - - useEffect(() => { - const socket = io(API_URL.replace(/\/api\/v1\/?$/, "")); - - socket.on("TRADE_EXECUTED", (data: TradeExecutedEvent) => { - const time = new Date().toLocaleTimeString('en-GB', { hour12: false }); - const newEntry: FeedRow = { - key: data.eventId, - time, - market: data.marketQuestion || `Market #${data.onChainMarketId}`, - size: `$${formatUsdc(BigInt(data.collateralRaw), { maxDecimals: 2, grouping: true })}`, - bet: `${data.isBuy ? "" : "SELL "}${data.direction}`, - prob: `YES ${(data.priceYesBps / 100).toFixed(1)}%`, - color: data.direction === "YES" ? "text-secondary" : "text-tertiary", - }; - setFeed(prev => [newEntry, ...prev.slice(0, 7)]); - }); - - return () => { - socket.disconnect(); - }; - }, []); + const { rows, burst } = useBurstFeed({ capacity: 8, type: "TRADE_EXECUTED" }); + const reduceMotion = useReducedMotion(); + const feed = rows.flatMap(event => { + if (event.type !== "TRADE_EXECUTED") return []; + const data = event.payload; + return [{ + key: data.eventId, + time: formatFeedTime(data.closedAt ?? event.receivedAt) + (data.closedAt ? "" : " received"), + market: data.marketQuestion || `Market #${data.onChainMarketId}`, + size: `$${formatUsdc(BigInt(data.collateralRaw), { maxDecimals: 2, grouping: true })}`, + bet: `${data.isBuy ? "BUY" : "SELL"} ${data.direction}`, + prob: `YES ${(data.priceYesBps / 100).toFixed(1)}%`, + color: data.direction === "YES" ? "text-secondary" : "text-tertiary", + }]; + }); + const animate = !reduceMotion && !burst; + const tableRows = feed.map(row => { + const cells = <> + {row.time} + {row.market} + {row.size} + {row.bet} + {row.prob} + ; + const className = "hover:bg-surface-container transition-colors border-b border-outline-variant/30"; + return animate + ? {cells} + : {cells}; + }); return (
@@ -235,6 +221,7 @@ const TradingFeed = () => { analytics

Real-time Trading Feed

+ {burst &&

{burst.count} trades in the last second{burst.overflow ? ` · ${burst.overflow} more events while this tab was hidden` : ""}.

}
@@ -247,29 +234,9 @@ const TradingFeed = () => { - - - {feed.length === 0 && ( - - - - )} - {feed.map((row) => ( - - - - - - - - ))} - + + {feed.length === 0 && } + {animate ? {tableRows} : tableRows}
Probability
Waiting for on-chain trades…
{row.time}{row.market}{row.size}{row.bet}{row.prob}
Waiting for on-chain trades…
@@ -332,7 +299,7 @@ export default function Markets() { {isLoading ? (
{[...Array(6)].map((_, i) => ( -
+
))}
) : error ? ( @@ -376,7 +343,7 @@ export default function Markets() { - +
); diff --git a/app/markets/quick-view/page.tsx b/app/markets/quick-view/page.tsx index 0fc5bea..63397eb 100644 --- a/app/markets/quick-view/page.tsx +++ b/app/markets/quick-view/page.tsx @@ -1,5 +1,10 @@ "use client"; +import { TxReview, TxReviewPreferenceControl, shouldSkipTxReview } from "@/components/tx-review"; +import type { ReviewModel } from "@/lib/stellar/tx-decode"; +import { useSubmitQueueState } from "@/lib/stellar/submit-queue"; + + import React, { Suspense, useCallback, useEffect, useRef, useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; import Link from "next/link"; @@ -8,12 +13,19 @@ import { useQuery } from "@tanstack/react-query"; import { useMarket } from "@/lib/hooks/useMarkets"; import { getMarketByOnChainId } from "@/lib/api/markets"; import { ApiError } from "@/lib/api/client"; -import { useBuy, useOnChainMarket, usePosition, useQuoteBuy, useUsdcBalance, useAccountReserve, usePrepareBuy } from "@/lib/hooks/useStellar"; +import { useBuy, useOnChainMarket, usePosition, useQuoteBuy, useUsdcBalance, useAccountReserve, usePrepareBuy, useMarketConfig, useLedgerNow, useEnableUsdc, useUsdcTrustline, useExactBuyQuote, usePositionState, useUsdcBalanceState } from "@/lib/hooks/useStellar"; import { useWallet } from "@/lib/contexts/WalletContext"; import { explorerTx } from "@/lib/stellar/config"; -import { formatUsdc, minOut, tryParseUsdc } from "@/lib/stellar/units"; +import { formatUsdc, formatXlm, maxIn, minOut, tryParseUsdc } from "@/lib/stellar/units"; import { buyBreakdown } from "@/lib/stellar/buy-breakdown"; +import type { TxPhase } from "@/lib/stellar/tx-lifecycle"; +import { preflightBuy } from "@/lib/stellar/preflight"; +import { useLedgerNow as useLedgerClock } from "@/lib/stellar/ledger-clock"; +import { MIN_TX_TIMEOUT_S, SAFETY_MARGIN_S } from "@/lib/stellar/deadline"; +import { TradeTape } from "@/components/market/trade-tape"; +import { PendingValue } from "@/components/pending-value"; + const SLIPPAGE_BPS = 100; // 1% const SourceTag = ({ source }: { source: "chain" | "backend" }) => ( @@ -91,6 +103,15 @@ function MarketQuickViewContent() { const position = usePosition(onChainMarketId, address); const balance = useUsdcBalance(address); const reserve = useAccountReserve(address); + const positionState = usePositionState(onChainMarketId, address); + const balanceState = useUsdcBalanceState(address); + const config = useMarketConfig(); + const ledgerNow = useLedgerNow(); + const ledger = useLedgerClock(); + const trustline = useUsdcTrustline(address); + const enableUsdc = useEnableUsdc(); + const trustlineMissing = trustline.data?.kind === "missing"; + const trustlineUnauthorized = trustline.data?.kind === "present" && !trustline.data.authorized; const buy = useBuy(); const submitQueue = useSubmitQueueState(address); const [now, setNow] = useState(null); @@ -108,7 +129,23 @@ function MarketQuickViewContent() { const reviewResolver = useRef<((approved: boolean) => void) | null>(null); const amountRaw = tryParseUsdc(amountInput); const debouncedAmount = useDebounced(amountRaw, 400); - const quote = useQuoteBuy(onChainMarketId, side, debouncedAmount); + const exactQuote = useExactBuyQuote(onChainMarketId, side, buyMode === "shares" ? debouncedAmount : null); + const spendRaw = buyMode === "shares" ? exactQuote.data?.collateralIn ?? null : amountRaw; + const quote = useQuoteBuy(onChainMarketId, side, buyMode === "usdc" ? debouncedAmount : spendRaw); + const prepareBuy = usePrepareBuy({ marketId: onChainMarketId, outcome: side, collateralIn: spendRaw, slippageBps: SLIPPAGE_BPS, holder: address }); + const fees = prepareBuy.data?.fees; + const isUnfunded = reserve.data?.funded === false; + const isInsufficientXlm = !!(reserve.data?.funded && fees && fees.totalFee > reserve.data.spendable); + const xlmDeficit = reserve.data?.funded && fees && fees.totalFee > reserve.data.spendable ? fees.totalFee - reserve.data.spendable : 0n; + const closeReview = useCallback((approved: boolean) => { + setReviewModel(null); reviewResolver.current?.(approved); reviewResolver.current = null; + }, []); + const review = useCallback(async (_prepared: unknown, model: ReviewModel, changedFrom?: ReviewModel, force = false) => { + if (!force && shouldSkipTxReview()) return true; + setReviewModel({ model, changedFrom }); + return new Promise(resolve => { reviewResolver.current = resolve; }); + }, []); + useEffect(() => () => { reviewResolver.current?.(false); }, []); const view = chain.data; const breakdown = view && debouncedAmount && debouncedAmount > 0n ? (() => { @@ -126,6 +163,9 @@ function MarketQuickViewContent() { const noProb = 100 - yesProb; const chainStatus = view?.market.status; // Use a timer-backed timestamp so freshness changes are reflected without impure render reads. + const secondsToClose = ledger.now !== null && view ? Number(view.market.close_time - BigInt(Math.floor(ledger.now))) : null; + const safeToTrade = secondsToClose !== null && secondsToClose > SAFETY_MARGIN_S + MIN_TX_TIMEOUT_S; + const countdown = secondsToClose !== null && secondsToClose >= 0 && secondsToClose < 3600 ? `Closes in ${Math.floor(secondsToClose / 60)}m ${secondsToClose % 60}s` : null; const isOpen = chainStatus?.tag === "Open" && Number(view?.market.close_time ?? 0) * 1000 > chain.dataUpdatedAt; const isFresh = (updatedAt: number, maxAge: number) => now !== null && now >= updatedAt && now - updatedAt <= maxAge; @@ -149,7 +189,7 @@ function MarketQuickViewContent() { : undefined, }, side, - amountRaw ?? 0n, + spendRaw ?? 0n, ); const buyPreflightFailure = tradePreflight.ok === false ? tradePreflight.failures[0] : null; @@ -191,7 +231,8 @@ function MarketQuickViewContent() { const result = await buy.mutateAsync({ marketId: onChainMarketId, outcome: side, - collateralIn: amountRaw, + collateralIn: spendRaw ?? 0n, + review, slippageBps: SLIPPAGE_BPS, prepared: prepareBuy.data, }); @@ -418,7 +459,7 @@ function MarketQuickViewContent() { ? `≈ ${formatUsdc(quote.data, { maxDecimals: 2 })} shares, at least ${formatUsdc(minOut(quote.data, SLIPPAGE_BPS), { maxDecimals: 2 })} (1% slippage)` : quote.error ? quote.error.message : quote.isFetching ? "Quoting…" : ""} - {balance.data !== undefined && Balance {formatUsdc(balance.data, { maxDecimals: 2, grouping: true })}} + {balance.data !== undefined && Balance {formatUsdc(balance.data, { maxDecimals: 2, grouping: true })}} {/* Network fee & reserve row */} @@ -475,10 +516,10 @@ function MarketQuickViewContent() { )} {trustlineUnauthorized &&

The issuer hasn't authorized your USDC trustline.

} - {position.data && (position.data.yes > 0n || position.data.no > 0n) && ( + {position.data && (position.data.yes > 0n || position.data.no > 0n || positionState.data?.provisional || positionState.data?.awaitingRpc) && (

Your position: {formatUsdc(position.data.yes, { maxDecimals: 2 })} YES · {formatUsdc(position.data.no, { maxDecimals: 2 })} NO - + } />

)} @@ -506,6 +547,8 @@ function MarketQuickViewContent() { )} + {onChainMarketId !== null && } + {/* AI Reasoning Snippet */}
diff --git a/app/notifications/page.tsx b/app/notifications/page.tsx index f970c39..85ffe45 100644 --- a/app/notifications/page.tsx +++ b/app/notifications/page.tsx @@ -1,219 +1,68 @@ "use client"; -import React, { useState, useEffect, useMemo } from "react"; +import { useMemo, useState } from "react"; import Link from "next/link"; -import { io } from "socket.io-client"; -import { usePositions } from "@/lib/hooks/usePortfolio"; -import { API_URL } from "@/lib/api/client"; -import { explorerTx } from "@/lib/stellar/config"; +import { useInbox } from "@/lib/notifications/InboxProvider"; +import type { InboxItem } from "@/lib/notifications/store"; +import { useWallet } from "@/lib/contexts/WalletContext"; +import { explorerTx, NETWORK_LABEL } from "@/lib/stellar/config"; import { formatUsdc } from "@/lib/stellar/units"; -type NotificationType = "all" | "critical" | "trading" | "ai"; - -interface NotificationItem { - id: string; - type: Exclude; - title: string; - message: string; - time: string; - timestamp: number; - payout?: string; - txHash?: string; - probability?: number; - icon: string; - iconBg: string; - iconColor: string; - borderColor: string; +type Filter = "all" | "trades" | "redemptions" | "reasoning" | "transactions"; +const group = (item: InboxItem): Exclude => item.kind === "fill" ? "trades" : item.kind === "trace" ? "reasoning" : item.kind === "redeemable" || item.kind === "redeemed" ? "redemptions" : "transactions"; +const labels: Record = { all: "All", trades: "Trades", redemptions: "Redemptions", reasoning: "Reasoning", transactions: "Transactions" }; +const title: Record = { fill: "Trade confirmed", redeemable: "Ready to redeem", redeemed: "Shares redeemed", trace: "New market reasoning", "tx-failed": "Transaction failed", "tx-pending": "Transaction pending" }; +function message(item: InboxItem) { + const d = item.data; + switch (item.kind) { + case "fill": return `${d.action} ${formatUsdc(BigInt(d.sharesRaw), { maxDecimals: 2 })} ${d.direction} shares for ${formatUsdc(BigInt(d.collateralRaw), { maxDecimals: 7, grouping: true })} USDC.`; + case "redeemable": return `Market #${item.marketId} ${d.outcome === "Void" ? "was voided" : `resolved ${d.outcome}`}. Review your payout and redeem your shares.`; + case "redeemed": return `${formatUsdc(BigInt(d.payoutRaw), { maxDecimals: 7, grouping: true })} USDC paid to your wallet.`; + case "trace": return `New reasoning on market #${item.marketId}: ${d.action}.`; + default: return `${d.action}: ${d.message}`; + } } - - export default function NotificationsPage() { - const [filter, setFilter] = useState("all"); - const [liveNotifications, setLiveNotifications] = useState([]); - const [clearedBefore, setClearedBefore] = useState(0); - const { data: positionsData } = usePositions({ limit: 10 }); - - // Positions become notifications; live socket events are added on top. - const localNotifications = useMemo(() => { - const positionNotifications: NotificationItem[] = (positionsData?.positions ?? []).map(pos => ({ - id: `pos-${pos.id}`, - type: "trading", - title: "Trade Confirmed", - message: `${pos.status === 'CLOSED' ? 'Closed' : 'Opened'} ${pos.direction} position on "${pos.market.question}" for ${pos.size.toLocaleString()} USDC.`, - time: new Date(pos.createdAt).toLocaleTimeString([], { hour: '2-digit', minute: '2-digit' }), - timestamp: new Date(pos.createdAt).getTime(), - txHash: pos.trade?.txHash || undefined, - icon: pos.status === 'CLOSED' ? "history" : "check_circle", - iconBg: "bg-secondary-fixed", - iconColor: "text-secondary", - borderColor: "border-l-secondary", - })); - - return [...liveNotifications, ...positionNotifications] - .filter(n => n.timestamp > clearedBefore) - .sort((a, b) => b.timestamp - a.timestamp); - }, [positionsData, liveNotifications, clearedBefore]); - - // Socket listener for real-time trades - useEffect(() => { - const socket = io(API_URL.replace(/\/api\/v1\/?$/, "")); - - // Payload documented in docs/api.md (Realtime). - socket.on("TRADE_EXECUTED", (data: { eventId: string; direction: string; isBuy: boolean; onChainMarketId: string; marketQuestion: string | null; collateralRaw: string; txHash: string }) => { - const newNotif: NotificationItem = { - id: `socket-${data.eventId}`, - type: "trading", - title: "New Trade Executed", - message: `${data.isBuy ? "Bought" : "Sold"} ${data.direction} on ${data.marketQuestion ? `"${data.marketQuestion}"` : `market #${data.onChainMarketId}`} for ${formatUsdc(BigInt(data.collateralRaw), { maxDecimals: 2, grouping: true })} USDC.`, - time: "Just now", - timestamp: Date.now(), - txHash: data.txHash, - icon: "bolt", - iconBg: "bg-secondary-fixed", - iconColor: "text-secondary", - borderColor: "border-l-secondary", - }; - setLiveNotifications(prev => [newNotif, ...prev]); - }); - - return () => { - socket.disconnect(); - }; - }, []); - - const filteredNotifications = localNotifications.filter( - (item) => filter === "all" || item.type === filter - ); - - const filterButtons = [ - { label: "All Notifications", type: "all" as const, icon: "dashboard", count: localNotifications.length }, - { label: "Critical", type: "critical" as const, icon: "warning", count: localNotifications.filter(n => n.type === 'critical').length }, - { label: "Trading", type: "trading" as const, icon: "payments", count: localNotifications.filter(n => n.type === 'trading').length }, - { label: "AI Signals", type: "ai" as const, icon: "psychology", count: localNotifications.filter(n => n.type === 'ai').length }, - ]; - - return ( -
-
- {/* Left Column: Filter and Summary */} -
-
-

Activity

-
- {filterButtons.map((btn) => ( - - ))} -
-
-
-
- - auto_awesome - - ORACLE INSIGHT -
-

- Live trades from the OracleDesk contracts on Stellar Testnet, plus agent positions. -

-
-
- - {/* Middle Column: Main Feed */} -
-
-

Alert Center

- -
- -
- {filteredNotifications.map((item) => ( -
-
- - {item.icon} - -
-
-
-

- {item.title} -

- - {item.time} - -
-

- {item.message} -

- - {item.probability !== undefined && ( -
-
-
-
-
- )} - - {item.txHash && ( - - - TX: {item.txHash.substring(0, 10)}... - - - open_in_new - - - )} -
-
- ))} - - {filteredNotifications.length === 0 && ( -
-

No active alerts. Monitoring the network...

-
- )} + const inbox = useInbox(); + const { address, openModal, isConnecting } = useWallet(); + const [filter, setFilter] = useState("all"); + const counts = useMemo(() => { + const next: Record = { all: inbox.items.length, trades: 0, redemptions: 0, reasoning: 0, transactions: 0 }; + inbox.items.forEach(item => next[group(item)]++); + return next; + }, [inbox.items]); + const items = inbox.items.filter(item => filter === "all" || group(item) === filter); + return
+
+

Notifications

Your wallet activity on {NETWORK_LABEL}.

+ {address && } +
+ {!address ?
+

Connect to see your inbox

+

Your fills, redemptions, pending transactions, and reasoning on markets you hold appear here.

+ +
:
+ +
+ {inbox.status === "memory" &&

Notifications won't be saved in this browser.

} + {inbox.error &&

{inbox.error} Retrying while this tab is visible.

} + {inbox.burst &&

{inbox.burst.count} network events received in a batch.{inbox.burst.overflow > 0 && ` ${inbox.burst.overflow} more events while this tab was hidden; open a market to refresh its history.`}

} + {inbox.status === "loading" &&
Loading your inbox…
} + {inbox.status !== "loading" && items.length === 0 &&

No {filter === "all" ? "notifications" : labels[filter].toLowerCase()} yet. Your wallet's next activity will appear here.

} + {items.map(item =>
+

{title[item.kind]}{item.readAt === null && Unread}

+

{message(item)}

+
+ {item.marketId && View market #{item.marketId}} + {item.kind === "trace" && item.data.traceId && Read reasoning} + {item.txHash && View transaction} + {item.readAt === null && }
-
-
-
- ); + )} +
+ } + ; } diff --git a/app/page.tsx b/app/page.tsx index 164d216..3ad43b1 100644 --- a/app/page.tsx +++ b/app/page.tsx @@ -1,82 +1,57 @@ "use client"; -import React, { useState, useEffect, useRef } from "react"; +import React, { useState, useEffect } from "react"; import Link from "next/link"; import { motion } from "framer-motion"; -import { io } from "socket.io-client"; import { useWallet } from "@/lib/contexts/WalletContext"; import { useMarkets } from "@/lib/hooks/useMarkets"; -import { API_URL } from "@/lib/api/client"; import { formatUsdc } from "@/lib/stellar/units"; +import { useBurstFeed } from "@/lib/realtime/useBurst"; +import { type RealtimeEvent, eventKey } from "@/lib/realtime/types"; +import { formatFeedTime } from "@/lib/realtime/format"; +import { RealtimeProfiler } from "@/components/realtime-profiler"; import { DemoBadge } from "@/components/ui/demo-badge"; -interface FeedItem { - time: string; - agent: string; - action: string; - details: string; - chain: string; - color: string; -} - -const AgentActivityFeed = () => { - const [feed, setFeed] = useState([]); - - useEffect(() => { - const socket = io(API_URL.replace(/\/api\/v1\/?$/, "")); +const AgentActivityRow = React.memo(function AgentActivityRow({ event }: { event: RealtimeEvent }) { + const data = event.payload; + const time = formatFeedTime(data.closedAt ?? event.receivedAt); + const item = event.type === "TRADE_EXECUTED" ? { + key: eventKey(event), time, receipt: !data.closedAt, agent: "Trader", action: event.payload.isBuy ? "BUY" : "SELL", + details: `${event.payload.direction} ${formatUsdc(BigInt(event.payload.collateralRaw), { maxDecimals: 2, grouping: true })} USDC on #${data.onChainMarketId} · YES ${(event.payload.priceYesBps / 100).toFixed(1)}%`, + chain: "Stellar", color: "text-secondary", + } : { + key: eventKey(event), time, receipt: !data.closedAt, agent: "Reasoner", action: "PUBLISH_TRACE", + details: `Trace #${event.payload.onChainTraceId} (${event.payload.action})`, chain: "Stellar", color: "text-primary-fixed", + }; - // Payloads are documented in docs/api.md (Realtime). - socket.on("TRADE_EXECUTED", (data: { direction: string; isBuy: boolean; collateralRaw: string; priceYesBps: number; onChainMarketId: string }) => { - const time = new Date().toLocaleTimeString('en-GB', { hour12: false }); - setFeed(prev => [{ - time, - agent: "Trader", - action: data.isBuy ? "BUY" : "SELL", - details: `${data.direction} ${formatUsdc(BigInt(data.collateralRaw), { maxDecimals: 2, grouping: true })} USDC on #${data.onChainMarketId} · YES ${(data.priceYesBps / 100).toFixed(1)}%`, - chain: "Stellar", - color: "text-secondary" - }, ...prev.slice(0, 5)]); - }); + return
+ [{item.time}]{item.receipt && " received"} + {item.agent} + {item.action} + {item.details} + {item.chain} +
; +}); - socket.on("REASONING_PUBLISHED", (data: { onChainTraceId: string; action: string }) => { - const time = new Date().toLocaleTimeString('en-GB', { hour12: false }); - setFeed(prev => [{ - time, - agent: "Reasoner", - action: "PUBLISH_TRACE", - details: `Trace #${data.onChainTraceId} (${data.action})`, - chain: "Stellar", - color: "text-primary-fixed" - }, ...prev.slice(0, 5)]); - }); - return () => { - socket.disconnect(); - }; - }, []); +const AgentActivityFeed = () => { + const { rows, burst } = useBurstFeed({ capacity: 6 }); return (
- monitoring + monitoring

Live Agent Activity

Stellar Testnet
- {feed.length === 0 && ( + {rows.length === 0 && (

Waiting for agent activity on-chain…

)} - {feed.map((item, i) => ( -
- [{item.time}] - {item.agent} - {item.action} - {item.details} - {item.chain} -
- ))} + {burst &&

{burst.count} events in the last second{burst.overflow ? ` · ${burst.overflow} more events while this tab was hidden` : ""}.

} + {rows.map(event => )}
); @@ -132,9 +107,9 @@ const Ticker = () => { return (
-
+
{[...tickerItems, ...tickerItems, ...tickerItems].map((item, i) => ( - {item} • + = tickerItems.length ? "motion-reduce:hidden" : ""}>{item} • ))}
@@ -322,7 +297,7 @@ const MarketsGrid = () => {
{isLoading ? ( [...Array(3)].map((_, i) => ( -
+
)) ) : markets.length > 0 ? ( markets.map((m) => { @@ -386,7 +361,7 @@ const ReasoningSection = () => {

> CORRELATION DETECTED: 0.89 WITH 2019 PIVOT

> CALCULATING CONFIDENCE INTERVALS...

> TRACE ANCHORED ON ETHEREUM MAINNET

-

> VERDICT: DOVISH SHIFT CONFIRMED

+

> VERDICT: DOVISH SHIFT CONFIRMED

@@ -529,7 +504,7 @@ export default function Home() {
- +
@@ -544,4 +519,4 @@ export default function Home() { ); -} \ No newline at end of file +} diff --git a/app/premium/page.tsx b/app/premium/page.tsx index 4cf1569..9cc5c0a 100644 --- a/app/premium/page.tsx +++ b/app/premium/page.tsx @@ -1,5 +1,10 @@ "use client"; +import { TxReview, TxReviewPreferenceControl, shouldSkipTxReview } from "@/components/tx-review"; +import type { ReviewModel } from "@/lib/stellar/tx-decode"; +import { useSubmitQueueState } from "@/lib/stellar/submit-queue"; + + import React, { useCallback, useEffect, useRef, useState } from "react"; import { motion, AnimatePresence } from "framer-motion"; @@ -109,6 +114,7 @@ const features = [ export default function PremiumPage() { const { address, isConnected, isWrongNetwork, openModal, ensureSession } = useWallet(); const transfer = useUsdcTransfer(); + const submitQueue = useSubmitQueueState(address); const unlock = useUnlockTrace(); const reserve = useAccountReserve(address); @@ -195,6 +201,7 @@ export default function PremiumPage() { to: PAYMENTS_RECIPIENT, amount: selectedTier.priceRaw, prepared: prepareTransfer.data, + review: reviewPreparedTransfer, followUp: { type: "unlock-daily-pass", traceId: DAILY_PASS_TRACE_PLACEHOLDER, diff --git a/components/layout/Navbar.tsx b/components/layout/Navbar.tsx index ba80518..095f52f 100644 --- a/components/layout/Navbar.tsx +++ b/components/layout/Navbar.tsx @@ -3,10 +3,12 @@ import React from "react"; import Link from "next/link"; import { usePathname } from "next/navigation"; +import { useInbox } from "@/lib/notifications/InboxProvider"; import WalletDropdown from "./WalletDropdown"; const Navbar = () => { const pathname = usePathname(); + const { unread } = useInbox(); const navLinks = [ { @@ -96,12 +98,13 @@ const Navbar = () => {
notifications - + {unread > 0 && } diff --git a/components/layout/WalletDropdown.tsx b/components/layout/WalletDropdown.tsx index 0010e2d..d1f8076 100644 --- a/components/layout/WalletDropdown.tsx +++ b/components/layout/WalletDropdown.tsx @@ -3,8 +3,9 @@ import React, { useState, useRef, useEffect } from "react"; import { motion, AnimatePresence } from "framer-motion"; import { useWallet } from "@/lib/contexts/WalletContext"; -import { useAccountReserve, useEnableUsdc, useRpcNetwork, useUsdcBalance, useUsdcTrustline } from "@/lib/hooks/useStellar"; +import { useAccountReserve, useEnableUsdc, useRpcNetwork, useUsdcBalance, useUsdcTrustline, useUsdcBalanceState } from "@/lib/hooks/useStellar"; import { NETWORK_LABEL, NETWORK_PASSPHRASE, TESTNET_PASSPHRASE, explorerAccount } from "@/lib/stellar/config"; +import { PendingValue } from "@/components/pending-value"; import { formatUsdc, formatXlm, shortAddress } from "@/lib/stellar/units"; const WalletDropdown = () => { @@ -13,6 +14,7 @@ const WalletDropdown = () => { const [copied, setCopied] = useState(false); const dropdownRef = useRef(null); const balance = useUsdcBalance(address); + const balanceState = useUsdcBalanceState(address); const trustline = useUsdcTrustline(address); const enableUsdc = useEnableUsdc(); const reserve = useAccountReserve(address); @@ -120,7 +122,7 @@ const WalletDropdown = () => {
- {balanceText} + {balanceText} USDC diff --git a/components/market/trade-tape.tsx b/components/market/trade-tape.tsx new file mode 100644 index 0000000..f8f583e --- /dev/null +++ b/components/market/trade-tape.tsx @@ -0,0 +1,61 @@ +"use client"; +import { useState } from "react"; +import { useMarketTrades } from "@/lib/hooks/useMarketTrades"; +import { useWallet } from "@/lib/contexts/WalletContext"; +import { explorerAccount, explorerTx } from "@/lib/stellar/config"; +import { formatUsdc, shortAddress } from "@/lib/stellar/units"; + +const ROW_HEIGHT = 48; +const VISIBLE = 8; +const OVERSCAN = 8; +function relativeTime(iso: string, now: number) { + const seconds = Math.max(0, Math.floor((now - Date.parse(iso)) / 1000)); + if (seconds < 60) return `${seconds}s ago`; + if (seconds < 3600) return `${Math.floor(seconds / 60)}m ago`; + if (seconds < 86400) return `${Math.floor(seconds / 3600)}h ago`; + return `${Math.floor(seconds / 86400)}d ago`; +} +export function TradeTape({ onChainMarketId, limit = 20, compact = false }: { onChainMarketId: string; limit?: number; compact?: boolean }) { + return ; +} +function MarketTape({ id, limit, compact }: { id: string; limit: number; compact: boolean }) { + const [want, setWant] = useState(limit); + const [scrollTop, setScrollTop] = useState(0); + const query = useMarketTrades(id, { limit: want }); + const rows = query.data?.trades ?? []; + const { address } = useWallet(); + const first = Math.max(0, Math.floor(scrollTop / ROW_HEIGHT) - OVERSCAN); + const last = Math.min(rows.length, first + VISIBLE + OVERSCAN * 2); + const titles = ["Time", "Side", "Action", "Trader", "USDC", "Shares", "Fee", "YES after", "Transaction"]; + return
+

Recent trades

+

Market #{id} · Public contract events within the RPC retention window.

+ {query.isPending &&
Loading trades…
} + {query.isError &&

Couldn't load trades.

} + {!query.isPending && !query.isError && rows.length === 0 &&

No trades on this market in the RPC's retention window.

} + {rows.length > 0 &&
setScrollTop(e.currentTarget.scrollTop)}> + + {titles.map(title => )} + + {first > 0 && } + {rows.slice(first, last).map((row, offset) => { + const previous = rows[first + offset + 1]?.yesBpsAfter; + const movement = previous === undefined ? null : row.yesBpsAfter - previous; + return + + + + + {[row.collateral, row.shares, row.fee].map((value, index) => )} + + + ; + })} + {last < rows.length && } + +
{title}
{row.origin === "socket" && live}{row.outcome.toUpperCase()}{row.isBuy ? "BUY" : "SELL"}{row.trader === address ? "you" : shortAddress(row.trader)}{formatUsdc(value, { maxDecimals: index === 2 ? 7 : 2, grouping: true })}{(row.yesBpsAfter / 100).toFixed(2)}%{movement !== null && = 0 ? "up" : "down"} ${Math.abs(movement)} basis points`}> {movement > 0 ? "↑" : movement < 0 ? "↓" : "→"}}{row.txHash.slice(0, 8)}…
+
} + {query.data?.reachedRetention ?

Older trades are beyond the RPC's retention window.

: want < 2000 && } + {want === 2000 &&

Showing up to the newest 2,000 trades.

} +
; +} diff --git a/components/pending-value.tsx b/components/pending-value.tsx new file mode 100644 index 0000000..0c43afc --- /dev/null +++ b/components/pending-value.tsx @@ -0,0 +1,11 @@ +import type { ReactNode } from "react"; +import { explorerTx } from "@/lib/stellar/config"; +import type { Stamped } from "@/lib/stellar/optimistic"; + +export function PendingValue({ state, fallback = null }: { state: Stamped | undefined; fallback?: ReactNode }) { + const pending = state?.provisional ?? state?.awaitingRpc; + if (!pending) return fallback; + const label = state?.awaitingRpc ? "awaiting RPC" : "pending confirmation"; + return {pending.txHash + ? {label} : label}; +} diff --git a/components/realtime-profiler.tsx b/components/realtime-profiler.tsx new file mode 100644 index 0000000..4fd4540 --- /dev/null +++ b/components/realtime-profiler.tsx @@ -0,0 +1,21 @@ +"use client"; +import { Profiler, useEffect, useRef, useState, type ProfilerOnRenderCallback, type ReactNode } from "react"; + +export function RealtimeProfiler({ id, children }: { id: string; children: ReactNode }) { + const [enabled, setEnabled] = useState(false); + const metrics = useRef({ commits: 0, actualDuration: 0 }); + useEffect(() => { + if (process.env.NODE_ENV === "production" || new URLSearchParams(location.search).get("profile") !== "1") return; + const activate = setTimeout(() => setEnabled(true), 0); + const timer = setInterval(() => { + console.info("[realtime-profile]", id, metrics.current); + metrics.current = { commits: 0, actualDuration: 0 }; + }, 1000); + return () => { clearTimeout(activate); clearInterval(timer); }; + }, [id]); + const onRender: ProfilerOnRenderCallback = (_id, _phase, actualDuration) => { + metrics.current.commits++; metrics.current.actualDuration += actualDuration; + }; + if (process.env.NODE_ENV === "production" || !enabled) return children; + return {children}; +} diff --git a/docs/STATUS.md b/docs/STATUS.md index 6350c2c..ce56c9d 100644 --- a/docs/STATUS.md +++ b/docs/STATUS.md @@ -1,6 +1,66 @@ # Status -Last updated: 2026-10-07. This is the honest account of the frontend's Stellar port: what works, what isn't verified, and what a human has to decide. "Verified" means a command was run and its output observed in the session that wrote this file. The backend's [docs/STATUS.md](https://github.com/OracleDesk/OracleDesk-Backend/blob/main/docs/STATUS.md) covers the server side. +Last updated: 2026-10-18. The latest issue verification is below; the original Stellar port record follows it. "Verified" means a command was run and its output observed in the session that wrote this file. The backend's [docs/STATUS.md](https://github.com/OracleDesk/OracleDesk-Backend/blob/main/docs/STATUS.md) covers the server side. + +## Wallet activity issues #39–#42 (2026-10-18) + +- #39: confirmed bigint position/balance deltas, ledger guards, reconciliation, + pending/awaiting-RPC labels and transaction links. +- #40: contract-spec Trade filters/decoding, backward window probing, cursor + pagination, live deduplication, retention notices and a bounded virtual tape. +- #41: shared socket delivery once per frame, bounded catch-up, rolling burst + summaries, static burst rows, memoized home rows and reduced motion. +- #42: address/network-scoped IndexedDB inbox, explicit persisted read state, + own-wallet classification, resolution/redemption polling and a real bell count. + +Validation: `check:bindings`, `typecheck`, `lint`, `test` and `build` pass. +The suite has 176 tests across 25 files; lint reports 31 existing warnings and +no errors. Production prerender still reports the existing Recharts container +size warnings. Baseline merge defects in trading-hook imports, SDK 16 trustline +calls, ledger-clock RPC access, transaction review and tracker polling were +repaired because these paths are required by the four issues. +The lockfile also includes the wallet kit's peer dependency tree, so plain +`npm ci` can validate and install it without `--legacy-peer-deps`. +Clean installation succeeds. npm reports 69 dependency advisories (18 low, +16 moderate, 30 high, 5 critical); dependency security upgrades are outside +these four issues and were not applied. + +Browser checks in headless Chrome (1440×1100): the actual app rendered both +feeds without exceptions; hidden-tab simulation held the feed DOM unchanged, +then delivered eight visible rows and an overflow summary for 1,000 events. +Emulated reduced motion left all feed-row transforms at `none`. A separate +temporary component harness using actual inbox/tape components and fixture +wallets/RPC confirmed read state after reload, switching A→B→A without leaking +A's fills, disconnect clearing the inbox, and **25 DOM rows for 2,000 trades** +(24 data rows plus a spacer, both at the top and after scrolling). + +Profiler comparison: 500 synthetic trades per sample, normal motion, +development React Profiler, Node 24.15.0, Next 16.2.6, React 19.2.4. The baseline +uses the `develop@529912f` feed implementations with the same profiler wrapper; +the final feed uses the shared batcher. Each row sums the measured intervals +including the final settling interval. These are single local samples, not a +hardware-independent FPS claim. See CONTRIBUTING.md for reproduction. + +| Feed | Events/second | Before commits | After commits | Before render ms | After render ms | +|---|---:|---:|---:|---:|---:| +| Market | 500 | 40 | 57 | 463.1 | 247.0 | +| Market | 50 | 323 | 496 | 3,191.4 | 2,112.5 | +| Home | 500 | 102 | 61 | 232.8 | 203.1 | +| Home | 50 | 497 | 499 | 1,412.1 | 1,007.3 | + +The slower baseline market feed coalesces more events under main-thread load; +its lower commit count does not mean it delivered more responsive updates. +The new scheduler emits at most one subscriber batch per frame and removes +animation wrappers during sustained bursts. Render time decreased in these +samples, while the ring bounds and scheduler behavior are also unit tested. + +Still manual: signing buy/sell/redeem with a real browser wallet, real +two-wallet event delivery, and live seeded-market resolution/redemption. No +transactions were submitted during verification. Event decoding tests use +contract-shaped XDR fixtures, not a newly captured testnet trade. Follow the +new checklist in [manual-test.md](manual-test.md). + +[Production inbox screenshot, disconnected](images/notifications-disconnected.png). ## ⚠️ Action required before anything else diff --git a/docs/architecture.md b/docs/architecture.md index c5a8dc8..eb9982b 100644 --- a/docs/architecture.md +++ b/docs/architecture.md @@ -9,12 +9,14 @@ The frontend has two sources that can disagree, and it says which one a value ca | Prices, reserves, market status, positions, USDC balance | **Chain**, via simulated contract calls (`lib/hooks/useStellar.ts`) | The contract is the truth; the backend's copy can lag. | | Question text, category, reasoning traces (metadata), subscriptions, platform counts | **Backend** (`lib/api/*`, `docs/api.md`) | Not stored on-chain in readable form. | | Trace content | **IPFS**, then hash-checked against `reasoning_registry.get_trace` | Gateways are not authenticated; content is shown only if its sha256 matches the chain (`lib/stellar/trace.ts`). | +| Per-market trade tape | **Chain** `Trade` events, extended by validated socket events | Contract-spec topic filters select one market. Backward ledger windows with forward cursor pagination find the newest rows; RPC retention limits history. Socket copies are replaced by decoded RPC copies on refetch. | +| Wallet inbox | **IndexedDB**, populated from validated own-wallet events and the transaction tracker | Scoped by address and network passphrase. It contains no agent portfolio data. Resolution/redemption events are polled every 60 seconds while visible; current contract status catches resolutions outside retention. | A backend market links to the chain through `onChainMarketId` (market-core's `u64` id, a decimal string in JSON). On-chain markets the backend doesn't know are still listed on `/markets`, with on-chain data only. ## Wallet and login -`lib/stellar/WalletProvider.tsx` wraps `@creit.tech/stellar-wallets-kit` 2.5.0 (pinned: the last release built on `@stellar/stellar-sdk` 16, the same major as the contract bindings, so the app ships one SDK copy). +`lib/stellar/WalletProvider.tsx` wraps `@creit.tech/stellar-wallets-kit` 2.5.0 (pinned: the last release built on `@stellar/stellar-sdk` 16, the same major as the contract bindings). The application and bindings share SDK 16; hardware-wallet internals can carry separate transitive versions. - The kit is imported dynamically in an effect, because it reads `localStorage` when its module loads. - A session the kit persisted is restored without prompting and without redirecting. Only a connect completed in the modal redirects to `/markets`. @@ -27,6 +29,14 @@ A backend market links to the chain through `onChainMarketId` (market-core's `u6 - Reads are simulations (no fee, no signature). - Writes (`useBuy`, `useSell`, `useRedeem`, `useUsdcTransfer`) check that a wallet is connected and on the right network, run fresh contract preflight checks, simulate, then `signAndSend`. A per-address queue serializes complete build/sign/send jobs in this tab and coordinates browser tabs with Web Locks (or a local-storage lease fallback); stale-sequence errors receive one fresh-build retry. The buy, sell, redeem, and USDC-transfer paths can decode the final simulated contract invocation and show its arguments and transaction envelope for review before the wallet prompt. - `useBuy` quotes again immediately before building the transaction and sets `min_shares_out = minOut(quote, slippageBps)`, so slippage is enforced on-chain. + +### Confirmed wallet updates and reconciliation + +Position and USDC balance caches hold `{ value, ledger, provisional?, awaitingRpc? }`; existing hooks select `value`, while `usePositionState` and `useUsdcBalanceState` expose the metadata. Simulations supply the read ledger. A confirmed buy/sell/redeem applies only contract-returned bigint amounts, using the confirmed transaction ledger, and immediately updates an existing cache. Cached values already read at or above that ledger are left alone. Missing caches and unknown transaction ledgers use invalidation instead. + +Reads are cancelled before signing and again before the confirmed delta is applied. No delta is applied while awaiting a signature, so rejection and simulation failure leave values unchanged. Consecutive confirmations compose against the latest cache. Negative deltas that exceed cached holdings or balance trigger invalidation. No quote cache is invalidated by this flow. + +Reconciliation refetches after 1, 3 and 8 seconds. Reads below the cached ledger cannot overwrite it. At or above that ledger, matching reads remove the provisional marker; different reads replace the value and log `[optimistic] mismatch`. After three lagging reads, the value remains labelled **awaiting RPC**, with its transaction link, until an authoritative read arrives. Query removal on disconnect disposes reconciliation timers. Fee previews are rebuilt with the current wallet before transaction review and signing. ## Write path and transaction lifecycle Every state-changing write (`useBuy`, `useSell`, `useRedeem`, `useUsdcTransfer`) follows an explicit, observable lifecycle governed by `TxLifecycle` in `lib/stellar/tx-lifecycle.ts`: @@ -68,3 +78,11 @@ USDC has 7 decimals. Anything that can reach a transaction is a `bigint` in base ## Rendering All routes prerender as static pages. Pages that read query parameters (`/markets/quick-view`, `/copy-trade`, `/reasoning/verify`) keep `useSearchParams` inside a `` boundary; without it the production build fails. + +### Realtime lists and storage bounds + +`RealtimeProvider` owns one socket and one mixed-event scheduler. Valid payloads enter a 500-event bounded batch; subscribers receive at most one batch per animation frame. Hidden tabs accumulate a bounded backlog without updating realtime DOM, then receive one catch-up batch with overflow counts. Feed rings retain six home rows and eight market rows with event IDs as keys. A rolling second above ten events suppresses row animations and displays a summary; reduced motion also disables the ticker and realtime row animations. + +The market tape retains at most 2,000 trades per market and 32 cached markets. A fixed eight-row viewport with overscan renders at most 24 data rows plus spacer rows. USDC, shares and fees remain bigint base units; post-trade price uses the pinned FPMM calculation. Each row links to its trader and transaction. Event retention is shown explicitly, including empty histories. + +`InboxProvider` remounts for each address/network pair, closes its previous database handle, and renders nothing from the previous owner. `oracledesk-inbox` stores deterministic item IDs, read state and the last processed ledger. Duplicate deliveries preserve read state and creation time. The inbox retains 500 items, pruning oldest read items first, and displays the newest 200. Storage failures fall back to memory with a visible notice. Reading a page does not mark anything read; the user explicitly marks one item or all items. Held-market discovery uses the owner's saved fills and already-read position queries; it does not scan all ledger keys or use the treasury agent's portfolio. diff --git a/docs/images/notifications-disconnected.png b/docs/images/notifications-disconnected.png new file mode 100644 index 0000000..38a2b12 Binary files /dev/null and b/docs/images/notifications-disconnected.png differ diff --git a/docs/manual-test.md b/docs/manual-test.md index e89336b..dc0c71c 100644 --- a/docs/manual-test.md +++ b/docs/manual-test.md @@ -125,3 +125,47 @@ To try the same on market 0 (resolved): the buy button is disabled and the panel - [ ] `/copy-trade?marketId=&traceId=`: the side comes from the trace's edge. With no trace or zero edge the confirm button is disabled and the page explains why. - [ ] `/admin` with an address not in `NEXT_PUBLIC_ADMIN_ADDRESSES` shows "Access Denied". - [ ] `/tx?hash=<64-hex>` inspects live ledger state and fee charged for any testnet transaction, falling back cleanly to Stellar Expert when outside RPC retention. +# Wallet reconciliation, market tape and inbox (#39–#42) + +1. Connect a testnet wallet with XLM, a USDC trustline and test USDC. Open an + **open** on-chain market in quick-view; record its position and the wallet + dropdown balance. Buy, then confirm that contract-returned shares and + deducted collateral appear with a transaction link and pending indicator. + Matching reads clear the indicator. Repeat with a sell and a redemption, + including a losing-side redemption with zero payout. Reject a signature + and confirm both values remain unchanged. Make two consecutive buys and + check that the second composes with the first. Disconnect while the RPC + lags and verify the previous wallet's values do not reappear. +2. Open the same quick-view without a wallet. Its **Recent trades** table + reads only that market's contract events. Check bigint USDC, shares, fee, + YES price and movement against explorer events. Check trader/transaction + links, including a contract trader. Send a trade on that market and check + one live row appears; a later RPC refetch must replace it without a + duplicate. A trade on a different market must not appear. Load older + rows until the retention notice appears. With 2,000 fixture rows, verify + fewer than 60 DOM rows and scroll to the oldest row. +3. Use wallets A and B in separate browser profiles. Trade as A; only A's + **Notifications** page should receive a fill and increase the bell count. + Mark it read explicitly, reload, and verify that it remains read. Switch + wallets in the same browser and verify no previous-owner items flash. + Disconnect: show the connect prompt and an empty bell. Check testnet and + mainnet scopes separately if testing a second network build. +4. Publish reasoning on a market A holds and one it does not hold. Only the + held market creates a reasoning item. Resolve or void a held seeded + market; within a visible 60-second poll, a redeemable item links to it. + Redeem and check the exact payout item. Reload with an already-resolved + held market to check current-status detection outside event retention. + Submit a transaction that remains pending or fails and verify its tracker + item. Rejected signatures must not create submitted-transaction items. +5. Block IndexedDB in browser settings and reconnect. The inbox should work + in memory and show **Notifications won't be saved in this browser**. + Generate more than 500 items: read items should be pruned first and only + the newest 200 should render. Nothing becomes read merely by rendering. +6. Run the simulator in CONTRIBUTING.md at 500/s and 50/s. Check feed bounds, + a single burst summary, responsiveness and no console exceptions. Hide + the tab during a burst, return, and check one catch-up and overflow notice + instead of replayed animations. Enable OS reduced motion: ticker, pulse + indicators and incoming trade rows must remain static. + +These wallet-signing steps require a browser wallet and funded test account; +the automated suite does not submit financial transactions. diff --git a/lib/api/auth.ts b/lib/api/auth.ts index 25defb3..16787e1 100644 --- a/lib/api/auth.ts +++ b/lib/api/auth.ts @@ -100,7 +100,7 @@ export async function ensureAuthWithSingleFlight( // Start new re-auth attempt const reAuthPromise = signInWithWallet(address, signTransaction); - setReAuthPromise(reAuthPromise); + setReAuthPromise(reAuthPromise.then(() => undefined)); try { const session = await reAuthPromise; diff --git a/lib/hooks/useMarketTrades.ts b/lib/hooks/useMarketTrades.ts new file mode 100644 index 0000000..fc5e07a --- /dev/null +++ b/lib/hooks/useMarketTrades.ts @@ -0,0 +1,37 @@ +"use client"; +import { useQuery, useQueryClient } from "@tanstack/react-query"; +import { useRealtimeBatch } from "@/lib/realtime/RealtimeProvider"; +import { isMarketId, type TradeEvent } from "@/lib/realtime/types"; +import { fetchMarketTrades, mergeTrades, type TradeRow } from "@/lib/stellar/events"; + +export function socketTrade(payload: TradeEvent, marketId: string): TradeRow | null { + if (payload.onChainMarketId !== marketId) return null; + return { id: payload.eventId, ledger: payload.ledger, closedAt: payload.closedAt ?? null, txHash: payload.txHash, + marketId, trader: payload.trader, outcome: payload.direction === "YES" ? "Yes" : "No", isBuy: payload.isBuy, + collateral: BigInt(payload.collateralRaw), shares: BigInt(payload.sharesRaw), fee: BigInt(payload.feeRaw), + yesBpsAfter: payload.priceYesBps, reserveYes: null, reserveNo: null, origin: "socket" }; +} +type TapeData = Awaited>; +export function useMarketTrades(id: string, { limit = 20 }: { limit?: number } = {}) { + const client = useQueryClient(); + const queryKey = ["stellar", "marketTrades", id, limit] as const; + useRealtimeBatch(batch => { + const rows = batch.flatMap(event => { + if (event.type !== "TRADE_EXECUTED") return []; + const row = socketTrade(event.payload, id); return row ? [row] : []; + }); + if (rows.length) client.setQueryData(queryKey, previous => ({ + trades: mergeTrades(previous?.trades ?? [], rows).slice(0, limit), + reachedRetention: previous?.reachedRetention ?? false, latestLedger: previous?.latestLedger ?? 0, cursor: previous?.cursor ?? "", + })); + }); + return useQuery({ + queryKey, placeholderData: previous => previous, enabled: isMarketId(id), staleTime: 60_000, refetchInterval: 60_000, + queryFn: async ({ signal }) => { + const result = await fetchMarketTrades(BigInt(id), { want: limit, signal }); + const existing = client.getQueryData(queryKey); + // A socket event may arrive while the RPC request is in flight. + return { ...result, trades: mergeTrades(existing?.trades.filter(row => row.origin === "socket" && row.ledger > result.latestLedger) ?? [], result.trades).slice(0, limit) }; + }, + }); +} diff --git a/lib/hooks/useStellar.ts b/lib/hooks/useStellar.ts index 5748c4a..2eb974e 100644 --- a/lib/hooks/useStellar.ts +++ b/lib/hooks/useStellar.ts @@ -1,16 +1,16 @@ "use client"; -import { useCallback, useRef, useSyncExternalStore } from "react"; +import { useCallback, useEffect, useRef, useState, useSyncExternalStore } from "react"; import { useMutation, useQuery, useQueryClient } from "@tanstack/react-query"; import { Keypair, scValToNative, xdr } from "@stellar/stellar-sdk"; import type { AssembledTransaction } from "@stellar/stellar-sdk/contract"; import { useWallet } from "@/lib/contexts/WalletContext"; import { marketCore, reasoningRegistry, token, Outcomes, rpcServer, type OutcomeTag, type Signer } from "@/lib/stellar/clients"; -import { UserFacingError, type ContractName } from "@/lib/stellar/errors"; -import { collateralOutForShares, priceYesBps } from "@/lib/stellar/generated/fpmm"; +import { describeError, UserFacingError, type ContractName } from "@/lib/stellar/errors"; +import { BPS, priceYesBps } from "@/lib/stellar/generated/fpmm"; import type { Config, Market as OnChainMarket, Position } from "@/lib/stellar/generated/market-core"; import { fetchAndVerifyTrace, type TraceCheck } from "@/lib/stellar/trace"; -import { read, submit, bytesToHex, simulated } from "@/lib/stellar/typed-call"; +import { read, submit, bytesToHex, simulatedWithLedger, simulated } from "@/lib/stellar/typed-call"; import { maxIn, minOut } from "@/lib/stellar/units"; import { feeBreakdown, @@ -20,15 +20,22 @@ import { type FeeBreakdown, } from "@/lib/stellar/fees"; import { - simulated, TxLifecycle, - type TxPhase, } from "@/lib/stellar/tx-lifecycle"; import { useTxTracker } from "@/lib/stellar/tx-tracker"; import { tradeInvalidationKeys } from "@/lib/stellar/tx-tracker-core"; -import { NETWORK_PASSPHRASE } from "@/lib/stellar/config"; +import { CONTRACTS, NETWORK_PASSPHRASE } from "@/lib/stellar/config"; + +import { preflightBuy, preflightRedeem, type Preflight, type PreflightFailure, type PreflightInput } from "@/lib/stellar/preflight"; +import { getSubmitQueue } from "@/lib/stellar/submit-queue"; +import { assertIntentMatches, describeAssembled, type ReviewModel } from "@/lib/stellar/tx-decode"; +import { discoverRedeemable, loadRedeemAllResume, saveRedeemAllResume, type ItemState, type RedeemAllResume, type RedeemDiscovery } from "@/lib/stellar/redeem-all"; +import { enableUsdcTrustline, getTrustlineState, usdcClassicAsset, type TrustlineState } from "@/lib/stellar/trustline"; +import { minCollateralForShares } from "@/lib/stellar/quote"; +import { reconcileWithQuote, solveSellForShares } from "@/lib/stellar/sell-solver"; +import { confirmTrade, cancelTradeReads } from "@/lib/stellar/optimistic-cache"; +import { reconcileRead, type Stamped } from "@/lib/stellar/optimistic"; -export { simulated }; export { tradeInvalidationKeys }; export { read, submit, simulated, bytesToHex }; @@ -42,6 +49,7 @@ export const stellarKeys = { markets: (limit = 20) => ["stellar", "markets", limit] as const, position: (id: string, holder: string) => ["stellar", "position", id, holder] as const, balance: (owner: string) => ["stellar", "usdcBalance", owner] as const, + trustline: (owner: string) => ["stellar", "usdcTrustline", owner] as const, account: (addr: string) => ["stellar", "account", addr] as const, rpcNetwork: ["stellar", "rpcNetwork"] as const, quoteBuy: (id: string, outcome: OutcomeTag, amount: string) => ["stellar", "quoteBuy", id, outcome, amount] as const, @@ -50,11 +58,32 @@ export const stellarKeys = { }; const toId = (id: bigint | string | number) => BigInt(id); -const rpcServer = new rpc.Server(RPC_URL, { allowHttp: RPC_URL.startsWith("http://") }); + +async function trustlineFor(address: string): Promise { + return getTrustlineState(address, await usdcClassicAsset()); +} + +async function requireUsdcTrustline(address: string): Promise { + const state = await trustlineFor(address); + if (state.kind === "missing") { + throw new UserFacingError({ + kind: "no-trustline", + message: "Enable your USDC trustline before using USDC.", + action: "enable-usdc", + }); + } + if (state.kind === "account-missing") { + throw new UserFacingError({ kind: "unknown", message: "Your Stellar account is not funded yet. Fund it before continuing." }); + } + if (state.kind === "present" && !state.authorized) { + throw new UserFacingError({ kind: "no-trustline", message: "The issuer hasn't authorized your USDC trustline." }); + } + return state; +} async function fetchLedgerNow(): Promise { - const latest = await rpcServer.getLatestLedger(); - const response = await rpcServer.getTransactions({ + const latest = await rpcServer().getLatestLedger(); + const response = await rpcServer().getTransactions({ startLedger: latest.sequence, pagination: { limit: 1 }, }); @@ -62,12 +91,12 @@ async function fetchLedgerNow(): Promise { } async function fetchMarketConfig(): Promise { - return simulated(await marketCore().get_config()); + return simulated(await marketCore().get_config()); } async function fetchUsdcBalanceIfAvailable(owner: string): Promise { try { - return simulated(await token.balance(owner)); + return simulated(await token.balance(owner)); } catch (error) { if (describeError(error).kind === "no-trustline") return undefined; throw error; @@ -166,24 +195,45 @@ export function useOnChainMarkets(limit = 20) { }); } -export function usePosition(id: bigint | string | null | undefined, holder: string | null | undefined) { - return useQuery({ - queryKey: stellarKeys.position(String(id), holder ?? ""), - enabled: id !== null && id !== undefined && Boolean(holder), - queryFn: async () => - read(await marketCore().get_position({ market_id: toId(id as string), holder: holder as string })), +function positionOptions(client: ReturnType, id: bigint | string | null | undefined, holder: string | null | undefined) { + const queryKey = stellarKeys.position(String(id), holder ?? ""); + return { + queryKey, + enabled: id !== null && id !== undefined && id !== "" && Boolean(holder), + queryFn: async ({ signal }: { signal: AbortSignal }): Promise> => { + const fresh = simulatedWithLedger(await marketCore().get_position({ market_id: toId(id as string), holder: holder as string })); + signal.throwIfAborted(); + return reconcileRead(client.getQueryData(queryKey), fresh, (a, b) => a.yes === b.yes && a.no === b.no, queryKey); + }, staleTime: 15_000, - }); + }; } - -export function useUsdcBalance(owner: string | null | undefined) { - return useQuery({ - queryKey: stellarKeys.balance(owner ?? ""), +export function usePosition(id: bigint | string | null | undefined, holder: string | null | undefined) { + const client = useQueryClient(); + return useQuery({ ...positionOptions(client, id, holder), select: (data: Stamped) => data.value }); +} +export function usePositionState(id: bigint | string | null | undefined, holder: string | null | undefined) { + return useQuery(positionOptions(useQueryClient(), id, holder)); +} +function balanceOptions(client: ReturnType, owner: string | null | undefined) { + const queryKey = stellarKeys.balance(owner ?? ""); + return { + queryKey, enabled: Boolean(owner), - queryFn: async () => read(await token.balance(owner as string)), + queryFn: async ({ signal }: { signal: AbortSignal }): Promise> => { + const fresh = simulatedWithLedger(await token.balance(owner as string)); + signal.throwIfAborted(); + return reconcileRead(client.getQueryData(queryKey), fresh, (a, b) => a === b, queryKey); + }, staleTime: 15_000, refetchInterval: 30_000, - }); + }; +} +export function useUsdcBalance(owner: string | null | undefined) { + return useQuery({ ...balanceOptions(useQueryClient(), owner), select: (data: Stamped) => data.value }); +} +export function useUsdcBalanceState(owner: string | null | undefined) { + return useQuery(balanceOptions(useQueryClient(), owner)); } export interface AccountReserveState extends AccountReserve { @@ -198,8 +248,6 @@ export function useAccountReserve(address: string | null | undefined) { return useQuery({ queryKey: stellarKeys.account(address ?? ""), enabled: Boolean(address), - staleTime: 15_000, - refetchInterval: (q) => (q.state.data?.funded === false ? 10_000 : 60_000), queryFn: async (): Promise => { if (!address) { return { @@ -272,6 +320,61 @@ export function useAccountReserve(address: string | null | undefined) { }); } +export function useUsdcTrustline(owner: string | null | undefined) { + const { isWrongNetwork } = useWallet(); + return useQuery({ + queryKey: stellarKeys.trustline(owner ?? ""), + enabled: Boolean(owner) && !isWrongNetwork, + queryFn: () => trustlineFor(owner as string), + staleTime: 10_000, + refetchInterval: 10_000, + }); +} + +export function useEnableUsdc() { + const signer = useWriteGuard(); + const queryClient = useQueryClient(); + return useMutation({ + mutationFn: async () => { + const wallet = signer(); + const asset = await usdcClassicAsset(); + const current = await getTrustlineState(wallet.publicKey, asset); + if (current.kind === "native" || (current.kind === "present" && current.authorized)) { + return { hash: null, holder: wallet.publicKey }; + } + if (current.kind === "present" && !current.authorized) { + throw new UserFacingError({ + kind: "no-trustline", + message: "The issuer hasn't authorized your USDC trustline.", + }); + } + const { hash } = await enableUsdcTrustline(wallet.publicKey, asset, wallet); + const deadline = Date.now() + 60_000; + let delay = 1_000; + while (Date.now() < deadline) { + const state = await getTrustlineState(wallet.publicKey, asset); + if (state.kind === "present" && state.authorized) return { hash, holder: wallet.publicKey }; + if (state.kind === "present") { + throw new UserFacingError({ + kind: "no-trustline", + message: "The issuer hasn't authorized your USDC trustline.", + }); + } + await new Promise((resolve) => setTimeout(resolve, delay)); + delay = Math.min(delay * 2, 5_000); + } + throw new UserFacingError({ + kind: "network", + message: "The trustline transaction confirmed, but the new trustline is not visible yet. Refresh and check again.", + }); + }, + onSuccess: ({ holder }) => { + void queryClient.invalidateQueries({ queryKey: stellarKeys.trustline(holder) }); + void queryClient.invalidateQueries({ queryKey: stellarKeys.balance(holder) }); + }, + }); +} + /** Shares a buy would return right now. Debounce `collateralIn` at the call site. */ export function useQuoteBuy(id: bigint | string | null | undefined, outcome: OutcomeTag, collateralIn: bigint | null) { return useQuery({ @@ -285,6 +388,42 @@ export function useQuoteBuy(id: bigint | string | null | undefined, outcome: Out }); } +export function useExactBuyQuote( + id: bigint | string | null | undefined, + outcome: OutcomeTag, + targetShares: bigint | null, +) { + return useQuery({ + queryKey: stellarKeys.exactBuy(String(id), outcome, targetShares?.toString() ?? ""), + enabled: id !== null && id !== undefined && targetShares !== null && targetShares > 0n, + queryFn: async () => { + const view = await fetchOnChainMarket(id as string); + const local = minCollateralForShares(view.market, outcome, targetShares as bigint); + if (!local.ok) { + const message = + local.reason === "market-too-large" + ? "This target exceeds the market's remaining size limit." + : local.reason === "invalid-target" + ? "Enter a share amount greater than zero." + : "The requested share amount is too large."; + throw new UserFacingError({ kind: "contract", message }); + } + const quotedShares = simulated( + await marketCore().quote_buy({ + market_id: toId(id as string), + outcome: Outcomes[outcome], + collateral_in: local.collateralIn, + }), + ); + if (quotedShares < (targetShares as bigint)) { + throw new UserFacingError({ kind: "contract", message: "The price moved; refresh the quote and try again." }); + } + return { ...local, quotedShares, market: view.market }; + }, + staleTime: 5_000, + }); +} + export interface PreparedBuy { tx: AssembledTransaction; fees: FeeBreakdown; @@ -315,7 +454,7 @@ export function usePrepareBuy(params: { enabled: Boolean(marketId) && Boolean(holder) && collateralIn !== null && collateralIn > 0n, queryFn: async (): Promise => { const id = toId(marketId!); - const quote = simulated( + const quote = simulated( await marketCore().quote_buy({ market_id: id, outcome: Outcomes[outcome], @@ -467,37 +606,25 @@ export function useRpcNetwork() { }); } -function useTxRunner(contract: ContractName) { - const ref = useRef | null>(null); - const phase = useSyncExternalStore( - useCallback((cb: () => void) => ref.current?.subscribe(cb) ?? (() => {}), []), - useCallback(() => ref.current?.getSnapshot() ?? ({ name: "building" } as TxPhase), []), - useCallback(() => ({ name: "building" } as TxPhase), []), - ); - - const start = useCallback( - ( - tx: AssembledTransaction, - parse: (v: xdr.ScVal | undefined) => R, - onHash?: (hash: string) => void, - ): Promise<{ value: R; txHash: string }> => { - const runner = new TxLifecycle({ - server: rpcServer(), - contract, - onHash, - }); - ref.current = runner as TxLifecycle; - return runner.run(tx as unknown as AssembledTransaction, parse); - }, - [contract], - ); +function confirmedAmount(value: xdr.ScVal | undefined): bigint { + if (!value) throw new Error("Transaction confirmed without a return value. Refresh your balance."); + const decoded: unknown = scValToNative(value); + if (typeof decoded !== "bigint" || decoded < 0n) throw new Error("Invalid confirmed contract amount"); + return decoded; +} +function useTxRunner(contract: ContractName) { + const [runner] = useState(() => new TxLifecycle({ server: rpcServer(), contract })); + const phase = useSyncExternalStore(runner.subscribe, runner.getSnapshot, runner.getSnapshot); + const start = useCallback((tx: AssembledTransaction, parse: (v: xdr.ScVal | undefined) => R, onHash?: (hash: string) => void) => + runner.run(tx as AssembledTransaction, parse, onHash) as Promise<{ value: R; txHash: string; ledger: number }>, [runner]); return { phase, start }; } export interface BuyParams { marketId: bigint | string; outcome: OutcomeTag; + collateralIn: bigint; slippageBps: number; confirmBoundsChanged?: (previous: bigint, next: bigint) => Promise; review?: PreparedWriteReview; @@ -522,7 +649,7 @@ export type PreparedWriteReview = ( export async function prepareBuy(signer: Signer, params: Omit): Promise { const id = toId(params.marketId); - const quote = simulated( + const quote = simulated( await marketCore().quote_buy({ market_id: id, outcome: Outcomes[params.outcome], collateral_in: params.collateralIn }), ); const minSharesOut = minOut(quote, params.slippageBps); @@ -533,7 +660,7 @@ export async function prepareBuy(signer: Signer, params: Omit(tx); + simulated(tx); return { tx, kind: "buy", @@ -563,7 +690,7 @@ export async function prepareSell( collateral_out: params.collateralOut, max_shares_in: params.maxSharesIn, }); - simulated(tx); + simulated(tx); return { tx, kind: "sell", @@ -583,7 +710,7 @@ export async function prepareSell( export async function prepareRedeem(signer: Signer, marketId: bigint | string): Promise { const id = toId(marketId); const tx = await marketCore(signer).redeem({ holder: signer.publicKey, market_id: id }); - simulated(tx); + simulated(tx); return { tx, kind: "redeem", @@ -600,17 +727,17 @@ export async function redeemOnce( onSubmitting?: () => void, review?: PreparedWriteReview, attempt: 0 | 1 = 0, -): Promise<{ payout: bigint; txHash: string | null; holder: string }> { +): Promise<{ payout: bigint; txHash: string | null; ledger: number | null; holder: string }> { const id = toId(marketId); const view = await fetchOnChainMarket(id); - const position = simulated(await marketCore().get_position({ market_id: id, holder: signer.publicKey })); + const position = simulated(await marketCore().get_position({ market_id: id, holder: signer.publicKey })); throwPreflight(preflightRedeem({ market: view.market, position })); const initial = await prepareRedeem(signer, id); const reviewed = await reviewPreparedWrite(initial, review, () => prepareRedeem(signer, id), undefined, attempt === 1); const prepared = reviewed.prepared; onSubmitting?.(); - const { value, txHash } = await submitPrepared(prepared); - return { payout: value, txHash, holder: signer.publicKey }; + const { value, txHash, ledger } = await submitPrepared(prepared); + return { payout: value, txHash, ledger, holder: signer.publicKey }; } export async function prepareUsdcTransfer( @@ -618,7 +745,7 @@ export async function prepareUsdcTransfer( params: { to: string; amount: bigint }, ): Promise { const tx = await token.transfer({ from: signer.publicKey, to: params.to, amount: params.amount }, signer); - simulated(tx, "marketCore"); + simulated(tx, "marketCore"); return { tx, kind: "usdc-transfer", @@ -631,8 +758,8 @@ export async function prepareUsdcTransfer( export async function submitPrepared( prepared: PreparedWrite, -): Promise<{ value: T; txHash: string | null }> { - return send(prepared.tx, prepared.contract === "usdc" ? "marketCore" : prepared.contract); +): Promise<{ value: T; txHash: string | null; ledger: number | null }> { + return submit(prepared.tx, prepared.contract === "usdc" ? "marketCore" : prepared.contract) as Promise<{ value: T; txHash: string | null; ledger: number | null }>; } async function reviewPreparedWrite( @@ -715,7 +842,7 @@ async function fetchTradePreflightInput(marketId: bigint, holder: string, includ fetchLedgerNow(), marketCore() .get_position({ market_id: marketId, holder }) - .then((tx) => simulated(tx)) + .then((tx) => simulated(tx)) .catch((error: unknown) => { throw new UserFacingError(describeError(error), error); }), @@ -735,65 +862,44 @@ async function fetchTradePreflightInput(marketId: bigint, holder: string, includ */ export function useBuy() { const guard = useWriteGuard(); + const queryClient = useQueryClient(); const invalidate = useInvalidateAfterTrade(); const runner = useTxRunner("marketCore"); const tracker = useTxTracker(); const mutation = useMutation({ - mutationFn: async ({ - marketId, - outcome, - collateralIn, - slippageBps, - prepared, - }: BuyParams & { prepared?: PreparedBuy }) => { + onMutate: async (v: BuyParams & { prepared?: PreparedBuy }) => cancelTradeReads(queryClient, String(v.marketId), guard().publicKey), + onError: (error, v, context) => { + const kind = describeError(error).kind; + if (context && ["network", "unknown", "expired", "submission"].includes(kind)) invalidate(String(v.marketId), context.holder); + }, + mutationFn: async (params: BuyParams & { prepared?: PreparedBuy }) => { const signer = guard(); - await requireUsdcTrustline(signer.publicKey); - const id = toId(marketId); - - let tx: AssembledTransaction; - let quote: bigint; - let minSharesOut: bigint; - - const isFresh = prepared && Date.now() - prepared.preparedAt < 15_000; - if (isFresh && prepared) { - tx = prepared.tx; - quote = prepared.quote; - minSharesOut = prepared.minSharesOut; - } else { - quote = simulated( - await marketCore().quote_buy({ market_id: id, outcome: Outcomes[outcome], collateral_in: collateralIn }), - ); - minSharesOut = minOut(quote, slippageBps); - tx = await marketCore(signer).buy({ - trader: signer.publicKey, - market_id: id, - outcome: Outcomes[outcome], - collateral_in: collateralIn, - min_shares_out: minSharesOut, - }); + const { marketId, outcome, collateralIn } = params; + return runQueuedWrite(signer, `Buy ${outcome} on #${marketId}`, dedupeValue("buy", marketId, outcome, collateralIn), guard, async () => { + await requireUsdcTrustline(signer.publicKey); + const preflight = await fetchTradePreflightInput(toId(marketId), signer.publicKey, true); + throwPreflight(preflightBuy(preflight, outcome, collateralIn)); + // Prepared transactions are fee previews signed by a dummy signer. Always + // rebuild with the actual wallet and fresh bounds before asking for approval. + const initial = await prepareBuy(signer, params); + const { prepared } = await reviewPreparedWrite(initial, params.review, () => prepareBuy(signer, params)); + const quote = prepared.quote!; + const minSharesOut = BigInt(prepared.intent.min_shares_out); + const onHash = (hash: string) => tracker.track({ hash, kind: "buy", holder: signer.publicKey, marketId: String(marketId), outcome, + amounts: { collateralIn: collateralIn.toString(), minSharesOut: minSharesOut.toString() }, networkPassphrase: NETWORK_PASSPHRASE }); + const { value, txHash, ledger } = await runner.start(prepared.tx, confirmedAmount, onHash); + return { sharesOut: value, quotedShares: quote, minSharesOut, txHash, ledger, collateralIn, holder: signer.publicKey }; + }); + }, + onSuccess: async (d, v) => { + // Cancel again: a focus/refetch may have started while the wallet was signing. + await cancelTradeReads(queryClient, String(v.marketId), d.holder); + confirmTrade(queryClient, { ...v, ...d, kind: "buy", marketId: String(v.marketId) }); + for (const key of [stellarKeys.market(String(v.marketId)), ["stellar", "markets"], stellarKeys.account(d.holder)]) { + void queryClient.invalidateQueries({ queryKey: key }); } - - const onHash = (hash: string) => { - tracker.track({ - hash, - kind: "buy", - holder: signer.publicKey, - marketId: String(marketId), - outcome, - amounts: { collateralIn: collateralIn.toString(), minSharesOut: minSharesOut.toString() }, - networkPassphrase: NETWORK_PASSPHRASE, - }); - }; - - const parse = (val: xdr.ScVal | undefined) => { - return (val ? scValToNative(val) : quote) as bigint; - }; - - const { value, txHash } = await runner.start(tx, parse, onHash); - return { sharesOut: value, quotedShares: quote, minSharesOut, txHash, holder: signer.publicKey }; }, - onSuccess: (d, v) => invalidate(String(v.marketId), d.holder), }); return { @@ -805,23 +911,33 @@ export function useBuy() { export type SellParams = { marketId: bigint | string; outcome: OutcomeTag; + shares: bigint; + mode?: "all" | "shares"; slippageBps: number; review?: PreparedWriteReview; } export function useSell() { const guard = useWriteGuard(); + const queryClient = useQueryClient(); const invalidate = useInvalidateAfterTrade(); const runner = useTxRunner("marketCore"); const tracker = useTxTracker(); const mutation = useMutation({ - mutationFn: async ({ marketId, outcome, shares, slippageBps }: SellParams) => { + onMutate: async (v: SellParams) => cancelTradeReads(queryClient, String(v.marketId), guard().publicKey), + onError: (error, v, context) => { + const kind = describeError(error).kind; + if (context && ["network", "unknown", "expired", "submission"].includes(kind)) invalidate(String(v.marketId), context.holder); + }, + mutationFn: async (params: SellParams) => { + const { marketId, outcome, slippageBps } = params; const signer = guard(); + return runQueuedWrite(signer, `Sell ${outcome} on #${marketId}`, dedupeValue("sell", marketId, outcome, params.shares), guard, async () => { await requireUsdcTrustline(signer.publicKey); const id = toId(marketId); let { market } = await fetchOnChainMarket(id); - const position = simulated( + const position = simulated( await marketCore().get_position({ market_id: id, holder: signer.publicKey }), ); const heldShares = outcome === "Yes" ? position.yes : position.no; @@ -846,7 +962,7 @@ export function useSell() { } const quoteAt = async (collateralOut: bigint) => - simulated( + simulated( await marketCore().quote_sell({ market_id: id, outcome: Outcomes[outcome], collateral_out: collateralOut }), ); let confirmed = await reconcileWithQuote(local, quoteAt); @@ -926,14 +1042,10 @@ export function useSell() { if (confirmed.sharesIn > shares) { throw new UserFacingError({ kind: "contract", code: "InsufficientShares", message: "You don't have enough shares for this sale." }); } - const maxSharesIn = maxIn(shares, slippageBps) + 1n; - const tx = await marketCore(signer).sell({ - trader: signer.publicKey, - market_id: id, - outcome: Outcomes[outcome], - collateral_out: confirmed.collateralOut, - max_shares_in: maxSharesIn, - }); + const maxSharesIn = [maxIn(confirmed.sharesIn, slippageBps), heldShares].reduce((a, b) => a < b ? a : b); + const initial = await prepareSell(signer, { marketId, outcome, collateralOut: confirmed.collateralOut, maxSharesIn }); + const { prepared } = await reviewPreparedWrite(initial, params.review, () => prepareSell(signer, { marketId, outcome, collateralOut: confirmed.collateralOut, maxSharesIn })); + const tx = prepared.tx; const onHash = (hash: string) => { tracker.track({ @@ -947,14 +1059,18 @@ export function useSell() { }); }; - const parse = (val: xdr.ScVal | undefined) => { - return (val ? scValToNative(val) : shares) as bigint; - }; - - const { value, txHash } = await runner.start(tx, parse, onHash); - return { sharesSold: value, collateralOut, maxSharesIn, txHash, holder: signer.publicKey }; + const { value, txHash, ledger } = await runner.start(tx, confirmedAmount, onHash); + return { sharesSold: value, collateralOut: confirmed.collateralOut, maxSharesIn, txHash, ledger, holder: signer.publicKey }; + }); + }, + onSuccess: async (d, v) => { + // Cancel again: a focus/refetch may have started while the wallet was signing. + await cancelTradeReads(queryClient, String(v.marketId), d.holder); + confirmTrade(queryClient, { ...v, ...d, kind: "sell", marketId: String(v.marketId) }); + for (const key of [stellarKeys.market(String(v.marketId)), ["stellar", "markets"], stellarKeys.account(d.holder)]) { + void queryClient.invalidateQueries({ queryKey: key }); + } }, - onSuccess: (d, v) => invalidate(String(v.marketId), d.holder), }); return { @@ -965,14 +1081,25 @@ export function useSell() { export function useRedeem() { const guard = useWriteGuard(); + const queryClient = useQueryClient(); const invalidate = useInvalidateAfterTrade(); const runner = useTxRunner("marketCore"); const tracker = useTxTracker(); const mutation = useMutation({ - mutationFn: async ({ marketId }: { marketId: bigint | string }) => { + onMutate: async (v: { marketId: bigint | string }) => cancelTradeReads(queryClient, String(v.marketId), guard().publicKey), + onError: (error, v, context) => { + const kind = describeError(error).kind; + if (context && ["network", "unknown", "expired", "submission"].includes(kind)) invalidate(String(v.marketId), context.holder); + }, + mutationFn: async ({ marketId, review }: { marketId: bigint | string; review?: PreparedWriteReview }) => { const signer = guard(); - const tx = await marketCore(signer).redeem({ holder: signer.publicKey, market_id: toId(marketId) }); + return runQueuedWrite(signer, `Redeem #${marketId}`, dedupeValue("redeem", marketId), guard, async () => { + await requireUsdcTrustline(signer.publicKey); + throwPreflight(preflightRedeem(await fetchTradePreflightInput(toId(marketId), signer.publicKey, false))); + const initial = await prepareRedeem(signer, marketId); + const { prepared } = await reviewPreparedWrite(initial, review, () => prepareRedeem(signer, marketId)); + const tx = prepared.tx; const onHash = (hash: string) => { tracker.track({ @@ -985,14 +1112,18 @@ export function useRedeem() { }); }; - const parse = (val: xdr.ScVal | undefined) => { - return (val ? scValToNative(val) : 0n) as bigint; - }; - - const { value, txHash } = await runner.start(tx, parse, onHash); - return { payout: value, txHash, holder: signer.publicKey }; + const { value, txHash, ledger } = await runner.start(tx, confirmedAmount, onHash); + return { payout: value, txHash, ledger, holder: signer.publicKey }; + }); + }, + onSuccess: async (d, v) => { + // Cancel again: a focus/refetch may have started while the wallet was signing. + await cancelTradeReads(queryClient, String(v.marketId), d.holder); + confirmTrade(queryClient, { ...v, ...d, kind: "redeem", marketId: String(v.marketId) }); + for (const key of [stellarKeys.market(String(v.marketId)), ["stellar", "markets"], stellarKeys.account(d.holder)]) { + void queryClient.invalidateQueries({ queryKey: key }); + } }, - onSuccess: (d, v) => invalidate(String(v.marketId), d.holder), }); return { @@ -1095,7 +1226,7 @@ export function useRedeemAll(): RedeemAllController { try { const signer = guard(); const holder = signer.publicKey; - const count = simulated(await marketCore().market_count()); + const count = simulated(await marketCore().market_count()); const result = await discoverRedeemable(holder, count); if (holderRef.current !== holder) return; const next = { ...statesRef.current }; @@ -1227,14 +1358,14 @@ export function useUsdcTransfer() { const tracker = useTxTracker(); const mutation = useMutation({ - mutationFn: async ({ to, amount, followUp, prepared }: UsdcTransferParams) => { + mutationFn: async ({ to, amount, followUp, review }: UsdcTransferParams) => { const signer = guard(); if (amount <= 0n) throw new UserFacingError({ kind: "contract", message: "Enter an amount greater than zero." }); - const isFresh = prepared && Date.now() - prepared.preparedAt < 15_000; - const tx = isFresh && prepared - ? (prepared.tx as unknown as AssembledTransaction) - : await token.transfer({ from: signer.publicKey, to, amount }, signer); + await requireUsdcTrustline(signer.publicKey); + const initial = await prepareUsdcTransfer(signer, { to, amount }); + const { prepared } = await reviewPreparedWrite(initial, review, () => prepareUsdcTransfer(signer, { to, amount })); + const tx = prepared.tx; const onHash = (hash: string) => { tracker.track({ @@ -1249,7 +1380,7 @@ export function useUsdcTransfer() { const parse = () => null; - const { txHash } = await runner.start(tx, parse, onHash); + const { txHash } = await runner.start(tx, parse, onHash); if (!txHash) throw new UserFacingError({ kind: "unknown", message: "The payment was sent but no transaction hash came back." }); return { txHash, holder: signer.publicKey }; }, diff --git a/lib/notifications/InboxProvider.tsx b/lib/notifications/InboxProvider.tsx new file mode 100644 index 0000000..08ba4f8 --- /dev/null +++ b/lib/notifications/InboxProvider.tsx @@ -0,0 +1,173 @@ +"use client"; + +import { createContext, useCallback, useContext, useEffect, useEffectEvent, useMemo, useRef, useState, type ReactNode } from "react"; +import { useQueryClient } from "@tanstack/react-query"; +import { useWallet } from "@/lib/contexts/WalletContext"; +import { useTxTracker } from "@/lib/stellar/tx-tracker"; +import { marketCore, rpcServer } from "@/lib/stellar/clients"; +import { NETWORK_PASSPHRASE } from "@/lib/stellar/config"; +import { read } from "@/lib/stellar/typed-call"; +import { decodeEvent, eventTopicFilter, fetchEventRange, type ChainEvent } from "@/lib/stellar/events"; +import type { Position } from "@/lib/stellar/generated/market-core"; +import type { Stamped } from "@/lib/stellar/optimistic"; +import { useRealtimeBatch } from "@/lib/realtime/RealtimeProvider"; +import { classifyTrade, classifyTrace, classifyResolved, classifyRedeemed, classifyTracked } from "./classify"; +import { openInbox, type Inbox, type InboxItem } from "./store"; + +interface InboxView { + items: readonly InboxItem[]; unread: number; status: "disconnected" | "loading" | "saved" | "memory"; + error: string | null; burst: { count: number; overflow: number } | null; + markRead(ids: string[]): Promise; markAllRead(): Promise; +} +const empty: InboxView = { items: [], unread: 0, status: "disconnected", error: null, burst: null, markRead: async () => {}, markAllRead: async () => {} }; +const Context = createContext(empty); +export const useInbox = () => useContext(Context); + +export function InboxProvider({ children }: { children: ReactNode }) { + const { address } = useWallet(); + return address ? {children} + : {children}; +} +function ConnectedInbox({ owner, children }: { owner: string; children: ReactNode }) { + const client = useQueryClient(); + const tracker = useTxTracker(); + const [view, setView] = useState>({ ...empty, status: "loading" }); + const store = useRef(null); + const ready = useRef | null>(null); + const held = useRef(new Set()); + const known = useRef(new Set()); + const active = useRef(false); + const operations = useRef(Promise.resolve()); + const refresh = useCallback(async (inbox: Inbox) => { + const [items, unread] = await Promise.all([inbox.list({ limit: 200 }), inbox.unreadCount()]); + if (active.current && !document.hidden) setView(v => ({ ...v, items, unread, status: inbox.persistent ? "saved" : "memory" })); + }, []); + const enqueue = useCallback((operation: (inbox: Inbox) => Promise) => { + operations.current = operations.current.then(async () => { + const inbox = await ready.current; + if (inbox && active.current) await operation(inbox); + }).catch(error => { + if (active.current && !document.hidden) setView(v => ({ ...v, error: error instanceof Error ? error.message : "Inbox unavailable" })); + }); + return operations.current; + }, []); + + useEffect(() => { + active.current = true; + let cancelled = false; + ready.current = openInbox(owner, NETWORK_PASSPHRASE).then(async inbox => { + if (cancelled) { inbox.close(); return inbox; } + store.current = inbox; + const saved = await inbox.list(); + const ids = await inbox.getMeta("knownMarkets"); + try { for (const id of JSON.parse(ids ?? "[]")) if (typeof id === "string" && /^\d+$/.test(id)) known.current.add(id); } catch { /* Older malformed metadata is ignored. */ } + saved.forEach(item => { if (item.marketId && item.kind === "fill") known.current.add(item.marketId); }); + await refresh(inbox); + return inbox; + }); + const visibility = () => { if (!document.hidden) void enqueue(refresh); }; + document.addEventListener("visibilitychange", visibility); + return () => { cancelled = true; active.current = false; store.current?.close(); store.current = null; document.removeEventListener("visibilitychange", visibility); }; + }, [owner, enqueue, refresh]); + + useRealtimeBatch(batch => { + // A fill earlier in this batch can make a later trace relevant immediately. + const items: InboxItem[] = []; + for (const event of batch) { + if (event.type === "TRADE_EXECUTED") { + const item = classifyTrade(event.payload, owner, event.receivedAt); + if (item) { items.push(item); known.current.add(item.marketId!); if (event.payload.isBuy) held.current.add(item.marketId!); } + } else { + const item = classifyTrace(event.payload, held.current, owner, event.receivedAt); + if (item) items.push(item); + } + } + if (batch.length > 10 || batch.resumed) setView(v => ({ ...v, burst: { count: batch.length + batch.overflow, overflow: batch.overflow } })); + if (items.length) void enqueue(async inbox => { + await inbox.put(items); await inbox.prune(500); + await inbox.setMeta("knownMarkets", JSON.stringify([...known.current])); + await refresh(inbox); + }); + }); + + const recordTransactions = useEffectEvent(() => { + const own = tracker.list.filter(tx => tx.holder === owner && tx.networkPassphrase === NETWORK_PASSPHRASE); + void enqueue(async inbox => { + const items = own.flatMap(tx => { const item = classifyTracked(tx, owner); return item ? [item] : []; }); + await inbox.put(items); + await inbox.remove(own.filter(tx => tx.state !== "pending").map(tx => `tx-pending:${tx.hash}`)); + await inbox.prune(500); await refresh(inbox); + }); + }); + useEffect(() => { recordTransactions(); }, [tracker.list]); + + useEffect(() => { + let running = false, cancelled = false; + const controller = new AbortController(); + async function poll() { + if (running || cancelled || document.hidden) return; + running = true; + try { + const inbox = await ready.current; + if (!inbox || cancelled) return; + // Discover only positions already read by this wallet, plus its own fill history. + for (const [key, data] of client.getQueriesData>({ queryKey: ["stellar", "position"] })) { + if (key[3] === owner && data?.value && (data.value.yes > 0n || data.value.no > 0n)) known.current.add(String(key[2])); + } + const server = rpcServer(); + const health = await server.getHealth(); + const last = Number(await inbox.getMeta("lastLedger")); + const start = Math.max(health.oldestLedger, Number.isSafeInteger(last) && last > 0 ? last + 1 : health.oldestLedger); + const positions = new Map(); + const synthetic: InboxItem[] = []; + for (const id of known.current) { + if (cancelled || document.hidden) return; + const position = read(await marketCore().get_position({ market_id: BigInt(id), holder: owner })); + positions.set(id, position); + if (position.yes + position.no > 0n) { + held.current.add(id); + const marketTx = await marketCore().get_market({ market_id: BigInt(id) }); + const market = read(marketTx); + if (market.status.tag === "Resolved" || market.status.tag === "Void") { + // Current decoded contract status catches resolutions beyond event retention. + const item = classifyResolved({ id: `status:${id}`, name: "MarketResolved", ledger: marketTx.simulation!.latestLedger, + closedAt: new Date().toISOString(), txHash: "", data: { market_id: BigInt(id), outcome: market.status.tag === "Void" ? undefined : market.status.values[0] } }, owner, position); + if (item) synthetic.push(item); + } + } else held.current.delete(id); + } + const topics = [eventTopicFilter("Redeemed", { holder: owner }), ...[...held.current].map(id => eventTopicFilter("MarketResolved", { market_id: BigInt(id) }))]; + const events: ChainEvent[] = []; + for (let i = 0; i < topics.length; i += 5) { + const page = await fetchEventRange(server, topics.slice(i, i + 5), start, health.latestLedger + 1, controller.signal); + for (const raw of page.events) { const event = decodeEvent(raw); if (event) events.push(event); } + } + if (cancelled) return; + await enqueue(async current => { + await current.put([...synthetic, ...events.flatMap(event => { + const item = event.name === "Redeemed" ? classifyRedeemed(event, owner) + : classifyResolved(event, owner, positions.get(String(event.data.market_id)) ?? { yes: 0n, no: 0n }); + return item ? [item] : []; + })]); + await current.setMeta("knownMarkets", JSON.stringify([...known.current])); + await current.setMeta("lastLedger", String(health.latestLedger)); + await current.prune(500); await refresh(current); + if (!document.hidden) setView(v => ({ ...v, error: null })); + }); + } catch (error) { + if (!cancelled && !document.hidden) setView(v => ({ ...v, error: `Chain inbox refresh failed: ${error instanceof Error ? error.message : "RPC unavailable"}` })); + } finally { running = false; } + } + void poll(); + const timer = setInterval(() => void poll(), 60_000); + const visibility = () => { if (!document.hidden) void poll(); }; + document.addEventListener("visibilitychange", visibility); + return () => { cancelled = true; controller.abort(); clearInterval(timer); document.removeEventListener("visibilitychange", visibility); }; + }, [owner, client, enqueue, refresh]); + + const value = useMemo(() => ({ ...view, + markRead: ids => enqueue(async inbox => { await inbox.markRead(ids); await refresh(inbox); }), + markAllRead: () => enqueue(async inbox => { await inbox.markAllRead(); await refresh(inbox); }), + }), [view, enqueue, refresh]); + return {children}; +} diff --git a/lib/notifications/__tests__/classify.test.ts b/lib/notifications/__tests__/classify.test.ts new file mode 100644 index 0000000..fe2ac8f --- /dev/null +++ b/lib/notifications/__tests__/classify.test.ts @@ -0,0 +1,41 @@ +import { describe, expect, it } from "vitest"; +import { Keypair } from "@stellar/stellar-sdk"; +import { classifyTrade, classifyTrace, classifyResolved, classifyRedeemed } from "../classify"; +import { isTradeEvent, type TradeEvent } from "@/lib/realtime/types"; +import { socketTrade } from "@/lib/hooks/useMarketTrades"; + +const owner = Keypair.fromRawEd25519Seed(Buffer.alloc(32, 1)).publicKey(); +const other = Keypair.fromRawEd25519Seed(Buffer.alloc(32, 2)).publicKey(); +const payload: TradeEvent = { eventId: "event-1", onChainMarketId: "7", trader: owner, direction: "YES", isBuy: true, + collateralRaw: "10000000", sharesRaw: "15873015", feeRaw: "200000", priceYesBps: 5000, txHash: "a".repeat(64), ledger: 100, marketQuestion: null }; +const event = { id: "id", name: "MarketResolved", ledger: 100, closedAt: "2026-10-18T12:00:00Z", txHash: "b".repeat(64), data: { market_id: 7n, outcome: undefined } }; +describe("wallet-only classification", () => { + it("ignores other traders and preserves deterministic ids", () => { + expect(classifyTrade(payload, other)).toBeNull(); + expect(classifyTrade(payload, owner)?.id).toBe(classifyTrade(payload, owner)?.id); + expect(socketTrade(payload, "other")).toBeNull(); + }); + it.each(["-1", "1.5", "NaN", "1e7", "", "0x10"])("rejects malformed raw collateral %s", collateralRaw => { + expect(classifyTrade({ ...payload, collateralRaw }, owner)).toBeNull(); + }); + it("validates all socket fields before bigint conversion", () => { + expect(isTradeEvent({ ...payload, sharesRaw: null })).toBe(false); + expect(isTradeEvent({ ...payload, txHash: "not-a-hash" })).toBe(false); + expect(isTradeEvent({ ...payload, ledger: NaN })).toBe(false); + expect(isTradeEvent(payload)).toBe(true); + }); + it("publishes reasoning only for a held market", () => { + const trace = { onChainTraceId: "1", onChainMarketId: "7", agent: owner, action: "buy", traceId: null, ledger: 100, txHash: "a".repeat(64) }; + expect(classifyTrace(trace, new Set(), owner)).toBeNull(); + expect(classifyTrace(trace, new Set(["7"]), owner)?.kind).toBe("trace"); + }); + it("void is redeemable when shares remain, including a losing-side payout of zero", () => { + expect(classifyResolved(event, owner, { yes: 1n, no: 0n })).toMatchObject({ kind: "redeemable", data: { outcome: "Void" } }); + expect(classifyResolved(event, owner, { yes: 0n, no: 0n })).toBeNull(); + }); + it("redemption belongs only to its holder", () => { + const redeemed = { ...event, name: "Redeemed", data: { market_id: 7n, holder: owner, payout: 0n } }; + expect(classifyRedeemed(redeemed, other)).toBeNull(); + expect(classifyRedeemed(redeemed, owner)?.data.payoutRaw).toBe("0"); + }); +}); diff --git a/lib/notifications/__tests__/store.test.ts b/lib/notifications/__tests__/store.test.ts new file mode 100644 index 0000000..123a115 --- /dev/null +++ b/lib/notifications/__tests__/store.test.ts @@ -0,0 +1,34 @@ +import { IDBFactory, IDBKeyRange } from "fake-indexeddb"; +import { afterEach, beforeEach, describe, expect, it, vi } from "vitest"; +import { openInbox, type InboxItem } from "../store"; + +beforeEach(() => { vi.stubGlobal("indexedDB", new IDBFactory()); vi.stubGlobal("IDBKeyRange", IDBKeyRange); }); +afterEach(() => vi.unstubAllGlobals()); +const item = (id: string, owner = "A", network = "test"): InboxItem => ({ id, owner, network, kind: "fill", createdAt: Number(id) || 1, readAt: null, data: {} }); +describe("persisted wallet inbox", () => { + it("put is idempotent and preserves readAt across reconnecting", async () => { + let store = await openInbox("A", "test"); await store.put([item("1"), item("1")]); + await store.markRead(["1"]); store.close(); store = await openInbox("A", "test"); + await store.put([item("1")]); expect(await store.list()).toHaveLength(1); expect((await store.list())[0].readAt).not.toBeNull(); + expect(await store.unreadCount()).toBe(0); store.close(); + }); + it("isolates owners and networks, including metadata and clear/read actions", async () => { + const a = await openInbox("A", "test"), b = await openInbox("B", "test"), main = await openInbox("A", "main"); + await a.put([item("1"), item("foreign", "B")]); await b.put([item("2", "B")]); await main.put([item("3", "A", "main")]); + await a.setMeta("ledger", "4"); await a.markAllRead(); + expect(await a.unreadCount()).toBe(0); expect(await b.unreadCount()).toBe(1); expect(await main.unreadCount()).toBe(1); + expect(await b.getMeta("ledger")).toBeUndefined(); expect((await a.list()).map(x => x.id)).toEqual(["1"]); + [a, b, main].forEach(store => store.close()); + }); + it("prunes oldest read items first and caps even a wholly unread inbox", async () => { + const store = await openInbox("prune", "test"); await store.put([1, 2, 3, 4].map(id => item(String(id), "prune"))); + await store.markRead(["3", "4"]); await store.prune(2); expect((await store.list()).map(x => x.id)).toEqual(["2", "1"]); + await store.put([item("5", "prune")]); await store.prune(2); expect((await store.list()).map(x => x.id)).toEqual(["5", "2"]); store.close(); + }); + it("falls back to memory if IndexedDB is unavailable and retains read state", async () => { + vi.stubGlobal("indexedDB", { open() { throw new Error("private browsing"); } }); + const store = await openInbox("fallback", "test"); expect(store.persistent).toBe(false); + await store.put([item("1", "fallback")]); await store.markAllRead(); await store.put([item("1", "fallback")]); + expect(await store.unreadCount()).toBe(0); expect(await store.list()).toHaveLength(1); store.close(); + }); +}); diff --git a/lib/notifications/classify.ts b/lib/notifications/classify.ts new file mode 100644 index 0000000..8b9fb63 --- /dev/null +++ b/lib/notifications/classify.ts @@ -0,0 +1,42 @@ +import { NETWORK_PASSPHRASE } from "@/lib/stellar/config"; +import type { Position } from "@/lib/stellar/generated/market-core"; +import type { ChainEvent } from "@/lib/stellar/events"; +import type { TrackedTx } from "@/lib/stellar/tx-tracker-core"; +import { isTradeEvent, isTraceEvent, isRaw, isHash } from "@/lib/realtime/types"; +import type { InboxItem } from "./store"; + +const base = (owner: string, kind: InboxItem["kind"], id: string, createdAt: number): InboxItem => ({ owner, network: NETWORK_PASSPHRASE, kind, id: `${kind}:${id}`, createdAt, readAt: null, data: {} }); +const eventTime = (event: ChainEvent) => Date.parse(event.closedAt); +export function classifyTrade(payload: unknown, owner: string, receivedAt = Date.now()): InboxItem | null { + if (!isTradeEvent(payload) || payload.trader !== owner) return null; + return { ...base(owner, "fill", payload.eventId, payload.closedAt ? Date.parse(payload.closedAt) : receivedAt), + marketId: payload.onChainMarketId, txHash: payload.txHash, ledger: payload.ledger, + data: { collateralRaw: payload.collateralRaw, sharesRaw: payload.sharesRaw, direction: payload.direction, action: payload.isBuy ? "Bought" : "Sold", question: payload.marketQuestion ?? "" } }; +} +export function classifyTrace(payload: unknown, heldMarketIds: ReadonlySet, owner: string, receivedAt = Date.now()): InboxItem | null { + if (!isTraceEvent(payload) || !heldMarketIds.has(payload.onChainMarketId)) return null; + return { ...base(owner, "trace", payload.onChainTraceId, payload.closedAt ? Date.parse(payload.closedAt) : receivedAt), + marketId: payload.onChainMarketId, txHash: payload.txHash, ledger: payload.ledger, + data: { traceId: payload.traceId ?? "", onChainTraceId: payload.onChainTraceId, action: payload.action } }; +} +export function classifyResolved(event: ChainEvent, owner: string, position: Position): InboxItem | null { + if (event.name !== "MarketResolved" || typeof event.data.market_id !== "bigint" || position.yes + position.no <= 0n || !Number.isFinite(eventTime(event))) return null; + const outcome = event.data.outcome as { tag?: unknown } | undefined; + if (outcome !== undefined && outcome !== null && outcome.tag !== "Yes" && outcome.tag !== "No") return null; + const marketId = event.data.market_id.toString(); + return { ...base(owner, "redeemable", marketId, eventTime(event)), marketId, ledger: event.ledger, + ...(isHash(event.txHash) ? { txHash: event.txHash } : {}), data: { outcome: outcome?.tag ? String(outcome.tag) : "Void" } }; +} +export function classifyRedeemed(event: ChainEvent, owner: string): InboxItem | null { + if (event.name !== "Redeemed" || event.data.holder !== owner || typeof event.data.market_id !== "bigint" || + typeof event.data.payout !== "bigint" || !isRaw(String(event.data.payout)) || !Number.isFinite(eventTime(event)) || !isHash(event.txHash)) return null; + return { ...base(owner, "redeemed", event.id, eventTime(event)), marketId: event.data.market_id.toString(), txHash: event.txHash, + ledger: event.ledger, data: { payoutRaw: String(event.data.payout) } }; +} +export function classifyTracked(tx: TrackedTx, owner: string): InboxItem | null { + if (tx.holder !== owner || tx.networkPassphrase !== NETWORK_PASSPHRASE || !isHash(tx.hash)) return null; + if (tx.state !== "pending" && tx.state !== "failed" && tx.state !== "expired") return null; + const kind = tx.state === "pending" ? "tx-pending" : "tx-failed"; + return { ...base(owner, kind, tx.hash, tx.submittedAt), txHash: tx.hash, marketId: tx.marketId, ledger: tx.ledger, + data: { action: tx.kind, message: tx.error?.message ?? (tx.state === "expired" ? "Transaction expired" : "Awaiting confirmation") } }; +} diff --git a/lib/notifications/store.ts b/lib/notifications/store.ts new file mode 100644 index 0000000..d3980d5 --- /dev/null +++ b/lib/notifications/store.ts @@ -0,0 +1,137 @@ +export type InboxKind = "fill" | "redeemable" | "redeemed" | "trace" | "tx-failed" | "tx-pending"; +export interface InboxItem { + id: string; owner: string; network: string; kind: InboxKind; marketId?: string; txHash?: string; ledger?: number; + createdAt: number; readAt: number | null; data: Record; +} +export interface Inbox { + readonly persistent: boolean; + put(items: InboxItem[]): Promise; + list(opts?: { limit?: number }): Promise; + markRead(ids: string[]): Promise; + markAllRead(): Promise; + remove(ids: string[]): Promise; + unreadCount(): Promise; + prune(max: number): Promise; + getMeta(key: string): Promise; + setMeta(key: string, value: string): Promise; + close(): void; +} +const memory = new Map; meta: Map }>(); +const newest = (items: Iterable) => [...items].sort((a, b) => b.createdAt - a.createdAt || b.id.localeCompare(a.id)); + +function connect(): Promise { + return new Promise((resolve, reject) => { + const request = indexedDB.open("oracledesk-inbox", 1); + request.onupgradeneeded = () => { + const db = request.result; + const items = db.createObjectStore("items", { keyPath: ["owner", "network", "id"] }); + items.createIndex("scope", ["owner", "network"]); + items.createIndex("createdAt", ["owner", "network", "createdAt"]); + items.createIndex("readAt", ["owner", "network", "readAt"]); + db.createObjectStore("meta", { keyPath: ["owner", "network", "key"] }); + }; + let blocked = false; + request.onblocked = () => { blocked = true; reject(new Error("Inbox database is blocked")); }; + request.onerror = () => reject(request.error); + request.onsuccess = () => { if (blocked) request.result.close(); else resolve(request.result); }; + }); +} + +export async function openInbox(owner: string, network: string): Promise { + const scope = JSON.stringify([owner, network]); + let fallback = memory.get(scope); + if (!fallback) { fallback = { items: new Map(), meta: new Map() }; memory.set(scope, fallback); } + const local = fallback; + let db: IDBDatabase | null; + try { db = await connect(); } catch { db = null; } + let closed = false; + if (db) db.onversionchange = () => { db?.close(); db = null; }; + const matches = (item: InboxItem) => item.owner === owner && item.network === network; + function transaction(storeName: string, run: (store: IDBObjectStore, result: (v: T) => void) => void): Promise { + return new Promise((resolve, reject) => { + const tx = db!.transaction(storeName, "readwrite"); + let value: T; + tx.oncomplete = () => resolve(value); + tx.onerror = () => reject(tx.error); + tx.onabort = () => reject(tx.error ?? new Error("Inbox transaction aborted")); + run(tx.objectStore(storeName), v => { value = v; }); + }); + } + async function safe(action: () => Promise, offline: () => T): Promise { + if (closed) throw new Error("Inbox is closed"); + if (db) { + try { return await action(); } + catch { db.close(); db = null; } + } + return offline(); + } + const all = () => newest(local.items.values()); + const readItems = () => safe(() => transaction("items", (store, result) => { + const request = store.index("scope").getAll(IDBKeyRange.only([owner, network])); + request.onsuccess = () => { + const items = (request.result as InboxItem[]).filter(matches); + local.items.clear(); + items.forEach(item => local.items.set(item.id, item)); + result(newest(items)); + }; + }), all); + const remove = async (ids: string[]) => { + await safe(() => transaction("items", store => { ids.forEach(id => store.delete([owner, network, id])); }), () => undefined); + ids.forEach(id => local.items.delete(id)); + }; + const mark = async (ids: string[] | null) => { + const now = Date.now(); + await safe(() => transaction("items", store => { + const request = store.index("scope").getAll(IDBKeyRange.only([owner, network])); + request.onsuccess = () => { + for (const item of request.result as InboxItem[]) { + if (matches(item) && item.readAt === null && (ids === null || ids.includes(item.id))) { + const next = { ...item, readAt: now }; store.put(next); local.items.set(item.id, next); + } + } + }; + }), () => undefined); + for (const [id, item] of local.items) if (item.readAt === null && (ids === null || ids.includes(id))) local.items.set(id, { ...item, readAt: now }); + }; + return { + get persistent() { return db !== null; }, + async put(items) { + const own = items.filter(matches); + await safe(() => transaction("items", store => { + // Deduplicate inside the transaction so two copies in one batch keep readAt. + for (const item of new Map(own.map(item => [item.id, item])).values()) { + const request = store.get([owner, network, item.id]); + request.onsuccess = () => { + const previous = request.result as InboxItem | undefined; + const next = { ...item, createdAt: previous?.createdAt ?? item.createdAt, readAt: previous?.readAt ?? item.readAt }; + store.put(next); local.items.set(item.id, next); + }; + } + }), () => undefined); + for (const item of own) { + const previous = local.items.get(item.id); + local.items.set(item.id, { ...item, createdAt: previous?.createdAt ?? item.createdAt, readAt: previous?.readAt ?? item.readAt }); + } + }, + async list(opts) { return (await readItems()).slice(0, opts?.limit ?? Infinity); }, + markRead: ids => mark(ids), markAllRead: () => mark(null), remove, + async unreadCount() { return (await readItems()).filter(item => item.readAt === null).length; }, + async prune(max) { + const items = await readItems(); + const removeCount = Math.max(0, items.length - max); + const oldestFirst = items.sort((a, b) => Number(a.readAt === null) - Number(b.readAt === null) || a.createdAt - b.createdAt); + await remove(oldestFirst.slice(0, removeCount).map(item => item.id)); + }, + async getMeta(key) { + return safe(() => transaction("meta", (store, result) => { + const request = store.get([owner, network, key]); + request.onsuccess = () => { const value = request.result?.value; if (typeof value === "string") local.meta.set(key, value); result(value); }; + }), () => local.meta.get(key)); + }, + async setMeta(key, value) { + await safe(() => transaction("meta", store => { store.put({ owner, network, key, value }); }), () => undefined); + local.meta.set(key, value); + }, + close() { closed = true; db?.close(); db = null; }, + }; +} diff --git a/lib/realtime/RealtimeProvider.tsx b/lib/realtime/RealtimeProvider.tsx new file mode 100644 index 0000000..c684b96 --- /dev/null +++ b/lib/realtime/RealtimeProvider.tsx @@ -0,0 +1,39 @@ +"use client"; + +import { createContext, useContext, useEffect, useEffectEvent, useState, type ReactNode } from "react"; +import { MotionConfig } from "framer-motion"; +import { io } from "socket.io-client"; +import { API_URL } from "@/lib/api/client"; +import { createBatcher, type Batch } from "./batch"; +import { isTradeEvent, isTraceEvent, type RealtimeEvent } from "./types"; + +type Listener = (batch: Batch) => void; +const Context = createContext | null>(null); + +export function RealtimeProvider({ children }: { children: ReactNode }) { + const [listeners] = useState(() => new Set()); + useEffect(() => { + // One scheduler for both event kinds: mixed bursts also commit once per frame. + const batcher = createBatcher(); + const stop = batcher.subscribe(batch => { for (const listener of listeners) listener(batch); }); + const visibility = () => batcher.setPaused(document.hidden); + visibility(); + document.addEventListener("visibilitychange", visibility); + const socket = io(API_URL.replace(/\/api\/v1\/?$/, "")); + socket.on("TRADE_EXECUTED", (payload: unknown) => { if (isTradeEvent(payload)) batcher.push({ type: "TRADE_EXECUTED", payload, receivedAt: Date.now() }); }); + socket.on("REASONING_PUBLISHED", (payload: unknown) => { if (isTraceEvent(payload)) batcher.push({ type: "REASONING_PUBLISHED", payload, receivedAt: Date.now() }); }); + return () => { socket.disconnect(); stop(); batcher.dispose(); document.removeEventListener("visibilitychange", visibility); }; + }, [listeners]); + return {children}; +} + +export function useRealtimeBatch(handler: Listener) { + const listeners = useContext(Context); + const onBatch = useEffectEvent(handler); + useEffect(() => { + if (!listeners) throw new Error("RealtimeProvider is missing"); + const listener: Listener = batch => onBatch(batch); + listeners.add(listener); + return () => { listeners.delete(listener); }; + }, [listeners]); +} diff --git a/lib/realtime/__tests__/batch.test.ts b/lib/realtime/__tests__/batch.test.ts new file mode 100644 index 0000000..7958518 --- /dev/null +++ b/lib/realtime/__tests__/batch.test.ts @@ -0,0 +1,53 @@ +import { describe, expect, it, vi } from "vitest"; +import { createBatcher } from "../batch"; +import { Ring } from "../ring"; +import { reduceFeed } from "../burst"; + +function scheduler() { + const callbacks: (() => void)[] = []; + return { callbacks, schedule: (cb: () => void) => { callbacks.push(cb); return callbacks.length; }, cancel: vi.fn(), flush: () => callbacks.splice(0).forEach(cb => cb()) }; +} +describe("bounded realtime delivery", () => { + it("coalesces 500 events into one notification per frame", () => { + const s = scheduler(), batcher = createBatcher(s), listener = vi.fn(); + batcher.subscribe(listener); + for (let i = 0; i < 500; i++) batcher.push(i); + expect(s.callbacks).toHaveLength(1); expect(listener).not.toHaveBeenCalled(); + s.flush(); expect(listener).toHaveBeenCalledTimes(1); expect(listener.mock.calls[0][0]).toHaveLength(500); + }); + it("pauses delivery, keeps the newest bounded backlog, and reports overflow on resume", () => { + const s = scheduler(), batcher = createBatcher({ ...s, maxBatch: 3 }), listener = vi.fn(); + batcher.subscribe(listener); batcher.push(0); batcher.setPaused(true); + [1, 2, 3, 4].forEach(batcher.push); s.flush(); expect(listener).not.toHaveBeenCalled(); + batcher.setPaused(false); s.flush(); + const batch = listener.mock.calls[0][0]; + expect([...batch]).toEqual([2, 3, 4]); expect(batch.overflow).toBe(2); expect(batch.resumed).toBe(true); + }); + it("dispose cancels scheduled work and subscriptions can be removed", () => { + const s = scheduler(), batcher = createBatcher(s), listener = vi.fn(); + const stop = batcher.subscribe(listener); batcher.push(1); stop(); batcher.dispose(); s.flush(); + expect(listener).not.toHaveBeenCalled(); expect(s.cancel).toHaveBeenCalled(); + }); +}); +describe("immutable rings and burst reducer", () => { + it("keeps the newest capacity, dedupes by stable key and preserves the old version", () => { + const ring = new Ring(3, String); + const next = ring.pushMany([1, 2, 3, 2, 4]); + expect(next.toArray()).toEqual([4, 2, 3]); expect(ring.toArray()).toEqual([]); + expect(next.toArray()).toBe(next.toArray()); expect(next.pushMany([])).toBe(next); + }); + it("summarises bursts and resumed batches, leaving normal batches unflagged", () => { + const state = { ring: new Ring(6, String), burst: null }; + const batch = (values: number[], resumed = false) => Object.assign(values, { overflow: 0, resumed }); + expect(reduceFeed(state, batch([1, 2]), 10).burst).toBeNull(); + expect(reduceFeed(state, batch(Array.from({ length: 42 }, (_, i) => i)), 10, 100).burst).toMatchObject({ count: 42, at: 100 }); + expect(reduceFeed(state, batch([1], true), 10).burst?.resumed).toBe(true); + }); + it("summarises sustained activity across frames and clears after a quiet second", () => { + const batch = (values: number[]) => Object.assign(values, { overflow: 0, resumed: false }); + const first = reduceFeed({ ring: new Ring(6, String), burst: null }, batch([1, 2, 3, 4, 5, 6]), 10, 100); + const next = reduceFeed(first, batch([7, 8, 9, 10, 11]), 10, 200); + expect(next.burst?.count).toBe(11); + expect(reduceFeed(next, batch([12]), 10, 1300).burst).toBeNull(); + }); +}); diff --git a/lib/realtime/batch.ts b/lib/realtime/batch.ts new file mode 100644 index 0000000..9a6ca1f --- /dev/null +++ b/lib/realtime/batch.ts @@ -0,0 +1,45 @@ +export type Batch = readonly E[] & { readonly overflow: number; readonly resumed: boolean }; + +export function createBatcher(opts: { + schedule?: (cb: () => void) => unknown; + cancel?: (handle: unknown) => void; + maxBatch?: number; +} = {}) { + const capacity = opts.maxBatch ?? 500; + if (!Number.isInteger(capacity) || capacity < 1) throw new Error("Invalid batch capacity"); + const schedule = opts.schedule ?? ((cb: () => void) => typeof requestAnimationFrame === "function" ? requestAnimationFrame(cb) : setTimeout(cb, 16)); + const cancel = opts.cancel ?? ((handle: unknown) => typeof cancelAnimationFrame === "function" ? cancelAnimationFrame(handle as number) : clearTimeout(handle as ReturnType)); + const listeners = new Set<(batch: Batch) => void>(); + let pending: E[] = [], overflow = 0, paused = false, disposed = false, scheduled = false, resumed = false; + let handle: unknown; + let generation = 0; + function enqueue() { + if (paused || disposed || scheduled || (!pending.length && !overflow)) return; + scheduled = true; + const token = generation; + handle = schedule(() => { + if (token !== generation || disposed || paused) return; + scheduled = false; + const batch = Object.assign(pending, { overflow, resumed }) as Batch; + pending = []; overflow = 0; resumed = false; + for (const fn of listeners) fn(batch); + }); + } + return { + push(event: E) { + if (disposed) return; + if (pending.length === capacity) { pending.shift(); overflow++; } + pending.push(event); + enqueue(); + }, + subscribe(fn: (batch: Batch) => void) { listeners.add(fn); return () => { listeners.delete(fn); }; }, + setPaused(value: boolean) { + if (paused === value) return; + paused = value; + if (paused) { + generation++; cancel(handle); scheduled = false; + } else { resumed = true; enqueue(); } + }, + dispose() { disposed = true; generation++; cancel(handle); pending = []; listeners.clear(); }, + }; +} diff --git a/lib/realtime/burst.ts b/lib/realtime/burst.ts new file mode 100644 index 0000000..5d7177e --- /dev/null +++ b/lib/realtime/burst.ts @@ -0,0 +1,21 @@ +import { Ring } from "./ring"; +import type { Batch } from "./batch"; + +export interface FeedState { + ring: Ring; + recent?: readonly { at: number; count: number }[]; + burst: { count: number; at: number; overflow: number; resumed: boolean } | null; +} +export function reduceFeed(state: FeedState, batch: Batch, threshold = 10, now = Date.now()): FeedState { + // Count a rolling second, so a steady stream across many small frames also + // suspends row animations. Bucket by flush, with a hard bound for custom clocks. + const recent = [...(state.recent ?? []).filter(bucket => bucket.at > now - 1000), + { at: now, count: batch.length + batch.overflow }].slice(-500); + const count = recent.reduce((sum, bucket) => sum + bucket.count, 0); + return { + ring: state.ring.pushMany(batch), + recent, + burst: count > threshold || batch.resumed + ? { count, at: now, overflow: batch.overflow, resumed: batch.resumed } : null, + }; +} diff --git a/lib/realtime/format.ts b/lib/realtime/format.ts new file mode 100644 index 0000000..ae875ba --- /dev/null +++ b/lib/realtime/format.ts @@ -0,0 +1,3 @@ +// Reuse Intl's formatter across rows and batches; locale setup is expensive. +const clock = new Intl.DateTimeFormat("en-GB", { hour: "2-digit", minute: "2-digit", second: "2-digit", hour12: false }); +export const formatFeedTime = (value: string | number) => clock.format(new Date(value)); diff --git a/lib/realtime/ring.ts b/lib/realtime/ring.ts new file mode 100644 index 0000000..fdca253 --- /dev/null +++ b/lib/realtime/ring.ts @@ -0,0 +1,18 @@ +/** Newest first, immutable on write. One cached array per version. */ +export class Ring { + constructor(readonly capacity: number, private readonly key: (item: T) => string, private readonly items: readonly T[] = []) { + if (!Number.isInteger(capacity) || capacity < 1) throw new Error("Invalid ring capacity"); + } + pushMany(incoming: readonly T[]): Ring { + if (!incoming.length) return this; + const seen = new Set(); + const next = [...incoming].reverse().concat([...this.items]).filter(item => { + const id = this.key(item); + if (seen.has(id)) return false; + seen.add(id); return true; + }).slice(0, this.capacity); + if (next.length === this.items.length && next.every((item, i) => item === this.items[i])) return this; + return new Ring(this.capacity, this.key, next); + } + toArray(): readonly T[] { return this.items; } +} diff --git a/lib/realtime/types.ts b/lib/realtime/types.ts new file mode 100644 index 0000000..7ce740a --- /dev/null +++ b/lib/realtime/types.ts @@ -0,0 +1,36 @@ +import { StrKey } from "@stellar/stellar-sdk"; + +export interface TradeEvent { + eventId: string; onChainMarketId: string; trader: string; direction: "YES" | "NO"; isBuy: boolean; + collateralRaw: string; sharesRaw: string; feeRaw: string; priceYesBps: number; + txHash: string; ledger: number; marketQuestion: string | null; closedAt?: string; +} +export interface TraceEvent { + onChainTraceId: string; onChainMarketId: string; traceId: string | null; + agent: string; action: string; ledger: number; txHash: string; closedAt?: string; +} +export type RealtimeEvent = + | { type: "TRADE_EXECUTED"; payload: TradeEvent; receivedAt: number } + | { type: "REASONING_PUBLISHED"; payload: TraceEvent; receivedAt: number }; + +export const isHash = (v: unknown): v is string => typeof v === "string" && /^[a-f\d]{64}$/i.test(v); +export const isRaw = (v: unknown): v is string => typeof v === "string" && /^(0|[1-9]\d{0,38})$/.test(v) && BigInt(v) <= (1n << 127n) - 1n; +export const isMarketId = (v: unknown): v is string => typeof v === "string" && /^(0|[1-9]\d{0,19})$/.test(v) && BigInt(v) < 1n << 64n; +export const isAddress = (v: unknown): v is string => typeof v === "string" && (StrKey.isValidEd25519PublicKey(v) || StrKey.isValidContract(v)); +const isLedger = (v: unknown) => Number.isSafeInteger(v) && (v as number) > 0; +const object = (v: unknown): v is Record => !!v && typeof v === "object" && !Array.isArray(v); +const validTime = (v: unknown) => v === undefined || (typeof v === "string" && Number.isFinite(Date.parse(v))); + +export function isTradeEvent(v: unknown): v is TradeEvent { + return object(v) && typeof v.eventId === "string" && v.eventId.length > 0 && v.eventId.length < 150 && + isMarketId(v.onChainMarketId) && isAddress(v.trader) && (v.direction === "YES" || v.direction === "NO") && + typeof v.isBuy === "boolean" && isRaw(v.collateralRaw) && isRaw(v.sharesRaw) && isRaw(v.feeRaw) && + Number.isInteger(v.priceYesBps) && (v.priceYesBps as number) >= 0 && (v.priceYesBps as number) <= 10000 && + isHash(v.txHash) && isLedger(v.ledger) && (v.marketQuestion === null || typeof v.marketQuestion === "string") && validTime(v.closedAt); +} +export function isTraceEvent(v: unknown): v is TraceEvent { + return object(v) && isMarketId(v.onChainTraceId) && isMarketId(v.onChainMarketId) && isAddress(v.agent) && + typeof v.action === "string" && v.action.length > 0 && v.action.length < 200 && + (v.traceId === null || typeof v.traceId === "string") && isHash(v.txHash) && isLedger(v.ledger) && validTime(v.closedAt); +} +export const eventKey = (event: RealtimeEvent) => event.type === "TRADE_EXECUTED" ? `trade:${event.payload.eventId}` : `trace:${event.payload.onChainTraceId}`; diff --git a/lib/realtime/useBurst.ts b/lib/realtime/useBurst.ts new file mode 100644 index 0000000..0d914b4 --- /dev/null +++ b/lib/realtime/useBurst.ts @@ -0,0 +1,17 @@ +"use client"; +import { useState } from "react"; +import { Ring } from "./ring"; +import { reduceFeed, type FeedState } from "./burst"; +import { useRealtimeBatch } from "./RealtimeProvider"; +import { eventKey, type RealtimeEvent } from "./types"; + +export function useBurstFeed(opts: { capacity: number; burstThreshold?: number; type?: RealtimeEvent["type"] }) { + const [state, setState] = useState>(() => ({ ring: new Ring(opts.capacity, eventKey), burst: null })); + useRealtimeBatch(batch => { + const selected = batch.filter(event => !opts.type || event.type === opts.type); + if (!selected.length && !batch.overflow) return; + const filtered = Object.assign(selected, { overflow: batch.overflow, resumed: batch.resumed }); + setState(previous => reduceFeed(previous, filtered, opts.burstThreshold)); + }); + return { rows: state.ring.toArray(), burst: state.burst }; +} diff --git a/lib/stellar/WalletProvider.tsx b/lib/stellar/WalletProvider.tsx index 6e87d0b..ce680ce 100644 --- a/lib/stellar/WalletProvider.tsx +++ b/lib/stellar/WalletProvider.tsx @@ -76,7 +76,9 @@ export function WalletProvider({ children, kitLoader = loadStellarWalletsKit }: ); // All wallet logic lives in the state machine; this component only renders it. + // The initializer stores deferred navigation callbacks; it never reads the ref. const [interpreter] = useState( + // eslint-disable-next-line react-hooks/refs () => new WalletInterpreter({ loadKit: kitLoader, diff --git a/lib/stellar/__tests__/errors.test.ts b/lib/stellar/__tests__/errors.test.ts index 430e15a..6151a75 100644 --- a/lib/stellar/__tests__/errors.test.ts +++ b/lib/stellar/__tests__/errors.test.ts @@ -1,6 +1,6 @@ import { describe, expect, it } from "vitest"; import { xdr, type rpc } from "@stellar/stellar-sdk"; -import { describeError, describeSubmission } from "../errors"; +import { classifySequenceError, describeError, describeSubmission } from "../errors"; describe("describeError", () => { it("maps market-core contract errors by code", () => { diff --git a/lib/stellar/__tests__/events.test.ts b/lib/stellar/__tests__/events.test.ts new file mode 100644 index 0000000..9c365ab --- /dev/null +++ b/lib/stellar/__tests__/events.test.ts @@ -0,0 +1,56 @@ +import { describe, expect, it, vi } from "vitest"; +import { Contract, Keypair, nativeToScVal, xdr, type rpc } from "@stellar/stellar-sdk"; +import { CONTRACTS } from "../config"; +import { decodeTrade, fetchMarketTrades, tradeTopicFilter, mergeTrades } from "../events"; +import { marketCore } from "../clients"; +import { priceYesBps } from "../generated/fpmm"; + +const trader = Keypair.fromRawEd25519Seed(Buffer.alloc(32, 1)).publicKey(); +export function tradeFixture(ledger = 100, index = 1): rpc.Api.EventResponse { + const fields: Record = { + outcome: xdr.ScVal.scvVec([xdr.ScVal.scvSymbol("Yes")]), is_buy: nativeToScVal(true), + collateral: nativeToScVal(10000000n, { type: "i128" }), shares: nativeToScVal(15873015n, { type: "i128" }), + fee: nativeToScVal(200000n, { type: "i128" }), reserve_yes: nativeToScVal(421n, { type: "i128" }), reserve_no: nativeToScVal(579n, { type: "i128" }), + }; + return { + id: `${String(ledger).padStart(19, "0")}-${String(index).padStart(10, "0")}`, type: "contract", ledger, + ledgerClosedAt: "2026-10-18T12:00:00Z", contractId: new Contract(CONTRACTS.marketCore), + transactionIndex: 1, operationIndex: 0, inSuccessfulContractCall: true, txHash: "a".repeat(64), + topic: [xdr.ScVal.scvSymbol("trade"), nativeToScVal(7n, { type: "u64" }), nativeToScVal(trader, { type: "address" })], + value: xdr.ScVal.scvMap(Object.keys(fields).sort().map(key => new xdr.ScMapEntry({ key: xdr.ScVal.scvSymbol(key), val: fields[key] }))), + }; +} +describe("market contract events", () => { + it("derives the trade prefix from the pinned spec and encodes market id as scvU64", () => { + const filter = tradeTopicFilter(7n); + expect(filter[0]).toBe(tradeFixture().topic[0].toXDR("base64")); + expect(xdr.ScVal.fromXDR(filter[1], "base64").switch().name).toBe("scvU64"); + expect(filter[2]).toBe("*"); + }); + it("decodes the contract-shaped XDR fixture with exact bigint amounts and implied price", () => { + const row = decodeTrade(tradeFixture()); + expect(marketCore().spec.parseEvent(tradeFixture().topic, tradeFixture().value)?.name).toBe("Trade"); + expect(row).toMatchObject({ marketId: "7", trader, collateral: 10000000n, shares: 15873015n, fee: 200000n, yesBpsAfter: Number(priceYesBps(421n, 579n)) }); + expect(decodeTrade({ ...tradeFixture(), inSuccessfulContractCall: false })).toBeNull(); + }); + it("widens backward until enough rows are found, using only the selected market topics", async () => { + const getEvents = vi.fn().mockResolvedValueOnce({ events: [], cursor: "recent" }) + .mockResolvedValueOnce({ events: [tradeFixture(5000), tradeFixture(5001)], cursor: "older" }); + const result = await fetchMarketTrades(7n, { want: 2, server: { getHealth: async () => ({ oldestLedger: 1, latestLedger: 12000, ledgerRetentionWindow: 12000, status: "healthy" }), getEvents } }); + expect(result.trades.map(row => row.ledger)).toEqual([5001, 5000]); expect(result.reachedRetention).toBe(false); + expect(getEvents.mock.calls[0][0]).toMatchObject({ startLedger: 10001, endLedger: 12001, filters: [{ topics: [tradeTopicFilter(7n)], contractIds: [CONTRACTS.marketCore] }] }); + expect(getEvents.mock.calls[1][0].endLedger).toBe(10001); + }); + it("reports RPC retention, and aborted reads do not return partial history", async () => { + const server = { getHealth: async () => ({ oldestLedger: 50, latestLedger: 100, ledgerRetentionWindow: 50, status: "healthy" as const }), getEvents: vi.fn().mockResolvedValue({ events: [], cursor: "end" }) }; + expect((await fetchMarketTrades(7n, { want: 20, server })).reachedRetention).toBe(true); + const controller = new AbortController(); controller.abort(); + await expect(fetchMarketTrades(7n, { want: 20, server, signal: controller.signal })).rejects.toThrow(); + }); + it("replaces socket copies by event id, retaining separate events from the same transaction", () => { + const row = decodeTrade(tradeFixture())!; + const socket = { ...row, origin: "socket" as const, reserveYes: null, reserveNo: null }; + const merged = mergeTrades([socket], [row, decodeTrade(tradeFixture(100, 2))!]); + expect(merged).toHaveLength(2); expect(merged.every(row => row.origin === "rpc")).toBe(true); + }); +}); diff --git a/lib/stellar/__tests__/optimistic.test.ts b/lib/stellar/__tests__/optimistic.test.ts new file mode 100644 index 0000000..cdbaf1d --- /dev/null +++ b/lib/stellar/__tests__/optimistic.test.ts @@ -0,0 +1,81 @@ +import { afterEach, describe, expect, it, vi } from "vitest"; +import { QueryClient, MutationObserver } from "@tanstack/react-query"; +import { applyBuy, applySell, applyRedeem, balanceDelta, reconcileRead, type Stamped } from "../optimistic"; +import { balanceKey, positionKey, cancelTradeReads, confirmTrade } from "../optimistic-cache"; +import type { Position } from "../generated/market-core"; + +afterEach(() => { vi.useRealTimers(); vi.restoreAllMocks(); }); +describe("confirmed bigint deltas", () => { + it("updates only the selected side and rejects negative amounts", () => { + const p = { yes: 1n, no: 2n }; + expect(applyBuy(p, "Yes", 3n)).toEqual({ yes: 4n, no: 2n }); + expect(applySell(p, "No", 1n)).toEqual({ yes: 1n, no: 1n }); + expect(() => applySell(p, "Yes", 2n)).toThrow(); expect(p).toEqual({ yes: 1n, no: 2n }); + expect(applyRedeem()).toEqual({ yes: 0n, no: 0n }); + expect(balanceDelta("buy", { collateralIn: 5n })).toBe(-5n); + expect(balanceDelta("sell", { collateralOut: 5n })).toBe(5n); + expect(balanceDelta("redeem", { payout: 0n })).toBe(0n); + }); +}); +describe("ledger reconciliation with a real QueryClient", () => { + const pkey = positionKey("1", "wallet"), bkey = balanceKey("wallet"); + function setup() { + vi.useFakeTimers(); + const client = new QueryClient({ defaultOptions: { queries: { retry: false, gcTime: Infinity } } }); + client.setQueryData(pkey, { value: { yes: 10n, no: 2n }, ledger: 10 }); + client.setQueryData(bkey, { value: 100n, ledger: 10 }); + return client; + } + const buy = { kind: "buy" as const, marketId: "1", holder: "wallet", ledger: 11, txHash: "a", outcome: "Yes" as const, collateralIn: 5n, sharesOut: 8n }; + it("shows exact returned values immediately and a lower-ledger read cannot overwrite them", async () => { + const client = setup(); await cancelTradeReads(client, "1", "wallet"); confirmTrade(client, buy); + const position = client.getQueryData>(pkey)!; + expect(position).toMatchObject({ value: { yes: 18n, no: 2n }, ledger: 11, provisional: { txHash: "a" } }); + expect(client.getQueryData>(bkey)?.value).toBe(95n); + expect(reconcileRead(position, { value: { yes: 10n, no: 2n }, ledger: 10 }, (a, b) => a.yes === b.yes, pkey)).toBe(position); + client.clear(); + }); + it("clears provisional at the confirmed ledger and replaces a mismatch with a warning", () => { + const client = setup(); confirmTrade(client, buy); + const previous = client.getQueryData>(pkey)!; + const fresh = { value: { yes: 18n, no: 2n }, ledger: 11 }; + const equal = (a: Position, b: Position) => a.yes === b.yes && a.no === b.no; + expect(reconcileRead(previous, fresh, equal, pkey)).toBe(fresh); + const warning = vi.spyOn(console, "warn").mockImplementation(() => {}); + const different = { value: { yes: 20n, no: 2n }, ledger: 12 }; + expect(reconcileRead(previous, different, equal, pkey)).toBe(different); expect(warning).toHaveBeenCalledOnce(); client.clear(); + }); + it("stacks two confirmed buys and zero-payout redemption, without touching other owners", () => { + const client = setup(); confirmTrade(client, buy); confirmTrade(client, { ...buy, ledger: 12, txHash: "b" }); + expect(client.getQueryData>(pkey)?.value.yes).toBe(26n); + expect(client.getQueryData>(bkey)?.value).toBe(90n); + confirmTrade(client, { ...buy, kind: "redeem", ledger: 13, payout: 0n }); + expect(client.getQueryData>(pkey)?.value).toEqual({ yes: 0n, no: 0n }); + expect(client.getQueryData>(bkey)?.value).toBe(90n); + expect(client.getQueryData(positionKey("1", "other"))).toBeUndefined(); client.clear(); + }); + it("a rejected signature leaves snapshots exactly unchanged", async () => { + const client = setup(), before = client.getQueryData(pkey); + const observer = new MutationObserver(client, { mutationFn: async () => { throw new Error("User rejected"); }, onMutate: () => cancelTradeReads(client, "1", "wallet"), onSuccess: () => confirmTrade(client, buy) }); + await expect(observer.mutate()).rejects.toThrow("User rejected"); + expect(client.getQueryData(pkey)).toBe(before); expect(client.getQueryData>(bkey)?.value).toBe(100n); client.clear(); + }); + it("unknown ledgers invalidate without applying deltas; disconnect never recreates removed keys", async () => { + const client = setup(); confirmTrade(client, { ...buy, ledger: null }); + expect(client.getQueryData>(pkey)?.value.yes).toBe(10n); + expect(client.getQueryState(pkey)?.isInvalidated).toBe(true); + confirmTrade(client, buy); client.removeQueries({ queryKey: ["stellar"] }); + await vi.advanceTimersByTimeAsync(10000); expect(client.getQueryData(pkey)).toBeUndefined(); client.clear(); + }); + it("keeps a lagging value labelled awaiting RPC after retries, without applying the same confirmation twice", async () => { + const client = setup(); confirmTrade(client, buy); + await vi.advanceTimersByTimeAsync(8000); + const position = client.getQueryData>(pkey)!; + expect(position.provisional).toBeUndefined(); + expect(position.awaitingRpc).toMatchObject({ ledger: 11, txHash: "a" }); + expect(reconcileRead(position, { value: { yes: 10n, no: 2n }, ledger: 10 }, (a, b) => a.yes === b.yes, pkey)).toBe(position); + confirmTrade(client, buy); + expect(client.getQueryData>(pkey)?.value.yes).toBe(18n); + client.clear(); + }); +}); diff --git a/lib/stellar/__tests__/quote.test.ts b/lib/stellar/__tests__/quote.test.ts index 5185693..d13a46b 100644 --- a/lib/stellar/__tests__/quote.test.ts +++ b/lib/stellar/__tests__/quote.test.ts @@ -9,7 +9,7 @@ const market = (yes: bigint, no: bigint, fee = 0n, sets = 100_000n) => reserve_no: no, fee_bps: fee, sets_minted: sets, - }) as Market; + }) as unknown as Market; describe("minCollateralForShares", () => { it("finds the minimum collateral for a target in the reference pool", () => { diff --git a/lib/stellar/__tests__/typed-call.test.ts b/lib/stellar/__tests__/typed-call.test.ts index 759d44b..2bb78de 100644 --- a/lib/stellar/__tests__/typed-call.test.ts +++ b/lib/stellar/__tests__/typed-call.test.ts @@ -27,7 +27,7 @@ function mockTx( function mockThrowingTx( errorToThrow: unknown, simulation?: { error?: string }, -): AssembledTransaction { +): AssembledTransaction { const signAndSend = vi.fn(); return { get result() { @@ -35,7 +35,7 @@ function mockThrowingTx( }, simulation, signAndSend, - } as unknown as AssembledTransaction; + } as unknown as AssembledTransaction; } describe("bytesToHex utility", () => { @@ -72,7 +72,7 @@ describe("read() runtime helper", () => { }); it("throws UserFacingError on Err with contract error code mapped", () => { - const tx = mockTx(new Err("Error(Contract, #12)"), { + const tx = mockTx(new Err({ message: "Error(Contract, #12)" }), { simulation: { error: "HostError Error(Contract, #12)" }, }); expect(() => read(tx, "marketCore")).toThrow(UserFacingError); @@ -144,6 +144,7 @@ describe("submit() runtime helper", () => { const res = await submit(tx, "marketCore"); expect(res).toEqual({ value: okValue, + ledger: null, txHash: "mocked-hash-xyz", }); expect(tx.signAndSend).toHaveBeenCalledTimes(1); @@ -158,6 +159,7 @@ describe("submit() runtime helper", () => { const res = await submit(tx); expect(res).toEqual({ value: 42n, + ledger: null, txHash: null, }); }); diff --git a/lib/stellar/__tests__/wallet-machine.test.ts b/lib/stellar/__tests__/wallet-machine.test.ts index cb1e57c..721cf0b 100644 --- a/lib/stellar/__tests__/wallet-machine.test.ts +++ b/lib/stellar/__tests__/wallet-machine.test.ts @@ -112,7 +112,7 @@ describe("wallet machine: connect", () => { { type: "KIT_STATE_UPDATED", address: A, passphrase: null, storedSessionWallet: null }, ], disconnected); expect(state.tag).toBe("connecting"); - expect(effects).toEqual([]); + expect(effects).toEqual([{ type: "openAuthModal", epoch: 0 }]); }); }); @@ -147,7 +147,7 @@ describe("wallet machine: sign-in", () => { connectedA, ); expect(failed.state).toMatchObject({ tag: "connected", address: A, session: null, error: "Login was declined" }); - expect(failed.effects).toEqual([{ type: "resolveEnsure", ok: false }]); + expect(failed.effects).toEqual([{ type: "signIn", address: A, epoch: 0, attempt: 1 }, { type: "resolveEnsure", ok: false }]); }); it("account change during authenticating drops the stale sign-in result", () => { diff --git a/lib/stellar/errors.ts b/lib/stellar/errors.ts index bb3000c..c697d1b 100644 --- a/lib/stellar/errors.ts +++ b/lib/stellar/errors.ts @@ -47,6 +47,8 @@ import type { rpc } from "@stellar/stellar-sdk"; export interface DescribedError { kind: + | "stale-sequence" + | "low-reserve" | "rejected" | "insufficient-balance" | "insufficient-xlm" @@ -120,7 +122,7 @@ export function describeError(err: unknown, contract: ContractName = "marketCore }; } if (/op_low_reserve|opLowReserve|txInsufficientBalance|tx_insufficient_balance/i.test(text)) { - return { kind: "low-reserve", message: "Your account needs more spendable XLM to add the USDC trustline." }; + return { kind: "insufficient-xlm", message: "Your account needs more spendable XLM to add the USDC trustline." }; } if (/balance is not sufficient|resulting balance is not within the allowed range|insufficient (balance|funds)|BalanceError|op_underfunded/i.test(text)) { return { kind: "insufficient-balance", message: "You don't have enough USDC for this." }; @@ -253,3 +255,11 @@ export class UserFacingError extends Error { this.name = "UserFacingError"; } } + +export function classifySequenceError(error: unknown): boolean { + if (/tx_bad_seq|txBadSeq|bad sequence/i.test(textOf(error))) return true; + if (!error || typeof error !== "object") return false; + const response = error as { errorResult?: { result(): { switch(): { name: string } } }; sendTransactionResponse?: unknown }; + if (response.sendTransactionResponse) return classifySequenceError(response.sendTransactionResponse); + try { return response.errorResult?.result().switch().name === "txBadSeq"; } catch { return false; } +} diff --git a/lib/stellar/events.ts b/lib/stellar/events.ts new file mode 100644 index 0000000..ec1cbd1 --- /dev/null +++ b/lib/stellar/events.ts @@ -0,0 +1,125 @@ +import type { rpc } from "@stellar/stellar-sdk"; +import { marketCore, rpcServer, type OutcomeTag } from "./clients"; +import { CONTRACTS, RPC_URL } from "./config"; +import { priceYesBps } from "./generated/fpmm"; + +export interface TradeRow { + id: string; ledger: number; closedAt: string | null; txHash: string; + marketId: string; trader: string; outcome: OutcomeTag; isBuy: boolean; + collateral: bigint; shares: bigint; fee: bigint; yesBpsAfter: number; + reserveYes: bigint | null; reserveNo: bigint | null; origin: "rpc" | "socket"; +} +export interface ChainEvent { + id: string; ledger: number; closedAt: string; txHash: string; + name: string; data: Record; +} +let eventSpec: ReturnType["spec"] | undefined; +const spec = () => eventSpec ??= marketCore().spec; + +export function eventTopicFilter(name: string, values: Record = {}): string[] { + return spec().eventTopicFilter(name, values); +} +export const tradeTopicFilter = (marketId: bigint) => eventTopicFilter("Trade", { market_id: marketId }); + +export function decodeEvent(event: rpc.Api.EventResponse): ChainEvent | null { + if (event.type !== "contract" || event.contractId?.contractId() !== CONTRACTS.marketCore || !event.inSuccessfulContractCall) return null; + const parsed = spec().parseEvent(event.topic, event.value); + if (!parsed) return null; + return { id: event.id, ledger: event.ledger, closedAt: event.ledgerClosedAt, txHash: event.txHash, name: parsed.name, data: parsed.data }; +} +export function decodeTrade(event: rpc.Api.EventResponse): TradeRow | null { + const decoded = decodeEvent(event); + if (decoded?.name !== "Trade") return null; + const d = decoded.data; + const outcome = d.outcome as { tag?: unknown } | undefined; + if (typeof d.market_id !== "bigint" || typeof d.trader !== "string" || (outcome?.tag !== "Yes" && outcome?.tag !== "No") || + typeof d.is_buy !== "boolean" || ![d.collateral, d.shares, d.fee, d.reserve_yes, d.reserve_no].every(v => typeof v === "bigint" && v >= 0n)) return null; + const reserveYes = d.reserve_yes as bigint, reserveNo = d.reserve_no as bigint; + return { + id: decoded.id, ledger: decoded.ledger, closedAt: decoded.closedAt, txHash: decoded.txHash, + marketId: String(d.market_id), trader: d.trader, outcome: outcome.tag, isBuy: d.is_buy, + collateral: d.collateral as bigint, shares: d.shares as bigint, fee: d.fee as bigint, + reserveYes, reserveNo, yesBpsAfter: Number(priceYesBps(reserveYes, reserveNo)), origin: "rpc", + }; +} + +/** Prefer decoded RPC copies over provisional socket rows, keyed by event id. */ +export function mergeTrades(existing: readonly TradeRow[], incoming: readonly TradeRow[], max = 2000): TradeRow[] { + const byId = new Map(existing.map(row => [row.id, row])); + for (const row of incoming) { + if (byId.get(row.id)?.origin === "rpc" && row.origin === "socket") continue; + byId.set(row.id, row); + } + return [...byId.values()].sort((a, b) => b.ledger - a.ledger || b.id.localeCompare(a.id)).slice(0, max); +} + +type EventServer = Pick; + +/** SDK 16 separates cursor and ledger-range requests. Stop at the snapshot end + * even though subsequent cursor pages no longer carry an endLedger. */ +export async function fetchEventRange(server: EventServer, topics: string[][], start: number, end: number, signal?: AbortSignal, maxEvents = 2000) { + const filters: rpc.Api.EventFilter[] = [{ type: "contract", contractIds: [CONTRACTS.marketCore], topics }]; + const events: rpc.Api.EventResponse[] = []; + let cursor = ""; + if (start >= end || !topics.length) return { events, cursor }; + const limit = 1000; + while (true) { + signal?.throwIfAborted(); + const page = await server.getEvents(cursor ? { filters, cursor, limit } : { filters, startLedger: start, endLedger: end, limit }); + signal?.throwIfAborted(); + events.push(...page.events.filter(event => event.ledger >= start && event.ledger < end)); + if (events.length > maxEvents) events.splice(0, events.length - maxEvents); + if (page.events.some(event => event.ledger >= end) || page.events.length < limit || !page.cursor || page.cursor === cursor) { + cursor = page.cursor; break; + } + cursor = page.cursor; + } + return { events, cursor }; +} + +interface CachedTrades { trades: TradeRow[]; fromLedger: number; latestLedger: number; cursor: string } +const cache = new Map(); +export function clearTradeCache() { cache.clear(); } + +export async function fetchMarketTrades(marketId: bigint, opts: { want: number; signal?: AbortSignal; server?: EventServer }) { + const server = opts.server ?? rpcServer(); + const want = Math.min(2000, Math.max(1, opts.want)); + const key = `${RPC_URL}:${CONTRACTS.marketCore}:${marketId}`; + for (let attempt = 0; ; attempt++) { + const health = await server.getHealth(); + const previous = opts.server ? undefined : cache.get(key); + let state: CachedTrades = previous && previous.latestLedger <= health.latestLedger + ? { ...previous, trades: previous.trades.filter(row => row.ledger >= health.oldestLedger) } + : { trades: [], fromLedger: health.latestLedger + 1, latestLedger: health.latestLedger, cursor: "" }; + const topics = [tradeTopicFilter(marketId)]; + const readWindow = async (start: number, end: number) => { + const page = await fetchEventRange(server, topics, start, end, opts.signal); + state.trades = mergeTrades(state.trades, page.events.flatMap(event => { + const row = decodeTrade(event); return row && row.marketId === marketId.toString() ? [row] : []; + })); + state.cursor = page.cursor; + }; + try { + if (previous) await readWindow(Math.max(health.oldestLedger, previous.latestLedger + 1), health.latestLedger + 1); + let window = 2000; + while (state.trades.length < want && state.fromLedger > health.oldestLedger) { + const from = Math.max(health.oldestLedger, state.fromLedger - window); + await readWindow(from, state.fromLedger); + state.fromLedger = from; + window *= 4; + } + opts.signal?.throwIfAborted(); + state = { ...state, latestLedger: health.latestLedger }; + if (!opts.server) { + cache.delete(key); cache.set(key, state); + if (cache.size > 32) cache.delete(cache.keys().next().value!); + } + return { trades: state.trades.slice(0, want), reachedRetention: state.fromLedger <= health.oldestLedger && state.trades.length <= want, + latestLedger: health.latestLedger, cursor: state.cursor }; + } catch (error) { + opts.signal?.throwIfAborted(); + if (attempt > 0 || !/oldest|retention|startLedger|start ledger/i.test(String(error))) throw error; + cache.delete(key); + } + } +} diff --git a/lib/stellar/ledger-clock.ts b/lib/stellar/ledger-clock.ts index 624dbbe..d71202a 100644 --- a/lib/stellar/ledger-clock.ts +++ b/lib/stellar/ledger-clock.ts @@ -13,7 +13,7 @@ interface LedgerPoint { } export async function fetchLatestLedger(): Promise { - const latest = await rpcServer.getLatestLedger(); + const latest = await rpcServer().getLatestLedger(); const closeTime = Number(latest.closeTime); if (!Number.isSafeInteger(latest.sequence) || !Number.isFinite(closeTime) || closeTime < 0) { throw new Error("The Stellar RPC returned an invalid ledger clock sample."); @@ -125,3 +125,5 @@ export function useLedgerNow(): { now: number | null; uncertainty: number } { useSyncExternalStore(ledgerClock.subscribe, () => ledgerClock.version, () => 0); return { now: ledgerClock.now(), uncertainty: ledgerClock.uncertainty() }; } + +export const getLedgerNow = () => ledgerClock.now(); diff --git a/lib/stellar/optimistic-cache.ts b/lib/stellar/optimistic-cache.ts new file mode 100644 index 0000000..bec0647 --- /dev/null +++ b/lib/stellar/optimistic-cache.ts @@ -0,0 +1,75 @@ +import type { QueryClient, QueryKey } from "@tanstack/react-query"; +import { applyBuy, applySell, applyRedeem, balanceDelta, type Stamped, type TradeKind } from "./optimistic"; +import type { Position } from "./generated/market-core"; +import type { OutcomeTag } from "./clients"; + +export const positionKey = (id: string, holder: string) => ["stellar", "position", id, holder] as const; +export const balanceKey = (holder: string) => ["stellar", "usdcBalance", holder] as const; +interface ReconcileJob { timer?: ReturnType; stop(): void } +const timers = new WeakMap>(); + +/** One retry schedule per key. Replacements and disconnects dispose timers and subscriptions. */ +export function scheduleReconcile(client: QueryClient, key: QueryKey) { + let jobs = timers.get(client); + if (!jobs) { jobs = new Map(); timers.set(client, jobs); } + const id = JSON.stringify(key); + jobs.get(id)?.stop(); + const expectedQuery = client.getQueryCache().find({ queryKey: key, exact: true }); + if (!expectedQuery) return; + let attempt = 0; + const job: ReconcileJob = { stop() { clearTimeout(job.timer); unsubscribe(); if (jobs.get(id) === job) jobs.delete(id); } }; + const unsubscribe = client.getQueryCache().subscribe(event => { + if (event.type === "removed" && event.query === expectedQuery) job.stop(); + }); + jobs.set(id, job); + async function retry() { + if (jobs!.get(id) !== job || client.getQueryCache().find({ queryKey: key, exact: true }) !== expectedQuery) { job.stop(); return; } + await client.invalidateQueries({ queryKey: key, exact: true }); + if (jobs!.get(id) !== job) return; + const current = client.getQueryData>(key); + if (!current?.provisional) { job.stop(); return; } + attempt++; + if (attempt < 3) job.timer = setTimeout(retry, [1000, 2000, 5000][attempt]); + else { + client.setQueryData(key, { ...current, provisional: undefined, awaitingRpc: current.provisional }); + job.stop(); + } + } + job.timer = setTimeout(retry, 1000); +} + +export async function cancelTradeReads(client: QueryClient, marketId: string, holder: string) { + const keys = [positionKey(marketId, holder), balanceKey(holder)]; + await Promise.all(keys.map(queryKey => client.cancelQueries({ queryKey, exact: true }))); + // No speculative changes happen before confirmation, so rejection needs no rollback. + return { holder, position: client.getQueryData>(keys[0]), balance: client.getQueryData>(keys[1]) }; +} + +export function confirmTrade(client: QueryClient, params: { + kind: TradeKind; marketId: string; holder: string; ledger: number | null; txHash: string | null; + outcome?: OutcomeTag; sharesOut?: bigint; sharesSold?: bigint; collateralIn?: bigint; collateralOut?: bigint; payout?: bigint; +}) { + const { marketId, holder, ledger, txHash, kind } = params; + const keys = [positionKey(marketId, holder), balanceKey(holder)]; + if (ledger === null) { keys.forEach(queryKey => { void client.invalidateQueries({ queryKey }); }); return; } + const provisional = { ledger, txHash, kind }; + // Reading the current cache composes concurrent confirmations. A cache already + // stamped at this ledger includes this transaction and must not receive it twice. + const update = (key: QueryKey, apply: (v: T) => T) => { + client.setQueryData>(key, previous => { + const pending = previous?.provisional ?? previous?.awaitingRpc; + if (pending?.txHash && pending.txHash === txHash) return previous; + if (!previous || (previous.ledger >= ledger && !previous.provisional && !previous.awaitingRpc)) return previous; + if (previous.ledger > ledger) return previous; + try { return { value: apply(previous.value), ledger, provisional }; } + catch { void client.invalidateQueries({ queryKey: key }); return previous; } + }); + scheduleReconcile(client, key); + }; + update(keys[0], p => kind === "buy" ? applyBuy(p, params.outcome!, params.sharesOut!) : kind === "sell" ? applySell(p, params.outcome!, params.sharesSold!) : applyRedeem()); + update(keys[1], value => { + const next = value + balanceDelta(kind, params); + if (next < 0n) throw new Error("Insufficient cached balance"); + return next; + }); +} diff --git a/lib/stellar/optimistic.ts b/lib/stellar/optimistic.ts new file mode 100644 index 0000000..b2b2006 --- /dev/null +++ b/lib/stellar/optimistic.ts @@ -0,0 +1,32 @@ +import type { OutcomeTag } from "./clients"; +import type { Position } from "./generated/market-core"; + +export type TradeKind = "buy" | "sell" | "redeem"; +export interface Provisional { txHash: string | null; ledger: number; kind: TradeKind } +export interface Stamped { value: T; ledger: number; provisional?: Provisional; awaitingRpc?: Provisional } + +export function applyBuy(p: Position, outcome: OutcomeTag, sharesOut: bigint): Position { + if (sharesOut < 0n) throw new Error("Negative shares"); + return outcome === "Yes" ? { ...p, yes: p.yes + sharesOut } : { ...p, no: p.no + sharesOut }; +} +export function applySell(p: Position, outcome: OutcomeTag, sharesSold: bigint): Position { + if (sharesSold < 0n || (outcome === "Yes" ? p.yes : p.no) < sharesSold) throw new Error("Insufficient cached shares"); + return outcome === "Yes" ? { ...p, yes: p.yes - sharesSold } : { ...p, no: p.no - sharesSold }; +} +export function applyRedeem(): Position { return { yes: 0n, no: 0n }; } +export function balanceDelta(kind: TradeKind, r: { collateralIn?: bigint; collateralOut?: bigint; payout?: bigint }): bigint { + const value = kind === "buy" ? r.collateralIn : kind === "sell" ? r.collateralOut : r.payout; + if (value === undefined || value < 0n) throw new Error("Missing or negative confirmed amount"); + return kind === "buy" ? -value : value; +} + +export function reconcileRead(previous: Stamped | undefined, fresh: Stamped, equal: (a: T, b: T) => boolean, key: readonly unknown[]): Stamped { + if (!previous) return fresh; + // Also protect already-reconciled values against a lagging RPC or an old read. + if (fresh.ledger < previous.ledger) return previous; + const pending = previous.provisional ?? previous.awaitingRpc; + if (pending && !equal(previous.value, fresh.value)) { + console.warn("[optimistic] mismatch", { key, expected: previous.value, actual: fresh.value, txHash: pending.txHash }); + } + return fresh; +} diff --git a/lib/stellar/sep10-validator.ts b/lib/stellar/sep10-validator.ts index ee72465..01a859c 100644 --- a/lib/stellar/sep10-validator.ts +++ b/lib/stellar/sep10-validator.ts @@ -1,4 +1,4 @@ -import { TransactionBuilder, Networks, FeeBumpTransaction } from "@stellar/stellar-sdk"; +import { TransactionBuilder, Transaction, FeeBumpTransaction } from "@stellar/stellar-sdk"; import type { AuthChallenge } from "@/lib/api/auth"; import { NETWORK_PASSPHRASE } from "./config"; @@ -15,7 +15,7 @@ export interface SEP10ValidationError { /** * Parse transaction XDR safely. */ -function parseTransactionXDR(xdr: string): { transaction: any; isFeeBump: boolean } | null { +function parseTransactionXDR(xdr: string): { transaction: Transaction | FeeBumpTransaction; isFeeBump: boolean } | null { try { const tx = TransactionBuilder.fromXDR(xdr, NETWORK_PASSPHRASE); const isFeeBump = tx instanceof FeeBumpTransaction; @@ -28,7 +28,7 @@ function parseTransactionXDR(xdr: string): { transaction: any; isFeeBump: boolea /** * Get the inner transaction from a fee-bump or regular transaction. */ -function getInnerTransaction(tx: any): any { +function getInnerTransaction(tx: Transaction | FeeBumpTransaction): Transaction { return tx instanceof FeeBumpTransaction ? tx.innerTransaction : tx; } diff --git a/lib/stellar/trustline.ts b/lib/stellar/trustline.ts index 27c78dd..d0b632e 100644 --- a/lib/stellar/trustline.ts +++ b/lib/stellar/trustline.ts @@ -1,5 +1,5 @@ import { - Address, + Keypair, Asset, BASE_FEE, Operation, @@ -49,14 +49,14 @@ export function usdcClassicAsset(): Promise { export async function getTrustlineState(account: string, asset: Asset): Promise { if (asset.isNative()) return { kind: "native" }; - const address = new Address(account).toScAddress(); + const address = Keypair.fromPublicKey(account).xdrPublicKey(); const keys = [ xdr.LedgerKey.account(new xdr.LedgerKeyAccount({ accountId: address })), - xdr.LedgerKey.trustLine( + xdr.LedgerKey.trustline( new xdr.LedgerKeyTrustLine({ accountId: address, asset: asset.toTrustLineXDRObject() }), ), ]; - const { entries } = await rpcServer.getLedgerEntries(...keys); + const { entries } = await rpcServer().getLedgerEntries(...keys); const accountEntry = entries.find((entry) => entry.key.switch().name === "account"); if (!accountEntry) return { kind: "account-missing" }; const trustlineEntry = entries.find((entry) => entry.key.switch().name === "trustline"); @@ -80,14 +80,14 @@ function accountExtensionV1(account: xdr.AccountEntry) { export async function reserveCheck( address: string, ): Promise<{ ok: boolean; spendable: bigint; required: bigint }> { - const account = await rpcServer.getAccountEntry(address); + const account = await rpcServer().getAccountEntry(address); const v1 = accountExtensionV1(account); const v2 = v1?.ext().switch() === 2 ? v1.ext().v2() : null; const numSponsoring = BigInt(v2?.numSponsoring() ?? 0); const numSponsored = BigInt(v2?.numSponsored() ?? 0); let baseReserve = 5_000_000n; try { - const latest = await rpcServer.getLatestLedger(); + const latest = await rpcServer().getLatestLedger(); baseReserve = BigInt(latest.headerXdr.baseReserve()); } catch { // Older RPC endpoints may not return ledger headers; Stellar's base reserve is 0.5 XLM. @@ -101,7 +101,7 @@ export async function reserveCheck( } export async function buildChangeTrustXdr(address: string, asset: Asset): Promise { - const [account, feeStats] = await Promise.all([rpcServer.getAccount(address), rpcServer.getFeeStats()]); + const [account, feeStats] = await Promise.all([rpcServer().getAccount(address), rpcServer().getFeeStats()]); const recommended = BigInt(feeStats.inclusionFee.p70 || BASE_FEE); const fee = recommended > 10_000n ? 10_000n : recommended > BigInt(BASE_FEE) ? recommended : BigInt(BASE_FEE); return new TransactionBuilder(account, { @@ -121,14 +121,14 @@ export async function submitAndPoll( opts: { timeoutMs?: number } = {}, ): Promise<{ hash: string }> { const tx = TransactionBuilder.fromXDR(signedXdr, NETWORK_PASSPHRASE); - let response = await rpcServer.sendTransaction(tx); + let response = await rpcServer().sendTransaction(tx); if (response.status === "TRY_AGAIN_LATER") { await wait(2_000); - response = await rpcServer.sendTransaction(tx); + response = await rpcServer().sendTransaction(tx); } if (response.status === "ERROR") { - const described = describeError(response.errorResultXdr ?? "Transaction submission failed"); - throw new UserFacingError(described, response.errorResultXdr); + const described = describeError(response.errorResult ?? "Transaction submission failed"); + throw new UserFacingError(described, response.errorResult); } if (response.status !== "PENDING" && response.status !== "DUPLICATE") { throw new Error(`Unexpected transaction submission status: ${response.status}`); @@ -138,7 +138,7 @@ export async function submitAndPoll( let delay = 500; while (Date.now() < deadline) { await wait(delay); - const result = await rpcServer.getTransaction(response.hash); + const result = await rpcServer().getTransaction(response.hash); if (result.status === "SUCCESS") return { hash: response.hash }; if (result.status === "FAILED") { throw new UserFacingError(describeError(result.resultXdr.toXDR("base64")), result.resultXdr); diff --git a/lib/stellar/tx-lifecycle.ts b/lib/stellar/tx-lifecycle.ts index 1ec48dd..6bb9653 100644 --- a/lib/stellar/tx-lifecycle.ts +++ b/lib/stellar/tx-lifecycle.ts @@ -1,5 +1,4 @@ import type { rpc, xdr } from "@stellar/stellar-sdk"; -import { scValToNative } from "@stellar/stellar-sdk"; import type { AssembledTransaction } from "@stellar/stellar-sdk/contract"; import { describeError, @@ -99,7 +98,8 @@ export class TxLifecycle { async run( tx: AssembledTransaction, parse: (v: xdr.ScVal | undefined) => T, - ): Promise<{ value: T; txHash: string }> { + onHash?: (hash: string) => void, + ): Promise<{ value: T; txHash: string; ledger: number }> { // 1. Simulating this.setPhase({ name: "simulating" }); try { @@ -134,6 +134,7 @@ export class TxLifecycle { const hash = signed.hash().toString("hex").toLowerCase(); this.onHash?.(hash); + onHash?.(hash); // 3. Submitting this.setPhase({ name: "submitting", hash }); @@ -199,8 +200,8 @@ export class TxLifecycle { let value: T; try { value = parse(getRes.returnValue); - } catch { - value = (getRes.returnValue ? scValToNative(getRes.returnValue) : null) as T; + } catch (error) { + throw new UserFacingError({ kind: "unknown", message: "The transaction confirmed, but its returned amount could not be decoded. Refresh your balance." }, error); } this.setPhase({ name: "confirmed", @@ -208,7 +209,7 @@ export class TxLifecycle { ledger: getRes.ledger ?? getRes.latestLedger, returnValue: getRes.returnValue, }); - return { value, txHash: hash }; + return { value, txHash: hash, ledger: getRes.ledger }; } if (getRes.status === "FAILED") { diff --git a/lib/stellar/tx-tracker.tsx b/lib/stellar/tx-tracker.tsx index 6aa8c2a..5b91640 100644 --- a/lib/stellar/tx-tracker.tsx +++ b/lib/stellar/tx-tracker.tsx @@ -10,6 +10,7 @@ import { mergeRecords, saveRecords, tradeInvalidationKeys, + isValidRecord, type TrackedKind, type TrackedTx, } from "./tx-tracker-core"; @@ -94,7 +95,7 @@ export function TransactionTrackerProvider({ children }: { children: React.React const data = event.data; if (!data || typeof data !== "object") return; - if (data.type === "record_update" && data.record) { + if (data.type === "record_update" && isValidRecord(data.record, NETWORK_PASSPHRASE)) { updateRecords((prev) => mergeRecords(prev, [data.record])); if (data.record.state === "confirmed") { invalidateFor(data.record); @@ -180,7 +181,7 @@ export function TransactionTrackerProvider({ children }: { children: React.React document.removeEventListener("visibilitychange", handleVisibilityChange); } }; - }, [updateRecords, invalidateFor]); + }, [records, updateRecords, invalidateFor]); const track = useCallback( (tx: Omit) => { @@ -193,25 +194,8 @@ export function TransactionTrackerProvider({ children }: { children: React.React updateRecords((prev) => mergeRecords(prev, [newRecord])); channelRef.current?.postMessage({ type: "record_update", record: newRecord }); - // Trigger poller immediately - if (!isPollingRef.current) { - setTimeout(async () => { - try { - const server = rpcServer(); - const res = await server.getTransaction(newRecord.hash); - const updated = evaluateTxStatus(newRecord, res); - if (updated.state !== newRecord.state) { - updateRecords((prev) => mergeRecords(prev, [updated])); - if (updated.state === "confirmed") invalidateFor(updated); - channelRef.current?.postMessage({ type: "record_update", record: updated }); - } - } catch { - // ignore - } - }, 1500); - } }, - [updateRecords, invalidateFor], + [updateRecords], ); const dismiss = useCallback( diff --git a/lib/stellar/typed-call.ts b/lib/stellar/typed-call.ts index b80e0ee..15f8b4f 100644 --- a/lib/stellar/typed-call.ts +++ b/lib/stellar/typed-call.ts @@ -1,3 +1,4 @@ +/* eslint-disable @typescript-eslint/no-explicit-any -- SDK assembled transactions are invariant; these boundary helpers preserve the exact binding return type. */ import { Ok, Err } from "@stellar/stellar-sdk/contract"; import type { AssembledTransaction, Result } from "@stellar/stellar-sdk/contract"; import type { ContractName } from "./errors"; @@ -19,14 +20,12 @@ export type TxValue = Tx extends AssembledTransaction ? T : never; * Unwrapped value: Result -> V, otherwise T itself. * Wrapped in a tuple to prevent distributive conditional typing over unions. */ -export type Unwrapped = [T] extends [Result] +export type Unwrapped = [T] extends [Result] ? V - : [T] extends [Result] - ? V - : T; + : T; /** For a client method: (args, opts?) => Promise> -> Unwrapped */ -export type MethodValue = M extends (...a: any[]) => Promise ? Unwrapped> : never; +export type MethodValue = M extends (...a: never[]) => Promise ? Unwrapped> : never; export type MarketCoreClient = ReturnType; export type RegistryClient = ReturnType; @@ -68,7 +67,7 @@ export function read>( export async function submit>( tx: Tx, contract: ContractName = "marketCore", -): Promise<{ value: Unwrapped>; txHash: string | null }> { +): Promise<{ value: Unwrapped>; txHash: string | null; ledger: number | null }> { // Surface simulation failures before asking the wallet to sign. read(tx, contract); try { @@ -84,6 +83,7 @@ export async function submit>( return { value: unwrapped as Unwrapped>, txHash: sent.sendTransactionResponse?.hash ?? null, + ledger: sent.getTransactionResponse && "ledger" in sent.getTransactionResponse ? sent.getTransactionResponse.ledger : null, }; } catch (err) { if (err instanceof UserFacingError) throw err; @@ -96,3 +96,10 @@ export async function submit>( * the binding method signature without requiring explicit type arguments. */ export const simulated = read; + +export function simulatedWithLedger>(tx: Tx, contract: ContractName = "marketCore") { + const value = read(tx, contract); + const ledger = tx.simulation?.latestLedger; + if (!Number.isSafeInteger(ledger) || !ledger || ledger < 0) throw new Error("RPC returned a read without its ledger"); + return { value, ledger }; +} diff --git a/package-lock.json b/package-lock.json index dd3aacf..ff3b181 100644 --- a/package-lock.json +++ b/package-lock.json @@ -36,7 +36,9 @@ "autoprefixer": "^10.5.0", "eslint": "^9", "eslint-config-next": "16.2.6", + "fake-indexeddb": "6.2.4", "postcss": "^8.5.14", + "socket.io": "4.8.3", "tailwindcss": "^3.4.19", "typescript": "^5", "vite": "^8.3.4", @@ -1100,6 +1102,20 @@ "tslib": "^2.4.0" } }, + "node_modules/@emurgo/cardano-serialization-lib-browser": { + "version": "13.2.1", + "resolved": "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-browser/-/cardano-serialization-lib-browser-13.2.1.tgz", + "integrity": "sha512-7RfX1gI16Vj2DgCp/ZoXqyLAakWo6+X95ku/rYGbVzuS/1etrlSiJmdbmdm+eYmszMlGQjrtOJQeVLXoj4L/Ag==", + "license": "MIT", + "peer": true + }, + "node_modules/@emurgo/cardano-serialization-lib-nodejs": { + "version": "13.2.0", + "resolved": "https://registry.npmjs.org/@emurgo/cardano-serialization-lib-nodejs/-/cardano-serialization-lib-nodejs-13.2.0.tgz", + "integrity": "sha512-Bz1zLGEqBQ0BVkqt1OgMxdBOE3BdUWUd7Ly9Ecr/aUwkA8AV1w1XzBMe4xblmJHnB1XXNlPH4SraXCvO+q0Mig==", + "license": "MIT", + "peer": true + }, "node_modules/@eslint-community/eslint-utils": { "version": "4.9.1", "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.9.1.tgz", @@ -1244,6 +1260,131 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, + "node_modules/@ethereumjs/common": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/@ethereumjs/common/-/common-10.1.3.tgz", + "integrity": "sha512-ECWwbc4s8Ji7U0mkfZP6gIDy1FxXe0ZyVOTt0adkztPQRzX2DbH2ul7a83DSS8qYAiG6//vVXMkP60TyYOnv8Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "@ethereumjs/util": "^10.1.3", + "eventemitter3": "^5.0.1" + } + }, + "node_modules/@ethereumjs/rlp": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/@ethereumjs/rlp/-/rlp-10.1.3.tgz", + "integrity": "sha512-d795YWkN6O8EJh/PgJ2zZKCdBMl8OlqkuVQESoidH666gaFtUuJ01jDtiuqfyOqTNcTdGhHi8LkQv1WEZzvZZw==", + "license": "MPL-2.0", + "peer": true, + "bin": { + "rlp": "bin/rlp.cjs" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@ethereumjs/tx": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/@ethereumjs/tx/-/tx-10.1.3.tgz", + "integrity": "sha512-5uk9qB2MjyF4axL6GGPvYODe3uR/h6k36boDbvjH81WYFphw/JAIrI1thkWQX4BCyF8bi6suxORhi+vDc3lPlg==", + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "@ethereumjs/common": "^10.1.3", + "@ethereumjs/rlp": "^10.1.3", + "@ethereumjs/util": "^10.1.3", + "@noble/curves": "^2.0.1", + "@noble/hashes": "^2.0.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@ethereumjs/tx/node_modules/@noble/curves": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.4.0.tgz", + "integrity": "sha512-P4/62zrgfH33CneE3Dn4WhJVA22YUU0eR51wKIan4NVRvwsA0YnPTwWGpNbpuacSujmSFLvyzpyuR30+fbq2Ew==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "2.4.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@ethereumjs/tx/node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@ethereumjs/util": { + "version": "10.1.3", + "resolved": "https://registry.npmjs.org/@ethereumjs/util/-/util-10.1.3.tgz", + "integrity": "sha512-PhezOVe4DFaC+QLIsnK/tiOaXjpjS2F9AH4Ez0EfWevFw9NWNSg95l7I0dWuHF0B7v9odm2Ijqwo0EnARXtUmA==", + "license": "MPL-2.0", + "peer": true, + "dependencies": { + "@ethereumjs/rlp": "^10.1.3", + "@noble/curves": "^2.0.1", + "@noble/hashes": "^2.0.1" + }, + "engines": { + "node": ">=20" + } + }, + "node_modules/@ethereumjs/util/node_modules/@noble/curves": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.4.0.tgz", + "integrity": "sha512-P4/62zrgfH33CneE3Dn4WhJVA22YUU0eR51wKIan4NVRvwsA0YnPTwWGpNbpuacSujmSFLvyzpyuR30+fbq2Ew==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "2.4.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@ethereumjs/util/node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@fivebinaries/coin-selection": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/@fivebinaries/coin-selection/-/coin-selection-3.0.0.tgz", + "integrity": "sha512-h25Pn1ZA7oqQBQDodGAgIsQt66T2wDge9onBKNqE66WNWL0KJiKJbpij8YOLo5AAlEIg5IS7EB1QjBgDOIg6DQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@emurgo/cardano-serialization-lib-browser": "^13.2.0", + "@emurgo/cardano-serialization-lib-nodejs": "13.2.0" + } + }, "node_modules/@floating-ui/core": { "version": "1.7.5", "resolved": "https://registry.npmjs.org/@floating-ui/core/-/core-1.7.5.tgz", @@ -2071,6 +2212,16 @@ "integrity": "sha512-jwlVyzMFF296iaNgMWn1lu+EU6BeUD4mgPurEsy8EygYNCrjA8igLpsDlXhfvXhst9tX5w4wRuTDX+FZtpfCug==", "license": "GPL-3.0" }, + "node_modules/@mobily/ts-belt": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/@mobily/ts-belt/-/ts-belt-3.13.1.tgz", + "integrity": "sha512-K5KqIhPI/EoCTbA6CGbrenM9s41OouyK8A03fGJJcla/zKucsgLbz8HNbeseoLarRPgyWJsUyCYqFhI7t3Ra9Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 10.*" + } + }, "node_modules/@modelcontextprotocol/sdk": { "version": "1.29.0", "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", @@ -2178,6 +2329,47 @@ "@tybys/wasm-util": "^0.10.0" } }, + "node_modules/@near-js/accounts": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/@near-js/accounts/-/accounts-1.4.1.tgz", + "integrity": "sha512-ni3QT9H3NdrbVVKyx56yvz93r89Dvpc/vgVtiIK2OdXjkK6jcj+UKMDRQ6F7rd9qJOInLkHZbVBtcR6j1CXLjw==", + "license": "ISC", + "peer": true, + "dependencies": { + "@near-js/crypto": "1.4.2", + "@near-js/providers": "1.0.3", + "@near-js/signers": "0.2.2", + "@near-js/transactions": "1.3.3", + "@near-js/types": "0.3.1", + "@near-js/utils": "1.1.0", + "@noble/hashes": "1.7.1", + "borsh": "1.0.0", + "depd": "2.0.0", + "is-my-json-valid": "^2.20.6", + "lru_map": "0.4.1", + "near-abi": "0.2.0" + } + }, + "node_modules/@near-js/accounts/node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@near-js/accounts/node_modules/borsh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-1.0.0.tgz", + "integrity": "sha512-fSVWzzemnyfF89EPwlUNsrS5swF5CrtiN4e+h0/lLf4dz2he4L3ndM20PS9wj7ICSkXJe/TQUHdaPTq15b1mNQ==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/@near-js/crypto": { "version": "1.4.2", "resolved": "https://registry.npmjs.org/@near-js/crypto/-/crypto-1.4.2.tgz", @@ -2225,6 +2417,145 @@ "integrity": "sha512-fSVWzzemnyfF89EPwlUNsrS5swF5CrtiN4e+h0/lLf4dz2he4L3ndM20PS9wj7ICSkXJe/TQUHdaPTq15b1mNQ==", "license": "Apache-2.0" }, + "node_modules/@near-js/keystores": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@near-js/keystores/-/keystores-0.2.2.tgz", + "integrity": "sha512-DLhi/3a4qJUY+wgphw2Jl4S+L0AKsUYm1mtU0WxKYV5OBwjOXvbGrXNfdkheYkfh3nHwrQgtjvtszX6LrRXLLw==", + "license": "ISC", + "peer": true, + "dependencies": { + "@near-js/crypto": "1.4.2", + "@near-js/types": "0.3.1" + } + }, + "node_modules/@near-js/keystores-browser": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@near-js/keystores-browser/-/keystores-browser-0.2.2.tgz", + "integrity": "sha512-Pxqm7WGtUu6zj32vGCy9JcEDpZDSB5CCaLQDTQdF3GQyL0flyRv2I/guLAgU5FLoYxU7dJAX9mslJhPW7P2Bfw==", + "license": "ISC", + "peer": true, + "dependencies": { + "@near-js/crypto": "1.4.2", + "@near-js/keystores": "0.2.2" + } + }, + "node_modules/@near-js/keystores-node": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/@near-js/keystores-node/-/keystores-node-0.1.2.tgz", + "integrity": "sha512-MWLvTszZOVziiasqIT/LYNhUyWqOJjDGlsthOsY6dTL4ZcXjjmhmzrbFydIIeQr+CcEl5wukTo68ORI9JrHl6g==", + "license": "ISC", + "peer": true, + "dependencies": { + "@near-js/crypto": "1.4.2", + "@near-js/keystores": "0.2.2" + } + }, + "node_modules/@near-js/providers": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@near-js/providers/-/providers-1.0.3.tgz", + "integrity": "sha512-VJMboL14R/+MGKnlhhE3UPXCGYvMd1PpvF9OqZ9yBbulV7QVSIdTMfY4U1NnDfmUC2S3/rhAEr+3rMrIcNS7Fg==", + "license": "ISC", + "peer": true, + "dependencies": { + "@near-js/transactions": "1.3.3", + "@near-js/types": "0.3.1", + "@near-js/utils": "1.1.0", + "borsh": "1.0.0", + "exponential-backoff": "^3.1.2" + }, + "optionalDependencies": { + "node-fetch": "2.6.7" + } + }, + "node_modules/@near-js/providers/node_modules/borsh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-1.0.0.tgz", + "integrity": "sha512-fSVWzzemnyfF89EPwlUNsrS5swF5CrtiN4e+h0/lLf4dz2he4L3ndM20PS9wj7ICSkXJe/TQUHdaPTq15b1mNQ==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@near-js/providers/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "license": "MIT", + "optional": true, + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/@near-js/signers": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/@near-js/signers/-/signers-0.2.2.tgz", + "integrity": "sha512-M6ib+af9zXAPRCjH2RyIS0+RhCmd9gxzCeIkQ+I2A3zjgGiEDkBZbYso9aKj8Zh2lPKKSH7h+u8JGymMOSwgyw==", + "license": "ISC", + "peer": true, + "dependencies": { + "@near-js/crypto": "1.4.2", + "@near-js/keystores": "0.2.2", + "@noble/hashes": "1.3.3" + } + }, + "node_modules/@near-js/signers/node_modules/@noble/hashes": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.3.3.tgz", + "integrity": "sha512-V7/fPHgl+jsVPXqqeOzT8egNj2iBIVt+ECeMMG8TdcnTikP3oaBtUVqpT/gYCR68aEBJSF+XbYUxStjbFMqIIA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@near-js/transactions": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@near-js/transactions/-/transactions-1.3.3.tgz", + "integrity": "sha512-1AXD+HuxlxYQmRTLQlkVmH+RAmV3HwkAT8dyZDu+I2fK/Ec9BQHXakOJUnOBws3ihF+akQhamIBS5T0EXX/Ylw==", + "license": "ISC", + "peer": true, + "dependencies": { + "@near-js/crypto": "1.4.2", + "@near-js/signers": "0.2.2", + "@near-js/types": "0.3.1", + "@near-js/utils": "1.1.0", + "@noble/hashes": "1.7.1", + "borsh": "1.0.0" + } + }, + "node_modules/@near-js/transactions/node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@near-js/transactions/node_modules/borsh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-1.0.0.tgz", + "integrity": "sha512-fSVWzzemnyfF89EPwlUNsrS5swF5CrtiN4e+h0/lLf4dz2he4L3ndM20PS9wj7ICSkXJe/TQUHdaPTq15b1mNQ==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/@near-js/types": { "version": "0.3.1", "resolved": "https://registry.npmjs.org/@near-js/types/-/types-0.3.1.tgz", @@ -2243,6 +2574,31 @@ "mustache": "4.0.0" } }, + "node_modules/@near-js/wallet-account": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@near-js/wallet-account/-/wallet-account-1.3.3.tgz", + "integrity": "sha512-GDzg/Kz0GBYF7tQfyQQQZ3vviwV8yD+8F2lYDzsWJiqIln7R1ov0zaXN4Tii86TeS21KPn2hHAsVu3Y4txa8OQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "@near-js/accounts": "1.4.1", + "@near-js/crypto": "1.4.2", + "@near-js/keystores": "0.2.2", + "@near-js/providers": "1.0.3", + "@near-js/signers": "0.2.2", + "@near-js/transactions": "1.3.3", + "@near-js/types": "0.3.1", + "@near-js/utils": "1.1.0", + "borsh": "1.0.0" + } + }, + "node_modules/@near-js/wallet-account/node_modules/borsh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-1.0.0.tgz", + "integrity": "sha512-fSVWzzemnyfF89EPwlUNsrS5swF5CrtiN4e+h0/lLf4dz2he4L3ndM20PS9wj7ICSkXJe/TQUHdaPTq15b1mNQ==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/@near-wallet-selector/core": { "version": "8.10.2", "resolved": "https://registry.npmjs.org/@near-wallet-selector/core/-/core-8.10.2.tgz", @@ -2582,6 +2938,79 @@ "url": "https://opencollective.com/preact" } }, + "node_modules/@protobufjs/aspromise": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/aspromise/-/aspromise-1.1.2.tgz", + "integrity": "sha512-j+gKExEuLmKwvz3OgROXtrJ2UG2x8Ch2YZUxahh+s1F2HZ+wAceUNLkvy6zKCPVRkU++ZWQrdxsUeQXmcg4uoQ==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/base64": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/base64/-/base64-1.1.2.tgz", + "integrity": "sha512-AZkcAA5vnN/v4PDqKyMR5lx7hZttPDgClv83E//FMNhR2TMcLUhfRUBHCmSl0oi9zMgDDqRUJkSxO3wm85+XLg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/codegen": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@protobufjs/codegen/-/codegen-2.0.5.tgz", + "integrity": "sha512-zgXFLzW3Ap33e6d0Wlj4MGIm6Ce8O89n/apUaGNB/jx+hw+ruWEp7EwGUshdLKVRCxZW12fp9r40E1mQrf/34g==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/eventemitter": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/eventemitter/-/eventemitter-1.1.1.tgz", + "integrity": "sha512-vW1GmwMZNnL+gMRaovlh9yZX74kc+TTU3FObkkurpMaRtBfLP3ldjS9KQWlwZgraRE0+dheEEoAxdzcJQ8eXZg==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/fetch": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@protobufjs/fetch/-/fetch-1.1.1.tgz", + "integrity": "sha512-GpptLrs57adMSuHi3VNj0mAF8dwh36LMaYF6XyJ6JMWlVsc+t42tm1HSEDmOs3A8fC9yyeisgLhsTVQokOZ0zw==", + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.1" + } + }, + "node_modules/@protobufjs/float": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@protobufjs/float/-/float-1.0.2.tgz", + "integrity": "sha512-Ddb+kVXlXst9d+R9PfTIxh1EdNkgoRe5tOX6t01f1lYWOvJnSPDBlG241QLzcyPdoNTsblLUdujGSE4RzrTZGQ==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/inquire": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/inquire/-/inquire-1.1.2.tgz", + "integrity": "sha512-pa0vFRuws4wkvaXKK1uXZMAwAX4/t8ANaJo45iw/oQHNQ9q5xUzwgFmVJGXiga2BeN+zpX7Vf9vmsiIa2J+MUw==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/path": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/path/-/path-1.1.2.tgz", + "integrity": "sha512-6JOcJ5Tm08dOHAbdR3GrvP+yUUfkjG5ePsHYczMFLq3ZmMkAD98cDgcT2iA1lJ9NVwFd4tH/iSSoe44YWkltEA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/pool": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@protobufjs/pool/-/pool-1.1.0.tgz", + "integrity": "sha512-0kELaGSIDBKvcgS4zkjz1PeddatrjYcmMWOlAuAPwAeccUrPHdUqo/J6LiymHHEiJT5NrF1UVwxY14f+fy4WQw==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/@protobufjs/utf8": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@protobufjs/utf8/-/utf8-1.1.2.tgz", + "integrity": "sha512-b1UQwcEZ4yCnMCD8DAL1VlbvBJE9/IX4FTIp7BG1xYpf29SLazLSrqUkj4w7Y5y7cCVP6E5tcqqcI0xemPkHug==", + "license": "BSD-3-Clause", + "peer": true + }, "node_modules/@radix-ui/number": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/@radix-ui/number/-/number-1.1.1.tgz", @@ -4620,6 +5049,13 @@ "integrity": "sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==", "license": "MIT" }, + "node_modules/@sinclair/typebox": { + "version": "0.33.24", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.33.24.tgz", + "integrity": "sha512-91up4t0BLjfkCZ4Ap/BcgaPrMaX6n0I2YRu+kc03xGhLy9G3N05nbTpnK8q7GXdv0PxwUoxi67A8o3Wr5U7j3w==", + "license": "MIT", + "peer": true + }, "node_modules/@sindresorhus/merge-streams": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/@sindresorhus/merge-streams/-/merge-streams-4.0.0.tgz", @@ -5824,6 +6260,42 @@ "pnpm": ">=9.0.0" } }, + "node_modules/@stellar/stellar-base": { + "version": "14.1.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-base/-/stellar-base-14.1.0.tgz", + "integrity": "sha512-A8kFli6QGy22SRF45IjgPAJfUNGjnI+R7g4DF5NZYVsD1kGf7B4ITyc4OPclLV9tqNI4/lXxafGEw0JEUbHixw==", + "deprecated": "This package is now rolled into @stellar/stellar-sdk. Please use @stellar/stellar-sdk to continue receiving updates and support.", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@noble/curves": "^1.9.6", + "@stellar/js-xdr": "^3.1.2", + "base32.js": "^0.1.0", + "bignumber.js": "^9.3.1", + "buffer": "^6.0.3", + "sha.js": "^2.4.12" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@stellar/stellar-base/node_modules/@stellar/js-xdr": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@stellar/js-xdr/-/js-xdr-3.1.2.tgz", + "integrity": "sha512-VVolPL5goVEIsvuGqDc5uiKxV03lzfWdvYg1KikvwheDmTBO68CKDji3bAZ/kppZrx5iTA8z3Ld5yuytcvhvOQ==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/@stellar/stellar-base/node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, "node_modules/@stellar/stellar-sdk": { "version": "16.3.1", "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-16.3.1.tgz", @@ -5909,179 +6381,2229 @@ "react": "^18 || ^19" } }, - "node_modules/@trezor/connect-plugin-stellar": { - "version": "10.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@trezor/connect-plugin-stellar/-/connect-plugin-stellar-10.0.0-alpha.1.tgz", - "integrity": "sha512-v4lMnnBPxLoV/Yf8F0WmtkZGNsqurpxK62kqvaDeVoAtn5Ud+l6POQkLeQRiGsJYVm0vSDc4LHb5vLMrZTH/KA==", + "node_modules/@trezor/analytics": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@trezor/analytics/-/analytics-1.5.0.tgz", + "integrity": "sha512-evILW5XJEmfPlf0TY1duOLtGJ47pdGeSKVE3P75ODEUsRNxtPVqlkOUBPmYpCxPnzS8XDmkatT8lf9/DF0G6nA==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@trezor/env-utils": "1.5.0", + "@trezor/utils": "9.5.0" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/blockchain-link": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/@trezor/blockchain-link/-/blockchain-link-2.6.2.tgz", + "integrity": "sha512-HMz+Dm6nMflqRkaebMqpv3uNnVkRrb6lD2HKTHNBGhjVbqf0wRzJ8JoQ08wn/sF00ljJZz8pGnpcMYHJNxE+wQ==", "license": "SEE LICENSE IN LICENSE.md", + "peer": true, + "dependencies": { + "@solana-program/compute-budget": "^0.8.0", + "@solana-program/stake": "^0.2.1", + "@solana-program/token": "^0.5.1", + "@solana-program/token-2022": "^0.4.2", + "@solana/kit": "^2.3.0", + "@solana/rpc-types": "^2.3.0", + "@stellar/stellar-sdk": "14.2.0", + "@trezor/blockchain-link-types": "1.5.1", + "@trezor/blockchain-link-utils": "1.5.2", + "@trezor/env-utils": "1.5.0", + "@trezor/utils": "9.5.0", + "@trezor/utxo-lib": "2.5.0", + "@trezor/websocket-client": "1.3.0", + "@types/web": "^0.0.197", + "crypto-browserify": "3.12.0", + "socks-proxy-agent": "8.0.5", + "stream-browserify": "^3.0.0", + "xrpl": "4.4.3" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/blockchain-link-types": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/@trezor/blockchain-link-types/-/blockchain-link-types-1.5.1.tgz", + "integrity": "sha512-Idavz6LwLBW8sXc69fh5AJEnl666EDl2Nt3io7updvBgOR0/P12I900DgjNhCKtiWuv66A33/5RE7zLcj3lfnw==", + "license": "See LICENSE.md in repo root", + "peer": true, "dependencies": { - "@trezor/utils": "10.0.0-alpha.1" + "@trezor/utils": "9.5.0", + "@trezor/utxo-lib": "2.5.0" }, "peerDependencies": { - "@stellar/stellar-sdk": "^13.3.0", - "@trezor/connect": "9.x.x", "tslib": "^2.6.2" } }, - "node_modules/@trezor/connect-plugin-stellar/node_modules/@trezor/utils": { - "version": "10.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@trezor/utils/-/utils-10.0.0-alpha.1.tgz", - "integrity": "sha512-A+txzREoeX2MO1AqocDBsMNe+R21jTx5kungXGuzeTRRevARLsYh8MPes0170uMjq4qNSgopBnYZK18WyksOmQ==", - "license": "MIT", + "node_modules/@trezor/blockchain-link-utils": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@trezor/blockchain-link-utils/-/blockchain-link-utils-1.5.2.tgz", + "integrity": "sha512-OSS5OEE98FMnYfjoEALPjBt7ebjC/FKnq3HOolHdEWXBpVlXZNN2+Vo1R9J6WbZUU087sHuUTJJy/GJYWY13Tg==", + "license": "See LICENSE.md in repo root", + "peer": true, "dependencies": { - "bignumber.js": "^9.3.1" + "@mobily/ts-belt": "^3.13.1", + "@stellar/stellar-sdk": "14.2.0", + "@trezor/env-utils": "1.5.0", + "@trezor/protobuf": "1.5.2", + "@trezor/utils": "9.5.0", + "xrpl": "4.4.3" }, "peerDependencies": { "tslib": "^2.6.2" } }, - "node_modules/@trezor/connect-plugin-stellar/node_modules/bignumber.js": { + "node_modules/@trezor/blockchain-link-utils/node_modules/@stellar/stellar-sdk": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-14.2.0.tgz", + "integrity": "sha512-7nh2ogzLRMhfkIC0fGjn1LHUzk3jqVw8tjAuTt5ADWfL9CSGBL18ILucE9igz2L/RU2AZgeAvhujAnW91Ut/oQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@stellar/stellar-base": "^14.0.1", + "axios": "^1.12.2", + "bignumber.js": "^9.3.1", + "eventsource": "^2.0.2", + "feaxios": "^0.0.23", + "randombytes": "^2.1.0", + "toml": "^3.0.0", + "urijs": "^1.19.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@trezor/blockchain-link-utils/node_modules/@trezor/protobuf": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@trezor/protobuf/-/protobuf-1.5.2.tgz", + "integrity": "sha512-zViaL1jKue8DUTVEDg0C/lMipqNMd/Z3kr29/+MeZOoupjaXIQ2Lqp3WAMe8hvNTKKX8aNQH9JrbapJ6w9FMXw==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@trezor/schema-utils": "1.4.0", + "long": "5.2.5", + "protobufjs": "7.4.0" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/blockchain-link-utils/node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "license": "MIT", + "peer": true, "engines": { "node": "*" } }, - "node_modules/@trezor/connect-web": { - "version": "10.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@trezor/connect-web/-/connect-web-10.0.0-alpha.1.tgz", - "integrity": "sha512-CW1ZXzXNVYrJ2WuYneLK2LznkRAfKxRPB3vJyv1U2gia5Q7hsblImITsutLOztO0C2xO1sfETTCyVzfRkRVJEA==", + "node_modules/@trezor/blockchain-link-utils/node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@trezor/blockchain-link-utils/node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana-program/compute-budget": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@solana-program/compute-budget/-/compute-budget-0.8.0.tgz", + "integrity": "sha512-qPKxdxaEsFxebZ4K5RPuy7VQIm/tfJLa1+Nlt3KNA8EYQkz9Xm8htdoEaXVrer9kpgzzp9R3I3Bh6omwCM06tQ==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@solana/kit": "^2.1.0" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana-program/stake": { + "version": "0.2.1", + "resolved": "https://registry.npmjs.org/@solana-program/stake/-/stake-0.2.1.tgz", + "integrity": "sha512-ssNPsJv9XHaA+L7ihzmWGYcm/+XYURQ8UA3wQMKf6ccEHyHOUgoglkkDU/BoA0+wul6HxZUN0tHFymC0qFw6sg==", "license": "MIT", + "peer": true, + "peerDependencies": { + "@solana/kit": "^2.1.0" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana-program/token": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@solana-program/token/-/token-0.5.1.tgz", + "integrity": "sha512-bJvynW5q9SFuVOZ5vqGVkmaPGA0MCC+m9jgJj1nk5m20I389/ms69ASnhWGoOPNcie7S9OwBX0gTj2fiyWpfag==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@solana/kit": "^2.1.0" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana-program/token-2022": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@solana-program/token-2022/-/token-2022-0.4.2.tgz", + "integrity": "sha512-zIpR5t4s9qEU3hZKupzIBxJ6nUV5/UVyIT400tu9vT1HMs5JHxaTTsb5GUhYjiiTvNwU0MQavbwc4Dl29L0Xvw==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@solana/kit": "^2.1.0", + "@solana/sysvars": "^2.1.0" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/accounts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/accounts/-/accounts-2.3.0.tgz", + "integrity": "sha512-QgQTj404Z6PXNOyzaOpSzjgMOuGwG8vC66jSDB+3zHaRcEPRVRd2sVSrd1U6sHtnV3aiaS6YyDuPQMheg4K2jw==", + "license": "MIT", + "peer": true, "dependencies": { - "@trezor/connect-common": "10.0.0-alpha.1", - "@trezor/utils": "10.0.0-alpha.1", - "@trezor/websocket-client": "10.0.0-alpha.1" + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/rpc-spec": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" }, "peerDependencies": { - "tslib": "^2.6.2" + "typescript": ">=5.3.3" } }, - "node_modules/@trezor/connect-web/node_modules/@sinclair/typebox": { - "version": "0.34.52", - "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", - "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", - "license": "MIT" + "node_modules/@trezor/blockchain-link/node_modules/@solana/addresses": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/addresses/-/addresses-2.3.0.tgz", + "integrity": "sha512-ypTNkY2ZaRFpHLnHAgaW8a83N0/WoqdFvCqf4CQmnMdFsZSdC7qOwcbd7YzdaQn9dy+P2hybewzB+KP7LutxGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/assertions": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/nominal-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } }, - "node_modules/@trezor/connect-web/node_modules/@trezor/connect-common": { - "version": "10.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@trezor/connect-common/-/connect-common-10.0.0-alpha.1.tgz", - "integrity": "sha512-tuvM72XHACogT/fkp9D8wyzTE8h4X9emYUhvFs2pdTyIfTFs5RHDbcHQWFsvJgJoTK0H44XSSJb8pORijRfn9A==", + "node_modules/@trezor/blockchain-link/node_modules/@solana/assertions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/assertions/-/assertions-2.3.0.tgz", + "integrity": "sha512-Ekoet3khNg3XFLN7MIz8W31wPQISpKUGDGTylLptI+JjCDWx3PIa88xjEMqFo02WJ8sBj2NLV64Xg1sBcsHjZQ==", "license": "MIT", + "peer": true, "dependencies": { - "@trezor/device-utils": "10.0.0-alpha.1", - "@trezor/protobuf": "10.0.0-alpha.1", - "@trezor/protocol": "^10.0.0-alpha.1", - "@trezor/schema-utils": "10.0.0-alpha.1", - "@trezor/type-utils": "10.0.0-alpha.1", - "@trezor/utils": "10.0.0-alpha.1" + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" }, "peerDependencies": { - "tslib": "^2.6.2" + "typescript": ">=5.3.3" } }, - "node_modules/@trezor/connect-web/node_modules/@trezor/device-utils": { - "version": "10.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@trezor/device-utils/-/device-utils-10.0.0-alpha.1.tgz", - "integrity": "sha512-93LhEqH8uKCZHLtF+62uxd3/PXCfqpK4Z98ztfGu5vrtmoONb9dV8Falha0b3KXPDrQ10TUKasFGKLg570Z/Ew==", - "license": "MIT" + "node_modules/@trezor/blockchain-link/node_modules/@solana/codecs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-2.3.0.tgz", + "integrity": "sha512-JVqGPkzoeyU262hJGdH64kNLH0M+Oew2CIPOa/9tR3++q2pEd4jU2Rxdfye9sd0Ce3XJrR5AIa8ZfbyQXzjh+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/codecs-data-structures": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/options": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } }, - "node_modules/@trezor/connect-web/node_modules/@trezor/protobuf": { - "version": "10.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@trezor/protobuf/-/protobuf-10.0.0-alpha.1.tgz", - "integrity": "sha512-aCUyqAr7Sho9TequaNAjnf27mgae6qzesNrOgB19bUrDrhaDDd/2eyr2SrrY1G4hPYkTyBC0jymvQnozn9f/kA==", + "node_modules/@trezor/blockchain-link/node_modules/@solana/codecs-core": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", + "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", "license": "MIT", + "peer": true, "dependencies": { - "@bufbuild/protobuf": "^2.11.0", - "@trezor/schema-utils": "10.0.0-alpha.1" + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/codecs-data-structures": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-2.3.0.tgz", + "integrity": "sha512-qvU5LE5DqEdYMYgELRHv+HMOx73sSoV1ZZkwIrclwUmwTbTaH8QAJURBj0RhQ/zCne7VuLLOZFFGv6jGigWhSw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/codecs-numbers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", + "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/codecs-strings": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-2.3.0.tgz", + "integrity": "sha512-y5pSBYwzVziXu521hh+VxqUtp0hYGTl1eWGoc1W+8mdvBdC1kTqm/X7aYQw33J42hw03JjryvYOvmGgk3Qz/Ug==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "fastestsmallesttextencoderdecoder": "^1.0.22", + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/errors": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", + "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^5.4.1", + "commander": "^14.0.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/fast-stable-stringify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/fast-stable-stringify/-/fast-stable-stringify-2.3.0.tgz", + "integrity": "sha512-KfJPrMEieUg6D3hfQACoPy0ukrAV8Kio883llt/8chPEG3FVTX9z/Zuf4O01a15xZmBbmQ7toil2Dp0sxMJSxw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/functional": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/functional/-/functional-2.3.0.tgz", + "integrity": "sha512-AgsPh3W3tE+nK3eEw/W9qiSfTGwLYEvl0rWaxHht/lRcuDVwfKRzeSa5G79eioWFFqr+pTtoCr3D3OLkwKz02Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/instructions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/instructions/-/instructions-2.3.0.tgz", + "integrity": "sha512-PLMsmaIKu7hEAzyElrk2T7JJx4D+9eRwebhFZpy2PXziNSmFF929eRHKUsKqBFM3cYR1Yy3m6roBZfA+bGE/oQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/keys": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/keys/-/keys-2.3.0.tgz", + "integrity": "sha512-ZVVdga79pNH+2pVcm6fr2sWz9HTwfopDVhYb0Lh3dh+WBmJjwkabXEIHey2rUES7NjFa/G7sV8lrUn/v8LDCCQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/assertions": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/nominal-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/kit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-2.3.0.tgz", + "integrity": "sha512-sb6PgwoW2LjE5oTFu4lhlS/cGt/NB3YrShEyx7JgWFWysfgLdJnhwWThgwy/4HjNsmtMrQGWVls0yVBHcMvlMQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/accounts": "2.3.0", + "@solana/addresses": "2.3.0", + "@solana/codecs": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/instructions": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/programs": "2.3.0", + "@solana/rpc": "2.3.0", + "@solana/rpc-parsed-types": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/rpc-subscriptions": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/signers": "2.3.0", + "@solana/sysvars": "2.3.0", + "@solana/transaction-confirmation": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/nominal-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/nominal-types/-/nominal-types-2.3.0.tgz", + "integrity": "sha512-uKlMnlP4PWW5UTXlhKM8lcgIaNj8dvd8xO4Y9l+FVvh9RvW2TO0GwUO6JCo7JBzCB0PSqRJdWWaQ8pu1Ti/OkA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/options": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/options/-/options-2.3.0.tgz", + "integrity": "sha512-PPnnZBRCWWoZQ11exPxf//DRzN2C6AoFsDI/u2AsQfYih434/7Kp4XLpfOMT/XESi+gdBMFNNfbES5zg3wAIkw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/codecs-data-structures": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/programs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/programs/-/programs-2.3.0.tgz", + "integrity": "sha512-UXKujV71VCI5uPs+cFdwxybtHZAIZyQkqDiDnmK+DawtOO9mBn4Nimdb/6RjR2CXT78mzO9ZCZ3qfyX+ydcB7w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/promises": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/promises/-/promises-2.3.0.tgz", + "integrity": "sha512-GjVgutZKXVuojd9rWy1PuLnfcRfqsaCm7InCiZc8bqmJpoghlyluweNc7ml9Y5yQn1P2IOyzh9+p/77vIyNybQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/rpc": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc/-/rpc-2.3.0.tgz", + "integrity": "sha512-ZWN76iNQAOCpYC7yKfb3UNLIMZf603JckLKOOLTHuy9MZnTN8XV6uwvDFhf42XvhglgUjGCEnbUqWtxQ9pa/pQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/fast-stable-stringify": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/rpc-api": "2.3.0", + "@solana/rpc-spec": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/rpc-transformers": "2.3.0", + "@solana/rpc-transport-http": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/rpc-api": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-api/-/rpc-api-2.3.0.tgz", + "integrity": "sha512-UUdiRfWoyYhJL9PPvFeJr4aJ554ob2jXcpn4vKmRVn9ire0sCbpQKYx6K8eEKHZWXKrDW8IDspgTl0gT/aJWVg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/rpc-parsed-types": "2.3.0", + "@solana/rpc-spec": "2.3.0", + "@solana/rpc-transformers": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/rpc-parsed-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-parsed-types/-/rpc-parsed-types-2.3.0.tgz", + "integrity": "sha512-B5pHzyEIbBJf9KHej+zdr5ZNAdSvu7WLU2lOUPh81KHdHQs6dEb310LGxcpCc7HVE8IEdO20AbckewDiAN6OCg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/rpc-spec": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec/-/rpc-spec-2.3.0.tgz", + "integrity": "sha512-fA2LMX4BMixCrNB2n6T83AvjZ3oUQTu7qyPLyt8gHQaoEAXs8k6GZmu6iYcr+FboQCjUmRPgMaABbcr9j2J9Sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/rpc-spec-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/rpc-spec-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec-types/-/rpc-spec-types-2.3.0.tgz", + "integrity": "sha512-xQsb65lahjr8Wc9dMtP7xa0ZmDS8dOE2ncYjlvfyw/h4mpdXTUdrSMi6RtFwX33/rGuztQ7Hwaid5xLNSLvsFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/rpc-subscriptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions/-/rpc-subscriptions-2.3.0.tgz", + "integrity": "sha512-Uyr10nZKGVzvCOqwCZgwYrzuoDyUdwtgQRefh13pXIrdo4wYjVmoLykH49Omt6abwStB0a4UL5gX9V4mFdDJZg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/fast-stable-stringify": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/promises": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/rpc-subscriptions-api": "2.3.0", + "@solana/rpc-subscriptions-channel-websocket": "2.3.0", + "@solana/rpc-subscriptions-spec": "2.3.0", + "@solana/rpc-transformers": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/subscribable": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/rpc-subscriptions-api": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-api/-/rpc-subscriptions-api-2.3.0.tgz", + "integrity": "sha512-9mCjVbum2Hg9KGX3LKsrI5Xs0KX390lS+Z8qB80bxhar6MJPugqIPH8uRgLhCW9GN3JprAfjRNl7our8CPvsPQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/rpc-subscriptions-spec": "2.3.0", + "@solana/rpc-transformers": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/rpc-subscriptions-channel-websocket": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-channel-websocket/-/rpc-subscriptions-channel-websocket-2.3.0.tgz", + "integrity": "sha512-2oL6ceFwejIgeWzbNiUHI2tZZnaOxNTSerszcin7wYQwijxtpVgUHiuItM/Y70DQmH9sKhmikQp+dqeGalaJxw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/rpc-subscriptions-spec": "2.3.0", + "@solana/subscribable": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3", + "ws": "^8.18.0" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/rpc-subscriptions-spec": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-spec/-/rpc-subscriptions-spec-2.3.0.tgz", + "integrity": "sha512-rdmVcl4PvNKQeA2l8DorIeALCgJEMSu7U8AXJS1PICeb2lQuMeaR+6cs/iowjvIB0lMVjYN2sFf6Q3dJPu6wWg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/promises": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/subscribable": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/rpc-transformers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-transformers/-/rpc-transformers-2.3.0.tgz", + "integrity": "sha512-UuHYK3XEpo9nMXdjyGKkPCOr7WsZsxs7zLYDO1A5ELH3P3JoehvrDegYRAGzBS2VKsfApZ86ZpJToP0K3PhmMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/nominal-types": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/rpc-transport-http": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-transport-http/-/rpc-transport-http-2.3.0.tgz", + "integrity": "sha512-HFKydmxGw8nAF5N+S0NLnPBDCe5oMDtI2RAmW8DMqP4U3Zxt2XWhvV1SNkAldT5tF0U1vP+is6fHxyhk4xqEvg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/rpc-spec": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "undici-types": "^7.11.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/rpc-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-types/-/rpc-types-2.3.0.tgz", + "integrity": "sha512-O09YX2hED2QUyGxrMOxQ9GzH1LlEwwZWu69QbL4oYmIf6P5dzEEHcqRY6L1LsDVqc/dzAdEs/E1FaPrcIaIIPw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/nominal-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/signers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/signers/-/signers-2.3.0.tgz", + "integrity": "sha512-OSv6fGr/MFRx6J+ZChQMRqKNPGGmdjkqarKkRzkwmv7v8quWsIRnJT5EV8tBy3LI4DLO/A8vKiNSPzvm1TdaiQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/instructions": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/nominal-types": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/subscribable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/subscribable/-/subscribable-2.3.0.tgz", + "integrity": "sha512-DkgohEDbMkdTWiKAoatY02Njr56WXx9e/dKKfmne8/Ad6/2llUIrax78nCdlvZW9quXMaXPTxZvdQqo9N669Og==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/sysvars": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-2.3.0.tgz", + "integrity": "sha512-LvjADZrpZ+CnhlHqfI5cmsRzX9Rpyb1Ox2dMHnbsRNzeKAMhu9w4ZBIaeTdO322zsTr509G1B+k2ABD3whvUBA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/accounts": "2.3.0", + "@solana/codecs": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/transaction-confirmation": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/transaction-confirmation/-/transaction-confirmation-2.3.0.tgz", + "integrity": "sha512-UiEuiHCfAAZEKdfne/XljFNJbsKAe701UQHKXEInYzIgBjRbvaeYZlBmkkqtxwcasgBTOmEaEKT44J14N9VZDw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/promises": "2.3.0", + "@solana/rpc": "2.3.0", + "@solana/rpc-subscriptions": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/transaction-messages": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/transaction-messages/-/transaction-messages-2.3.0.tgz", + "integrity": "sha512-bgqvWuy3MqKS5JdNLH649q+ngiyOu5rGS3DizSnWwYUd76RxZl1kN6CoqHSrrMzFMvis6sck/yPGG3wqrMlAww==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-data-structures": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/instructions": "2.3.0", + "@solana/nominal-types": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@solana/transactions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/transactions/-/transactions-2.3.0.tgz", + "integrity": "sha512-LnTvdi8QnrQtuEZor5Msje61sDpPstTVwKg4y81tNxDhiyomjuvnSNLAq6QsB9gIxUqbNzPZgOG9IU4I4/Uaug==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-data-structures": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/instructions": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/nominal-types": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/transaction-messages": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/@stellar/stellar-sdk": { + "version": "14.2.0", + "resolved": "https://registry.npmjs.org/@stellar/stellar-sdk/-/stellar-sdk-14.2.0.tgz", + "integrity": "sha512-7nh2ogzLRMhfkIC0fGjn1LHUzk3jqVw8tjAuTt5ADWfL9CSGBL18ILucE9igz2L/RU2AZgeAvhujAnW91Ut/oQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "@stellar/stellar-base": "^14.0.1", + "axios": "^1.12.2", + "bignumber.js": "^9.3.1", + "eventsource": "^2.0.2", + "feaxios": "^0.0.23", + "randombytes": "^2.1.0", + "toml": "^3.0.0", + "urijs": "^1.19.1" + }, + "engines": { + "node": ">=20.0.0" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/eventsource": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/eventsource/-/eventsource-2.0.2.tgz", + "integrity": "sha512-IzUmBGPR3+oUG9dUeXynyNmf91/3zUSJg1lCktzKw47OXuhco54U3r9B7O4XX+Rb1Itm9OZ2b0RkTs10bICOxA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@trezor/blockchain-link/node_modules/undici-types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.29.0.tgz", + "integrity": "sha512-vamA8dGlzMwhpyYpQp9d8vka3o4D/yn5I7ez7Or+msDA4bZ8Uh+Zy91WvWf3I73gDAkFha9JcYRqm2li0Npfgg==", + "license": "MIT", + "peer": true + }, + "node_modules/@trezor/connect": { + "version": "9.7.3", + "resolved": "https://registry.npmjs.org/@trezor/connect/-/connect-9.7.3.tgz", + "integrity": "sha512-oAOfvJHT8tPqOXTmCOhn8uTZcoqSDuWAiWXQegx7C46tq+NLg+enkYXxUYEHq/9PmfZAOrUT9VMxZDsXM2thkA==", + "license": "SEE LICENSE IN LICENSE.md", + "peer": true, + "dependencies": { + "@ethereumjs/common": "^10.1.0", + "@ethereumjs/tx": "^10.1.0", + "@fivebinaries/coin-selection": "3.0.0", + "@mobily/ts-belt": "^3.13.1", + "@noble/hashes": "^1.6.1", + "@scure/bip39": "^1.5.1", + "@solana-program/compute-budget": "^0.8.0", + "@solana-program/system": "^0.7.0", + "@solana-program/token": "^0.5.1", + "@solana-program/token-2022": "^0.4.2", + "@solana/kit": "^2.3.0", + "@trezor/blockchain-link": "2.6.2", + "@trezor/blockchain-link-types": "1.5.1", + "@trezor/blockchain-link-utils": "1.5.2", + "@trezor/connect-analytics": "1.4.0", + "@trezor/connect-common": "0.5.1", + "@trezor/crypto-utils": "1.2.0", + "@trezor/device-authenticity": "1.1.2", + "@trezor/device-utils": "1.2.0", + "@trezor/env-utils": "^1.5.0", + "@trezor/protobuf": "1.5.3", + "@trezor/protocol": "1.3.1", + "@trezor/schema-utils": "1.4.0", + "@trezor/transport": "1.6.3", + "@trezor/type-utils": "1.2.0", + "@trezor/utils": "9.5.0", + "@trezor/utxo-lib": "2.5.0", + "blakejs": "^1.2.1", + "bs58": "^6.0.0", + "bs58check": "^4.0.0", + "cbor": "^10.0.10", + "cross-fetch": "^4.0.0", + "jws": "^4.0.0" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect-analytics": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@trezor/connect-analytics/-/connect-analytics-1.4.0.tgz", + "integrity": "sha512-hy2J2oeIhRC/e1bOWXo5dsVMVnDwO2UKnxhR6FD8PINR3jgM6PWAXc6k33WJsBcyiTzwMP7/xPysLcgNJH5o4w==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@trezor/analytics": "1.5.0" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect-common": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@trezor/connect-common/-/connect-common-0.5.1.tgz", + "integrity": "sha512-wdpVCwdylBh4SBO5Ys40tB/d59UlfjmxgBHDkkLgaR+JcqkthCfiw5VlUrV9wu65lquejAZhA5KQL4mUUUhCow==", + "license": "SEE LICENSE IN LICENSE.md", + "peer": true, + "dependencies": { + "@trezor/env-utils": "1.5.0", + "@trezor/type-utils": "1.2.0", + "@trezor/utils": "9.5.0" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect-plugin-stellar": { + "version": "10.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@trezor/connect-plugin-stellar/-/connect-plugin-stellar-10.0.0-alpha.1.tgz", + "integrity": "sha512-v4lMnnBPxLoV/Yf8F0WmtkZGNsqurpxK62kqvaDeVoAtn5Ud+l6POQkLeQRiGsJYVm0vSDc4LHb5vLMrZTH/KA==", + "license": "SEE LICENSE IN LICENSE.md", + "dependencies": { + "@trezor/utils": "10.0.0-alpha.1" + }, + "peerDependencies": { + "@stellar/stellar-sdk": "^13.3.0", + "@trezor/connect": "9.x.x", + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect-plugin-stellar/node_modules/@trezor/utils": { + "version": "10.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@trezor/utils/-/utils-10.0.0-alpha.1.tgz", + "integrity": "sha512-A+txzREoeX2MO1AqocDBsMNe+R21jTx5kungXGuzeTRRevARLsYh8MPes0170uMjq4qNSgopBnYZK18WyksOmQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.3.1" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect-plugin-stellar/node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@trezor/connect-web": { + "version": "10.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@trezor/connect-web/-/connect-web-10.0.0-alpha.1.tgz", + "integrity": "sha512-CW1ZXzXNVYrJ2WuYneLK2LznkRAfKxRPB3vJyv1U2gia5Q7hsblImITsutLOztO0C2xO1sfETTCyVzfRkRVJEA==", + "license": "MIT", + "dependencies": { + "@trezor/connect-common": "10.0.0-alpha.1", + "@trezor/utils": "10.0.0-alpha.1", + "@trezor/websocket-client": "10.0.0-alpha.1" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect-web/node_modules/@sinclair/typebox": { + "version": "0.34.52", + "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.34.52.tgz", + "integrity": "sha512-XiMQh7qqVlxZzcVD+kkGMNGMzcTrDMLWI7S4x7z1MkCkbDPrekpZXEUK0eZqZFMuHQg2a2DZOcDIh9o5v3Gonw==", + "license": "MIT" + }, + "node_modules/@trezor/connect-web/node_modules/@trezor/connect-common": { + "version": "10.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@trezor/connect-common/-/connect-common-10.0.0-alpha.1.tgz", + "integrity": "sha512-tuvM72XHACogT/fkp9D8wyzTE8h4X9emYUhvFs2pdTyIfTFs5RHDbcHQWFsvJgJoTK0H44XSSJb8pORijRfn9A==", + "license": "MIT", + "dependencies": { + "@trezor/device-utils": "10.0.0-alpha.1", + "@trezor/protobuf": "10.0.0-alpha.1", + "@trezor/protocol": "^10.0.0-alpha.1", + "@trezor/schema-utils": "10.0.0-alpha.1", + "@trezor/type-utils": "10.0.0-alpha.1", + "@trezor/utils": "10.0.0-alpha.1" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect-web/node_modules/@trezor/device-utils": { + "version": "10.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@trezor/device-utils/-/device-utils-10.0.0-alpha.1.tgz", + "integrity": "sha512-93LhEqH8uKCZHLtF+62uxd3/PXCfqpK4Z98ztfGu5vrtmoONb9dV8Falha0b3KXPDrQ10TUKasFGKLg570Z/Ew==", + "license": "MIT" + }, + "node_modules/@trezor/connect-web/node_modules/@trezor/protobuf": { + "version": "10.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@trezor/protobuf/-/protobuf-10.0.0-alpha.1.tgz", + "integrity": "sha512-aCUyqAr7Sho9TequaNAjnf27mgae6qzesNrOgB19bUrDrhaDDd/2eyr2SrrY1G4hPYkTyBC0jymvQnozn9f/kA==", + "license": "MIT", + "dependencies": { + "@bufbuild/protobuf": "^2.11.0", + "@trezor/schema-utils": "10.0.0-alpha.1" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect-web/node_modules/@trezor/protocol": { + "version": "10.0.1", + "resolved": "https://registry.npmjs.org/@trezor/protocol/-/protocol-10.0.1.tgz", + "integrity": "sha512-uoFyMzfr3/p7uXhTJSxXurfwM6N0vDicTKHlQRrwH9dMcidEEOxuOJZBrhKnoj4osLfDPt5+ctQ+mqFjxJA9Tw==", + "license": "MIT", + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect-web/node_modules/@trezor/schema-utils": { + "version": "10.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@trezor/schema-utils/-/schema-utils-10.0.0-alpha.1.tgz", + "integrity": "sha512-0beYv0b0De3Z60sZzM1T+IrYwXeNL+7RKfLokTvqEFDzsQkwxhYrIX47FsBhGsOmGWx9x4+65dxDbnyd4wZT6g==", + "license": "MIT", + "dependencies": { + "@sinclair/typebox": "^0.34.49", + "@trezor/type-utils": "10.0.0-alpha.1", + "ts-mixer": "^6.0.4" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect-web/node_modules/@trezor/type-utils": { + "version": "10.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@trezor/type-utils/-/type-utils-10.0.0-alpha.1.tgz", + "integrity": "sha512-wK75rrwlyXFgPfdHQFRbeKCoj+orQLXKj7rliMK30z+F1y+rEcfv/MW2VP48Q9mL5pea+ZX1nRnb4hYx1v38Ow==", + "license": "MIT" + }, + "node_modules/@trezor/connect-web/node_modules/@trezor/utils": { + "version": "10.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@trezor/utils/-/utils-10.0.0-alpha.1.tgz", + "integrity": "sha512-A+txzREoeX2MO1AqocDBsMNe+R21jTx5kungXGuzeTRRevARLsYh8MPes0170uMjq4qNSgopBnYZK18WyksOmQ==", + "license": "MIT", + "dependencies": { + "bignumber.js": "^9.3.1" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect-web/node_modules/@trezor/websocket-client": { + "version": "10.0.0-alpha.1", + "resolved": "https://registry.npmjs.org/@trezor/websocket-client/-/websocket-client-10.0.0-alpha.1.tgz", + "integrity": "sha512-129Ot1m+fDodRFBvav2d6v8nLxZLAl/udQP5l5W6jxQZcWUuviphDceT/juF8EvNGdw+L6OIu6fH+yCXARpFWQ==", + "license": "MIT", + "dependencies": { + "@trezor/utils": "10.0.0-alpha.1", + "ws": "^8.20.0" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/connect-web/node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "engines": { + "node": "*" + } + }, + "node_modules/@trezor/connect-web/node_modules/ws": { + "version": "8.22.0", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", + "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@trezor/connect/node_modules/@solana-program/compute-budget": { + "version": "0.8.0", + "resolved": "https://registry.npmjs.org/@solana-program/compute-budget/-/compute-budget-0.8.0.tgz", + "integrity": "sha512-qPKxdxaEsFxebZ4K5RPuy7VQIm/tfJLa1+Nlt3KNA8EYQkz9Xm8htdoEaXVrer9kpgzzp9R3I3Bh6omwCM06tQ==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@solana/kit": "^2.1.0" + } + }, + "node_modules/@trezor/connect/node_modules/@solana-program/system": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/@solana-program/system/-/system-0.7.0.tgz", + "integrity": "sha512-FKTBsKHpvHHNc1ATRm7SlC5nF/VdJtOSjldhcyfMN9R7xo712Mo2jHIzvBgn8zQO5Kg0DcWuKB7268Kv1ocicw==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@solana/kit": "^2.1.0" + } + }, + "node_modules/@trezor/connect/node_modules/@solana-program/token": { + "version": "0.5.1", + "resolved": "https://registry.npmjs.org/@solana-program/token/-/token-0.5.1.tgz", + "integrity": "sha512-bJvynW5q9SFuVOZ5vqGVkmaPGA0MCC+m9jgJj1nk5m20I389/ms69ASnhWGoOPNcie7S9OwBX0gTj2fiyWpfag==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@solana/kit": "^2.1.0" + } + }, + "node_modules/@trezor/connect/node_modules/@solana-program/token-2022": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/@solana-program/token-2022/-/token-2022-0.4.2.tgz", + "integrity": "sha512-zIpR5t4s9qEU3hZKupzIBxJ6nUV5/UVyIT400tu9vT1HMs5JHxaTTsb5GUhYjiiTvNwU0MQavbwc4Dl29L0Xvw==", + "license": "Apache-2.0", + "peer": true, + "peerDependencies": { + "@solana/kit": "^2.1.0", + "@solana/sysvars": "^2.1.0" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/accounts": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/accounts/-/accounts-2.3.0.tgz", + "integrity": "sha512-QgQTj404Z6PXNOyzaOpSzjgMOuGwG8vC66jSDB+3zHaRcEPRVRd2sVSrd1U6sHtnV3aiaS6YyDuPQMheg4K2jw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/rpc-spec": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/addresses": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/addresses/-/addresses-2.3.0.tgz", + "integrity": "sha512-ypTNkY2ZaRFpHLnHAgaW8a83N0/WoqdFvCqf4CQmnMdFsZSdC7qOwcbd7YzdaQn9dy+P2hybewzB+KP7LutxGA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/assertions": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/nominal-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/assertions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/assertions/-/assertions-2.3.0.tgz", + "integrity": "sha512-Ekoet3khNg3XFLN7MIz8W31wPQISpKUGDGTylLptI+JjCDWx3PIa88xjEMqFo02WJ8sBj2NLV64Xg1sBcsHjZQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/codecs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs/-/codecs-2.3.0.tgz", + "integrity": "sha512-JVqGPkzoeyU262hJGdH64kNLH0M+Oew2CIPOa/9tR3++q2pEd4jU2Rxdfye9sd0Ce3XJrR5AIa8ZfbyQXzjh+g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/codecs-data-structures": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/options": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/codecs-core": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-core/-/codecs-core-2.3.0.tgz", + "integrity": "sha512-oG+VZzN6YhBHIoSKgS5ESM9VIGzhWjEHEGNPSibiDTxFhsFWxNaz8LbMDPjBUE69r9wmdGLkrQ+wVPbnJcZPvw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/codecs-data-structures": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-data-structures/-/codecs-data-structures-2.3.0.tgz", + "integrity": "sha512-qvU5LE5DqEdYMYgELRHv+HMOx73sSoV1ZZkwIrclwUmwTbTaH8QAJURBj0RhQ/zCne7VuLLOZFFGv6jGigWhSw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/codecs-numbers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-numbers/-/codecs-numbers-2.3.0.tgz", + "integrity": "sha512-jFvvwKJKffvG7Iz9dmN51OGB7JBcy2CJ6Xf3NqD/VP90xak66m/Lg48T01u5IQ/hc15mChVHiBm+HHuOFDUrQg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/codecs-strings": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/codecs-strings/-/codecs-strings-2.3.0.tgz", + "integrity": "sha512-y5pSBYwzVziXu521hh+VxqUtp0hYGTl1eWGoc1W+8mdvBdC1kTqm/X7aYQw33J42hw03JjryvYOvmGgk3Qz/Ug==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "fastestsmallesttextencoderdecoder": "^1.0.22", + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/errors": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/errors/-/errors-2.3.0.tgz", + "integrity": "sha512-66RI9MAbwYV0UtP7kGcTBVLxJgUxoZGm8Fbc0ah+lGiAw17Gugco6+9GrJCV83VyF2mDWyYnYM9qdI3yjgpnaQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "chalk": "^5.4.1", + "commander": "^14.0.0" + }, + "bin": { + "errors": "bin/cli.mjs" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/fast-stable-stringify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/fast-stable-stringify/-/fast-stable-stringify-2.3.0.tgz", + "integrity": "sha512-KfJPrMEieUg6D3hfQACoPy0ukrAV8Kio883llt/8chPEG3FVTX9z/Zuf4O01a15xZmBbmQ7toil2Dp0sxMJSxw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/functional": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/functional/-/functional-2.3.0.tgz", + "integrity": "sha512-AgsPh3W3tE+nK3eEw/W9qiSfTGwLYEvl0rWaxHht/lRcuDVwfKRzeSa5G79eioWFFqr+pTtoCr3D3OLkwKz02Q==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/instructions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/instructions/-/instructions-2.3.0.tgz", + "integrity": "sha512-PLMsmaIKu7hEAzyElrk2T7JJx4D+9eRwebhFZpy2PXziNSmFF929eRHKUsKqBFM3cYR1Yy3m6roBZfA+bGE/oQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/keys": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/keys/-/keys-2.3.0.tgz", + "integrity": "sha512-ZVVdga79pNH+2pVcm6fr2sWz9HTwfopDVhYb0Lh3dh+WBmJjwkabXEIHey2rUES7NjFa/G7sV8lrUn/v8LDCCQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/assertions": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/nominal-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/kit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/kit/-/kit-2.3.0.tgz", + "integrity": "sha512-sb6PgwoW2LjE5oTFu4lhlS/cGt/NB3YrShEyx7JgWFWysfgLdJnhwWThgwy/4HjNsmtMrQGWVls0yVBHcMvlMQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/accounts": "2.3.0", + "@solana/addresses": "2.3.0", + "@solana/codecs": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/instructions": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/programs": "2.3.0", + "@solana/rpc": "2.3.0", + "@solana/rpc-parsed-types": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/rpc-subscriptions": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/signers": "2.3.0", + "@solana/sysvars": "2.3.0", + "@solana/transaction-confirmation": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/nominal-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/nominal-types/-/nominal-types-2.3.0.tgz", + "integrity": "sha512-uKlMnlP4PWW5UTXlhKM8lcgIaNj8dvd8xO4Y9l+FVvh9RvW2TO0GwUO6JCo7JBzCB0PSqRJdWWaQ8pu1Ti/OkA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/options": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/options/-/options-2.3.0.tgz", + "integrity": "sha512-PPnnZBRCWWoZQ11exPxf//DRzN2C6AoFsDI/u2AsQfYih434/7Kp4XLpfOMT/XESi+gdBMFNNfbES5zg3wAIkw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/codecs-core": "2.3.0", + "@solana/codecs-data-structures": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/programs": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/programs/-/programs-2.3.0.tgz", + "integrity": "sha512-UXKujV71VCI5uPs+cFdwxybtHZAIZyQkqDiDnmK+DawtOO9mBn4Nimdb/6RjR2CXT78mzO9ZCZ3qfyX+ydcB7w==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/promises": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/promises/-/promises-2.3.0.tgz", + "integrity": "sha512-GjVgutZKXVuojd9rWy1PuLnfcRfqsaCm7InCiZc8bqmJpoghlyluweNc7ml9Y5yQn1P2IOyzh9+p/77vIyNybQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/rpc": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc/-/rpc-2.3.0.tgz", + "integrity": "sha512-ZWN76iNQAOCpYC7yKfb3UNLIMZf603JckLKOOLTHuy9MZnTN8XV6uwvDFhf42XvhglgUjGCEnbUqWtxQ9pa/pQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/fast-stable-stringify": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/rpc-api": "2.3.0", + "@solana/rpc-spec": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/rpc-transformers": "2.3.0", + "@solana/rpc-transport-http": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/rpc-api": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-api/-/rpc-api-2.3.0.tgz", + "integrity": "sha512-UUdiRfWoyYhJL9PPvFeJr4aJ554ob2jXcpn4vKmRVn9ire0sCbpQKYx6K8eEKHZWXKrDW8IDspgTl0gT/aJWVg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/rpc-parsed-types": "2.3.0", + "@solana/rpc-spec": "2.3.0", + "@solana/rpc-transformers": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/rpc-parsed-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-parsed-types/-/rpc-parsed-types-2.3.0.tgz", + "integrity": "sha512-B5pHzyEIbBJf9KHej+zdr5ZNAdSvu7WLU2lOUPh81KHdHQs6dEb310LGxcpCc7HVE8IEdO20AbckewDiAN6OCg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/rpc-spec": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec/-/rpc-spec-2.3.0.tgz", + "integrity": "sha512-fA2LMX4BMixCrNB2n6T83AvjZ3oUQTu7qyPLyt8gHQaoEAXs8k6GZmu6iYcr+FboQCjUmRPgMaABbcr9j2J9Sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/rpc-spec-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/rpc-spec-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-spec-types/-/rpc-spec-types-2.3.0.tgz", + "integrity": "sha512-xQsb65lahjr8Wc9dMtP7xa0ZmDS8dOE2ncYjlvfyw/h4mpdXTUdrSMi6RtFwX33/rGuztQ7Hwaid5xLNSLvsFQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/rpc-subscriptions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions/-/rpc-subscriptions-2.3.0.tgz", + "integrity": "sha512-Uyr10nZKGVzvCOqwCZgwYrzuoDyUdwtgQRefh13pXIrdo4wYjVmoLykH49Omt6abwStB0a4UL5gX9V4mFdDJZg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/fast-stable-stringify": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/promises": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/rpc-subscriptions-api": "2.3.0", + "@solana/rpc-subscriptions-channel-websocket": "2.3.0", + "@solana/rpc-subscriptions-spec": "2.3.0", + "@solana/rpc-transformers": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/subscribable": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/rpc-subscriptions-api": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-api/-/rpc-subscriptions-api-2.3.0.tgz", + "integrity": "sha512-9mCjVbum2Hg9KGX3LKsrI5Xs0KX390lS+Z8qB80bxhar6MJPugqIPH8uRgLhCW9GN3JprAfjRNl7our8CPvsPQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/rpc-subscriptions-spec": "2.3.0", + "@solana/rpc-transformers": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/rpc-subscriptions-channel-websocket": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-channel-websocket/-/rpc-subscriptions-channel-websocket-2.3.0.tgz", + "integrity": "sha512-2oL6ceFwejIgeWzbNiUHI2tZZnaOxNTSerszcin7wYQwijxtpVgUHiuItM/Y70DQmH9sKhmikQp+dqeGalaJxw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/rpc-subscriptions-spec": "2.3.0", + "@solana/subscribable": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3", + "ws": "^8.18.0" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/rpc-subscriptions-spec": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-subscriptions-spec/-/rpc-subscriptions-spec-2.3.0.tgz", + "integrity": "sha512-rdmVcl4PvNKQeA2l8DorIeALCgJEMSu7U8AXJS1PICeb2lQuMeaR+6cs/iowjvIB0lMVjYN2sFf6Q3dJPu6wWg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/promises": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/subscribable": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/rpc-transformers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-transformers/-/rpc-transformers-2.3.0.tgz", + "integrity": "sha512-UuHYK3XEpo9nMXdjyGKkPCOr7WsZsxs7zLYDO1A5ELH3P3JoehvrDegYRAGzBS2VKsfApZ86ZpJToP0K3PhmMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/nominal-types": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/rpc-transport-http": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-transport-http/-/rpc-transport-http-2.3.0.tgz", + "integrity": "sha512-HFKydmxGw8nAF5N+S0NLnPBDCe5oMDtI2RAmW8DMqP4U3Zxt2XWhvV1SNkAldT5tF0U1vP+is6fHxyhk4xqEvg==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0", + "@solana/rpc-spec": "2.3.0", + "@solana/rpc-spec-types": "2.3.0", + "undici-types": "^7.11.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/rpc-types": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/rpc-types/-/rpc-types-2.3.0.tgz", + "integrity": "sha512-O09YX2hED2QUyGxrMOxQ9GzH1LlEwwZWu69QbL4oYmIf6P5dzEEHcqRY6L1LsDVqc/dzAdEs/E1FaPrcIaIIPw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/nominal-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/signers": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/signers/-/signers-2.3.0.tgz", + "integrity": "sha512-OSv6fGr/MFRx6J+ZChQMRqKNPGGmdjkqarKkRzkwmv7v8quWsIRnJT5EV8tBy3LI4DLO/A8vKiNSPzvm1TdaiQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/instructions": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/nominal-types": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/subscribable": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/subscribable/-/subscribable-2.3.0.tgz", + "integrity": "sha512-DkgohEDbMkdTWiKAoatY02Njr56WXx9e/dKKfmne8/Ad6/2llUIrax78nCdlvZW9quXMaXPTxZvdQqo9N669Og==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/errors": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/sysvars": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/sysvars/-/sysvars-2.3.0.tgz", + "integrity": "sha512-LvjADZrpZ+CnhlHqfI5cmsRzX9Rpyb1Ox2dMHnbsRNzeKAMhu9w4ZBIaeTdO322zsTr509G1B+k2ABD3whvUBA==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/accounts": "2.3.0", + "@solana/codecs": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/transaction-confirmation": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/transaction-confirmation/-/transaction-confirmation-2.3.0.tgz", + "integrity": "sha512-UiEuiHCfAAZEKdfne/XljFNJbsKAe701UQHKXEInYzIgBjRbvaeYZlBmkkqtxwcasgBTOmEaEKT44J14N9VZDw==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/promises": "2.3.0", + "@solana/rpc": "2.3.0", + "@solana/rpc-subscriptions": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/transaction-messages": "2.3.0", + "@solana/transactions": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/transaction-messages": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/transaction-messages/-/transaction-messages-2.3.0.tgz", + "integrity": "sha512-bgqvWuy3MqKS5JdNLH649q+ngiyOu5rGS3DizSnWwYUd76RxZl1kN6CoqHSrrMzFMvis6sck/yPGG3wqrMlAww==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-data-structures": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/instructions": "2.3.0", + "@solana/nominal-types": "2.3.0", + "@solana/rpc-types": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/@solana/transactions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/@solana/transactions/-/transactions-2.3.0.tgz", + "integrity": "sha512-LnTvdi8QnrQtuEZor5Msje61sDpPstTVwKg4y81tNxDhiyomjuvnSNLAq6QsB9gIxUqbNzPZgOG9IU4I4/Uaug==", + "license": "MIT", + "peer": true, + "dependencies": { + "@solana/addresses": "2.3.0", + "@solana/codecs-core": "2.3.0", + "@solana/codecs-data-structures": "2.3.0", + "@solana/codecs-numbers": "2.3.0", + "@solana/codecs-strings": "2.3.0", + "@solana/errors": "2.3.0", + "@solana/functional": "2.3.0", + "@solana/instructions": "2.3.0", + "@solana/keys": "2.3.0", + "@solana/nominal-types": "2.3.0", + "@solana/rpc-types": "2.3.0", + "@solana/transaction-messages": "2.3.0" + }, + "engines": { + "node": ">=20.18.0" + }, + "peerDependencies": { + "typescript": ">=5.3.3" + } + }, + "node_modules/@trezor/connect/node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@trezor/connect/node_modules/undici-types": { + "version": "7.29.0", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-7.29.0.tgz", + "integrity": "sha512-vamA8dGlzMwhpyYpQp9d8vka3o4D/yn5I7ez7Or+msDA4bZ8Uh+Zy91WvWf3I73gDAkFha9JcYRqm2li0Npfgg==", + "license": "MIT", + "peer": true + }, + "node_modules/@trezor/crypto-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@trezor/crypto-utils/-/crypto-utils-1.2.0.tgz", + "integrity": "sha512-9i1NrfW1IE6JO910ut7xrx4u5LxE++GETbpJhWLj4P5xpuGDDSDLEn/MXaYisls2DpE897aOrGPaa1qyt8V6tw==", + "license": "SEE LICENSE IN LICENSE.md", + "peer": true, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/device-authenticity": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@trezor/device-authenticity/-/device-authenticity-1.1.2.tgz", + "integrity": "sha512-313uSXYR4XKDv3CjtCpgHA+yEe9xxqN7EFl/D68FEn70SPsuWI0+2zUvjPPh6TIOh/EcLv7hCO/QTHUAGd7ZWQ==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@noble/curves": "^2.0.1", + "@trezor/crypto-utils": "1.2.0", + "@trezor/protobuf": "1.5.2", + "@trezor/schema-utils": "1.4.0", + "@trezor/utils": "9.5.0" + } + }, + "node_modules/@trezor/device-authenticity/node_modules/@noble/curves": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-2.4.0.tgz", + "integrity": "sha512-P4/62zrgfH33CneE3Dn4WhJVA22YUU0eR51wKIan4NVRvwsA0YnPTwWGpNbpuacSujmSFLvyzpyuR30+fbq2Ew==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "2.4.0" + }, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@trezor/device-authenticity/node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@trezor/device-authenticity/node_modules/@trezor/protobuf": { + "version": "1.5.2", + "resolved": "https://registry.npmjs.org/@trezor/protobuf/-/protobuf-1.5.2.tgz", + "integrity": "sha512-zViaL1jKue8DUTVEDg0C/lMipqNMd/Z3kr29/+MeZOoupjaXIQ2Lqp3WAMe8hvNTKKX8aNQH9JrbapJ6w9FMXw==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@trezor/schema-utils": "1.4.0", + "long": "5.2.5", + "protobufjs": "7.4.0" + }, + "peerDependencies": { + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/device-authenticity/node_modules/protobufjs": { + "version": "7.4.0", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.4.0.tgz", + "integrity": "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/@trezor/device-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@trezor/device-utils/-/device-utils-1.2.0.tgz", + "integrity": "sha512-Aqp7pIooFTx21zRUtTI6i1AS4d9Lrx7cclvksh2nJQF9WJvbzuCXshEGkLoOsHwhQrCl3IXfbGuMdA12yDenPA==", + "license": "See LICENSE.md in repo root", + "peer": true + }, + "node_modules/@trezor/env-utils": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/@trezor/env-utils/-/env-utils-1.5.0.tgz", + "integrity": "sha512-u1TN7dMQ5Qhpbae08Z4JJmI9fQrbbJ4yj8eIAsuzMQn6vb+Sg9vbntl+IDsZ1G9WeI73uHTLu1wWMmAgiujH8w==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "ua-parser-js": "^2.0.4" + }, + "peerDependencies": { + "expo-constants": "*", + "expo-localization": "*", + "react-native": "*", + "tslib": "^2.6.2" + }, + "peerDependenciesMeta": { + "expo-constants": { + "optional": true + }, + "expo-localization": { + "optional": true + }, + "react-native": { + "optional": true + } + } + }, + "node_modules/@trezor/protobuf": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/@trezor/protobuf/-/protobuf-1.5.3.tgz", + "integrity": "sha512-ZYQtapkT2NaiVrAgb91Zprnk3uhl2Wca0bsnJcWgE7vwzqKegjrSorRnkZLqLTKTbCaT6sgkCYtM2hPl4Hqw5Q==", + "license": "See LICENSE.md in repo root", + "peer": true, + "dependencies": { + "@trezor/schema-utils": "1.4.0", + "long": "5.2.5", + "protobufjs": "7.5.5" }, "peerDependencies": { "tslib": "^2.6.2" } }, - "node_modules/@trezor/connect-web/node_modules/@trezor/protocol": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/@trezor/protocol/-/protocol-10.0.1.tgz", - "integrity": "sha512-uoFyMzfr3/p7uXhTJSxXurfwM6N0vDicTKHlQRrwH9dMcidEEOxuOJZBrhKnoj4osLfDPt5+ctQ+mqFjxJA9Tw==", - "license": "MIT", + "node_modules/@trezor/protocol": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/@trezor/protocol/-/protocol-1.3.1.tgz", + "integrity": "sha512-uNJ83n38+BM+AJKsWza1ocvQ206d6NXJQ8/g+DelPLTj5c37Rj6hFiY5Nb5zgM7DBnq6T8Aa2K8t4TVCzHT1qg==", + "license": "See LICENSE.md in repo root", + "peer": true, "peerDependencies": { "tslib": "^2.6.2" } }, - "node_modules/@trezor/connect-web/node_modules/@trezor/schema-utils": { - "version": "10.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@trezor/schema-utils/-/schema-utils-10.0.0-alpha.1.tgz", - "integrity": "sha512-0beYv0b0De3Z60sZzM1T+IrYwXeNL+7RKfLokTvqEFDzsQkwxhYrIX47FsBhGsOmGWx9x4+65dxDbnyd4wZT6g==", - "license": "MIT", + "node_modules/@trezor/schema-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@trezor/schema-utils/-/schema-utils-1.4.0.tgz", + "integrity": "sha512-K7upSeh7VDrORaIC4KAxYVW93XNlohmUnH5if/5GKYmTdQSRp1nBkO6Jm+Z4hzIthdnz/1aLgnbeN3bDxWLRxA==", + "license": "See LICENSE.md in repo root", + "peer": true, "dependencies": { - "@sinclair/typebox": "^0.34.49", - "@trezor/type-utils": "10.0.0-alpha.1", - "ts-mixer": "^6.0.4" + "@sinclair/typebox": "^0.33.7", + "ts-mixer": "^6.0.3" }, "peerDependencies": { "tslib": "^2.6.2" } }, - "node_modules/@trezor/connect-web/node_modules/@trezor/type-utils": { - "version": "10.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@trezor/type-utils/-/type-utils-10.0.0-alpha.1.tgz", - "integrity": "sha512-wK75rrwlyXFgPfdHQFRbeKCoj+orQLXKj7rliMK30z+F1y+rEcfv/MW2VP48Q9mL5pea+ZX1nRnb4hYx1v38Ow==", - "license": "MIT" - }, - "node_modules/@trezor/connect-web/node_modules/@trezor/utils": { - "version": "10.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@trezor/utils/-/utils-10.0.0-alpha.1.tgz", - "integrity": "sha512-A+txzREoeX2MO1AqocDBsMNe+R21jTx5kungXGuzeTRRevARLsYh8MPes0170uMjq4qNSgopBnYZK18WyksOmQ==", - "license": "MIT", + "node_modules/@trezor/transport": { + "version": "1.6.3", + "resolved": "https://registry.npmjs.org/@trezor/transport/-/transport-1.6.3.tgz", + "integrity": "sha512-GEi9KfiJsBgT/MCGNDIn9RDeXceLaAPjJT/GCayirXaCr3KnlpZCs6LGYkrjqapxiBm73nxM+BctKBKhpUl2cQ==", + "license": "SEE LICENSE IN LICENSE.md", + "peer": true, "dependencies": { - "bignumber.js": "^9.3.1" + "@trezor/protobuf": "1.5.3", + "@trezor/protocol": "1.3.1", + "@trezor/type-utils": "1.2.0", + "@trezor/utils": "9.5.0", + "cross-fetch": "^4.0.0", + "usb": "^2.15.0" }, "peerDependencies": { "tslib": "^2.6.2" } }, - "node_modules/@trezor/connect-web/node_modules/@trezor/websocket-client": { - "version": "10.0.0-alpha.1", - "resolved": "https://registry.npmjs.org/@trezor/websocket-client/-/websocket-client-10.0.0-alpha.1.tgz", - "integrity": "sha512-129Ot1m+fDodRFBvav2d6v8nLxZLAl/udQP5l5W6jxQZcWUuviphDceT/juF8EvNGdw+L6OIu6fH+yCXARpFWQ==", - "license": "MIT", + "node_modules/@trezor/type-utils": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@trezor/type-utils/-/type-utils-1.2.0.tgz", + "integrity": "sha512-+E2QntxkyQuYfQQyl8RvT01tq2i5Dp/LFUOXuizF+KVOqsZBjBY43j5hewcCO3+MokD7deDiPyekbUEN5/iVlw==", + "license": "See LICENSE.md in repo root", + "peer": true + }, + "node_modules/@trezor/utils": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/@trezor/utils/-/utils-9.5.0.tgz", + "integrity": "sha512-kdyMyDbxzvOZmwBNvTjAK+C/kzyOz8T4oUbFvq+KaXn5mBFf1uf8rq5X2HkxgdYRPArtHS3PxLKsfkNCdhCYtQ==", + "license": "SEE LICENSE IN LICENSE.md", + "peer": true, "dependencies": { - "@trezor/utils": "10.0.0-alpha.1", - "ws": "^8.20.0" + "bignumber.js": "^9.3.1" }, "peerDependencies": { "tslib": "^2.6.2" } }, - "node_modules/@trezor/connect-web/node_modules/bignumber.js": { + "node_modules/@trezor/utils/node_modules/bignumber.js": { "version": "9.3.1", "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", "license": "MIT", + "peer": true, "engines": { "node": "*" } }, - "node_modules/@trezor/connect-web/node_modules/ws": { - "version": "8.22.0", - "resolved": "https://registry.npmjs.org/ws/-/ws-8.22.0.tgz", - "integrity": "sha512-Ydggc987+RO0AnWtZ/7Wq9FtNvcrL1b/RO0ud9mWjUPgDrsAAwQSF51sm2hm1XofbU/4jkpGEsLFsZZxU+1DOg==", - "license": "MIT", - "engines": { - "node": ">=10.0.0" + "node_modules/@trezor/utxo-lib": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/@trezor/utxo-lib/-/utxo-lib-2.5.0.tgz", + "integrity": "sha512-Fa2cZh0037oX6AHNLfpFIj65UR/OoX0ZJTocFuQASe77/1PjZHysf6BvvGfmzuFToKfrAQ+DM/1Sx+P/vnyNmA==", + "license": "SEE LICENSE IN LICENSE.md", + "peer": true, + "dependencies": { + "@trezor/utils": "9.5.0", + "bech32": "^2.0.0", + "bip66": "^2.0.0", + "bitcoin-ops": "^1.4.1", + "blake-hash": "^2.0.0", + "blakejs": "^1.2.1", + "bn.js": "^5.2.2", + "bs58": "^6.0.0", + "bs58check": "^4.0.0", + "cashaddrjs": "0.4.4", + "create-hmac": "^1.1.7", + "int64-buffer": "^1.1.0", + "pushdata-bitcoin": "^1.0.1", + "tiny-secp256k1": "^1.1.7", + "typeforce": "^1.18.0", + "varuint-bitcoin": "2.0.0", + "wif": "^5.0.0" }, "peerDependencies": { - "bufferutil": "^4.0.1", - "utf-8-validate": ">=5.0.2" + "tslib": "^2.6.2" + } + }, + "node_modules/@trezor/websocket-client": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@trezor/websocket-client/-/websocket-client-1.3.0.tgz", + "integrity": "sha512-9KQSaVc3NtmM6rFFj1e+9bM0C5mVKVidbnxlfzuBJu7G2YMRdIdLPcAXhvmRZjs40uzDuBeApK+p547kODz2ug==", + "license": "SEE LICENSE IN LICENSE.md", + "peer": true, + "dependencies": { + "@trezor/utils": "9.5.0", + "ws": "^8.18.0" }, - "peerDependenciesMeta": { - "bufferutil": { - "optional": true - }, - "utf-8-validate": { - "optional": true - } + "peerDependencies": { + "tslib": "^2.6.2" } }, "node_modules/@ts-morph/common": { @@ -6190,6 +8712,16 @@ "@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, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, "node_modules/@types/d3-array": { "version": "3.2.2", "resolved": "https://registry.npmjs.org/@types/d3-array/-/d3-array-3.2.2.tgz", @@ -6271,7 +8803,6 @@ "version": "7.0.15", "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz", "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==", - "dev": true, "license": "MIT" }, "node_modules/@types/json5": { @@ -6294,7 +8825,7 @@ "version": "19.2.14", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.2.14.tgz", "integrity": "sha512-ilcTH/UniCkMdtexkoCN0bI7pMcJDvmQFPvuPvmEaYA/NSfFTAgdUSLAoVjaRJm7+6PvcM+q1zYOwS4wTYMF9w==", - "dev": true, + "devOptional": true, "license": "MIT", "dependencies": { "csstype": "^3.2.2" @@ -6304,7 +8835,7 @@ "version": "19.2.3", "resolved": "https://registry.npmjs.org/@types/react-dom/-/react-dom-19.2.3.tgz", "integrity": "sha512-jp2L/eY6fn+KgVVQAOqYItbF0VY/YApe5Mz2F0aykSO8gx31bYCZyvSeYxCHKvzHG5eZjc+zyaS5BrBWya2+kQ==", - "dev": true, + "devOptional": true, "license": "MIT", "peerDependencies": { "@types/react": "^19.2.0" @@ -6349,6 +8880,20 @@ "integrity": "sha512-lrpDziQipxCEeK5kWxvljWYhUvOiB2A9izZd9B2AFarYAkqZshb4lPbRs7zKEic6eGtH8V/2qJW+dPp9OtF6bw==", "license": "MIT" }, + "node_modules/@types/w3c-web-usb": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@types/w3c-web-usb/-/w3c-web-usb-1.0.14.tgz", + "integrity": "sha512-Qu3Nn6JFuF4+sHKYl+IcX9vYiI40ogleXzFFSxoE1W94rG98o/kXs8uJ0QSfFzuwBCZWlGfUGpPkgwuuX4PchA==", + "license": "MIT", + "peer": true + }, + "node_modules/@types/web": { + "version": "0.0.197", + "resolved": "https://registry.npmjs.org/@types/web/-/web-0.0.197.tgz", + "integrity": "sha512-V4sOroWDADFx9dLodWpKm298NOJ1VJ6zoDVgaP+WBb/utWxqQ6gnMzd9lvVDAr/F3ibiKaxH9i45eS0gQPSTaQ==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/@types/ws": { "version": "7.4.7", "resolved": "https://registry.npmjs.org/@types/ws/-/ws-7.4.7.tgz", @@ -7682,6 +10227,74 @@ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", "license": "0BSD" }, + "node_modules/@xrplf/isomorphic": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@xrplf/isomorphic/-/isomorphic-1.0.2.tgz", + "integrity": "sha512-ncZUdMXr6VlSXtdoiDi0jTH+gBrgGxwVeEidhoegII3PmyErbQsyj6e+j7acmR4LW/lvBkPkzb9QzRfJH0n3rA==", + "license": "ISC", + "peer": true, + "dependencies": { + "@noble/hashes": "^2.0.1", + "eventemitter3": "5.0.1", + "ws": "^8.20.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/@xrplf/isomorphic/node_modules/@noble/hashes": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.4.0.tgz", + "integrity": "sha512-X5XaVWZIBCT7HHZGm5I7ZQXDwLG+bGXuSrMQAW+7Zvl87h1kmc1ZB1VSRJcpUfoUrGQp4Fkoxm5kZ+Ms+aW+eA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 20.19.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@xrplf/isomorphic/node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT", + "peer": true + }, + "node_modules/@xrplf/isomorphic/node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/@xrplf/secret-numbers": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@xrplf/secret-numbers/-/secret-numbers-2.0.0.tgz", + "integrity": "sha512-z3AOibRTE9E8MbjgzxqMpG1RNaBhQ1jnfhNCa1cGf2reZUJzPMYs4TggQTc7j8+0WyV3cr7y/U8Oz99SXIkN5Q==", + "license": "ISC", + "peer": true, + "dependencies": { + "@xrplf/isomorphic": "^1.0.1", + "ripple-keypairs": "^2.0.0" + } + }, "node_modules/abitype": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.3.0.tgz", @@ -8058,6 +10671,25 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/asn1.js": { + "version": "4.10.1", + "resolved": "https://registry.npmjs.org/asn1.js/-/asn1.js-4.10.1.tgz", + "integrity": "sha512-p32cOF5q0Zqs9uBiONKYLm6BClCoBCM5O9JfeUSlnQLBTxYdTK+pW+nXflm8UkKd2UYlEbYz5qEi0JuZR9ckSw==", + "license": "MIT", + "peer": true, + "dependencies": { + "bn.js": "^4.0.0", + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, + "node_modules/asn1.js/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT", + "peer": true + }, "node_modules/assertion-error": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-2.0.1.tgz", @@ -8153,7 +10785,6 @@ "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, "license": "MIT", "dependencies": { "possible-typed-array-names": "^1.0.0" @@ -8277,6 +10908,16 @@ ], "license": "MIT" }, + "node_modules/base64id": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base64id/-/base64id-2.0.0.tgz", + "integrity": "sha512-lGe34o6EHj9y3Kts9R4ZYs/Gr+6N7MCaMlIFA3F1R2O5/m7K06AxfSeO5530PEERE6/WyEg3lsuyw4GHlPZHog==", + "dev": true, + "license": "MIT", + "engines": { + "node": "^4.5.0 || >= 5.9" + } + }, "node_modules/baseline-browser-mapping": { "version": "2.10.30", "resolved": "https://registry.npmjs.org/baseline-browser-mapping/-/baseline-browser-mapping-2.10.30.tgz", @@ -8289,6 +10930,23 @@ "node": ">=6.0.0" } }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", + "license": "MIT", + "peer": true + }, + "node_modules/big-integer": { + "version": "1.6.36", + "resolved": "https://registry.npmjs.org/big-integer/-/big-integer-1.6.36.tgz", + "integrity": "sha512-t70bfa7HYEA1D9idDbmuv7YbsbVkQ+Hp+8KFSul4aE5e/i1bjCNIRYJZlA8Q8p0r9T8cF/RVvwUgRA//FydEyg==", + "license": "Unlicense", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/big.js": { "version": "6.2.2", "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz", @@ -8321,12 +10979,52 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, "node_modules/bip32-path": { "version": "0.4.2", "resolved": "https://registry.npmjs.org/bip32-path/-/bip32-path-0.4.2.tgz", "integrity": "sha512-ZBMCELjJfcNMkz5bDuJ1WrYvjlhEF5k6mQ8vUr4N7MbVRsXei7ZOg8VhhwMfNiW68NWmLkgkc6WvTickrLGprQ==", "license": "MIT" }, + "node_modules/bip66": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-2.0.0.tgz", + "integrity": "sha512-kBG+hSpgvZBrkIm9dt5T1Hd/7xGCPEX2npoxAWZfsK1FvjgaxySEh2WizjyIstWXriKo9K9uJ4u0OnsyLDUPXQ==", + "license": "MIT", + "peer": true + }, + "node_modules/bitcoin-ops": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/bitcoin-ops/-/bitcoin-ops-1.4.1.tgz", + "integrity": "sha512-pef6gxZFztEhaE9RY9HmWVmiIHqCb2OyS4HPKkpc6CIiiOa3Qmuoylxc5P2EkU3w+5eTSifI9SEZC88idAIGow==", + "license": "MIT", + "peer": true + }, + "node_modules/blake-hash": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/blake-hash/-/blake-hash-2.0.0.tgz", + "integrity": "sha512-Igj8YowDu1PRkRsxZA7NVkdFNxH5rKv5cpLxQ0CVXSIA77pVYwCPRQJ2sMew/oneUpfuYRyjG6r8SmmmnbZb1w==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "node-addon-api": "^3.0.0", + "node-gyp-build": "^4.2.2", + "readable-stream": "^3.6.0" + }, + "engines": { + "node": ">= 10" + } + }, "node_modules/blakejs": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", @@ -8398,6 +11096,129 @@ "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", "license": "MIT" }, + "node_modules/browserify-aes": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/browserify-aes/-/browserify-aes-1.2.0.tgz", + "integrity": "sha512-+7CHXqGuspUn/Sl5aO7Ea0xWGAtETPXNSAjHo48JfLdPWcMng33Xe4znFvQweqc/uzk5zSOI3H52CYnjCfb5hA==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-xor": "^1.0.3", + "cipher-base": "^1.0.0", + "create-hash": "^1.1.0", + "evp_bytestokey": "^1.0.3", + "inherits": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/browserify-cipher": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/browserify-cipher/-/browserify-cipher-1.0.1.tgz", + "integrity": "sha512-sPhkz0ARKbf4rRQt2hTpAHqn47X3llLkUGn+xEJzLjwY8LRs2p0v7ljvI5EyoRO/mexrNunNECisZs+gw2zz1w==", + "license": "MIT", + "peer": true, + "dependencies": { + "browserify-aes": "^1.0.4", + "browserify-des": "^1.0.0", + "evp_bytestokey": "^1.0.0" + } + }, + "node_modules/browserify-des": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/browserify-des/-/browserify-des-1.0.2.tgz", + "integrity": "sha512-BioO1xf3hFwz4kc6iBhI3ieDFompMhrMlnDFC4/0/vd5MokpuAc3R+LYbwTA9A5Yc9pq9UYPqffKpW2ObuwX5A==", + "license": "MIT", + "peer": true, + "dependencies": { + "cipher-base": "^1.0.1", + "des.js": "^1.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/browserify-rsa": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/browserify-rsa/-/browserify-rsa-4.1.1.tgz", + "integrity": "sha512-YBjSAiTqM04ZVei6sXighu679a3SqWORA3qZTEqZImnlkDIFtKc6pNutpjyZ8RJTjQtuYfeetkxM11GwoYXMIQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "bn.js": "^5.2.1", + "randombytes": "^2.1.0", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/browserify-sign/-/browserify-sign-4.2.6.tgz", + "integrity": "sha512-sd+Q65fjlWCYWtZKXiKfrUc8d+4jtp/8f0W2NkwzLtoW4bI6UDnWusLWIurHnmurW0XShIRxpwiOX4EoPtXUAg==", + "license": "ISC", + "peer": true, + "dependencies": { + "bn.js": "^5.2.3", + "browserify-rsa": "^4.1.1", + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "elliptic": "^6.6.1", + "inherits": "^2.0.4", + "parse-asn1": "^5.1.9", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/browserify-sign/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT", + "peer": true + }, + "node_modules/browserify-sign/node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/browserify-sign/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT", + "peer": true + }, + "node_modules/browserify-sign/node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/browserify-sign/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT", + "peer": true + }, "node_modules/browserslist": { "version": "4.28.2", "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.28.2.tgz", @@ -8440,6 +11261,17 @@ "base-x": "^5.0.0" } }, + "node_modules/bs58check": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-4.0.0.tgz", + "integrity": "sha512-FsGDOnFg9aVI9erdriULkd/JjEWONV/lQE5aYziB5PoBsXRind56lh8doIZIc9X4HoxT5x4bLjMWN1/NB8Zp5g==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^6.0.0" + } + }, "node_modules/buffer": { "version": "6.0.3", "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", @@ -8464,6 +11296,20 @@ "ieee754": "^1.2.1" } }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause", + "peer": true + }, + "node_modules/buffer-xor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/buffer-xor/-/buffer-xor-1.0.3.tgz", + "integrity": "sha512-571s0T7nZWK6vB67HI5dyUF7wXiNcfaPPPTl6zYCNApANjIvYJTg7hlud/+cJpdAhS7dVzqMLmfhfHR3rAcOjQ==", + "license": "MIT", + "peer": true + }, "node_modules/bufferutil": { "version": "4.1.0", "resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.1.0.tgz", @@ -8506,7 +11352,6 @@ "version": "1.0.9", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.9.tgz", "integrity": "sha512-a/hy+pNsFUTR+Iz8TCJvXudKVLAnz/DyeSUo10I5yvFDQJBFU2s9uqQpoSrJlroHUKoKqzg+epxyP9lqFdzfBQ==", - "dev": true, "license": "MIT", "dependencies": { "call-bind-apply-helpers": "^1.0.2", @@ -8598,6 +11443,29 @@ ], "license": "CC-BY-4.0" }, + "node_modules/cashaddrjs": { + "version": "0.4.4", + "resolved": "https://registry.npmjs.org/cashaddrjs/-/cashaddrjs-0.4.4.tgz", + "integrity": "sha512-xZkuWdNOh0uq/mxJIng6vYWfTowZLd9F4GMAlp2DwFHlcCqCm91NtuAc47RuV4L7r4PYcY5p6Cr2OKNb4hnkWA==", + "license": "MIT", + "peer": true, + "dependencies": { + "big-integer": "1.6.36" + } + }, + "node_modules/cbor": { + "version": "10.0.12", + "resolved": "https://registry.npmjs.org/cbor/-/cbor-10.0.12.tgz", + "integrity": "sha512-exQDevYd7ZQLP4moMQcZkKCVZsXLAtUSflObr3xTh4xzFIv/xBCdvCd6L259kQOUP2kcTC0jvC6PpZIf/WmRXA==", + "license": "MIT", + "peer": true, + "dependencies": { + "nofilter": "^3.0.2" + }, + "engines": { + "node": ">=20" + } + }, "node_modules/chai": { "version": "6.3.0", "resolved": "https://registry.npmjs.org/chai/-/chai-6.3.0.tgz", @@ -8666,10 +11534,25 @@ "dev": true, "license": "ISC", "dependencies": { - "is-glob": "^4.0.1" + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/cipher-base": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cipher-base/-/cipher-base-1.0.7.tgz", + "integrity": "sha512-Mz9QMT5fJe7bKI7MH31UilT5cEK5EHHRCccw/YRFsRY47AuNgaV6HY3rscp0/I4Q+tTW/5zoqpSeRRI54TkDWA==", + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" }, "engines": { - "node": ">= 6" + "node": ">= 0.10" } }, "node_modules/class-variance-authority": { @@ -8894,6 +11777,13 @@ "node": ">=6.6.0" } }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT", + "peer": true + }, "node_modules/cors": { "version": "2.8.6", "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", @@ -8937,6 +11827,84 @@ } } }, + "node_modules/create-ecdh": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/create-ecdh/-/create-ecdh-4.0.4.tgz", + "integrity": "sha512-mf+TCx8wWc9VpuxfP2ht0iSISLZnt0JgWlrOKZiNqyUZWnjIaCIVNQArMHnCZKfEYRg6IM7A+NeJoN8gf/Ws0A==", + "license": "MIT", + "peer": true, + "dependencies": { + "bn.js": "^4.1.0", + "elliptic": "^6.5.3" + } + }, + "node_modules/create-ecdh/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT", + "peer": true + }, + "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==", + "license": "MIT", + "peer": true, + "dependencies": { + "cipher-base": "^1.0.1", + "inherits": "^2.0.1", + "md5.js": "^1.3.4", + "ripemd160": "^2.0.1", + "sha.js": "^2.4.0" + } + }, + "node_modules/create-hmac": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/create-hmac/-/create-hmac-1.1.7.tgz", + "integrity": "sha512-MJG9liiZ+ogc4TzUwuvbER1JRdgvUFSB5+VR/g5h82fGaIRWMWddtKBHi7/sVhfjQZ6SehlyhvQYrcYkaUIpLg==", + "license": "MIT", + "peer": true, + "dependencies": { + "cipher-base": "^1.0.3", + "create-hash": "^1.1.0", + "inherits": "^2.0.1", + "ripemd160": "^2.0.0", + "safe-buffer": "^5.0.1", + "sha.js": "^2.4.8" + } + }, + "node_modules/cross-fetch": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-4.1.0.tgz", + "integrity": "sha512-uKm5PU+MHTootlWEY+mZ4vvXoCn4fLQxT9dSc1sXVMSFkINTJVN8cAQROpwcKm8bJ/c7rgZVIBWzH5T78sNZZw==", + "license": "MIT", + "peer": true, + "dependencies": { + "node-fetch": "^2.7.0" + } + }, + "node_modules/cross-fetch/node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, "node_modules/cross-spawn": { "version": "7.0.6", "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.6.tgz", @@ -8969,6 +11937,29 @@ "node": "*" } }, + "node_modules/crypto-browserify": { + "version": "3.12.0", + "resolved": "https://registry.npmjs.org/crypto-browserify/-/crypto-browserify-3.12.0.tgz", + "integrity": "sha512-fz4spIh+znjO2VjL+IdhEpRJ3YN6sMzITSBijk6FK2UvTqruSQW+/cCZTSNsMiZNvUeq0CqurF+dAbyiGOY6Wg==", + "license": "MIT", + "peer": true, + "dependencies": { + "browserify-cipher": "^1.0.0", + "browserify-sign": "^4.0.0", + "create-ecdh": "^4.0.0", + "create-hash": "^1.1.0", + "create-hmac": "^1.1.0", + "diffie-hellman": "^5.0.0", + "inherits": "^2.0.1", + "pbkdf2": "^3.0.3", + "public-encrypt": "^4.0.0", + "randombytes": "^2.0.0", + "randomfill": "^1.0.3" + }, + "engines": { + "node": "*" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -9278,7 +12269,6 @@ "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, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0", @@ -9358,6 +12348,17 @@ "node": ">= 0.8" } }, + "node_modules/des.js": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/des.js/-/des.js-1.1.0.tgz", + "integrity": "sha512-r17GxjhUCjSRy8aiJpr8/UadFIzMzJGexI3Nmz4ADi9LYSFx4gTBp80+NaX/YsXWWLhpZ7v/v/ubEc/bCNfKwg==", + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.1", + "minimalistic-assert": "^1.0.0" + } + }, "node_modules/destr": { "version": "2.0.5", "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", @@ -9370,6 +12371,27 @@ "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==", "license": "MIT" }, + "node_modules/detect-europe-js": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/detect-europe-js/-/detect-europe-js-0.1.2.tgz", + "integrity": "sha512-lgdERlL3u0aUdHocoouzT10d9I89VVhk0qNRmll7mXdGfJT1/wqZ2ZLA4oJAjeACPY5fT1wsbq2AT+GkuInsow==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "license": "MIT", + "peer": true + }, "node_modules/detect-libc": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", @@ -9402,6 +12424,25 @@ "node": ">=0.3.1" } }, + "node_modules/diffie-hellman": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/diffie-hellman/-/diffie-hellman-5.0.3.tgz", + "integrity": "sha512-kqag/Nl+f3GwyK25fhUMYj81BUOrZ9IuJsjIcDE5icNM9FJHAVm3VcUDxdLPoQtTuUylWm6ZIknYJwwaPxsUzg==", + "license": "MIT", + "peer": true, + "dependencies": { + "bn.js": "^4.1.0", + "miller-rabin": "^4.0.0", + "randombytes": "^2.0.0" + } + }, + "node_modules/diffie-hellman/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT", + "peer": true + }, "node_modules/dijkstrajs": { "version": "1.0.3", "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", @@ -9454,6 +12495,16 @@ "node": ">= 0.4" } }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "peer": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, "node_modules/eciesjs": { "version": "0.4.18", "resolved": "https://registry.npmjs.org/eciesjs/-/eciesjs-0.4.18.tgz", @@ -9526,6 +12577,27 @@ "node": ">= 0.8" } }, + "node_modules/engine.io": { + "version": "6.6.11", + "resolved": "https://registry.npmjs.org/engine.io/-/engine.io-6.6.11.tgz", + "integrity": "sha512-z34mGpLoY/82ZbkX1LqGMIaf3wjVMPL6gVC681QDqnmzJ26synI8qAE32P+a7UazmpJZ8UGrFIeAavgRfXzYrg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/cors": "^2.8.12", + "@types/node": ">=10.0.0", + "@types/ws": "^8.5.12", + "accepts": "~1.3.4", + "cookie": "~0.7.2", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io-parser": "~5.2.1", + "ws": "~8.21.0" + }, + "engines": { + "node": ">=10.2.0" + } + }, "node_modules/engine.io-client": { "version": "6.6.4", "resolved": "https://registry.npmjs.org/engine.io-client/-/engine.io-client-6.6.4.tgz", @@ -9548,6 +12620,85 @@ "node": ">=10.0.0" } }, + "node_modules/engine.io/node_modules/@types/ws": { + "version": "8.18.1", + "resolved": "https://registry.npmjs.org/@types/ws/-/ws-8.18.1.tgz", + "integrity": "sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==", + "dev": true, + "license": "MIT", + "dependencies": { + "@types/node": "*" + } + }, + "node_modules/engine.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/engine.io/node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/env-paths": { "version": "2.2.1", "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz", @@ -10273,6 +13424,17 @@ "node": ">=18.0.0" } }, + "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==", + "license": "MIT", + "peer": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, "node_modules/execa": { "version": "9.6.1", "resolved": "https://registry.npmjs.org/execa/-/execa-9.6.1.tgz", @@ -10309,6 +13471,13 @@ "node": ">=12.0.0" } }, + "node_modules/exponential-backoff": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/exponential-backoff/-/exponential-backoff-3.1.3.tgz", + "integrity": "sha512-ZgEeZXj30q+I0EN+CbSSpIyPaJ5HVQD18Z1m+u1FXbAeT94mr1zw50q4q6jiiC447Nl/YTcIYSAftiGqetwXCA==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/express": { "version": "5.2.1", "resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz", @@ -10378,6 +13547,16 @@ "node": "> 0.1.90" } }, + "node_modules/fake-indexeddb": { + "version": "6.2.4", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.2.4.tgz", + "integrity": "sha512-INKeIKEtSViN4yVtEWEUqbsqmaIy7Ls+MfU0yxQVXg67pOJ/sH1ZxcVrP8XrKULUFohcPD9gnmym+qBfEybACw==", + "dev": true, + "license": "Apache-2.0", + "engines": { + "node": ">=18" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -10418,7 +13597,6 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", - "dev": true, "license": "MIT" }, "node_modules/fast-levenshtein": { @@ -10474,6 +13652,13 @@ "fast-string-width": "^3.0.2" } }, + "node_modules/fastestsmallesttextencoderdecoder": { + "version": "1.0.22", + "resolved": "https://registry.npmjs.org/fastestsmallesttextencoderdecoder/-/fastestsmallesttextencoderdecoder-1.0.22.tgz", + "integrity": "sha512-Pb8d48e+oIuY4MaM64Cd7OW1gt4nxCHs7/ddPPZ/Ic3sg8yVGM7O9wDvZ7us6ScaUupzM+pfBolwtYhN1IxBIw==", + "license": "CC0-1.0", + "peer": true + }, "node_modules/fastq": { "version": "1.20.1", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.20.1.tgz", @@ -10555,6 +13740,13 @@ "node": ">=16.0.0" } }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT", + "peer": true + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -10650,7 +13842,6 @@ "version": "0.3.5", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", - "dev": true, "license": "MIT", "dependencies": { "is-callable": "^1.2.7" @@ -10845,6 +14036,26 @@ "integrity": "sha512-sR9BNCjBg6LNgwvxlBd0sBABvQitkLzoVY9MYYROQVX/FvfJ4Mai9LsGhDgd8qYdds0bY77VzYd5iuB+v5rwQQ==", "license": "MIT" }, + "node_modules/generate-function": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/generate-function/-/generate-function-2.3.1.tgz", + "integrity": "sha512-eeB5GfMNeevm/GRYq20ShmsaGcmI81kIX2K9XQx5miC8KdHaC6Jm0qQ8ZNeGOi7wYB8OsdxKs+Y2oVuTFuVwKQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-property": "^1.0.2" + } + }, + "node_modules/generate-object-property": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/generate-object-property/-/generate-object-property-1.2.0.tgz", + "integrity": "sha512-TuOwZWgJ2VAMEGJvAyPWvpqxSANF0LDpmyHauMjFYzaACvn+QTT/AZomvPCzVBV7yDN3OmwHQ5OvHaeLKre3JQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "is-property": "^1.0.0" + } + }, "node_modules/generator-function": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/generator-function/-/generator-function-2.0.1.tgz", @@ -11104,7 +14315,6 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", - "dev": true, "license": "MIT", "dependencies": { "es-define-property": "^1.0.0" @@ -11156,6 +14366,69 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/hash-base": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/hash-base/-/hash-base-3.1.2.tgz", + "integrity": "sha512-Bb33KbowVTIj5s7Ked1OsqHUeCpz//tPwR+E2zJgJKo9Z5XolZ9b6bdUgjmYlwnWhoOQKoTd1TYToZGn5mAYOg==", + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.4", + "readable-stream": "^2.3.8", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/hash-base/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT", + "peer": true + }, + "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==", + "license": "MIT", + "peer": true, + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/hash-base/node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT", + "peer": true + }, + "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==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/hash-base/node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT", + "peer": true + }, "node_modules/hash.js": { "version": "1.1.7", "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", @@ -11375,6 +14648,13 @@ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", "license": "ISC" }, + "node_modules/int64-buffer": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/int64-buffer/-/int64-buffer-1.1.1.tgz", + "integrity": "sha512-xp/v0/im2q7n7ISFJXcYsr/aVsUdZKPUZS1uYtoM9I9Cfmm5YuAi7SQts2TrEXwAKIE0wyn3KxJbo35goJgvwQ==", + "license": "MIT", + "peer": true + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -11550,7 +14830,6 @@ "version": "1.2.7", "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -11746,6 +15025,27 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/is-my-ip-valid": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/is-my-ip-valid/-/is-my-ip-valid-1.0.1.tgz", + "integrity": "sha512-jxc8cBcOWbNK2i2aTkCZP6i7wkHF1bqKFrwEHuN5Jtg5BSaZHUZQ/JTOJwoV41YvHnOaRyWWh72T/KvfNz9DJg==", + "license": "MIT", + "peer": true + }, + "node_modules/is-my-json-valid": { + "version": "2.20.6", + "resolved": "https://registry.npmjs.org/is-my-json-valid/-/is-my-json-valid-2.20.6.tgz", + "integrity": "sha512-1JQwulVNjx8UqkPE/bqDaxtH4PXCe/2VRh/y3p99heOV87HG4Id5/VfDswd+YiAfHcRTfDlWgISycnHuhZq1aw==", + "license": "MIT", + "peer": true, + "dependencies": { + "generate-function": "^2.0.0", + "generate-object-property": "^1.1.0", + "is-my-ip-valid": "^1.0.0", + "jsonpointer": "^5.0.0", + "xtend": "^4.0.0" + } + }, "node_modules/is-negative-zero": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.3.tgz", @@ -11821,6 +15121,13 @@ "integrity": "sha512-hvpoI6korhJMnej285dSg6nu1+e6uxs7zG3BYAm5byqDsgJNWwxzM6z6iZiAgQR4TJ30JmBTOwqZUw3WlyH3AQ==", "license": "MIT" }, + "node_modules/is-property": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-property/-/is-property-1.0.2.tgz", + "integrity": "sha512-Ks/IoX00TtClbGQr4TWXemAnktAQvYB7HzcCxDGqEZU6oCmb2INHuOoKxbtR+HFkmYWBKv/dOZtGRiAjDhj92g==", + "license": "MIT", + "peer": true + }, "node_modules/is-regex": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", @@ -11884,15 +15191,36 @@ "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", "dev": true, "license": "MIT", - "dependencies": { - "call-bound": "^1.0.3" - }, - "engines": { - "node": ">= 0.4" - }, - "funding": { - "url": "https://github.com/sponsors/ljharb" - } + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-standalone-pwa": { + "version": "0.1.1", + "resolved": "https://registry.npmjs.org/is-standalone-pwa/-/is-standalone-pwa-0.1.1.tgz", + "integrity": "sha512-9Cbovsa52vNQCjdXOzeQq5CnCbAcRk05aU62K20WO372NrTv0NxibLFCK6lQ4/iZEFdEA3p3t2VNOn8AJ53F5g==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "license": "MIT", + "peer": true }, "node_modules/is-stream": { "version": "4.0.1", @@ -11945,7 +15273,6 @@ "version": "1.1.15", "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.15.tgz", "integrity": "sha512-p3EcsicXjit7SaskXHs1hA91QxgTw46Fv6EFKKGS5DRFLD8yKnohjF3hxoju94b/OcMZoQukzpPpBE9uLVKzgQ==", - "dev": true, "license": "MIT", "dependencies": { "which-typed-array": "^1.1.16" @@ -12034,7 +15361,6 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true, "license": "MIT" }, "node_modules/isexe": { @@ -12262,6 +15588,16 @@ "graceful-fs": "^4.1.6" } }, + "node_modules/jsonpointer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/jsonpointer/-/jsonpointer-5.0.1.tgz", + "integrity": "sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/jsx-ast-utils": { "version": "3.3.5", "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.5.tgz", @@ -12278,6 +15614,29 @@ "node": ">=4.0" } }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "peer": true, + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "peer": true, + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, "node_modules/keyv": { "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", @@ -12711,6 +16070,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/long": { + "version": "5.2.5", + "resolved": "https://registry.npmjs.org/long/-/long-5.2.5.tgz", + "integrity": "sha512-e0r9YBBgNCq1D1o5Dp8FMH0N5hsFtXDBiVa0qoJPHpakvZkmDKPRoGffZJII/XsHvj9An9blm+cRJ01yQqU+Dw==", + "license": "Apache-2.0", + "peer": true + }, "node_modules/loose-envify": { "version": "1.4.0", "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz", @@ -12724,6 +16090,13 @@ "loose-envify": "cli.js" } }, + "node_modules/lru_map": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/lru_map/-/lru_map-0.4.1.tgz", + "integrity": "sha512-I+lBvqMMFfqaV8CJCISjI3wbjmwVu/VyOoU7+qtu9d7ioW5klMgsTTiUOUp+DJvfTTzKXoPbyC6YfgkNcyPSOg==", + "license": "MIT", + "peer": true + }, "node_modules/lru-cache": { "version": "11.5.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-11.5.3.tgz", @@ -12773,6 +16146,18 @@ "is-buffer": "~1.1.6" } }, + "node_modules/md5.js": { + "version": "1.3.5", + "resolved": "https://registry.npmjs.org/md5.js/-/md5.js-1.3.5.tgz", + "integrity": "sha512-xitP+WxNPcTTOgnTJcrhM0xvdPepipPSf3I8EIpGKeFLjt3PlJLIDG3u8EX53ZIubkb+5U2+3rELYpEhHhzdkg==", + "license": "MIT", + "peer": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "node_modules/media-typer": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-1.1.0.tgz", @@ -12822,6 +16207,27 @@ "node": ">=8.6" } }, + "node_modules/miller-rabin": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/miller-rabin/-/miller-rabin-4.0.1.tgz", + "integrity": "sha512-115fLhvZVqWwHPbClyntxEVfVDfl9DLLTuJvq3g2O/Oxi8AiNouAHvDSzHS0viUJc+V5vm3eq91Xwqn9dp4jRA==", + "license": "MIT", + "peer": true, + "dependencies": { + "bn.js": "^4.0.0", + "brorand": "^1.0.1" + }, + "bin": { + "miller-rabin": "bin/miller-rabin" + } + }, + "node_modules/miller-rabin/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT", + "peer": true + }, "node_modules/mime-db": { "version": "1.54.0", "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", @@ -13019,6 +16425,13 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nan": { + "version": "2.28.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.28.0.tgz", + "integrity": "sha512-fTsDz99OTq2sVePhGdp4qQhggZFtKr64ZNVyVajRKtMOkJxYekplBh577PiJB12v/D3s2E5cGtOI45LWp6rnLQ==", + "license": "MIT", + "peer": true + }, "node_modules/nanoid": { "version": "3.3.20", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.20.tgz", @@ -13060,6 +16473,160 @@ "dev": true, "license": "MIT" }, + "node_modules/near-abi": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/near-abi/-/near-abi-0.2.0.tgz", + "integrity": "sha512-kCwSf/3fraPU2zENK18sh+kKG4uKbEUEQdyWQkmW8ZofmLarObIz2+zAYjA1teDZLeMvEQew3UysnPDXgjneaA==", + "license": "(MIT AND Apache-2.0)", + "peer": true, + "dependencies": { + "@types/json-schema": "^7.0.11" + } + }, + "node_modules/near-api-js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/near-api-js/-/near-api-js-5.1.1.tgz", + "integrity": "sha512-h23BGSKxNv8ph+zU6snicstsVK1/CTXsQz4LuGGwoRE24Hj424nSe4+/1tzoiC285Ljf60kPAqRCmsfv9etF2g==", + "license": "(MIT AND Apache-2.0)", + "peer": true, + "dependencies": { + "@near-js/accounts": "1.4.1", + "@near-js/crypto": "1.4.2", + "@near-js/keystores": "0.2.2", + "@near-js/keystores-browser": "0.2.2", + "@near-js/keystores-node": "0.1.2", + "@near-js/providers": "1.0.3", + "@near-js/signers": "0.2.2", + "@near-js/transactions": "1.3.3", + "@near-js/types": "0.3.1", + "@near-js/utils": "1.1.0", + "@near-js/wallet-account": "1.3.3", + "@noble/curves": "1.8.1", + "borsh": "1.0.0", + "depd": "2.0.0", + "http-errors": "1.7.2", + "near-abi": "0.2.0", + "node-fetch": "2.6.7" + } + }, + "node_modules/near-api-js/node_modules/@noble/curves": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.1.tgz", + "integrity": "sha512-warwspo+UYUPep0Q+vtdVB4Ugn8GGQj8iyB3gnRWsztmUHTI3S1nhdiWNsPUGL0vud7JlRRk1XEu7Lq1KGTnMQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "@noble/hashes": "1.7.1" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/near-api-js/node_modules/@noble/hashes": { + "version": "1.7.1", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.1.tgz", + "integrity": "sha512-B8XBPsn4vT/KJAGqDzbwztd+6Yte3P4V7iafm24bxgDe/mlRuK6xmWPuCNrKt2vDafZ8MfJLlchDG/vYafQEjQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/near-api-js/node_modules/borsh": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/borsh/-/borsh-1.0.0.tgz", + "integrity": "sha512-fSVWzzemnyfF89EPwlUNsrS5swF5CrtiN4e+h0/lLf4dz2he4L3ndM20PS9wj7ICSkXJe/TQUHdaPTq15b1mNQ==", + "license": "Apache-2.0", + "peer": true + }, + "node_modules/near-api-js/node_modules/http-errors": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-1.7.2.tgz", + "integrity": "sha512-uUQBt3H/cSIVfch6i1EuPNy/YsRSOUBXTVfZ+yR7Zjez3qjBz6i9+i4zjNaoqcoFVI4lQJ5plg63TvGfRSDCRg==", + "license": "MIT", + "peer": true, + "dependencies": { + "depd": "~1.1.2", + "inherits": "2.0.3", + "setprototypeof": "1.1.1", + "statuses": ">= 1.5.0 < 2", + "toidentifier": "1.0.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/near-api-js/node_modules/http-errors/node_modules/depd": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/depd/-/depd-1.1.2.tgz", + "integrity": "sha512-7emPTl6Dpo6JRXOXjLRxck+FlLRX5847cLKEn00PLAgc3g2hTZZgr+e4c2v6QpSmLeFP3n5yUo7ft6avBK/5jQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/near-api-js/node_modules/inherits": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.3.tgz", + "integrity": "sha512-x00IRNXNy63jwGkJmzPigoySHbaqpNuzKbBOmzK+g2OdZpQ9w+sxCN+VSB3ja7IAge2OP2qpfxTjeNcyjmW1uw==", + "license": "ISC", + "peer": true + }, + "node_modules/near-api-js/node_modules/node-fetch": { + "version": "2.6.7", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.7.tgz", + "integrity": "sha512-ZjMPFEfVx5j+y2yF35Kzx5sF7kDzxuDj6ziH4FFbOp87zKDZNx8yExJIb05OGF4Nlt9IHFIMBkRl41VdvcNdbQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/near-api-js/node_modules/setprototypeof": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.1.1.tgz", + "integrity": "sha512-JvdAWfbXeIGaZ9cILp38HntZSFSo3mWg6xGcJJsd+d4aRMOqauag1C63dJfDw7OaMYwEbHMOxEZ1lqVRYP2OAw==", + "license": "ISC", + "peer": true + }, + "node_modules/near-api-js/node_modules/statuses": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-1.5.0.tgz", + "integrity": "sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/near-api-js/node_modules/toidentifier": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.0.tgz", + "integrity": "sha512-yaOH/Pk/VEhBWWTlhI+qXxDFXlejDGcQipMlyxda9nthulaxLZUNcUqFxokp0vcYnvteJln5FNQDRrxj3YcbVw==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.6" + } + }, "node_modules/negotiator": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-1.0.0.tgz", @@ -13150,6 +16717,13 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-3.2.1.tgz", + "integrity": "sha512-mmcei9JghVNDYydghQmeDX8KoAm0FAiYyIcUt/N4nhyAipB17pllZQDOJD2fotxABnt4Mdz+dKTO7eftLg4d0A==", + "license": "MIT", + "peer": true + }, "node_modules/node-domexception": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", @@ -13236,6 +16810,16 @@ "integrity": "sha512-5WUyunoPMsvvEhS8AxHtRzP+oA8UCkJ7YRxatWKjngndhDGLiqEVAQKWjFAiAiuL8zMRGzGSJxFnLetoa43qGQ==", "license": "MIT" }, + "node_modules/nofilter": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/nofilter/-/nofilter-3.1.0.tgz", + "integrity": "sha512-l2NNj07e9afPnhAhvgVrCD/oy2Ai1yfLpuo3EpiO1jFTsB4sFz6oIfAfSZyQzVpkZQ9xS8ZS5g1jCBgq4Hwo0g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=12.19" + } + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", @@ -13670,6 +17254,23 @@ "node": ">=6" } }, + "node_modules/parse-asn1": { + "version": "5.1.9", + "resolved": "https://registry.npmjs.org/parse-asn1/-/parse-asn1-5.1.9.tgz", + "integrity": "sha512-fIYNuZ/HastSb80baGOuPRo1O9cf4baWw5WsAp7dBuUzeTD/BoaG8sVTdlPFksBE2lF21dN+A1AnrpIjSWqHHg==", + "license": "ISC", + "peer": true, + "dependencies": { + "asn1.js": "^4.10.1", + "browserify-aes": "^1.2.0", + "evp_bytestokey": "^1.0.3", + "pbkdf2": "^3.1.5", + "safe-buffer": "^5.2.1" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/parse-json": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz", @@ -13746,6 +17347,24 @@ "integrity": "sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==", "license": "MIT" }, + "node_modules/pbkdf2": { + "version": "3.1.6", + "resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.6.tgz", + "integrity": "sha512-BT6eelPB1EyGHo8pC0o9Bl6k6SYVhKO1jEbd3lcTrtr7XHdjP8BW1YpfCV3G9Kwkxgattk+S5q2/RvuttCsS1g==", + "license": "MIT", + "peer": true, + "dependencies": { + "create-hash": "^1.2.0", + "create-hmac": "^1.1.7", + "ripemd160": "^2.0.3", + "safe-buffer": "^5.2.1", + "sha.js": "^2.4.12", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/picocolors": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.1.1.tgz", @@ -13843,7 +17462,6 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", - "dev": true, "license": "MIT", "engines": { "node": ">= 0.4" @@ -14101,6 +17719,13 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT", + "peer": true + }, "node_modules/process-warning": { "version": "5.1.0", "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-5.1.0.tgz", @@ -14158,6 +17783,31 @@ "dev": true, "license": "MIT" }, + "node_modules/protobufjs": { + "version": "7.5.5", + "resolved": "https://registry.npmjs.org/protobufjs/-/protobufjs-7.5.5.tgz", + "integrity": "sha512-3wY1AxV+VBNW8Yypfd1yQY9pXnqTAN+KwQxL8iYm3/BjKYMNg4i0owhEe26PWDOMaIrzeeF98Lqd5NGz4omiIg==", + "hasInstallScript": true, + "license": "BSD-3-Clause", + "peer": true, + "dependencies": { + "@protobufjs/aspromise": "^1.1.2", + "@protobufjs/base64": "^1.1.2", + "@protobufjs/codegen": "^2.0.4", + "@protobufjs/eventemitter": "^1.1.0", + "@protobufjs/fetch": "^1.1.0", + "@protobufjs/float": "^1.0.2", + "@protobufjs/inquire": "^1.1.0", + "@protobufjs/path": "^1.1.2", + "@protobufjs/pool": "^1.1.0", + "@protobufjs/utf8": "^1.1.0", + "@types/node": ">=13.7.0", + "long": "^5.0.0" + }, + "engines": { + "node": ">=12.0.0" + } + }, "node_modules/proxy-addr": { "version": "2.0.7", "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", @@ -14186,6 +17836,28 @@ "node": ">=10" } }, + "node_modules/public-encrypt": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/public-encrypt/-/public-encrypt-4.0.3.tgz", + "integrity": "sha512-zVpa8oKZSz5bTMTFClc1fQOnyyEzpl5ozpi1B5YcvBrdohMjH2rfsBtyXcuNuwjsDIXmBYlF2N5FlJYhR29t8Q==", + "license": "MIT", + "peer": true, + "dependencies": { + "bn.js": "^4.1.0", + "browserify-rsa": "^4.0.0", + "create-hash": "^1.1.0", + "parse-asn1": "^5.0.0", + "randombytes": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/public-encrypt/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT", + "peer": true + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -14196,6 +17868,16 @@ "node": ">=6" } }, + "node_modules/pushdata-bitcoin": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/pushdata-bitcoin/-/pushdata-bitcoin-1.0.1.tgz", + "integrity": "sha512-hw7rcYTJRAl4olM8Owe8x0fBuJJ+WGbMhQuLWOXEMN3PxPCKQHRkhfL+XG0+iXUmSHjkMmb3Ba55Mt21cZc9kQ==", + "license": "MIT", + "peer": true, + "dependencies": { + "bitcoin-ops": "^1.3.0" + } + }, "node_modules/qrcode": { "version": "1.5.3", "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", @@ -14506,6 +18188,17 @@ "safe-buffer": "^5.1.0" } }, + "node_modules/randomfill": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/randomfill/-/randomfill-1.0.4.tgz", + "integrity": "sha512-87lcbR8+MhcWcUiQ+9e+Rwx8MyR2P7qnt15ynUlbm3TU/fjbgz4GsvfSUDTemtCCtVCqb4ZcEFlyPNTh9bBTLw==", + "license": "MIT", + "peer": true, + "dependencies": { + "randombytes": "^2.0.5", + "safe-buffer": "^5.1.0" + } + }, "node_modules/range-parser": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", @@ -14659,6 +18352,21 @@ "pify": "^2.3.0" } }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -14865,30 +18573,105 @@ "integrity": "sha512-oMA2dcrw6u0YfxJQXm342bFKX/E4sG9rbTzO9ptUcR/e8A33cHuvStiYOwH7fszkZlZ1z/ta9AAoPk2F4qIOHA==", "license": "MIT", "dependencies": { - "onetime": "^7.0.0", - "signal-exit": "^4.1.0" + "onetime": "^7.0.0", + "signal-exit": "^4.1.0" + }, + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/rettime": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.11.11.tgz", + "integrity": "sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==", + "license": "MIT" + }, + "node_modules/reusify": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", + "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "license": "MIT", + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/ripemd160": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.3.tgz", + "integrity": "sha512-5Di9UC0+8h1L6ZD2d7awM7E/T4uA1fJRlx6zk/NvdCCVEoAnFqvHmCuNeIKoCeIixBX/q8uM+6ycDvF8woqosA==", + "license": "MIT", + "peer": true, + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/ripple-address-codec": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ripple-address-codec/-/ripple-address-codec-5.0.1.tgz", + "integrity": "sha512-JQHLKuVJV8lv9Qobmn4aUM2Dpv9WRRLKnNWfM8tN02fAbUtG8mUPsu9q9UYX8P76G4qzytEc5ZKMp/3JggNYmw==", + "license": "ISC", + "peer": true, + "dependencies": { + "@scure/base": "^2.0.0", + "@xrplf/isomorphic": "^1.0.2" + }, + "engines": { + "node": ">= 18" + } + }, + "node_modules/ripple-address-codec/node_modules/@scure/base": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-2.4.0.tgz", + "integrity": "sha512-thZ1TuJwFwBblOhgsjDKvvGirBxNp+wSvY/DR6tJBJOTDhdAAcHJ8Vbr2eFnqaxeca4+t0i9KBf+uHYGWwZORg==", + "license": "MIT", + "peer": true, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ripple-binary-codec": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/ripple-binary-codec/-/ripple-binary-codec-2.10.0.tgz", + "integrity": "sha512-H4pBm2WdOMmVnA0HGUdWAShmUFdmqavWAsi2WlMCzOknc1XjrZssa/RxEyziaPww8MXe1OpgGU7uCs8G2nYe/A==", + "license": "ISC", + "peer": true, + "dependencies": { + "@xrplf/isomorphic": "^1.0.2", + "bignumber.js": "^10.0.2", + "ripple-address-codec": "^5.0.1" }, "engines": { - "node": ">=18" - }, - "funding": { - "url": "https://github.com/sponsors/sindresorhus" + "node": ">= 18" } }, - "node_modules/rettime": { - "version": "0.11.11", - "resolved": "https://registry.npmjs.org/rettime/-/rettime-0.11.11.tgz", - "integrity": "sha512-ILJRqVWBCTlg9r42fFgwVZx1gnFAcQF8mRoMkbgQfIrjEDf9nbBFDFx00oloOa+Q869FUtaYDXZvEfnecQSCoQ==", - "license": "MIT" - }, - "node_modules/reusify": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.1.0.tgz", - "integrity": "sha512-g6QUff04oZpHs0eG5p83rFLhHeV00ug/Yf9nZM6fLeUrPguBTkTQOdpAWWspMh55TZfVQDPaN3NQJfbVRAxdIw==", + "node_modules/ripple-binary-codec/node_modules/bignumber.js": { + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-10.0.2.tgz", + "integrity": "sha512-E8Wp9O06QA6lneJ4aRUXKYf/1GIomqUEmUMwtIOMtDxf1U52ffJY+y7JBk/8wRafA8qOIqLnXQGqonYXZdBnFQ==", "license": "MIT", + "peer": true + }, + "node_modules/ripple-keypairs": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ripple-keypairs/-/ripple-keypairs-2.0.0.tgz", + "integrity": "sha512-b5rfL2EZiffmklqZk1W+dvSy97v3V/C7936WxCCgDynaGPp7GE6R2XO7EU9O2LlM/z95rj870IylYnOQs+1Rag==", + "license": "ISC", + "peer": true, + "dependencies": { + "@noble/curves": "^1.0.0", + "@xrplf/isomorphic": "^1.0.0", + "ripple-address-codec": "^5.0.0" + }, "engines": { - "iojs": ">=1.0.0", - "node": ">=0.10.0" + "node": ">= 16" } }, "node_modules/rolldown": { @@ -15227,7 +19010,6 @@ "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, "license": "MIT", "dependencies": { "define-data-property": "^1.1.4", @@ -15278,6 +19060,27 @@ "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", "license": "ISC" }, + "node_modules/sha.js": { + "version": "2.4.12", + "resolved": "https://registry.npmjs.org/sha.js/-/sha.js-2.4.12.tgz", + "integrity": "sha512-8LzC5+bvI45BjpfXU8V5fdU2mfeKiQe1D1gIMn7XUlF3OTUrpdJpPPH4EMAnF0DsHHdSZqCdSss5qCmJKuiO3w==", + "license": "(MIT AND BSD-3-Clause)", + "peer": true, + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.0" + }, + "bin": { + "sha.js": "bin.js" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, "node_modules/sha1": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/sha1/-/sha1-1.1.1.tgz", @@ -15562,6 +19365,17 @@ "integrity": "sha512-MseHyi2+E/hBRqdOi5COy6wZ7j7DxXRz9NkseavNYSvvWC06D8a5cidVZX3tcG5eCW3NIyVU4zT63hw0Q486jw==", "license": "MIT" }, + "node_modules/smart-buffer": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz", + "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">= 6.0.0", + "npm": ">= 3.0.0" + } + }, "node_modules/smol-toml": { "version": "1.9.0", "resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.9.0.tgz", @@ -15574,6 +19388,58 @@ "url": "https://github.com/sponsors/cyyynthia" } }, + "node_modules/socket.io": { + "version": "4.8.3", + "resolved": "https://registry.npmjs.org/socket.io/-/socket.io-4.8.3.tgz", + "integrity": "sha512-2Dd78bqzzjE6KPkD5fHZmDAKRNe3J15q+YHDrIsy9WEkqttc7GY+kT9OBLSMaPbQaEd0x1BjcmtMtXkfpc+T5A==", + "dev": true, + "license": "MIT", + "dependencies": { + "accepts": "~1.3.4", + "base64id": "~2.0.0", + "cors": "~2.8.5", + "debug": "~4.4.1", + "engine.io": "~6.6.0", + "socket.io-adapter": "~2.5.2", + "socket.io-parser": "~4.2.4" + }, + "engines": { + "node": ">=10.2.0" + } + }, + "node_modules/socket.io-adapter": { + "version": "2.5.8", + "resolved": "https://registry.npmjs.org/socket.io-adapter/-/socket.io-adapter-2.5.8.tgz", + "integrity": "sha512-6Oy52pbg+kvdCVvjcN+FnY7BvxZ7cIHNScbvztT/It5d0vbwoJoVZmF2gjJmnV0/4WlXRfG15zc45ySk9Ah8bw==", + "dev": true, + "license": "MIT", + "dependencies": { + "debug": "~4.4.1", + "ws": "~8.21.0" + } + }, + "node_modules/socket.io-adapter/node_modules/ws": { + "version": "8.21.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.21.3.tgz", + "integrity": "sha512-201TZ/kPWxoPr/OKWjquZR1SWKXcvxdH+e1xrx89b3YbmzLMFCLfnaG1HFIgWzJOEWZ7MvpK++odZufgYR50Rw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/socket.io-client": { "version": "4.8.3", "resolved": "https://registry.npmjs.org/socket.io-client/-/socket.io-client-4.8.3.tgz", @@ -15602,6 +19468,83 @@ "node": ">=10.0.0" } }, + "node_modules/socket.io/node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dev": true, + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socket.io/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/socks": { + "version": "2.8.9", + "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.9.tgz", + "integrity": "sha512-LJhUYUvItdQ0LkJTmPeaEObWXAqFyfmP85x0tch/ez9cahmhlBBLbIqDFnvBnUJGagb0JbIQrkBs1wJ+yRYpEw==", + "license": "MIT", + "peer": true, + "dependencies": { + "ip-address": "^10.1.1", + "smart-buffer": "^4.2.0" + }, + "engines": { + "node": ">= 10.0.0", + "npm": ">= 3.0.0" + } + }, + "node_modules/socks-proxy-agent": { + "version": "8.0.5", + "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-8.0.5.tgz", + "integrity": "sha512-HehCEsotFqbPW9sJ8WVYB6UbmIMv7kUUORIF2Nncq4VQvBfNBLibW9YZR5dlYCSUhwcD628pRllm7n+E+YTzJw==", + "license": "MIT", + "peer": true, + "dependencies": { + "agent-base": "^7.1.2", + "debug": "^4.3.4", + "socks": "^2.8.3" + }, + "engines": { + "node": ">= 14" + } + }, "node_modules/sonic-boom": { "version": "4.2.1", "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.1.tgz", @@ -15687,6 +19630,17 @@ "node": ">= 0.4" } }, + "node_modules/stream-browserify": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", + "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", + "license": "MIT", + "peer": true, + "dependencies": { + "inherits": "~2.0.4", + "readable-stream": "^3.5.0" + } + }, "node_modules/stream-chain": { "version": "2.2.5", "resolved": "https://registry.npmjs.org/stream-chain/-/stream-chain-2.2.5.tgz", @@ -15708,6 +19662,16 @@ "integrity": "sha512-vMgjE/GGEPEFnhFub6pa4FmJBRBVOLpIII2hvCZ8Kzb7K0hlHo7mQv6xYrBvCL2LtAIBwFUK8wvuJgTVSQ5MFQ==", "license": "MIT" }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "peer": true, + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, "node_modules/string-width": { "version": "7.2.0", "resolved": "https://registry.npmjs.org/string-width/-/string-width-7.2.0.tgz", @@ -16176,6 +20140,31 @@ "integrity": "sha512-+FbBPE1o9QAYvviau/qC5SE3caw21q3xkvWKBtja5vgqOWIHHJ3ioaq1VPfn/Szqctz2bU/oYeKd9/z5BL+PVg==", "license": "MIT" }, + "node_modules/tiny-secp256k1": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/tiny-secp256k1/-/tiny-secp256k1-1.1.7.tgz", + "integrity": "sha512-eb+F6NabSnjbLwNoC+2o5ItbmP1kg7HliWue71JgLegQt6A5mTN8YbvTLCazdlg6e5SV6A+r8OGvZYskdlmhqQ==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "bindings": "^1.3.0", + "bn.js": "^4.11.8", + "create-hmac": "^1.1.7", + "elliptic": "^6.4.0", + "nan": "^2.13.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/tiny-secp256k1/node_modules/bn.js": { + "version": "4.12.5", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.5.tgz", + "integrity": "sha512-3aRg6/JxfffFD+OlOjOFR3Vo79l39ooBTFucxx+MT3dhCtzn3EmiUPQo+6/OZuI2jbXi3YKgmiTFBgChQMwIRQ==", + "license": "MIT", + "peer": true + }, "node_modules/tinybench": { "version": "6.2.1", "resolved": "https://registry.npmjs.org/tinybench/-/tinybench-6.2.1.tgz", @@ -16262,6 +20251,21 @@ "integrity": "sha512-uiHN8PIB1VmWyS98eZYja4xzlYqeFZVjb4OuYlJQnZAuJhMw4PbKQOKgHKhBdJR3FE/t5mUQ1Kd80++B+qhD1Q==", "license": "MIT" }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "peer": true, + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + } + }, "node_modules/to-regex-range": { "version": "5.0.1", "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", @@ -16283,6 +20287,13 @@ "node": ">=0.6" } }, + "node_modules/toml": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/toml/-/toml-3.0.0.tgz", + "integrity": "sha512-y/mWCZinnvxjTKYhJ+pYxwD0mRLVvOtdS2Awbgxln6iEnt4rk0yBxeSBHkGJcPucRiG0e55mwWp+g/05rsrd6w==", + "license": "MIT", + "peer": true + }, "node_modules/tough-cookie": { "version": "6.0.1", "resolved": "https://registry.npmjs.org/tough-cookie/-/tough-cookie-6.0.1.tgz", @@ -16453,7 +20464,6 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, "license": "MIT", "dependencies": { "call-bound": "^1.0.3", @@ -16527,11 +20537,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/typeforce": { + "version": "1.18.0", + "resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz", + "integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g==", + "license": "MIT", + "peer": true + }, "node_modules/typescript": { "version": "5.9.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.9.3.tgz", "integrity": "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw==", - "dev": true, "license": "Apache-2.0", "bin": { "tsc": "bin/tsc", @@ -16565,6 +20581,59 @@ "typescript": ">=4.8.4 <6.1.0" } }, + "node_modules/ua-is-frozen": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/ua-is-frozen/-/ua-is-frozen-0.1.2.tgz", + "integrity": "sha512-RwKDW2p3iyWn4UbaxpP2+VxwqXh0jpvdxsYpZ5j/MLLiQOfbsV5shpgQiw93+KMYQPcteeMQ289MaAFzs3G9pw==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + } + ], + "license": "MIT", + "peer": true + }, + "node_modules/ua-parser-js": { + "version": "2.0.10", + "resolved": "https://registry.npmjs.org/ua-parser-js/-/ua-parser-js-2.0.10.tgz", + "integrity": "sha512-t+3Ktbq0Ies2vaSezfOaWiolH4OigQIO1dk+1xDpOydB1COVPocVYOrEV5rqZ0kFY9XYG1v9LutCyMgYBpABcw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/ua-parser-js" + }, + { + "type": "paypal", + "url": "https://paypal.me/faisalman" + }, + { + "type": "github", + "url": "https://github.com/sponsors/faisalman" + } + ], + "license": "AGPL-3.0-or-later", + "peer": true, + "dependencies": { + "detect-europe-js": "^0.1.2", + "is-standalone-pwa": "^0.1.1", + "ua-is-frozen": "^0.1.2" + }, + "bin": { + "ua-parser-js": "script/cli.js" + }, + "engines": { + "node": "*" + } + }, "node_modules/ufo": { "version": "1.6.4", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.4.tgz", @@ -16583,6 +20652,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/uint8array-tools": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.8.tgz", + "integrity": "sha512-xS6+s8e0Xbx++5/0L+yyexukU7pz//Yg6IHg3BKhXotg1JcYtgxVcUctQ0HxLByiJzpAkNFawz1Nz5Xadzo82g==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=14.0.0" + } + }, "node_modules/uint8arrays": { "version": "3.1.1", "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", @@ -16861,6 +20940,39 @@ "punycode": "^2.1.0" } }, + "node_modules/urijs": { + "version": "1.19.11", + "resolved": "https://registry.npmjs.org/urijs/-/urijs-1.19.11.tgz", + "integrity": "sha512-HXgFDgDommxn5/bIv0cnQZsPhHDA90NPHD6+c/v21U5+Sx5hoP8+dP9IZXBU1gIfvdRfhG8cel9QNPeionfcCQ==", + "license": "MIT", + "peer": true + }, + "node_modules/usb": { + "version": "2.18.0", + "resolved": "https://registry.npmjs.org/usb/-/usb-2.18.0.tgz", + "integrity": "sha512-klAJPUTaSxRvTgrIh7om6UTrgRxdzioD+rJc/MaoiN8+OGdqRzai39tR00asnoCesPNikItWB9zBZoo0pJsWaA==", + "hasInstallScript": true, + "license": "MIT", + "peer": true, + "dependencies": { + "@types/w3c-web-usb": "^1.0.6", + "node-addon-api": "^8.0.0", + "node-gyp-build": "^4.5.0" + }, + "engines": { + "node": ">=12.22.0 <13.0 || >=14.17.0" + } + }, + "node_modules/usb/node_modules/node-addon-api": { + "version": "8.9.2", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-8.9.2.tgz", + "integrity": "sha512-VijLXbi3UACN69I0JVXJsX4tjACjNoQDgv2gTF6sx2wWEi8tkSg2eX8p5gSIFi8z2+DL3oHmY6OyKce38SDolg==", + "license": "MIT", + "peer": true, + "engines": { + "node": "^18 || ^20 || >= 21" + } + }, "node_modules/use-callback-ref": { "version": "1.3.3", "resolved": "https://registry.npmjs.org/use-callback-ref/-/use-callback-ref-1.3.3.tgz", @@ -16982,6 +21094,16 @@ } } }, + "node_modules/varuint-bitcoin": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-2.0.0.tgz", + "integrity": "sha512-6QZbU/rHO2ZQYpWFDALCDSRsXbAs1VOEmXAxtbtjLtKuMJ/FQ8YbhfxlaiKv5nklci0M6lZtlZyxo9Q+qNnyog==", + "license": "MIT", + "peer": true, + "dependencies": { + "uint8array-tools": "^0.0.8" + } + }, "node_modules/vary": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", @@ -17440,7 +21562,6 @@ "version": "1.1.20", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", - "dev": true, "license": "MIT", "dependencies": { "available-typed-arrays": "^1.0.7", @@ -17471,6 +21592,16 @@ "node": ">=20.11" } }, + "node_modules/wif": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/wif/-/wif-5.0.0.tgz", + "integrity": "sha512-iFzrC/9ne740qFbNjTZ2FciSRJlHIXoxqk/Y5EnE08QOXu1WjJyCCswwDTYbohAOEnlCtLaAAQBhyaLRFh2hMA==", + "license": "MIT", + "peer": true, + "dependencies": { + "bs58check": "^4.0.0" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -17590,6 +21721,48 @@ "node": ">=0.4.0" } }, + "node_modules/xrpl": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/xrpl/-/xrpl-4.4.3.tgz", + "integrity": "sha512-vi2OjuNkiaP8nv1j+nqHp8GZwwEjO6Y8+j/OuVMg6M4LwXEwyHdIj33dlg7cyY1Lw5+jb9HqFOQvABhaywVbTQ==", + "license": "ISC", + "peer": true, + "dependencies": { + "@scure/bip32": "^1.3.1", + "@scure/bip39": "^1.2.1", + "@xrplf/isomorphic": "^1.0.1", + "@xrplf/secret-numbers": "^2.0.0", + "bignumber.js": "^9.0.0", + "eventemitter3": "^5.0.1", + "fast-json-stable-stringify": "^2.1.0", + "ripple-address-codec": "^5.0.0", + "ripple-binary-codec": "^2.5.0", + "ripple-keypairs": "^2.0.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/xrpl/node_modules/bignumber.js": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/bignumber.js/-/bignumber.js-9.3.1.tgz", + "integrity": "sha512-Ko0uX15oIUS7wJ3Rb30Fs6SkVbLmPBAKdlm7q9+ak9bbIeFf0MwuBsQV6z7+X768/cHsfg+WlysDWJcmthjsjQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": "*" + } + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "peer": true, + "engines": { + "node": ">=0.4" + } + }, "node_modules/y18n": { "version": "5.0.8", "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz", diff --git a/package.json b/package.json index 4e2e52c..dbb1238 100644 --- a/package.json +++ b/package.json @@ -41,7 +41,9 @@ "autoprefixer": "^10.5.0", "eslint": "^9", "eslint-config-next": "16.2.6", + "fake-indexeddb": "6.2.4", "postcss": "^8.5.14", + "socket.io": "4.8.3", "tailwindcss": "^3.4.19", "typescript": "^5", "vite": "^8.3.4", diff --git a/scripts/realtime-burst.mjs b/scripts/realtime-burst.mjs new file mode 100644 index 0000000..ef5757a --- /dev/null +++ b/scripts/realtime-burst.mjs @@ -0,0 +1,41 @@ +import { createServer } from "node:http"; +import { performance } from "node:perf_hooks"; +import { StrKey } from "@stellar/stellar-sdk"; +import { Server } from "socket.io"; + +const args = process.argv.slice(2); +const option = (name, fallback) => Number(args[args.indexOf(name) + 1]) || fallback; +const port = option("--port", 4310), count = option("--count", 500), rate = option("--rate", 500); +const trader = StrKey.encodeEd25519PublicKey(Buffer.alloc(32, 1)); +let serial = 0, running = false; +const server = createServer((req, res) => { + res.setHeader("Access-Control-Allow-Origin", "*"); + res.setHeader("Content-Type", "application/json"); + const url = new URL(req.url, `http://localhost:${port}`); + if (url.pathname === "/burst") { + void burst(Number(url.searchParams.get("count")) || count, Number(url.searchParams.get("rate")) || rate); + res.end(JSON.stringify({ success: true, data: { running: true } })); + } else if (/^\/api\/v1\/markets\//.test(url.pathname)) { + res.statusCode = 404; + res.end(JSON.stringify({ ok: false, data: null, error: { code: "MARKET_NOT_FOUND", message: "No metadata in the local simulator" } })); + } else res.end(JSON.stringify({ ok: true, data: [], error: null, meta: { page: 1, limit: 12, total: 0, totalPages: 0 } })); +}); +const io = new Server(server, { cors: { origin: "*" } }); +async function burst(n, perSecond) { + if (running) return; + running = true; + const started = performance.now(); + for (let i = 0; i < n; i++) { + const due = started + i * 1000 / perSecond; + if (due > performance.now()) await new Promise(resolve => setTimeout(resolve, due - performance.now())); + serial++; + io.emit("TRADE_EXECUTED", { eventId: `burst-${String(serial).padStart(12, "0")}`, onChainMarketId: "7", marketId: null, + marketQuestion: "Will the test market resolve YES?", trader, direction: serial % 2 ? "YES" : "NO", isBuy: true, + collateralRaw: "10000000", sharesRaw: "15873015", feeRaw: "200000", priceYesBps: 5000 + serial % 1000, + txHash: serial.toString(16).padStart(64, "0"), ledger: 100000 + serial, closedAt: new Date().toISOString() }); + } + running = false; + console.info(`Emitted ${n} trades at ${perSecond}/s`); +} +if (!args.includes("--manual")) io.once("connection", () => setTimeout(() => void burst(count, rate), 2000)); +server.listen(port, "127.0.0.1", () => console.info(`Realtime simulator: http://localhost:${port}; trader ${trader}`));