From 56fcd1eb19f42946fa7eb4c27e13c9b4d5b49d70 Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 18:26:04 +0100 Subject: [PATCH 01/24] feat: add tournament contract --- contracts/tournament.clar | 345 ++++++++++++++++++++++++++++++++++++++ 1 file changed, 345 insertions(+) create mode 100644 contracts/tournament.clar diff --git a/contracts/tournament.clar b/contracts/tournament.clar new file mode 100644 index 0000000..256b0d2 --- /dev/null +++ b/contracts/tournament.clar @@ -0,0 +1,345 @@ +;; ERRORS +(define-constant ERR_UNAUTHORIZED u200) +(define-constant ERR_INVALID_PARAMS u201) +(define-constant ERR_TOURNAMENT_NOT_FOUND u202) +(define-constant ERR_TOURNAMENT_NOT_OPEN u203) +(define-constant ERR_TOURNAMENT_FULL u204) +(define-constant ERR_ALREADY_REGISTERED u205) +(define-constant ERR_NOT_CREATOR u206) +(define-constant ERR_ALREADY_STARTED u207) +(define-constant ERR_NOT_IN_PROGRESS u208) +(define-constant ERR_INVALID_WINNER u209) +(define-constant ERR_MATCH_NOT_FOUND u210) +(define-constant ERR_MATCH_ALREADY_COMPLETED u211) + + +;; TRAITS +(use-trait tournament-game-trait .tournament-game-trait.tournament-game-trait) + + +;; CONSTANTS + +(define-constant THIS_CONTRACT (as-contract tx-sender)) + +;; status enum : 0=open, 1=in-progress, 2=completed, 3=cancelled +(define-constant STATUS_OPEN u0) +(define-constant STATUS_IN_PROGRESS u1) +(define-constant STATUS_COMPLETED u2) +(define-constant STATUS_CANCELLED u3) + + +;; GLOBAL VARIABLES +(define-data-var latest-tournament-id uint u0) + + +;; MAPPINGS + +;; tournaments: id -> metadata +(define-map tournaments + uint + { + creator: principal, + name: (string-utf8 50), + entry-fee: uint, + max-players: uint, + prize-pool: uint, + status: uint, + start-time: (optional uint), + winner: (optional principal), + current-round: uint + } +) + +;; player registry and ordering per tournament +(define-map tournament-participants + { tournament-id: uint, player: principal } + { registration-time: uint, bracket-position: uint, eliminated: bool } +) + +;; quick lookup by index (1-based) +(define-map tournament-player-index + { tournament-id: uint, index: uint } + principal +) + +;; count of players joined per tournament +(define-map tournament-player-count + uint ;; tournament-id + uint ;; count +) + +;; matches per round +(define-map tournament-matches + { tournament-id: uint, round: uint, match-number: uint } + { game-id: (optional uint), player1: (optional principal), player2: (optional principal), winner: (optional principal), completed: bool } +) + + +;; PUBLIC FUNCTIONS + +;; Create a new tournament, returns tournament-id +(define-public (create-tournament (name (string-utf8 50)) (entry-fee uint) (max-players uint)) + (begin + + ;; assert conditions for successful creation of tournament + (asserts! (> entry-fee u0) (err ERR_INVALID_PARAMS)) + (asserts! (is-power-of-two max-players) (err ERR_INVALID_PARAMS)) + + ;; create new tournament + (let ( + (tid (var-get latest-tournament-id)) + (meta { + creator: tx-sender, + name: name, + entry-fee: entry-fee, + max-players: max-players, + prize-pool: u0, + status: STATUS_OPEN, + start-time: none, + winner: none, + current-round: u0 + }) + ) + ;; update mappings in state with new values + (map-set tournaments tid meta) + (set-player-count tid u0) + (var-set latest-tournament-id (+ tid u1)) + + ;; emit event and return new tournament id + (print { action: "create-tournament", id: tid, data: meta }) + (ok tid) + ) + ) +) + +;; Join an open tournament: transfers entry fee and assigns bracket position +(define-public (join-tournament (tid uint)) + ;; fetch tournament data and player count + (let ( + (meta (try! (get-tournament-or-err tid))) + (count (get-player-count tid)) + ) + + ;; assert conditions for successful entry into tournament + (asserts! (is-eq (get status meta) STATUS_OPEN) (err ERR_TOURNAMENT_NOT_OPEN)) + (asserts! (< count (get max-players meta)) (err ERR_TOURNAMENT_FULL)) + (asserts! (is-none (map-get? tournament-participants { tournament-id: tid, player: tx-sender })) (err ERR_ALREADY_REGISTERED)) + + ;; transfer entry fee into this contract to build prize pool + (try! (stx-transfer? (get entry-fee meta) tx-sender THIS_CONTRACT)) + + ;; create participant record, add player entry into pool, assign player to next bracket position + (let ( + (new-index (+ count u1)) + (participant { registration-time: u0, bracket-position: new-index, eliminated: false }) + (updated-meta (merge meta { prize-pool: (+ (get prize-pool meta) (get entry-fee meta)) })) + ) + + ;; save player's registration; save update tournament meta to mapping + (map-set tournament-participants { tournament-id: tid, player: tx-sender } participant) + (set-player-at tid new-index tx-sender) + (set-player-count tid new-index) + (map-set tournaments tid updated-meta) + + ;; emit event for tracking; return new player's bracket position + (print { action: "join-tournament", id: tid, player: tx-sender, position: new-index }) + (ok new-index) + ) + ) +) + +;; Start the tournament: only creator, only when full. Creates round 1 matches automatically. +(define-public (start-tournament (tid uint) (game-contract )) + ;; fetch tournament metdata + (let ( + (meta (try! (get-tournament-or-err tid))) + (count (get-player-count tid)) + ) + + ;; assert tournament is open and ready to start by creator + (asserts! (is-eq (get creator meta) tx-sender) (err ERR_NOT_CREATOR)) + (asserts! (is-eq (get status meta) STATUS_OPEN) (err ERR_TOURNAMENT_NOT_OPEN)) + (asserts! (is-eq count (get max-players meta)) (err ERR_TOURNAMENT_FULL)) + + ;; start tournament with updated values + (let ( + (round u1) + ;; TODO: start time should be stacks-block-timestamp + (started-meta (merge meta { status: STATUS_IN_PROGRESS, start-time: (some u0), current-round: round })) + ) + ;; update tournament mapping + (map-set tournaments tid started-meta) + + ;; create round 1 matches (pair adjacent players: 1v2, 3v4, etc.) + (unwrap-panic (create-round-one-matches tid count game-contract)) + + ;; emit event; return tourname nt id + (print { action: "start-tournament", id: tid, round: round }) + (ok tid) + ) + ) +) + + +;; automatically trigger trustless match result reporting when a tournament game finishes +(define-public (report-game-winner (tid uint) (round uint) (match-number uint) (winner principal)) + ;; fetch tournament data + (let ( + (meta (try! (get-tournament-or-err tid))) + (key { tournament-id: tid, round: round, match-number: match-number }) + (match (unwrap! (map-get? tournament-matches key) (err ERR_MATCH_NOT_FOUND))) + ) + + ;; assert caller is tic-tac-toe contract and verify match details + (asserts! (is-eq contract-caller .tic-tac-toe) (err ERR_UNAUTHORIZED)) + (asserts! (is-eq (get status meta) STATUS_IN_PROGRESS) (err ERR_NOT_IN_PROGRESS)) + (asserts! (is-eq (get completed match) false) (err ERR_MATCH_ALREADY_COMPLETED)) + + ;; fetch match players + (let ( + (p1 (unwrap! (get player1 match) (err ERR_INVALID_PARAMS))) + (p2 (unwrap! (get player2 match) (err ERR_INVALID_PARAMS))) + ) + ;; verify winner is one of the two players + (asserts! (or (is-eq winner p1) (is-eq winner p2)) (err ERR_INVALID_WINNER)) + + ;; update match with winner and mark as completed + (map-set tournament-matches key (merge match { winner: (some winner), completed: true })) + + ;; emit event; return true + (print { action: "match-complete", id: tid, round: round, match: match-number, winner: winner }) + (ok true) + ) + ) +) + +;; ;; Old manual reporting function - DEPRECATED, kept for backwards compatibility but should not be used +;; ;; Report a match result. For phase 1, restrict to creator for manual advancement. +;; (define-public (report-match-result (tid uint) (round uint) (match-number uint) (winner principal)) +;; ;; fetch tournament data +;; (let ( +;; (meta (try! (get-tournament-or-err tid))) +;; (key { tournament-id: tid, round: round, match-number: match-number }) +;; (match (unwrap! (map-get? tournament-matches key) (err ERR_MATCH_NOT_FOUND))) +;; ) + +;; ;; assert only tournament creator can report results! +;; (asserts! (is-eq (get creator meta) tx-sender) (err ERR_UNAUTHORIZED)) +;; (asserts! (is-eq (get status meta) STATUS_IN_PROGRESS) (err ERR_NOT_IN_PROGRESS)) +;; (asserts! (is-eq (get completed match) false) (err ERR_MATCH_ALREADY_COMPLETED)) + +;; ;; get match players +;; (let ( +;; (p1 (default-to 'SP000000000000000000002Q6VF78 (get player1 match))) +;; (p2 (default-to 'SP000000000000000000002Q6VF78 (get player2 match))) +;; ) + +;; ;; confirm winner is verified player; update match record +;; (asserts! (or (is-eq winner p1) (is-eq winner p2)) (err ERR_INVALID_WINNER)) +;; (map-set tournament-matches key (merge match { winner: (some winner), completed: true })) + +;; ;; emit event; return true +;; (print { action: "match-complete", id: tid, round: round, match: match-number, winner: winner }) +;; (ok true) +;; ) +;; ) +;; ) + + +;; PRIVATE HELPER FUNCTIONS + +;; Create round 1 matches by pairing adjacent players and creating tic-tac-toe games +(define-private (create-round-one-matches (tid uint) (player-count uint) (game-contract )) + (begin + ;; For a 4-player tournament: create 2 matches (1v2, 3v4) + ;; For an 8-player tournament: create 4 matches (1v2, 3v4, 5v6, 7v8), etc. + (and (>= player-count u2) (is-ok (create-match tid u1 u1 u1 u2 game-contract))) + (and (>= player-count u4) (is-ok (create-match tid u1 u2 u3 u4 game-contract))) + (and (>= player-count u6) (is-ok (create-match tid u1 u3 u5 u6 game-contract))) + (and (>= player-count u8) (is-ok (create-match tid u1 u4 u7 u8 game-contract))) + (and (>= player-count u10) (is-ok (create-match tid u1 u5 u9 u10 game-contract))) + (and (>= player-count u12) (is-ok (create-match tid u1 u6 u11 u12 game-contract))) + (and (>= player-count u14) (is-ok (create-match tid u1 u7 u13 u14 game-contract))) + (and (>= player-count u16) (is-ok (create-match tid u1 u8 u15 u16 game-contract))) + (and (>= player-count u18) (is-ok (create-match tid u1 u9 u17 u18 game-contract))) + (and (>= player-count u20) (is-ok (create-match tid u1 u10 u19 u20 game-contract))) + (and (>= player-count u22) (is-ok (create-match tid u1 u11 u21 u22 game-contract))) + (and (>= player-count u24) (is-ok (create-match tid u1 u12 u23 u24 game-contract))) + (and (>= player-count u26) (is-ok (create-match tid u1 u13 u25 u26 game-contract))) + (and (>= player-count u28) (is-ok (create-match tid u1 u14 u27 u28 game-contract))) + (and (>= player-count u30) (is-ok (create-match tid u1 u15 u29 u30 game-contract))) + (and (>= player-count u32) (is-ok (create-match tid u1 u16 u31 u32 game-contract))) + (ok true) + ) +) + +;; Create a single match: look up players by position, create game, store match +(define-private (create-match (tid uint) (round uint) (match-num uint) (p1-pos uint) (p2-pos uint) (game-contract )) + ;; fetch player details and game id + (let ( + (p1 (unwrap! (get-player-at tid p1-pos) (err ERR_INVALID_PARAMS))) + (p2 (unwrap! (get-player-at tid p2-pos) (err ERR_INVALID_PARAMS))) + (game-id (try! (contract-call? game-contract create-tournament-game p1 p2 tid round match-num))) + ) + + ;; update tournament mapping with player values + (map-set tournament-matches + { tournament-id: tid, round: round, match-number: match-num } + { game-id: (some game-id), player1: (some p1), player2: (some p2), winner: none, completed: false }) + + ;; emit create match event; return game id + (print { action: "create-match", tournament: tid, round: round, match: match-num, game: game-id, p1: p1, p2: p2 }) + (ok game-id) + ) +) + +;; validates that n (no of players in tournament) is one of: 4, 8, 16, or 32 +(define-private (is-power-of-two (n uint)) + (or (is-eq n u4) (is-eq n u8) (is-eq n u16) (is-eq n u32)) +) + +;; fetches player count for a tournament +(define-private (get-player-count (tid uint)) + (default-to u0 (map-get? tournament-player-count tid)) +) + +;; updates the player count for a tournament +(define-private (set-player-count (tid uint) (n uint)) + (map-set tournament-player-count tid n) +) + +;; looks up which player is at a specific bracket position +(define-private (get-player-at (tid uint) (idx uint)) + (map-get? tournament-player-index { tournament-id: tid, index: idx }) +) + +;; assigns a player to a specific bracket position +(define-private (set-player-at (tid uint) (idx uint) (p principal)) + (map-set tournament-player-index { tournament-id: tid, index: idx } p) +) + +;; attempts to fetch tournament metadata +(define-private (get-tournament-or-err (tid uint)) + (match (map-get? tournaments tid) + meta (ok meta) + (err ERR_TOURNAMENT_NOT_FOUND) + ) +) + + +;; READ-ONLY FUNCTIONS + +;; public getter for tournament metadata +(define-read-only (get-tournament (tid uint)) + (map-get? tournaments tid) +) + +;; checks if a player is registered in a tournament +(define-read-only (get-participant (tid uint) (p principal)) + (map-get? tournament-participants { tournament-id: tid, player: p }) +) + +;; fetches details about a specific match +(define-read-only (get-match (tid uint) (round uint) (match-number uint)) + (map-get? tournament-matches { tournament-id: tid, round: round, match-number: match-number }) +) From b5e01ccced7b023ba67188523ff1b3670bacc34f Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 18:26:31 +0100 Subject: [PATCH 02/24] feat: update tictactoe contract and add tournament game trait --- contracts/tic-tac-toe.clar | 57 +++++++++++++++++++++++++++- contracts/tournament-game-trait.clar | 9 +++++ 2 files changed, 64 insertions(+), 2 deletions(-) create mode 100644 contracts/tournament-game-trait.clar diff --git a/contracts/tic-tac-toe.clar b/contracts/tic-tac-toe.clar index 4bc01e3..a45b714 100644 --- a/contracts/tic-tac-toe.clar +++ b/contracts/tic-tac-toe.clar @@ -5,9 +5,15 @@ (define-constant ERR_GAME_CANNOT_BE_JOINED u103) ;; Error thrown when a game cannot be joined, usually because it already has two players (define-constant ERR_NOT_YOUR_TURN u104) ;; Error thrown when a player tries to make a move when it is not their turn +;; Implement the tournament-game-trait +(impl-trait .tournament-game-trait.tournament-game-trait) + ;; The Game ID to use for the next game (define-data-var latest-game-id uint u0) +;; tournament contract will be set after deployment +(define-data-var tournament-contract (optional principal) none) + (define-map games uint ;; Key (Game ID) { ;; Value (Game Tuple) @@ -18,7 +24,11 @@ bet-amount: uint, board: (list 9 uint), - winner: (optional principal) + winner: (optional principal), + ;; Tournament integration + tournament-id: (optional uint), + tournament-round: (optional uint), + tournament-match: (optional uint) } ) @@ -37,7 +47,10 @@ is-player-one-turn: false, bet-amount: bet-amount, board: game-board, - winner: none + winner: none, + tournament-id: none, + tournament-round: none, + tournament-match: none }) ) @@ -139,6 +152,7 @@ ;; Log the action of a move being made (print {action: "play", data: game-data}) + ;; Return the Game ID of the game (ok game-id) )) @@ -167,6 +181,45 @@ (and (is-eq index-in-range true) (is-eq x-or-o true) empty-spot) )) +;; Admin function to set the tournament contract address after deployment +(define-public (set-tournament-contract (contract principal)) + (begin + ;; In production, add authorization check here (e.g., only contract deployer can call this) + (var-set tournament-contract (some contract)) + (ok true) + ) +) + +;; create a game on behalf of the tournament contract with no bet transfers +(define-public (create-tournament-game (player-one principal) (player-two principal) (tid uint) (round uint) (match-num uint)) + (let ( + (game-id (var-get latest-game-id)) + (starting-board (list u0 u0 u0 u0 u0 u0 u0 u0 u0)) + (game-data { + player-one: player-one, + player-two: (some player-two), + is-player-one-turn: true, + bet-amount: u0, + board: starting-board, + winner: none, + tournament-id: (some tid), + tournament-round: (some round), + tournament-match: (some match-num) + }) + ) + + ;; assert caller is tournament contract + (asserts! (is-eq (some contract-caller) (var-get tournament-contract)) (err u999)) + + ;; update games mapping with tournament game + (map-set games game-id game-data) + (var-set latest-game-id (+ game-id u1)) + + (print { action: "create-tournament-game", game-id: game-id, tournament-id: tid, round: round, match: match-num }) + (ok game-id) + ) +) + ;; Given a board, return true if any possible three-in-a-row line has been completed (define-private (has-won (board (list 9 uint))) (or diff --git a/contracts/tournament-game-trait.clar b/contracts/tournament-game-trait.clar new file mode 100644 index 0000000..6d912fd --- /dev/null +++ b/contracts/tournament-game-trait.clar @@ -0,0 +1,9 @@ +;; Trait for tournament game integration +;; This allows tournament contract to create games without circular dependency + +(define-trait tournament-game-trait + ( + ;; Create a tournament game between two players + (create-tournament-game (principal principal uint uint uint) (response uint uint)) + ) +) From 0ca73bdd4910998836c0ce52c81e877c0750d8af Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 18:27:06 +0100 Subject: [PATCH 03/24] fix: modify stacks wallet connection logic (package version upgrade) --- frontend/hooks/use-stacks.ts | 87 +- frontend/package-lock.json | 3439 +++++++++++++++++++++++++++++++--- frontend/package.json | 2 +- 3 files changed, 3200 insertions(+), 328 deletions(-) diff --git a/frontend/hooks/use-stacks.ts b/frontend/hooks/use-stacks.ts index 23f0b5c..c25de3d 100644 --- a/frontend/hooks/use-stacks.ts +++ b/frontend/hooks/use-stacks.ts @@ -2,8 +2,11 @@ import { createNewGame, joinGame, Move, play } from "@/lib/contract"; import { getStxBalance } from "@/lib/stx-utils"; import { AppConfig, + connect, + disconnect, + isConnected, + getLocalStorage, openContractCall, - showConnect, type UserData, UserSession, } from "@stacks/connect"; @@ -15,26 +18,48 @@ const appDetails = { icon: "https://cryptologos.cc/logos/stacks-stx-logo.png", }; -const appConfig = new AppConfig(["store_write"]); +const appConfig = new AppConfig(["store_write", "publish_data"]); const userSession = new UserSession({ appConfig }); export function useStacks() { const [userData, setUserData] = useState(null); const [stxBalance, setStxBalance] = useState(0); + const [userAddress, setUserAddress] = useState(null); - function connectWallet() { - showConnect({ - appDetails, - onFinish: () => { - window.location.reload(); - }, - userSession, - }); + async function connectWallet() { + try { + console.log("=== Connecting Wallet ==="); + await connect(); + console.log("Wallet connected successfully"); + + // Get user address after connection + let address = null; + if (isConnected()) { + const data = getLocalStorage(); + if (data?.addresses?.stx && data.addresses.stx.length > 0) { + address = data.addresses.stx[0].address; + setUserAddress(address); + } + } else if (userSession.isUserSignedIn()) { + const userData = userSession.loadUserData(); + address = userData.profile.stxAddress.testnet; + setUserAddress(address); + setUserData(userData); + } + + // Reload to update state + window.location.reload(); + } catch (error) { + console.error("Connection failed:", error); + window.alert("Failed to connect wallet. Please try again."); + } } function disconnectWallet() { - userSession.signUserOut(); + disconnect(); + userSession.signUserOut("/"); setUserData(null); + setUserAddress(null); } async function handleCreateGame( @@ -53,7 +78,7 @@ export function useStacks() { } try { - if (!userData) throw new Error("User not connected"); + if (!userData && !userAddress) throw new Error("User not connected"); const txOptions = await createNewGame(betAmount, moveIndex, move); await openContractCall({ ...txOptions, @@ -79,7 +104,7 @@ export function useStacks() { } try { - if (!userData) throw new Error("User not connected"); + if (!userData && !userAddress) throw new Error("User not connected"); const txOptions = await joinGame(gameId, moveIndex, move); await openContractCall({ ...txOptions, @@ -105,7 +130,7 @@ export function useStacks() { } try { - if (!userData) throw new Error("User not connected"); + if (!userData && !userAddress) throw new Error("User not connected"); const txOptions = await play(gameId, moveIndex, move); await openContractCall({ ...txOptions, @@ -124,31 +149,51 @@ export function useStacks() { } useEffect(() => { - if (userSession.isSignInPending()) { + // Check if user is connected via new connect method + if (isConnected()) { + const data = getLocalStorage(); + if (data?.addresses?.stx && data.addresses.stx.length > 0) { + const address = data.addresses.stx[0].address; + setUserAddress(address); + // Create minimal userData for compatibility + setUserData({ + profile: { + stxAddress: { + testnet: address, + mainnet: address, + }, + }, + } as UserData); + } + } else if (userSession.isSignInPending()) { userSession.handlePendingSignIn().then((userData) => { setUserData(userData); + setUserAddress(userData.profile.stxAddress.testnet); }); } else if (userSession.isUserSignedIn()) { - setUserData(userSession.loadUserData()); + const userData = userSession.loadUserData(); + setUserData(userData); + setUserAddress(userData.profile.stxAddress.testnet); } }, []); useEffect(() => { - if (userData) { - const address = userData.profile.stxAddress.testnet; - getStxBalance(address).then((balance) => { + if (userAddress) { + getStxBalance(userAddress).then((balance) => { setStxBalance(balance); }); } - }, [userData]); + }, [userAddress]); return { userData, + userAddress, stxBalance, connectWallet, disconnectWallet, handleCreateGame, handleJoinGame, handlePlayGame, + isConnected: isConnected() || userSession.isUserSignedIn(), }; -} +} \ No newline at end of file diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 289e6e5..e079382 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -8,7 +8,7 @@ "name": "frontend", "version": "0.1.0", "dependencies": { - "@stacks/connect": "^7.10.0", + "@stacks/connect": "^8.2.0", "@stacks/network": "^7.0.2", "@stacks/transactions": "^7.0.2", "next": "15.1.5", @@ -27,6 +27,12 @@ "typescript": "^5" } }, + "node_modules/@adraffy/ens-normalize": { + "version": "1.11.1", + "resolved": "https://registry.npmjs.org/@adraffy/ens-normalize/-/ens-normalize-1.11.1.tgz", + "integrity": "sha512-nhCBV3quEgesuf7c7KYfperqSS14T8bYuvJ8PcLJp6znkZpFc0AuW4qBtr8eKVyPPe/8RSr7sglCWPU5eaxwKQ==", + "license": "MIT" + }, "node_modules/@alloc/quick-lru": { "version": "5.2.0", "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", @@ -39,6 +45,16 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/@bitcoinerlab/secp256k1": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@bitcoinerlab/secp256k1/-/secp256k1-1.2.0.tgz", + "integrity": "sha512-jeujZSzb3JOZfmJYI0ph1PVpCRV5oaexCgy+RvCXV8XlY+XFB/2n3WOcvBsKLsOw78KYgnQrQWb2HrKE4be88Q==", + "license": "MIT", + "optional": true, + "dependencies": { + "@noble/curves": "^1.7.0" + } + }, "node_modules/@emnapi/runtime": { "version": "1.3.1", "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.3.1.tgz", @@ -635,6 +651,40 @@ "@jridgewell/sourcemap-codec": "^1.4.14" } }, + "node_modules/@lit-labs/ssr-dom-shim": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@lit-labs/ssr-dom-shim/-/ssr-dom-shim-1.4.0.tgz", + "integrity": "sha512-ficsEARKnmmW5njugNYKipTm4SFnbik7CXtoencDZzmzo/dQ+2Q0bgkzJuoJP20Aj0F+izzJjOqsnkd6F/o1bw==", + "license": "BSD-3-Clause" + }, + "node_modules/@lit/react": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@lit/react/-/react-1.0.8.tgz", + "integrity": "sha512-p2+YcF+JE67SRX3mMlJ1TKCSTsgyOVdAwd/nxp3NuV1+Cb6MWALbN6nT7Ld4tpmYofcE5kcaSY1YBB9erY+6fw==", + "license": "BSD-3-Clause", + "optional": true, + "peerDependencies": { + "@types/react": "17 || 18 || 19" + } + }, + "node_modules/@lit/reactive-element": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/@lit/reactive-element/-/reactive-element-2.1.1.tgz", + "integrity": "sha512-N+dm5PAYdQ8e6UlywyyrgI2t++wFGXfHx+dSJ1oBrg6FAxUj40jId++EaRm80MKX5JnlH1sBsyZ5h0bcZKemCg==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.4.0" + } + }, + "node_modules/@msgpack/msgpack": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/@msgpack/msgpack/-/msgpack-3.1.2.tgz", + "integrity": "sha512-JEW4DEtBzfe8HvUYecLU9e6+XJnKDlUAIve8FvPzF3Kzs6Xo/KuZkZJsDH0wJXl/qEZbeeE7edxDNY3kMs39hQ==", + "license": "ISC", + "engines": { + "node": ">= 18" + } + }, "node_modules/@next/env": { "version": "15.1.5", "resolved": "https://registry.npmjs.org/@next/env/-/env-15.1.5.tgz", @@ -769,6 +819,45 @@ "node": ">= 10" } }, + "node_modules/@noble/ciphers": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/@noble/ciphers/-/ciphers-1.3.0.tgz", + "integrity": "sha512-2I0gnIVPtfnMw9ee9h1dJG7tp81+8Ob3OJb3Mv37rx5L40/b0i7djjCVvGOVqc9AEIQyvyu1i6ypKdFw8R8gQw==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.8.0.tgz", + "integrity": "sha512-j84kjAbzEnQHaSIhRPUmB3/eVXu2k3dKPl2LOrR8fSOIL+89U+7lV117EWHtq/GHM3ReGHM46iRBdZfpc4HRUQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.7.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@noble/curves/node_modules/@noble/hashes": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.0.tgz", + "integrity": "sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/@noble/hashes": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.1.5.tgz", @@ -845,227 +934,675 @@ "node": ">=14" } }, - "node_modules/@rtsao/scc": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", - "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", - "dev": true - }, - "node_modules/@rushstack/eslint-patch": { - "version": "1.10.5", - "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.5.tgz", - "integrity": "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==", - "dev": true - }, - "node_modules/@scure/base": { - "version": "1.1.9", - "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz", - "integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==", - "funding": { - "url": "https://paulmillr.com/funding/" + "node_modules/@reown/appkit": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@reown/appkit/-/appkit-1.7.17.tgz", + "integrity": "sha512-gME4Ery7HGTNEGzLckWP7qfD2ec/1UEuUkcGskGeisUnGcAsPH9z2deFFX1szialsgzTNU4/H5ZGdWqZQA8p2w==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "@reown/appkit-common": "1.7.17", + "@reown/appkit-controllers": "1.7.17", + "@reown/appkit-pay": "1.7.17", + "@reown/appkit-polyfills": "1.7.17", + "@reown/appkit-scaffold-ui": "1.7.17", + "@reown/appkit-ui": "1.7.17", + "@reown/appkit-utils": "1.7.17", + "@reown/appkit-wallet": "1.7.17", + "@walletconnect/universal-provider": "2.21.5", + "bs58": "6.0.0", + "semver": "7.7.2", + "valtio": "2.1.5", + "viem": ">=2.32.0" + }, + "optionalDependencies": { + "@lit/react": "1.0.8", + "@reown/appkit-siwx": "1.7.17" } }, - "node_modules/@scure/bip39": { - "version": "1.1.0", - "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.1.0.tgz", - "integrity": "sha512-pwrPOS16VeTKg98dYXQyIjJEcWfz7/1YJIwxUEPFfQPtc86Ym/1sVgQ2RLoD43AazMk2l/unK4ITySSpW2+82w==", - "funding": [ - { - "type": "individual", - "url": "https://paulmillr.com/funding/" - } - ], + "node_modules/@reown/appkit-common": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@reown/appkit-common/-/appkit-common-1.7.17.tgz", + "integrity": "sha512-zfrlNosQ5XBGC7OBG56+lur0nJWCdRKoWVlUnr0dCVVfBmHIgdhFkRNzDqrX/zGqg4OoWDQLO7qaGiijRskfBQ==", + "license": "Apache-2.0", "dependencies": { - "@noble/hashes": "~1.1.1", - "@scure/base": "~1.1.0" + "big.js": "6.2.2", + "dayjs": "1.11.13", + "viem": ">=2.32.0" } }, - "node_modules/@stacks/auth": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@stacks/auth/-/auth-7.0.2.tgz", - "integrity": "sha512-1N0ylkK9mz6RqIH3SbuIvoUG4eTgSkun7hHiPirScGCyXvmugjereGdTowSRSrEnl32/qrnquAAaO7a1XF6TMA==", + "node_modules/@reown/appkit-controllers": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@reown/appkit-controllers/-/appkit-controllers-1.7.17.tgz", + "integrity": "sha512-rYgXf3nAzxgu1s10rSfibpAqnm/Y3wyY47v6BpN98Y57NArWqxYXhBtdRQL1ZKpSTV9OmrzwMxPNKePOmFgxZQ==", + "license": "Apache-2.0", "dependencies": { - "@noble/secp256k1": "1.7.1", - "@stacks/common": "^7.0.2", - "@stacks/encryption": "^7.0.2", - "@stacks/network": "^7.0.2", - "@stacks/profile": "^7.0.2", - "cross-fetch": "^3.1.5", - "jsontokens": "^4.0.1" - } - }, - "node_modules/@stacks/common": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@stacks/common/-/common-7.0.2.tgz", - "integrity": "sha512-+RSecHdkxOtswmE4tDDoZlYEuULpnTQVeDIG5eZ32opK8cFxf4EugAcK9CsIsHx/Se1yTEaQ21WGATmJGK84lQ==" - }, - "node_modules/@stacks/connect": { - "version": "7.10.0", - "resolved": "https://registry.npmjs.org/@stacks/connect/-/connect-7.10.0.tgz", - "integrity": "sha512-h6i/cFWUVWLFyn8Y62+oDh7PJYQR5Ultrc6dEUp8rZZbHO9ldLT5Qm+rwTS0tRXzoPXJ2KNbqsG70Sqa/lRnVQ==", - "dependencies": { - "@stacks/auth": "^7.0.0", - "@stacks/common": "^7.0.0", - "@stacks/connect-ui": "6.6.0", - "@stacks/network": "^7.0.0", - "@stacks/network-v6": "npm:@stacks/network@^6.16.0", - "@stacks/profile": "^7.0.0", - "@stacks/transactions": "^7.0.0", - "@stacks/transactions-v6": "npm:@stacks/transactions@^6.16.0", - "jsontokens": "^4.0.1" + "@reown/appkit-common": "1.7.17", + "@reown/appkit-wallet": "1.7.17", + "@walletconnect/universal-provider": "2.21.5", + "valtio": "2.1.5", + "viem": ">=2.32.0" } }, - "node_modules/@stacks/connect-ui": { - "version": "6.6.0", - "resolved": "https://registry.npmjs.org/@stacks/connect-ui/-/connect-ui-6.6.0.tgz", - "integrity": "sha512-uc22RH99umYzB94h5LiKPtGu34IBGrwUb3TfijGb2ZMudaMCiv/Fr1jjZKfQW5MRmexnbAEmGZpFlQKinCcsUA==", + "node_modules/@reown/appkit-pay": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@reown/appkit-pay/-/appkit-pay-1.7.17.tgz", + "integrity": "sha512-RukQ5oZ+zGzWy9gu4butVcscZ9GB9/h6zmQFXDo9qkAbOicwZKaLR5XMKrjLQIYisu+ODV/ff6NuxnUYs+/r9Q==", + "license": "Apache-2.0", "dependencies": { - "@stencil/core": "^2.17.1" + "@reown/appkit-common": "1.7.17", + "@reown/appkit-controllers": "1.7.17", + "@reown/appkit-ui": "1.7.17", + "@reown/appkit-utils": "1.7.17", + "lit": "3.3.0", + "valtio": "2.1.5" } }, - "node_modules/@stacks/encryption": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@stacks/encryption/-/encryption-7.0.2.tgz", - "integrity": "sha512-3evRvxPqVzQAhcZ8uacQrVfAETUMIV8VyKkHGsd4QZroGWlvXQheLV3CFeDttFb304QcKq/oKv1clOvQ2shaAw==", + "node_modules/@reown/appkit-polyfills": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@reown/appkit-polyfills/-/appkit-polyfills-1.7.17.tgz", + "integrity": "sha512-vWRIYS+wc2ByWKn76KMV7zxqTvQ+512KwXAKQcRulu13AdKvnBbr0eYx+ctvSKL+kZoAp9zj4R3RulX3eXnJ8Q==", + "license": "Apache-2.0", "dependencies": { - "@noble/hashes": "1.1.5", - "@noble/secp256k1": "1.7.1", - "@scure/bip39": "1.1.0", - "@stacks/common": "^7.0.2", - "base64-js": "^1.5.1", - "bs58": "^5.0.0", - "ripemd160-min": "^0.0.6", - "varuint-bitcoin": "^1.1.2" + "buffer": "6.0.3" } }, - "node_modules/@stacks/network": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@stacks/network/-/network-7.0.2.tgz", - "integrity": "sha512-XzHnoWqku/jRrTgMXhmh3c+I0O9vDH24KlhzGDZtBu+8CGGyHNPAZzGwvoUShonMXrXjEnfO9IYQwV5aJhfv6g==", + "node_modules/@reown/appkit-scaffold-ui": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@reown/appkit-scaffold-ui/-/appkit-scaffold-ui-1.7.17.tgz", + "integrity": "sha512-7nk8DEHQf9/7Ij8Eo85Uj1D/3M9Ybq/LjXyePyaGusZ9E8gf4u/UjKpQK7cTfMNsNl4nrB2mBI9Tk/rwNECdCg==", + "license": "Apache-2.0", "dependencies": { - "@stacks/common": "^7.0.2", - "cross-fetch": "^3.1.5" + "@reown/appkit-common": "1.7.17", + "@reown/appkit-controllers": "1.7.17", + "@reown/appkit-ui": "1.7.17", + "@reown/appkit-utils": "1.7.17", + "@reown/appkit-wallet": "1.7.17", + "lit": "3.3.0" } }, - "node_modules/@stacks/network-v6": { - "name": "@stacks/network", - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.17.0.tgz", - "integrity": "sha512-numHbfKjwco/rbkGPOEz8+FcJ2nBnS/tdJ8R422Q70h3SiA9eqk9RjSzB8p4JP8yW1SZvW+eihADHfMpBuZyfw==", + "node_modules/@reown/appkit-siwx": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@reown/appkit-siwx/-/appkit-siwx-1.7.17.tgz", + "integrity": "sha512-frTTDnj5111+ZNNyHmEWeXiX0IWFlRhP240kmxKTamLElc2PdLUfQq/1yX8Y3bUBHryISjcQYzEtWSEI2oRYKA==", + "license": "Apache-2.0", + "optional": true, "dependencies": { - "@stacks/common": "^6.16.0", - "cross-fetch": "^3.1.5" + "@reown/appkit-common": "1.7.17", + "@reown/appkit-controllers": "1.7.17", + "@reown/appkit-scaffold-ui": "1.7.17", + "@reown/appkit-ui": "1.7.17", + "@reown/appkit-utils": "1.7.17", + "bip322-js": "2.0.0", + "bs58": "6.0.0", + "tweetnacl": "1.0.3", + "viem": "2.32.0" + }, + "peerDependencies": { + "lit": "3.3.0" } }, - "node_modules/@stacks/network-v6/node_modules/@stacks/common": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/@stacks/common/-/common-6.16.0.tgz", - "integrity": "sha512-PnzvhrdGRMVZvxTulitlYafSK4l02gPCBBoI9QEoTqgSnv62oaOXhYAUUkTMFKxdHW1seVEwZsrahuXiZPIAwg==", + "node_modules/@reown/appkit-siwx/node_modules/@noble/curves": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.2.tgz", + "integrity": "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==", + "license": "MIT", + "optional": true, "dependencies": { - "@types/bn.js": "^5.1.0", - "@types/node": "^18.0.4" + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@stacks/network-v6/node_modules/@types/node": { - "version": "18.19.71", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.71.tgz", - "integrity": "sha512-evXpcgtZm8FY4jqBSN8+DmOTcVkkvTmAayeo4Wf3m1xAruyVGzGuDh/Fb/WWX2yLItUiho42ozyJjB0dw//Tkw==", - "dependencies": { - "undici-types": "~5.26.4" + "node_modules/@reown/appkit-siwx/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@stacks/network-v6/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" - }, - "node_modules/@stacks/profile": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@stacks/profile/-/profile-7.0.2.tgz", - "integrity": "sha512-BJis1ZAP2yzv0IFaJcm4mZFtauizcB1zBVpAeOSX06BDEUgM8h0L8uRvAbfTvSuSjsveNgblucZouZMSEsQMGA==", - "dependencies": { - "@stacks/common": "^7.0.2", - "@stacks/network": "^7.0.2", - "@stacks/transactions": "^7.0.2", - "jsontokens": "^4.0.1", - "schema-inspector": "^2.0.2", - "zone-file": "^2.0.0-beta.3" + "node_modules/@reown/appkit-siwx/node_modules/abitype": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", + "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", + "license": "MIT", + "optional": true, + "funding": { + "url": "https://github.com/sponsors/wevm" + }, + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } } }, - "node_modules/@stacks/transactions": { - "version": "7.0.2", - "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-7.0.2.tgz", - "integrity": "sha512-m2bvchqUeYv1ttXuC0EukW8UX4xBXTDcYb8bXmfI1RG89HXAvvCCgr5aiadU6zbutgoXvm8mquDt3nww0PO4Jg==", + "node_modules/@reown/appkit-siwx/node_modules/ox": { + "version": "0.8.1", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.8.1.tgz", + "integrity": "sha512-e+z5epnzV+Zuz91YYujecW8cF01mzmrUtWotJ0oEPym/G82uccs7q0WDHTYL3eiONbTUEvcZrptAKLgTBD3u2A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "optional": true, "dependencies": { - "@noble/hashes": "1.1.5", - "@noble/secp256k1": "1.7.1", - "@stacks/common": "^7.0.2", - "@stacks/network": "^7.0.2", - "c32check": "^2.0.0", - "lodash.clonedeep": "^4.5.0" + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "^1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.0.8", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@stacks/transactions-v6": { - "name": "@stacks/transactions", - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.17.0.tgz", - "integrity": "sha512-FUah2BRgV66ApLcEXGNGhwyFTRXqX5Zco3LpiM3essw8PF0NQlHwwdPgtDko5RfrJl3LhGXXe/30nwsfNnB3+g==", + "node_modules/@reown/appkit-siwx/node_modules/viem": { + "version": "2.32.0", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.32.0.tgz", + "integrity": "sha512-pHwKXQSyEWX+8ttOQJdU5dSBfYd6L9JxARY/Sx0MBj3uF/Zaiqt6o1SbzjFjQXkNzWSgtxK7H89ZI1SMIA2iLQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "optional": true, "dependencies": { - "@noble/hashes": "1.1.5", - "@noble/secp256k1": "1.7.1", - "@stacks/common": "^6.16.0", - "@stacks/network": "^6.17.0", - "c32check": "^2.0.0", - "lodash.clonedeep": "^4.5.0" + "@noble/curves": "1.9.2", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.0.8", + "isows": "1.0.7", + "ox": "0.8.1", + "ws": "8.18.2" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@stacks/transactions-v6/node_modules/@stacks/common": { - "version": "6.16.0", - "resolved": "https://registry.npmjs.org/@stacks/common/-/common-6.16.0.tgz", - "integrity": "sha512-PnzvhrdGRMVZvxTulitlYafSK4l02gPCBBoI9QEoTqgSnv62oaOXhYAUUkTMFKxdHW1seVEwZsrahuXiZPIAwg==", - "dependencies": { - "@types/bn.js": "^5.1.0", - "@types/node": "^18.0.4" + "node_modules/@reown/appkit-siwx/node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "optional": 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/@stacks/transactions-v6/node_modules/@stacks/network": { - "version": "6.17.0", - "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.17.0.tgz", - "integrity": "sha512-numHbfKjwco/rbkGPOEz8+FcJ2nBnS/tdJ8R422Q70h3SiA9eqk9RjSzB8p4JP8yW1SZvW+eihADHfMpBuZyfw==", - "dependencies": { - "@stacks/common": "^6.16.0", - "cross-fetch": "^3.1.5" + "node_modules/@reown/appkit-ui": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@reown/appkit-ui/-/appkit-ui-1.7.17.tgz", + "integrity": "sha512-7lscJjtFZIfdcUv5zAsmgiFG2dMziQE0IfqY3U/H5qhnGW8v4ITcTi1gNS3A4lQrNDbcA083LecfVdyKnTdi1A==", + "license": "Apache-2.0", + "dependencies": { + "@reown/appkit-common": "1.7.17", + "@reown/appkit-controllers": "1.7.17", + "@reown/appkit-wallet": "1.7.17", + "lit": "3.3.0", + "qrcode": "1.5.3" + } + }, + "node_modules/@reown/appkit-universal-connector": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@reown/appkit-universal-connector/-/appkit-universal-connector-1.7.17.tgz", + "integrity": "sha512-2LqcKuEURwoHFBYE+6BdsUsPQ5bCN8xXuqxGJeEkAJ95apXTWyLLlpadVofKRh7dzM4lf0Uam8NpWogYWPxnnQ==", + "license": "Apache-2.0", + "dependencies": { + "@reown/appkit": "1.7.17", + "@reown/appkit-common": "1.7.17", + "@walletconnect/types": "2.21.5", + "@walletconnect/universal-provider": "2.21.5", + "bs58": "6.0.0" + } + }, + "node_modules/@reown/appkit-utils": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@reown/appkit-utils/-/appkit-utils-1.7.17.tgz", + "integrity": "sha512-QWzHTmSDFy90Bp5pUUQASzcjnJXPiEvasJV68j3PZifenTPDCfFW+VsiHduWNodTHAA/rZ12O3uBQE+stM3xmQ==", + "license": "Apache-2.0", + "dependencies": { + "@reown/appkit-common": "1.7.17", + "@reown/appkit-controllers": "1.7.17", + "@reown/appkit-polyfills": "1.7.17", + "@reown/appkit-wallet": "1.7.17", + "@wallet-standard/wallet": "1.1.0", + "@walletconnect/logger": "2.1.2", + "@walletconnect/universal-provider": "2.21.5", + "valtio": "2.1.5", + "viem": ">=2.32.0" + }, + "peerDependencies": { + "valtio": "2.1.5" } }, - "node_modules/@stacks/transactions-v6/node_modules/@types/node": { - "version": "18.19.71", - "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.71.tgz", - "integrity": "sha512-evXpcgtZm8FY4jqBSN8+DmOTcVkkvTmAayeo4Wf3m1xAruyVGzGuDh/Fb/WWX2yLItUiho42ozyJjB0dw//Tkw==", + "node_modules/@reown/appkit-wallet": { + "version": "1.7.17", + "resolved": "https://registry.npmjs.org/@reown/appkit-wallet/-/appkit-wallet-1.7.17.tgz", + "integrity": "sha512-tgIqHZZJISGCir0reQ/pXcIKXuP7JNqSuEDunfi5whNJi6z27h3g468RGk1Zo+MC//DRnQb01xMrv+iWRr8mCQ==", + "license": "Apache-2.0", "dependencies": { - "undici-types": "~5.26.4" + "@reown/appkit-common": "1.7.17", + "@reown/appkit-polyfills": "1.7.17", + "@walletconnect/logger": "2.1.2", + "zod": "3.22.4" } }, - "node_modules/@stacks/transactions-v6/node_modules/undici-types": { - "version": "5.26.5", - "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", - "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + "node_modules/@rollup/rollup-darwin-arm64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.34.9.tgz", + "integrity": "sha512-0CY3/K54slrzLDjOA7TOjN1NuLKERBgk9nY5V34mhmuu673YNb+7ghaDUs6N0ujXR7fz5XaS5Aa6d2TNxZd0OQ==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@stencil/core": { - "version": "2.22.3", - "resolved": "https://registry.npmjs.org/@stencil/core/-/core-2.22.3.tgz", - "integrity": "sha512-kmVA0M/HojwsfkeHsifvHVIYe4l5tin7J5+DLgtl8h6WWfiMClND5K3ifCXXI2ETDNKiEk21p6jql3Fx9o2rng==", - "bin": { - "stencil": "bin/stencil" - }, - "engines": { - "node": ">=12.10.0", - "npm": ">=6.0.0" - } + "node_modules/@rollup/rollup-darwin-x64": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.34.9.tgz", + "integrity": "sha512-eOojSEAi/acnsJVYRxnMkPFqcxSMFfrw7r2iD9Q32SGkb/Q9FpUY1UlAu1DH9T7j++gZ0lHjnm4OyH2vCI7l7Q==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ] }, - "node_modules/@swc/counter": { - "version": "0.1.3", - "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", - "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" + "node_modules/@rollup/rollup-linux-arm64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.34.9.tgz", + "integrity": "sha512-6TZjPHjKZUQKmVKMUowF3ewHxctrRR09eYyvT5eFv8w/fXarEra83A2mHTVJLA5xU91aCNOUnM+DWFMSbQ0Nxw==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-arm64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.34.9.tgz", + "integrity": "sha512-LD2fytxZJZ6xzOKnMbIpgzFOuIKlxVOpiMAXawsAZ2mHBPEYOnLRK5TTEsID6z4eM23DuO88X0Tq1mErHMVq0A==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-gnu": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.34.9.tgz", + "integrity": "sha512-FwBHNSOjUTQLP4MG7y6rR6qbGw4MFeQnIBrMe161QGaQoBQLqSUEKlHIiVgF3g/mb3lxlxzJOpIBhaP+C+KP2A==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-linux-x64-musl": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.34.9.tgz", + "integrity": "sha512-cYRpV4650z2I3/s6+5/LONkjIz8MBeqrk+vPXV10ORBnshpn8S32bPqQ2Utv39jCiDcO2eJTuSlPXpnvmaIgRA==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "linux" + ] + }, + "node_modules/@rollup/rollup-win32-arm64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.34.9.tgz", + "integrity": "sha512-z4mQK9dAN6byRA/vsSgQiPeuO63wdiDxZ9yg9iyX2QTzKuQM7T4xlBoeUP/J8uiFkqxkcWndWi+W7bXdPbt27Q==", + "cpu": [ + "arm64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rollup/rollup-win32-x64-msvc": { + "version": "4.34.9", + "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.34.9.tgz", + "integrity": "sha512-AyleYRPU7+rgkMWbEh71fQlrzRfeP6SyMnRf9XX4fCdDPAJumdSBqYEcWPMzVQ4ScAl7E4oFfK0GUVn77xSwbw==", + "cpu": [ + "x64" + ], + "license": "MIT", + "optional": true, + "os": [ + "win32" + ] + }, + "node_modules/@rtsao/scc": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@rtsao/scc/-/scc-1.1.0.tgz", + "integrity": "sha512-zt6OdqaDoOnJ1ZYsCYGt9YmWzDXl4vQdKTyJev62gFhRGKdx7mcT54V9KIjg+d2wi9EXsPvAPKe7i7WjfVWB8g==", + "dev": true + }, + "node_modules/@rushstack/eslint-patch": { + "version": "1.10.5", + "resolved": "https://registry.npmjs.org/@rushstack/eslint-patch/-/eslint-patch-1.10.5.tgz", + "integrity": "sha512-kkKUDVlII2DQiKy7UstOR1ErJP8kUKAQ4oa+SQtM0K+lPdmmjj0YnnxBgtTVYH7mUKtbsxeFC9y0AmK7Yb78/A==", + "dev": true + }, + "node_modules/@scure/base": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@scure/base/-/base-1.2.6.tgz", + "integrity": "sha512-g/nm5FgUa//MCj1gV09zTJTaM6KBAHqLN907YVQqf7zC49+DcO4B1so4ZX07Ef10Twr6nuqYEH9GEggFXA4Fmg==", + "license": "MIT", + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@scure/bip32/-/bip32-1.7.0.tgz", + "integrity": "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw==", + "license": "MIT", + "dependencies": { + "@noble/curves": "~1.9.0", + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/curves": { + "version": "1.9.7", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.7.tgz", + "integrity": "sha512-gbKGcRUYIjA3/zCCNaWDciTMFI0dCkvou3TL8Zmy5Nc7sJ47a0jtOeZoTaMxkuqRo9cRhjOdZJXegxYE5FN/xw==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip32/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/@scure/bip39/-/bip39-1.6.0.tgz", + "integrity": "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "~1.8.0", + "@scure/base": "~1.2.5" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@scure/bip39/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@stacks/common": { + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/@stacks/common/-/common-7.0.2.tgz", + "integrity": "sha512-+RSecHdkxOtswmE4tDDoZlYEuULpnTQVeDIG5eZ32opK8cFxf4EugAcK9CsIsHx/Se1yTEaQ21WGATmJGK84lQ==" + }, + "node_modules/@stacks/connect": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/@stacks/connect/-/connect-8.2.0.tgz", + "integrity": "sha512-i4rmrRHiMbFWFzasR5Rtk6iC3JiBwpdkq358gnmjrjKVZWvla+m8kqeKnbsy8xBQmHIQvw6EL3fUcoUnsqxx4A==", + "license": "MIT", + "workspaces": [ + "packages/**" + ], + "dependencies": { + "@reown/appkit": "1.7.17", + "@reown/appkit-universal-connector": "1.7.17", + "@scure/base": "^1.2.4", + "@stacks/common": "^7.0.2", + "@stacks/connect-ui": "8.1.0", + "@stacks/network": "^7.0.2", + "@stacks/network-v6": "npm:@stacks/network@^6.16.0", + "@stacks/profile": "^7.0.5", + "@stacks/transactions": "^7.0.5", + "@stacks/transactions-v6": "npm:@stacks/transactions@^6.16.0" + } + }, + "node_modules/@stacks/connect-ui": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/@stacks/connect-ui/-/connect-ui-8.1.0.tgz", + "integrity": "sha512-5TXYasZUi8x3nSPao0k7AC/dwGwxUY4F4Y8w/bdT/XMFLD6bMYetI4w4Y5BBHCGv2eAP1fdJGc7l/J+I4yhZHg==", + "license": "MIT", + "dependencies": { + "@stencil/core": "^4.29.3" + } + }, + "node_modules/@stacks/network": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-7.2.0.tgz", + "integrity": "sha512-AkLougCF2RLbK97TtISZxAhF3cE757XMXWOGKvEFWNauiQ5/bYyI9W5jZypG3yI/AyYIo04NKoFWWTnpJcn1iA==", + "license": "MIT", + "dependencies": { + "@stacks/common": "^7.0.2", + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@stacks/network-v6": { + "name": "@stacks/network", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.17.0.tgz", + "integrity": "sha512-numHbfKjwco/rbkGPOEz8+FcJ2nBnS/tdJ8R422Q70h3SiA9eqk9RjSzB8p4JP8yW1SZvW+eihADHfMpBuZyfw==", + "dependencies": { + "@stacks/common": "^6.16.0", + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@stacks/network-v6/node_modules/@stacks/common": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/@stacks/common/-/common-6.16.0.tgz", + "integrity": "sha512-PnzvhrdGRMVZvxTulitlYafSK4l02gPCBBoI9QEoTqgSnv62oaOXhYAUUkTMFKxdHW1seVEwZsrahuXiZPIAwg==", + "dependencies": { + "@types/bn.js": "^5.1.0", + "@types/node": "^18.0.4" + } + }, + "node_modules/@stacks/network-v6/node_modules/@types/node": { + "version": "18.19.71", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.71.tgz", + "integrity": "sha512-evXpcgtZm8FY4jqBSN8+DmOTcVkkvTmAayeo4Wf3m1xAruyVGzGuDh/Fb/WWX2yLItUiho42ozyJjB0dw//Tkw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@stacks/network-v6/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/@stacks/profile": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@stacks/profile/-/profile-7.2.0.tgz", + "integrity": "sha512-pzPgn/NpmjA7TdeA5U9OjXLwBNqGPrjWhsMy/ZC3iUdnIUvthgWwlPpydgQOTJaRqaDMdY24hFgT+og6QbyQQA==", + "license": "MIT", + "dependencies": { + "@stacks/common": "^7.0.2", + "@stacks/network": "^7.2.0", + "@stacks/transactions": "^7.2.0", + "jsontokens": "^4.0.1", + "schema-inspector": "^2.0.2", + "zone-file": "^2.0.0-beta.3" + } + }, + "node_modules/@stacks/transactions": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-7.2.0.tgz", + "integrity": "sha512-U7wjlxM9Q+408ihRsv5mlKRslXGt2WCShKi1lduiqf5+dBSRGdVi8ttCIEckSsg3ulCVF3EHTQF3LZgw4kwKlQ==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.1.5", + "@noble/secp256k1": "1.7.1", + "@stacks/common": "^7.0.2", + "@stacks/network": "^7.2.0", + "c32check": "^2.0.0", + "lodash.clonedeep": "^4.5.0" + } + }, + "node_modules/@stacks/transactions-v6": { + "name": "@stacks/transactions", + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@stacks/transactions/-/transactions-6.17.0.tgz", + "integrity": "sha512-FUah2BRgV66ApLcEXGNGhwyFTRXqX5Zco3LpiM3essw8PF0NQlHwwdPgtDko5RfrJl3LhGXXe/30nwsfNnB3+g==", + "dependencies": { + "@noble/hashes": "1.1.5", + "@noble/secp256k1": "1.7.1", + "@stacks/common": "^6.16.0", + "@stacks/network": "^6.17.0", + "c32check": "^2.0.0", + "lodash.clonedeep": "^4.5.0" + } + }, + "node_modules/@stacks/transactions-v6/node_modules/@stacks/common": { + "version": "6.16.0", + "resolved": "https://registry.npmjs.org/@stacks/common/-/common-6.16.0.tgz", + "integrity": "sha512-PnzvhrdGRMVZvxTulitlYafSK4l02gPCBBoI9QEoTqgSnv62oaOXhYAUUkTMFKxdHW1seVEwZsrahuXiZPIAwg==", + "dependencies": { + "@types/bn.js": "^5.1.0", + "@types/node": "^18.0.4" + } + }, + "node_modules/@stacks/transactions-v6/node_modules/@stacks/network": { + "version": "6.17.0", + "resolved": "https://registry.npmjs.org/@stacks/network/-/network-6.17.0.tgz", + "integrity": "sha512-numHbfKjwco/rbkGPOEz8+FcJ2nBnS/tdJ8R422Q70h3SiA9eqk9RjSzB8p4JP8yW1SZvW+eihADHfMpBuZyfw==", + "dependencies": { + "@stacks/common": "^6.16.0", + "cross-fetch": "^3.1.5" + } + }, + "node_modules/@stacks/transactions-v6/node_modules/@types/node": { + "version": "18.19.71", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.71.tgz", + "integrity": "sha512-evXpcgtZm8FY4jqBSN8+DmOTcVkkvTmAayeo4Wf3m1xAruyVGzGuDh/Fb/WWX2yLItUiho42ozyJjB0dw//Tkw==", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@stacks/transactions-v6/node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==" + }, + "node_modules/@stencil/core": { + "version": "4.38.2", + "resolved": "https://registry.npmjs.org/@stencil/core/-/core-4.38.2.tgz", + "integrity": "sha512-opyjA+DYAtaKmaSnuC8Bb/PH7nuO+1GhVn6amsN8XT+TlT9biptlcpz4YWETwYZ+XxtX+nLdxWbW0TVafrqsvQ==", + "license": "MIT", + "bin": { + "stencil": "bin/stencil" + }, + "engines": { + "node": ">=16.0.0", + "npm": ">=7.10.0" + }, + "optionalDependencies": { + "@rollup/rollup-darwin-arm64": "4.34.9", + "@rollup/rollup-darwin-x64": "4.34.9", + "@rollup/rollup-linux-arm64-gnu": "4.34.9", + "@rollup/rollup-linux-arm64-musl": "4.34.9", + "@rollup/rollup-linux-x64-gnu": "4.34.9", + "@rollup/rollup-linux-x64-musl": "4.34.9", + "@rollup/rollup-win32-arm64-msvc": "4.34.9", + "@rollup/rollup-win32-x64-msvc": "4.34.9" + } + }, + "node_modules/@swc/counter": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/@swc/counter/-/counter-0.1.3.tgz", + "integrity": "sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==" }, "node_modules/@swc/helpers": { "version": "0.5.15", @@ -1113,7 +1650,7 @@ "version": "19.0.7", "resolved": "https://registry.npmjs.org/@types/react/-/react-19.0.7.tgz", "integrity": "sha512-MoFsEJKkAtZCrC1r6CM8U22GzhG7u2Wir8ons/aCKH6MBdD1ibV24zOSSkdZVUKqN5i396zG5VKLYZ3yaUZdLA==", - "dev": true, + "devOptional": true, "dependencies": { "csstype": "^3.0.2" } @@ -1127,6 +1664,12 @@ "@types/react": "^19.0.0" } }, + "node_modules/@types/trusted-types": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/@types/trusted-types/-/trusted-types-2.0.7.tgz", + "integrity": "sha512-ScaPdn1dQczgbl0QFTeTOmVHFULt394XJgOQNoyVhZ6r2vLnMLJfBPd53SB52T/3G36VI1/g2MZaX0cwDuXsfw==", + "license": "MIT" + }, "node_modules/@typescript-eslint/eslint-plugin": { "version": "8.21.0", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-8.21.0.tgz", @@ -1249,106 +1792,629 @@ "ts-api-utils": "^2.0.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { + "version": "3.3.3", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", + "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.8" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { + "version": "9.0.5", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", + "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.21.0.tgz", + "integrity": "sha512-xcXBfcq0Kaxgj7dwejMbFyq7IOHgpNMtVuDveK7w3ZGwG9owKzhALVwKpTF2yrZmEwl9SWdetf3fxNzJQaVuxw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@typescript-eslint/scope-manager": "8.21.0", + "@typescript-eslint/types": "8.21.0", + "@typescript-eslint/typescript-estree": "8.21.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^8.57.0 || ^9.0.0", + "typescript": ">=4.8.4 <5.8.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "8.21.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.21.0.tgz", + "integrity": "sha512-BkLMNpdV6prozk8LlyK/SOoWLmUFi+ZD+pcqti9ILCbVvHGk1ui1g4jJOc2WDLaeExz2qWwojxlPce5PljcT3w==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "8.21.0", + "eslint-visitor-keys": "^4.2.0" + }, + "engines": { + "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@wallet-standard/base": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@wallet-standard/base/-/base-1.1.0.tgz", + "integrity": "sha512-DJDQhjKmSNVLKWItoKThJS+CsJQjR9AOBOirBVT1F9YpRyC9oYHE+ZnSf8y8bxUphtKqdQMPVQ2mHohYdRvDVQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=16" + } + }, + "node_modules/@wallet-standard/wallet": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@wallet-standard/wallet/-/wallet-1.1.0.tgz", + "integrity": "sha512-Gt8TnSlDZpAl+RWOOAB/kuvC7RpcdWAlFbHNoi4gsXsfaWa1QCT6LBcfIYTPdOZC9OVZUDwqGuGAcqZejDmHjg==", + "license": "Apache-2.0", + "dependencies": { + "@wallet-standard/base": "^1.1.0" + }, + "engines": { + "node": ">=16" + } + }, + "node_modules/@walletconnect/core": { + "version": "2.21.5", + "resolved": "https://registry.npmjs.org/@walletconnect/core/-/core-2.21.5.tgz", + "integrity": "sha512-CxGbio1TdCkou/TYn8X6Ih1mUX3UtFTk+t618/cIrT3VX5IjQW09n9I/pVafr7bQbBtm9/ATr7ugUEMrLu5snA==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/jsonrpc-ws-connection": "1.0.16", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.5", + "@walletconnect/utils": "2.21.5", + "@walletconnect/window-getters": "1.0.1", + "es-toolkit": "1.39.3", + "events": "3.3.0", + "uint8arrays": "3.1.1" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/@walletconnect/environment": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/environment/-/environment-1.0.1.tgz", + "integrity": "sha512-T426LLZtHj8e8rYnKfzsw1aG6+M0BT1ZxayMdv/p8yM0MU+eJDISqNY3/bccxRr4LrF9csq02Rhqt08Ibl0VRg==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/environment/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/events": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/events/-/events-1.0.1.tgz", + "integrity": "sha512-NPTqaoi0oPBVNuLv7qPaJazmGHs5JGyO8eEAk5VGKmJzDR7AHzD4k6ilox5kxk1iwiOnFopBOOMLs86Oa76HpQ==", + "license": "MIT", + "dependencies": { + "keyvaluestorage-interface": "^1.0.0", + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/events/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/heartbeat": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/@walletconnect/heartbeat/-/heartbeat-1.2.2.tgz", + "integrity": "sha512-uASiRmC5MwhuRuf05vq4AT48Pq8RMi876zV8rr8cV969uTOzWdB/k+Lj5yI2PBtB1bGQisGen7MM1GcZlQTBXw==", + "license": "MIT", + "dependencies": { + "@walletconnect/events": "^1.0.1", + "@walletconnect/time": "^1.0.2", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-http-connection": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-http-connection/-/jsonrpc-http-connection-1.0.8.tgz", + "integrity": "sha512-+B7cRuaxijLeFDJUq5hAzNyef3e3tBDIxyaCNmFtjwnod5AGis3RToNqzFU33vpVcxFhofkpE7Cx+5MYejbMGw==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.6", + "@walletconnect/safe-json": "^1.0.1", + "cross-fetch": "^3.1.4", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-provider": { + "version": "1.0.14", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-provider/-/jsonrpc-provider-1.0.14.tgz", + "integrity": "sha512-rtsNY1XqHvWj0EtITNeuf8PHMvlCLiS3EjQL+WOkxEOA4KPxsohFnBDeyPYiNm4ZvkQdLnece36opYidmtbmow==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.8", + "@walletconnect/safe-json": "^1.0.2", + "events": "^3.3.0" + } + }, + "node_modules/@walletconnect/jsonrpc-types": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-types/-/jsonrpc-types-1.0.4.tgz", + "integrity": "sha512-P6679fG/M+wuWg9TY8mh6xFSdYnFyFjwFelxyISxMDrlbXokorEVXYOxiqEbrU3x1BmBoCAJJ+vtEaEoMlpCBQ==", + "license": "MIT", + "dependencies": { + "events": "^3.3.0", + "keyvaluestorage-interface": "^1.0.0" + } + }, + "node_modules/@walletconnect/jsonrpc-utils": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-utils/-/jsonrpc-utils-1.0.8.tgz", + "integrity": "sha512-vdeb03bD8VzJUL6ZtzRYsFMq1eZQcM3EAzT0a3st59dyLfJ0wq+tKMpmGH7HlB7waD858UWgfIcudbPFsbzVdw==", + "license": "MIT", + "dependencies": { + "@walletconnect/environment": "^1.0.1", + "@walletconnect/jsonrpc-types": "^1.0.3", + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/jsonrpc-utils/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/jsonrpc-ws-connection": { + "version": "1.0.16", + "resolved": "https://registry.npmjs.org/@walletconnect/jsonrpc-ws-connection/-/jsonrpc-ws-connection-1.0.16.tgz", + "integrity": "sha512-G81JmsMqh5nJheE1mPst1W0WfVv0SG3N7JggwLLGnI7iuDZJq8cRJvQwLGKHn5H1WTW7DEPCo00zz5w62AbL3Q==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-utils": "^1.0.6", + "@walletconnect/safe-json": "^1.0.2", + "events": "^3.3.0", + "ws": "^7.5.1" + } + }, + "node_modules/@walletconnect/keyvaluestorage": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/@walletconnect/keyvaluestorage/-/keyvaluestorage-1.1.1.tgz", + "integrity": "sha512-V7ZQq2+mSxAq7MrRqDxanTzu2RcElfK1PfNYiaVnJgJ7Q7G7hTVwF8voIBx92qsRyGHZihrwNPHuZd1aKkd0rA==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.1", + "idb-keyval": "^6.2.1", + "unstorage": "^1.9.0" + }, + "peerDependencies": { + "@react-native-async-storage/async-storage": "1.x" + }, + "peerDependenciesMeta": { + "@react-native-async-storage/async-storage": { + "optional": true + } + } + }, + "node_modules/@walletconnect/logger": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@walletconnect/logger/-/logger-2.1.2.tgz", + "integrity": "sha512-aAb28I3S6pYXZHQm5ESB+V6rDqIYfsnHaQyzFbwUUBFY4H0OXx/YtTl8lvhUNhMMfb9UxbwEBS253TlXUYJWSw==", + "license": "MIT", + "dependencies": { + "@walletconnect/safe-json": "^1.0.2", + "pino": "7.11.0" + } + }, + "node_modules/@walletconnect/relay-api": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-api/-/relay-api-1.0.11.tgz", + "integrity": "sha512-tLPErkze/HmC9aCmdZOhtVmYZq1wKfWTJtygQHoWtgg722Jd4homo54Cs4ak2RUFUZIGO2RsOpIcWipaua5D5Q==", + "license": "MIT", + "dependencies": { + "@walletconnect/jsonrpc-types": "^1.0.2" + } + }, + "node_modules/@walletconnect/relay-auth": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/@walletconnect/relay-auth/-/relay-auth-1.1.0.tgz", + "integrity": "sha512-qFw+a9uRz26jRCDgL7Q5TA9qYIgcNY8jpJzI1zAWNZ8i7mQjaijRnWFKsCHAU9CyGjvt6RKrRXyFtFOpWTVmCQ==", + "license": "MIT", + "dependencies": { + "@noble/curves": "1.8.0", + "@noble/hashes": "1.7.0", + "@walletconnect/safe-json": "^1.0.1", + "@walletconnect/time": "^1.0.2", + "uint8arrays": "^3.0.0" + } + }, + "node_modules/@walletconnect/relay-auth/node_modules/@noble/hashes": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.7.0.tgz", + "integrity": "sha512-HXydb0DgzTpDPwbVeDGCG1gIu7X6+AuU6Zl6av/E/KG8LMsvPntvq+w17CHRpKBmN6Ybdrt1eP3k4cj8DJa78w==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/safe-json": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/safe-json/-/safe-json-1.0.2.tgz", + "integrity": "sha512-Ogb7I27kZ3LPC3ibn8ldyUr5544t3/STow9+lzz7Sfo808YD7SBWk7SAsdBFlYgP2zDRy2hS3sKRcuSRM0OTmA==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/safe-json/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/sign-client": { + "version": "2.21.5", + "resolved": "https://registry.npmjs.org/@walletconnect/sign-client/-/sign-client-2.21.5.tgz", + "integrity": "sha512-IAs/IqmE1HVL9EsvqkNRU4NeAYe//h9NwqKi7ToKYZv4jhcC3BBemUD1r8iQJSTHMhO41EKn1G9/DiBln3ZiwQ==", + "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/core": "2.21.5", + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/logger": "2.1.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.5", + "@walletconnect/utils": "2.21.5", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/time": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/@walletconnect/time/-/time-1.0.2.tgz", + "integrity": "sha512-uzdd9woDcJ1AaBZRhqy5rNC9laqWGErfc4dxA9a87mPdKOgWMD85mcFo9dIYIts/Jwocfwn07EC6EzclKubk/g==", + "license": "MIT", + "dependencies": { + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/time/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/types": { + "version": "2.21.5", + "resolved": "https://registry.npmjs.org/@walletconnect/types/-/types-2.21.5.tgz", + "integrity": "sha512-kpTXbenKeMdaz6mgMN/jKaHHbu6mdY3kyyrddzE/mthOd2KLACVrZr7hrTf+Fg2coPVen5d1KKyQjyECEdzOCw==", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/heartbeat": "1.2.2", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/universal-provider": { + "version": "2.21.5", + "resolved": "https://registry.npmjs.org/@walletconnect/universal-provider/-/universal-provider-2.21.5.tgz", + "integrity": "sha512-SMXGGXyj78c8Ru2f665ZFZU24phn0yZyCP5Ej7goxVQxABwqWKM/odj3j/IxZv+hxA8yU13yxaubgVefnereqw==", + "deprecated": "Reliability and performance improvements. See: https://github.com/WalletConnect/walletconnect-monorepo/releases", + "license": "Apache-2.0", + "dependencies": { + "@walletconnect/events": "1.0.1", + "@walletconnect/jsonrpc-http-connection": "1.0.8", + "@walletconnect/jsonrpc-provider": "1.0.14", + "@walletconnect/jsonrpc-types": "1.0.4", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/logger": "2.1.2", + "@walletconnect/sign-client": "2.21.5", + "@walletconnect/types": "2.21.5", + "@walletconnect/utils": "2.21.5", + "es-toolkit": "1.39.3", + "events": "3.3.0" + } + }, + "node_modules/@walletconnect/utils": { + "version": "2.21.5", + "resolved": "https://registry.npmjs.org/@walletconnect/utils/-/utils-2.21.5.tgz", + "integrity": "sha512-RSPSxPvGMuvfGhd5au1cf9cmHB/KVVLFotJR9ltisjFABGtH2215U5oaVp+a7W18QX37aemejRkvacqOELVySA==", + "license": "Apache-2.0", + "dependencies": { + "@msgpack/msgpack": "3.1.2", + "@noble/ciphers": "1.3.0", + "@noble/curves": "1.9.2", + "@noble/hashes": "1.8.0", + "@scure/base": "1.2.6", + "@walletconnect/jsonrpc-utils": "1.0.8", + "@walletconnect/keyvaluestorage": "1.1.1", + "@walletconnect/relay-api": "1.0.11", + "@walletconnect/relay-auth": "1.1.0", + "@walletconnect/safe-json": "1.0.2", + "@walletconnect/time": "1.0.2", + "@walletconnect/types": "2.21.5", + "@walletconnect/window-getters": "1.0.1", + "@walletconnect/window-metadata": "1.0.1", + "blakejs": "1.2.1", + "bs58": "6.0.0", + "detect-browser": "5.3.0", + "query-string": "7.1.3", + "uint8arrays": "3.1.1", + "viem": "2.31.0" + } + }, + "node_modules/@walletconnect/utils/node_modules/@noble/curves": { + "version": "1.9.2", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.2.tgz", + "integrity": "sha512-HxngEd2XUcg9xi20JkwlLCtYwfoFw4JGkuZpT+WlsPD4gB/cxkvTD8fSsoAnphGZhFdZYKeQIPCuFlWPm1uE0g==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" }, - "peerDependencies": { - "typescript": ">=4.8.4 <5.8.0" + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/brace-expansion": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", - "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", - "dev": true, - "dependencies": { - "balanced-match": "^1.0.0" + "node_modules/@walletconnect/utils/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/fast-glob": { - "version": "3.3.3", - "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.3.tgz", - "integrity": "sha512-7MptL8U0cqcFdzIzwOTHoilX9x5BrNqye7Z/LuC7kCMRio1EMSyqRK3BEAUD7sXRq4iT4AzTVuZdhgQ2TCvYLg==", - "dev": true, - "dependencies": { - "@nodelib/fs.stat": "^2.0.2", - "@nodelib/fs.walk": "^1.2.3", - "glob-parent": "^5.1.2", - "merge2": "^1.3.0", - "micromatch": "^4.0.8" + "node_modules/@walletconnect/utils/node_modules/abitype": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.0.8.tgz", + "integrity": "sha512-ZeiI6h3GnW06uYDLx0etQtX/p8E24UaHHBj57RSjK7YBFe7iuVn07EDpOeP451D06sF27VOz9JJPlIKJmXgkEg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" }, - "engines": { - "node": ">=8.6.0" + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3 >=3.22.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/glob-parent": { - "version": "5.1.2", - "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", - "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", - "dev": true, + "node_modules/@walletconnect/utils/node_modules/ox": { + "version": "0.7.1", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.7.1.tgz", + "integrity": "sha512-+k9fY9PRNuAMHRFIUbiK9Nt5seYHHzSQs9Bj+iMETcGtlpS7SmBzcGSVUQO3+nqGLEiNK4598pHNFlVRaZbRsg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", "dependencies": { - "is-glob": "^4.0.1" + "@adraffy/ens-normalize": "^1.10.1", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "^1.6.0", + "@noble/hashes": "^1.5.0", + "@scure/bip32": "^1.5.0", + "@scure/bip39": "^1.4.0", + "abitype": "^1.0.6", + "eventemitter3": "5.0.1" }, - "engines": { - "node": ">= 6" + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@typescript-eslint/typescript-estree/node_modules/minimatch": { - "version": "9.0.5", - "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.5.tgz", - "integrity": "sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow==", - "dev": true, + "node_modules/@walletconnect/utils/node_modules/viem": { + "version": "2.31.0", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.31.0.tgz", + "integrity": "sha512-U7OMQ6yqK+bRbEIarf2vqxL7unSEQvNxvML/1zG7suAmKuJmipqdVTVJGKBCJiYsm/EremyO2FS4dHIPpGv+eA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", "dependencies": { - "brace-expansion": "^2.0.1" + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.0.8", + "isows": "1.0.7", + "ox": "0.7.1", + "ws": "8.18.2" }, - "engines": { - "node": ">=16 || 14 >=14.17" + "peerDependencies": { + "typescript": ">=5.0.4" }, - "funding": { - "url": "https://github.com/sponsors/isaacs" + "peerDependenciesMeta": { + "typescript": { + "optional": true + } } }, - "node_modules/@typescript-eslint/utils": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-8.21.0.tgz", - "integrity": "sha512-xcXBfcq0Kaxgj7dwejMbFyq7IOHgpNMtVuDveK7w3ZGwG9owKzhALVwKpTF2yrZmEwl9SWdetf3fxNzJQaVuxw==", - "dev": true, + "node_modules/@walletconnect/utils/node_modules/viem/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", "dependencies": { - "@eslint-community/eslint-utils": "^4.4.0", - "@typescript-eslint/scope-manager": "8.21.0", - "@typescript-eslint/types": "8.21.0", - "@typescript-eslint/typescript-estree": "8.21.0" + "@noble/hashes": "1.8.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^14.21.3 || >=16" }, "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/@walletconnect/utils/node_modules/ws": { + "version": "8.18.2", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.2.tgz", + "integrity": "sha512-DMricUmwGZUVr++AEAe2uiVM7UoO9MAVZMDu05UQOaUII0lp+zOzLLU4Xqh/JvTqklB1T4uELaaPBKyjE1r4fQ==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" }, "peerDependencies": { - "eslint": "^8.57.0 || ^9.0.0", - "typescript": ">=4.8.4 <5.8.0" + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } } }, - "node_modules/@typescript-eslint/visitor-keys": { - "version": "8.21.0", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-8.21.0.tgz", - "integrity": "sha512-BkLMNpdV6prozk8LlyK/SOoWLmUFi+ZD+pcqti9ILCbVvHGk1ui1g4jJOc2WDLaeExz2qWwojxlPce5PljcT3w==", - "dev": true, + "node_modules/@walletconnect/window-getters": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/window-getters/-/window-getters-1.0.1.tgz", + "integrity": "sha512-vHp+HqzGxORPAN8gY03qnbTMnhqIwjeRJNOMOAzePRg4xVEEE2WvYsI9G2NMjOknA8hnuYbU3/hwLcKbjhc8+Q==", + "license": "MIT", "dependencies": { - "@typescript-eslint/types": "8.21.0", - "eslint-visitor-keys": "^4.2.0" + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/window-getters/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/@walletconnect/window-metadata": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@walletconnect/window-metadata/-/window-metadata-1.0.1.tgz", + "integrity": "sha512-9koTqyGrM2cqFRW517BPY/iEtUDx2r1+Pwwu5m7sJ7ka79wi3EyqhqcICk/yDmv6jAS1rjKgTKXlEhanYjijcA==", + "license": "MIT", + "dependencies": { + "@walletconnect/window-getters": "^1.0.1", + "tslib": "1.14.1" + } + }, + "node_modules/@walletconnect/window-metadata/node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "license": "0BSD" + }, + "node_modules/abitype": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/abitype/-/abitype-1.1.0.tgz", + "integrity": "sha512-6Vh4HcRxNMLA0puzPjM5GBgT4aAcFGKZzSgAXvuZ27shJP6NEpielTuqbBmZILR5/xd0PizkBGy5hReKz9jl5A==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/wevm" }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "peerDependencies": { + "typescript": ">=5.0.4", + "zod": "^3.22.0 || ^4.0.0" }, - "funding": { - "type": "opencollective", - "url": "https://opencollective.com/typescript-eslint" + "peerDependenciesMeta": { + "typescript": { + "optional": true + }, + "zod": { + "optional": true + } } }, "node_modules/acorn": { @@ -1404,7 +2470,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", - "dev": true, "dependencies": { "color-convert": "^2.0.1" }, @@ -1425,7 +2490,6 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", - "dev": true, "dependencies": { "normalize-path": "^3.0.0", "picomatch": "^2.0.4" @@ -1614,15 +2678,25 @@ "version": "2.6.4", "resolved": "https://registry.npmjs.org/async/-/async-2.6.4.tgz", "integrity": "sha512-mzo5dfJYwAn29PeiJ0zvwTo04zj8HDJj0Mn8TD7sno7q12prdbnasKJHhkm2c1LgrhlJ0teaea8860oxi51mGA==", + "license": "MIT", "dependencies": { "lodash": "^4.17.14" } }, + "node_modules/atomic-sleep": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/atomic-sleep/-/atomic-sleep-1.0.0.tgz", + "integrity": "sha512-kNOjDqAh7px0XWNI+4QbzoiR/nTkHAWNud2uvnJquD1/x5a7EQZMJT0AczqK0Qn67oY/TTQ1LbUKajZpp3I9tQ==", + "license": "MIT", + "engines": { + "node": ">=8.0.0" + } + }, "node_modules/available-typed-arrays": { "version": "1.0.7", "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", - "dev": true, + "devOptional": true, "dependencies": { "possible-typed-array-names": "^1.0.0" }, @@ -1679,7 +2753,28 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT" + }, + "node_modules/bech32": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-2.0.0.tgz", + "integrity": "sha512-LcknSilhIGatDAsY1ak2I8VtGaHNhgMSYVxFrGLXv+xLHytaKZKcaUJJUE7qmBr7h33o5YQwP55pMI0xmkpJwg==", + "license": "MIT", + "optional": true + }, + "node_modules/big.js": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-6.2.2.tgz", + "integrity": "sha512-y/ie+Faknx7sZA5MfGA2xKlu0GDv8RWrXGsmlteyJQ2lvoKv9GBK/fpRMc2qlSoBAgNxrixICFCBefIq8WCQpQ==", + "license": "MIT", + "engines": { + "node": "*" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/bigjs" + } }, "node_modules/binary-extensions": { "version": "2.3.0", @@ -1693,6 +2788,174 @@ "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", + "optional": true, + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bip174": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/bip174/-/bip174-2.1.1.tgz", + "integrity": "sha512-mdFV5+/v0XyNYXjBS6CQPLo9ekCx4gtKZFnJm5PMto7Fs9hTTDpkkzOB7/FtluRI6JbUUAu+snTYfJRgHLZbZQ==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bip322-js": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/bip322-js/-/bip322-js-2.0.0.tgz", + "integrity": "sha512-wyewxyCLl+wudZWiyvA46SaNQL41dVDJ+sx4HvD6zRXScHzAycwuKEMmbvr2qN+P/IIYArF4XVqlyZVnjutELQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@bitcoinerlab/secp256k1": "^1.1.1", + "bitcoinjs-lib": "^6.1.5", + "bitcoinjs-message": "^2.2.0", + "ecpair": "^2.1.0", + "elliptic": "^6.5.5", + "fast-sha256": "^1.3.0", + "secp256k1": "^5.0.0" + } + }, + "node_modules/bip66": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/bip66/-/bip66-1.1.5.tgz", + "integrity": "sha512-nemMHz95EmS38a26XbbdxIYj5csHd3RMP3H5bwQknX0WYHF01qhpufP42mLOwVICuH2JmhIhXiWs89MfUGL7Xw==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/bitcoinjs-lib": { + "version": "6.1.7", + "resolved": "https://registry.npmjs.org/bitcoinjs-lib/-/bitcoinjs-lib-6.1.7.tgz", + "integrity": "sha512-tlf/r2DGMbF7ky1MgUqXHzypYHakkEnm0SZP23CJKIqNY/5uNAnMbFhMJdhjrL/7anfb/U8+AlpdjPWjPnAalg==", + "license": "MIT", + "optional": true, + "dependencies": { + "@noble/hashes": "^1.2.0", + "bech32": "^2.0.0", + "bip174": "^2.1.1", + "bs58check": "^3.0.1", + "typeforce": "^1.11.3", + "varuint-bitcoin": "^1.1.2" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/bitcoinjs-lib/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/bitcoinjs-message": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/bitcoinjs-message/-/bitcoinjs-message-2.2.0.tgz", + "integrity": "sha512-103Wy3xg8Y9o+pdhGP4M3/mtQQuUWs6sPuOp1mYphSUoSMHjHTlkj32K4zxU8qMH0Ckv23emfkGlFWtoWZ7YFA==", + "license": "MIT", + "optional": true, + "dependencies": { + "bech32": "^1.1.3", + "bs58check": "^2.1.2", + "buffer-equals": "^1.0.3", + "create-hash": "^1.1.2", + "secp256k1": "^3.0.1", + "varuint-bitcoin": "^1.0.1" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/bitcoinjs-message/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/bitcoinjs-message/node_modules/bech32": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/bech32/-/bech32-1.1.4.tgz", + "integrity": "sha512-s0IrSOzLlbvX7yp4WBfPITzpAU8sqQcpsmwXDiKwrG4r491vwCO/XpejasRNl0piBMe/DvP4Tz0mIS/X1DPJBQ==", + "license": "MIT", + "optional": true + }, + "node_modules/bitcoinjs-message/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "optional": true, + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/bitcoinjs-message/node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "license": "MIT", + "optional": true, + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, + "node_modules/bitcoinjs-message/node_modules/secp256k1": { + "version": "3.8.1", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-3.8.1.tgz", + "integrity": "sha512-tArjQw2P0RTdY7QmkNehgp6TVvQXq6ulIhxv8gaH6YubKG/wxxAoNKcbuXjDhybbc+b2Ihc7e0xxiGN744UIiQ==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "bindings": "^1.5.0", + "bip66": "^1.1.5", + "bn.js": "^4.11.8", + "create-hash": "^1.2.0", + "drbg.js": "^1.0.1", + "elliptic": "^6.5.7", + "nan": "^2.14.0", + "safe-buffer": "^5.1.2" + }, + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/blakejs": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/blakejs/-/blakejs-1.2.1.tgz", + "integrity": "sha512-QXUSXI3QVc/gJME0dBpXrag1kbzOqCjCX8/b54ntNyW6sjtoqxqRk3LTmXzaJoh71zMsDCjM+47jS7XiwN/+fQ==", + "license": "MIT" + }, + "node_modules/bn.js": { + "version": "4.12.2", + "resolved": "https://registry.npmjs.org/bn.js/-/bn.js-4.12.2.tgz", + "integrity": "sha512-n4DSx829VRTRByMRGdjQ9iqsN0Bh4OolPsFnaZBLcbi8iXcB+kJ9s7EnRt4wILZNV3kPLHkRVfOc/HvhC3ovDw==", + "license": "MIT", + "optional": true + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1715,14 +2978,118 @@ "node": ">=8" } }, - "node_modules/bs58": { + "node_modules/brorand": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/brorand/-/brorand-1.1.0.tgz", + "integrity": "sha512-cKV8tMCEpQs4hK/ik71d6LrPOnpkpGBR0wzxqr68g2m/LB2GxVYQroAjMJZRVM1Y4BCjCKc3vAamxSzOY2RP+w==", + "license": "MIT", + "optional": true + }, + "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", + "optional": 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/bs58": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-6.0.0.tgz", + "integrity": "sha512-PD0wEnEYg6ijszw/u8s+iI3H17cTymlrwkKhDhPZq+Sokl3AU4htyBFTjAeNAlCCmg0f53g6ih3jATyCKftTfw==", + "license": "MIT", + "dependencies": { + "base-x": "^5.0.0" + } + }, + "node_modules/bs58/node_modules/base-x": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-5.0.1.tgz", + "integrity": "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg==", + "license": "MIT" + }, + "node_modules/bs58check": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-3.0.1.tgz", + "integrity": "sha512-hjuuJvoWEybo7Hn/0xOrczQKKEKD63WguEjlhLExYs2wUBcebDC1jDNK17eEAD2lYfw82d5ASC1d7K3SWszjaQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "@noble/hashes": "^1.2.0", + "bs58": "^5.0.0" + } + }, + "node_modules/bs58check/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "optional": true, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/bs58check/node_modules/bs58": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/bs58/-/bs58-5.0.0.tgz", "integrity": "sha512-r+ihvQJvahgYT50JD05dyJNKlmmSlMoOGwn1lCcEzanPglg7TxYjioQUYehQ9mAR/+hOSd2jRc/Z2y5UxBymvQ==", + "license": "MIT", + "optional": true, "dependencies": { "base-x": "^4.0.0" } }, + "node_modules/buffer": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", + "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.2.1" + } + }, + "node_modules/buffer-equals": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/buffer-equals/-/buffer-equals-1.0.4.tgz", + "integrity": "sha512-99MsCq0j5+RhubVEtKQgKaD6EM+UP3xJgIvQqwJ3SOLDUekzxMX1ylXBng+Wa2sh7mGT0W6RUly8ojjr1Tt6nA==", + "license": "MIT", + "optional": true, + "engines": { + "node": ">=0.10.0" + } + }, + "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", + "optional": true + }, "node_modules/busboy": { "version": "1.6.0", "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", @@ -1750,7 +3117,7 @@ "version": "1.0.8", "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", - "dev": true, + "devOptional": true, "dependencies": { "call-bind-apply-helpers": "^1.0.0", "es-define-property": "^1.0.0", @@ -1768,7 +3135,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.1.tgz", "integrity": "sha512-BhYE+WDaywFg2TBWYNXAE+8B1ATnThNBqXHP5nQu0jWJdVvY2hvkpyB3qOmtmDePiS5/BDQ8wASEWGMWRG148g==", - "dev": true, + "devOptional": true, "dependencies": { "es-errors": "^1.3.0", "function-bind": "^1.1.2" @@ -1781,7 +3148,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.3.tgz", "integrity": "sha512-YTd+6wGlNlPxSuri7Y6X8tY2dmm12UMH66RpKMhiX6rsk5wXXnYgbUcOt8kiS31/AjfoTOvCsE+w8nZQLQnzHA==", - "dev": true, + "devOptional": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "get-intrinsic": "^1.2.6" @@ -1802,6 +3169,15 @@ "node": ">=6" } }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/camelcase-css": { "version": "2.0.1", "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", @@ -1882,11 +3258,92 @@ "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", + "optional": true, + "dependencies": { + "inherits": "^2.0.4", + "safe-buffer": "^5.2.1", + "to-buffer": "^1.2.2" + }, + "engines": { + "node": ">= 0.10" + } + }, "node_modules/client-only": { "version": "0.0.1", "resolved": "https://registry.npmjs.org/client-only/-/client-only-0.0.1.tgz", "integrity": "sha512-IV3Ou0jSMzZrd3pZ48nLkT9DA7Ag1pnPzaiQhpW7c3RbcqqzvzzVu+L8gfqMp/8IM2MQtSiqaCxrrcfu8I8rMA==" }, + "node_modules/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "license": "ISC", + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/cliui/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/cliui/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cliui/node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/color": { "version": "4.2.3", "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", @@ -1904,7 +3361,6 @@ "version": "2.0.1", "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", - "devOptional": true, "dependencies": { "color-name": "~1.1.4" }, @@ -1915,8 +3371,7 @@ "node_modules/color-name": { "version": "1.1.4", "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", - "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", - "devOptional": true + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" }, "node_modules/color-string": { "version": "1.9.1", @@ -1943,6 +3398,48 @@ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", "dev": true }, + "node_modules/cookie-es": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/cookie-es/-/cookie-es-1.2.2.tgz", + "integrity": "sha512-+W7VmiVINB+ywl1HGXJXmrqkOhpKrIiVZV6tQuV54ZyQC7MMuBt81Vc336GMLoHBq5hV/F9eXgt5Mnx0Rha5Fg==", + "license": "MIT" + }, + "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", + "optional": 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", + "optional": 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", + "optional": 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": "3.2.0", "resolved": "https://registry.npmjs.org/cross-fetch/-/cross-fetch-3.2.0.tgz", @@ -1965,6 +3462,15 @@ "node": ">= 8" } }, + "node_modules/crossws": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/crossws/-/crossws-0.3.5.tgz", + "integrity": "sha512-ojKiDvcmByhwa8YYqbQI/hg7MEU0NC03+pSdEq4ZUnZR9xXpwk7E43SMNGkn+JxJGPFtNvQ48+vV2p+P1ml5PA==", + "license": "MIT", + "dependencies": { + "uncrypto": "^0.1.3" + } + }, "node_modules/cssesc": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", @@ -1981,7 +3487,7 @@ "version": "3.1.3", "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz", "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==", - "dev": true + "devOptional": true }, "node_modules/damerau-levenshtein": { "version": "1.0.8", @@ -2040,6 +3546,12 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dayjs": { + "version": "1.11.13", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", + "integrity": "sha512-oaMBel6gjolK862uaPQOVTA7q3TZhuSvuMQAAglQDOWYO9A91IrAOUJEyKVlqJlHE0vq5p5UXxzdPfMH/x6xNg==", + "license": "MIT" + }, "node_modules/debug": { "version": "4.4.0", "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.0.tgz", @@ -2057,6 +3569,24 @@ } } }, + "node_modules/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/decode-uri-component": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/decode-uri-component/-/decode-uri-component-0.2.2.tgz", + "integrity": "sha512-FqUYQ+8o158GyGTrMFJms9qh3CqTKvAqgqsTnkLI8sKu0028orqBhxNMFkFen0zGyg6epACD32pjVk58ngIErQ==", + "license": "MIT", + "engines": { + "node": ">=0.10" + } + }, "node_modules/deep-is": { "version": "0.1.4", "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", @@ -2067,7 +3597,7 @@ "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, + "devOptional": true, "dependencies": { "es-define-property": "^1.0.0", "es-errors": "^1.3.0", @@ -2097,6 +3627,24 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/defu": { + "version": "6.1.4", + "resolved": "https://registry.npmjs.org/defu/-/defu-6.1.4.tgz", + "integrity": "sha512-mEQCMmwJu317oSz8CwdIOdwf3xMif1ttiM8LTufzc3g6kR+9Pe236twL8j3IYT1F7GfRgGcW6MWxzZjLIkuHIg==", + "license": "MIT" + }, + "node_modules/destr": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/destr/-/destr-2.0.5.tgz", + "integrity": "sha512-ugFTXCtDZunbzasqBxrK93Ik/DRYsO6S/fedkWEMKqt04xZ4csmnmwGDBAb07QWNaGMAmnTIemsYZCksjATwsA==", + "license": "MIT" + }, + "node_modules/detect-browser": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/detect-browser/-/detect-browser-5.3.0.tgz", + "integrity": "sha512-53rsFbGdwMwlF7qvCt0ypLM5V5/Mbl0szB7GPN8y9NCcbknYOeVVXdrXEq+90IwAfrrzt6Hd+u2E2ntakICU8w==", + "license": "MIT" + }, "node_modules/detect-libc": { "version": "2.0.3", "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz", @@ -2112,6 +3660,12 @@ "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", "dev": true }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "license": "MIT" + }, "node_modules/dlv": { "version": "1.1.3", "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", @@ -2130,11 +3684,26 @@ "node": ">=0.10.0" } }, + "node_modules/drbg.js": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/drbg.js/-/drbg.js-1.0.1.tgz", + "integrity": "sha512-F4wZ06PvqxYLFEZKkFxTDcns9oFNk34hvmJSEwdzsxVQ8YI5YaxtACgQatkYgv2VI2CFkUd2Y+xosPQnHv809g==", + "license": "MIT", + "optional": true, + "dependencies": { + "browserify-aes": "^1.0.6", + "create-hash": "^1.1.2", + "create-hmac": "^1.1.4" + }, + "engines": { + "node": ">=0.10" + } + }, "node_modules/dunder-proto": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", - "dev": true, + "devOptional": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-errors": "^1.3.0", @@ -2144,18 +3713,90 @@ "node": ">= 0.4" } }, + "node_modules/duplexify": { + "version": "4.1.3", + "resolved": "https://registry.npmjs.org/duplexify/-/duplexify-4.1.3.tgz", + "integrity": "sha512-M3BmBhwJRZsSx38lZyhE53Csddgzl5R7xGJNk7CVddZD6CcmwMCH8J+7AprIrQKH7TonKxaCjcv27Qmf+sQ+oA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.4.1", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1", + "stream-shift": "^1.0.2" + } + }, + "node_modules/duplexify/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, "node_modules/eastasianwidth": { "version": "0.2.0", "resolved": "https://registry.npmjs.org/eastasianwidth/-/eastasianwidth-0.2.0.tgz", "integrity": "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA==", "dev": true }, + "node_modules/ecpair": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/ecpair/-/ecpair-2.1.0.tgz", + "integrity": "sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw==", + "license": "MIT", + "optional": true, + "dependencies": { + "randombytes": "^2.1.0", + "typeforce": "^1.18.0", + "wif": "^2.0.6" + }, + "engines": { + "node": ">=8.0.0" + } + }, + "node_modules/elliptic": { + "version": "6.6.1", + "resolved": "https://registry.npmjs.org/elliptic/-/elliptic-6.6.1.tgz", + "integrity": "sha512-RaddvvMatK2LJHqFJ+YA4WysVN5Ita9E35botqIYspQ4TkRAlCicdzKOjlyv/1Za5RyTNn7di//eEV0uTAfe3g==", + "license": "MIT", + "optional": true, + "dependencies": { + "bn.js": "^4.11.9", + "brorand": "^1.1.0", + "hash.js": "^1.0.0", + "hmac-drbg": "^1.0.1", + "inherits": "^2.0.4", + "minimalistic-assert": "^1.0.1", + "minimalistic-crypto-utils": "^1.0.1" + } + }, "node_modules/emoji-regex": { "version": "9.2.2", "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-9.2.2.tgz", "integrity": "sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg==", "dev": true }, + "node_modules/encode-utf8": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", + "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==", + "license": "MIT" + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, "node_modules/enhanced-resolve": { "version": "5.18.0", "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz", @@ -2238,7 +3879,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.4" } @@ -2247,7 +3888,7 @@ "version": "1.3.0", "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.4" } @@ -2283,7 +3924,7 @@ "version": "1.1.1", "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", - "dev": true, + "devOptional": true, "dependencies": { "es-errors": "^1.3.0" }, @@ -2332,6 +3973,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/es-toolkit": { + "version": "1.39.3", + "resolved": "https://registry.npmjs.org/es-toolkit/-/es-toolkit-1.39.3.tgz", + "integrity": "sha512-Qb/TCFCldgOy8lZ5uC7nLGdqJwSabkQiYQShmw4jyiPk1pZzaYWTwaYKYP7EgLccWYgZocMrtItrwh683voaww==", + "license": "MIT", + "workspaces": [ + "docs", + "benchmarks" + ] + }, "node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2776,6 +4427,32 @@ "node": ">=0.10.0" } }, + "node_modules/eventemitter3": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-5.0.1.tgz", + "integrity": "sha512-GWkBvjiSZK87ELrYOSESUYeVIc9mvLLf/nXalMOS5dYrgZq9o5OVkbZAVM06CVxYsCwH9BDZFPlQTlPA1j4ahA==", + "license": "MIT" + }, + "node_modules/events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "license": "MIT", + "engines": { + "node": ">=0.8.x" + } + }, + "node_modules/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", + "optional": true, + "dependencies": { + "md5.js": "^1.3.4", + "safe-buffer": "^5.1.1" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -2822,6 +4499,22 @@ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", "dev": true }, + "node_modules/fast-redact": { + "version": "3.5.0", + "resolved": "https://registry.npmjs.org/fast-redact/-/fast-redact-3.5.0.tgz", + "integrity": "sha512-dwsoQlS7h9hMeYUq1W++23NDcBLV4KqONnITDV9DjfS3q1SgDGVrBdvvTLUotWtPSD7asWDV9/CmsZPy8Hf70A==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/fast-sha256": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", + "integrity": "sha512-n11RGP/lrWEFI/bWdygLxhI+pVeo1ZYIVwvvPkW7azl/rOy+F3HYRZ2K5zeE9mmkhQppyv9sQFx0JM9UabnpPQ==", + "license": "Unlicense", + "optional": true + }, "node_modules/fastq": { "version": "1.18.0", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.18.0.tgz", @@ -2843,6 +4536,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", + "optional": true + }, "node_modules/fill-range": { "version": "7.1.1", "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", @@ -2855,6 +4555,15 @@ "node": ">=8" } }, + "node_modules/filter-obj": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/filter-obj/-/filter-obj-1.1.0.tgz", + "integrity": "sha512-8rXg1ZnX7xzy2NGDVkBVaAy+lSlPNwad13BtgSlLuxfIslyt5Vg64U7tFcCt4WS1R0hvtnQybT/IyCkGZ3DpXQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, "node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2894,7 +4603,7 @@ "version": "0.3.3", "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", - "dev": true, + "devOptional": true, "dependencies": { "is-callable": "^1.1.3" } @@ -2933,7 +4642,7 @@ "version": "1.1.2", "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", - "dev": true, + "devOptional": true, "funding": { "url": "https://github.com/sponsors/ljharb" } @@ -2967,11 +4676,20 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "license": "ISC", + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, "node_modules/get-intrinsic": { "version": "1.2.7", "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.7.tgz", "integrity": "sha512-VW6Pxhsrk0KAOqs3WEd0klDiF/+V7gQOpAvY1jVU/LHmaD/kQO4523aiJuikX/QAKYiW6x8Jh+RJej1almdtCA==", - "dev": true, + "devOptional": true, "dependencies": { "call-bind-apply-helpers": "^1.0.1", "es-define-property": "^1.0.1", @@ -2995,7 +4713,7 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", - "dev": true, + "devOptional": true, "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" @@ -3121,7 +4839,7 @@ "version": "1.2.0", "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.4" }, @@ -3141,6 +4859,23 @@ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", "dev": true }, + "node_modules/h3": { + "version": "1.15.4", + "resolved": "https://registry.npmjs.org/h3/-/h3-1.15.4.tgz", + "integrity": "sha512-z5cFQWDffyOe4vQ9xIqNfCZdV4p//vy6fBnr8Q1AWnVZ0teurKMG66rLj++TKwKPUP3u7iMUvrvKaEUiQw2QWQ==", + "license": "MIT", + "dependencies": { + "cookie-es": "^1.2.2", + "crossws": "^0.3.5", + "defu": "^6.1.4", + "destr": "^2.0.5", + "iron-webcrypto": "^1.2.1", + "node-mock-http": "^1.0.2", + "radix3": "^1.1.2", + "ufo": "^1.6.1", + "uncrypto": "^0.1.3" + } + }, "node_modules/has-bigints": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", @@ -3166,7 +4901,7 @@ "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, + "devOptional": true, "dependencies": { "es-define-property": "^1.0.0" }, @@ -3193,7 +4928,7 @@ "version": "1.1.0", "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.4" }, @@ -3205,7 +4940,7 @@ "version": "1.0.2", "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", - "dev": true, + "devOptional": true, "dependencies": { "has-symbols": "^1.0.3" }, @@ -3216,11 +4951,38 @@ "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", + "optional": 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.js": { + "version": "1.1.7", + "resolved": "https://registry.npmjs.org/hash.js/-/hash.js-1.1.7.tgz", + "integrity": "sha512-taOaskGt4z4SOANNseOviYDvjEJinIkRgmp7LbKP2YTTmVxWBl87s/uzK9r+44BclBSp2X7K1hqeNfz9JbBeXA==", + "license": "MIT", + "optional": true, + "dependencies": { + "inherits": "^2.0.3", + "minimalistic-assert": "^1.0.1" + } + }, "node_modules/hasown": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", - "dev": true, + "devOptional": true, "dependencies": { "function-bind": "^1.1.2" }, @@ -3228,6 +4990,44 @@ "node": ">= 0.4" } }, + "node_modules/hmac-drbg": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", + "integrity": "sha512-Tti3gMqLdZfhOQY1Mzf/AanLiqh1WTiJgEj26ZuYQ9fbkLomzGchCws4FyrSd4VkpBfiNhaE1On+lOz894jvXg==", + "license": "MIT", + "optional": true, + "dependencies": { + "hash.js": "^1.0.3", + "minimalistic-assert": "^1.0.0", + "minimalistic-crypto-utils": "^1.0.1" + } + }, + "node_modules/idb-keyval": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz", + "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==", + "license": "Apache-2.0" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/ignore": { "version": "5.3.2", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.2.tgz", @@ -3262,6 +5062,12 @@ "node": ">=0.8.19" } }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, "node_modules/internal-slot": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", @@ -3276,6 +5082,15 @@ "node": ">= 0.4" } }, + "node_modules/iron-webcrypto": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/iron-webcrypto/-/iron-webcrypto-1.2.1.tgz", + "integrity": "sha512-feOM6FaSr6rEABp/eDfVseKyTMDt+KGpeB35SkVn9Tyn0CqvVsY3EwI0v5i8nMHyJnzCIQf7nsy3p41TPkJZhg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/brc-dd" + } + }, "node_modules/is-array-buffer": { "version": "3.0.5", "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", @@ -3373,7 +5188,7 @@ "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, + "devOptional": true, "engines": { "node": ">= 0.4" }, @@ -3457,7 +5272,6 @@ "version": "3.0.0", "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", - "dev": true, "engines": { "node": ">=8" } @@ -3611,7 +5425,7 @@ "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, + "devOptional": true, "dependencies": { "which-typed-array": "^1.1.16" }, @@ -3669,7 +5483,7 @@ "version": "2.0.5", "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", - "dev": true + "devOptional": true }, "node_modules/isexe": { "version": "2.0.0", @@ -3677,6 +5491,21 @@ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", "dev": true }, + "node_modules/isows": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/isows/-/isows-1.0.7.tgz", + "integrity": "sha512-I1fSfDCZL5P0v33sVqeTDSpcstAg/N+wF5HS033mogOVIp4B+oHC7oOCsA3axAbBSGTJ8QubbNmnIRN/h8U7hg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "peerDependencies": { + "ws": "*" + } + }, "node_modules/iterator.prototype": { "version": "1.1.5", "resolved": "https://registry.npmjs.org/iterator.prototype/-/iterator.prototype-1.1.5.tgz", @@ -3770,6 +5599,7 @@ "version": "4.0.1", "resolved": "https://registry.npmjs.org/jsontokens/-/jsontokens-4.0.1.tgz", "integrity": "sha512-+MO415LEN6M+3FGsRz4wU20g7N2JA+2j9d9+pGaNJHviG4L8N0qzavGyENw6fJqsq9CcrHOIL6iWX5yeTZ86+Q==", + "license": "MIT", "dependencies": { "@noble/hashes": "^1.1.2", "@noble/secp256k1": "^1.6.3", @@ -3800,6 +5630,12 @@ "json-buffer": "3.0.1" } }, + "node_modules/keyvaluestorage-interface": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/keyvaluestorage-interface/-/keyvaluestorage-interface-1.0.0.tgz", + "integrity": "sha512-8t6Q3TclQ4uZynJY9IGr2+SsIGwK9JHcO6ootkHCGA0CrQCRy+VkouYNO2xicET6b9al7QKzpebNow+gkpCL8g==", + "license": "MIT" + }, "node_modules/language-subtag-registry": { "version": "0.3.23", "resolved": "https://registry.npmjs.org/language-subtag-registry/-/language-subtag-registry-0.3.23.tgz", @@ -3849,6 +5685,37 @@ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", "dev": true }, + "node_modules/lit": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/lit/-/lit-3.3.0.tgz", + "integrity": "sha512-DGVsqsOIHBww2DqnuZzW7QsuCdahp50ojuDaBPC7jUDRpYoH0z7kHBBYZewRzer75FwtrkmkKk7iOAwSaWdBmw==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit/reactive-element": "^2.1.0", + "lit-element": "^4.2.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-element": { + "version": "4.2.1", + "resolved": "https://registry.npmjs.org/lit-element/-/lit-element-4.2.1.tgz", + "integrity": "sha512-WGAWRGzirAgyphK2urmYOV72tlvnxw7YfyLDgQ+OZnM9vQQBQnumQ7jUJe6unEzwGU3ahFOjuz1iz1jjrpCPuw==", + "license": "BSD-3-Clause", + "dependencies": { + "@lit-labs/ssr-dom-shim": "^1.4.0", + "@lit/reactive-element": "^2.1.0", + "lit-html": "^3.3.0" + } + }, + "node_modules/lit-html": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/lit-html/-/lit-html-3.3.1.tgz", + "integrity": "sha512-S9hbyDu/vs1qNrithiNyeyv64c9yqiW9l+DBgI18fL+MTvOtWoFR0FWiyq1TxaYef5wNlpEmzlXoBlZEO+WjoA==", + "license": "BSD-3-Clause", + "dependencies": { + "@types/trusted-types": "^2.0.2" + } + }, "node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -3867,7 +5734,8 @@ "node_modules/lodash": { "version": "4.17.21", "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", - "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==", + "license": "MIT" }, "node_modules/lodash.clonedeep": { "version": "4.5.0", @@ -3895,18 +5763,29 @@ "node_modules/lru-cache": { "version": "10.4.3", "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-10.4.3.tgz", - "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==", - "dev": true + "integrity": "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ==" }, "node_modules/math-intrinsics": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.4" } }, + "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", + "optional": true, + "dependencies": { + "hash-base": "^3.0.0", + "inherits": "^2.0.1", + "safe-buffer": "^5.1.2" + } + }, "node_modules/merge2": { "version": "1.4.1", "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", @@ -3929,6 +5808,20 @@ "node": ">=8.6" } }, + "node_modules/minimalistic-assert": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz", + "integrity": "sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A==", + "license": "ISC", + "optional": true + }, + "node_modules/minimalistic-crypto-utils": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/minimalistic-crypto-utils/-/minimalistic-crypto-utils-1.0.1.tgz", + "integrity": "sha512-JIYlbt6g8i5jKfJ3xz7rF0LXmv2TkDxBLUkiBeZ7bAx4GnnNMr8xFpGnOxn6GhTEHx3SjRrZEoU+j04prX1ktg==", + "license": "MIT", + "optional": true + }, "node_modules/minimatch": { "version": "3.1.2", "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", @@ -3965,6 +5858,12 @@ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", "dev": true }, + "node_modules/multiformats": { + "version": "9.9.0", + "resolved": "https://registry.npmjs.org/multiformats/-/multiformats-9.9.0.tgz", + "integrity": "sha512-HoMUjhH9T8DDBNT+6xzkrd9ga/XiBI4xLr58LJACwK6G3HTOPeMz4nB4KJs33L2BelrIJa7P0VuNaVF3hMYfjg==", + "license": "(Apache-2.0 AND MIT)" + }, "node_modules/mz": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", @@ -3976,6 +5875,13 @@ "thenify-all": "^1.0.0" } }, + "node_modules/nan": { + "version": "2.23.0", + "resolved": "https://registry.npmjs.org/nan/-/nan-2.23.0.tgz", + "integrity": "sha512-1UxuyYGdoQHcGg87Lkqm3FzefucTa0NAiOcuRsDmysep3c1LVCRK2krrUDafMWtjSG04htvAmvg96+SDknOmgQ==", + "license": "MIT", + "optional": true + }, "node_modules/nanoid": { "version": "3.3.8", "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.8.tgz", @@ -4079,6 +5985,13 @@ "node": "^10 || ^12 || >=14" } }, + "node_modules/node-addon-api": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/node-addon-api/-/node-addon-api-5.1.0.tgz", + "integrity": "sha512-eh0GgfEkpnoWDq+VY8OyvYhFEzBk6jIYbRKdIlyTiAXIVJ8PyBaKb0rp7oDtoddbdoHWhq8wwr+XZ81F1rpNdA==", + "license": "MIT", + "optional": true + }, "node_modules/node-fetch": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", @@ -4098,11 +6011,34 @@ } } }, + "node_modules/node-fetch-native": { + "version": "1.6.7", + "resolved": "https://registry.npmjs.org/node-fetch-native/-/node-fetch-native-1.6.7.tgz", + "integrity": "sha512-g9yhqoedzIUm0nTnTqAQvueMPVOuIY16bqgAJJC8XOOubYFNwz6IER9qs0Gq2Xd0+CecCKFjtdDTMA4u4xG06Q==", + "license": "MIT" + }, + "node_modules/node-gyp-build": { + "version": "4.8.4", + "resolved": "https://registry.npmjs.org/node-gyp-build/-/node-gyp-build-4.8.4.tgz", + "integrity": "sha512-LA4ZjwlnUblHVgq0oBF3Jl/6h/Nvs5fzBLwdEF4nuxnFdsfajde4WfxtJr3CaiH+F6ewcIB/q4jQ4UzPyid+CQ==", + "license": "MIT", + "optional": true, + "bin": { + "node-gyp-build": "bin.js", + "node-gyp-build-optional": "optional.js", + "node-gyp-build-test": "build-test.js" + } + }, + "node_modules/node-mock-http": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/node-mock-http/-/node-mock-http-1.0.3.tgz", + "integrity": "sha512-jN8dK25fsfnMrVsEhluUTPkBFY+6ybu7jSB1n+ri/vOGjJxU8J9CZhpSGkHXSkFjtUhbmoncG/YG9ta5Ludqog==", + "license": "MIT" + }, "node_modules/normalize-path": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4230,6 +6166,32 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ofetch": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/ofetch/-/ofetch-1.4.1.tgz", + "integrity": "sha512-QZj2DfGplQAr2oj9KzceK9Hwz6Whxazmn85yYeVuS3u9XTMOGMRx0kO95MQ+vLsj/S/NwBDMMLU5hpxvI6Tklw==", + "license": "MIT", + "dependencies": { + "destr": "^2.0.3", + "node-fetch-native": "^1.6.4", + "ufo": "^1.5.4" + } + }, + "node_modules/on-exit-leak-free": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-0.2.0.tgz", + "integrity": "sha512-dqaz3u44QbRXQooZLTUKU41ZrzYrcvLISVgbrzbyCMxpmSLJvZ3ZamIJIZ29P6OhZIkNIQKosdeM6t1LYbA9hg==", + "license": "MIT" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, "node_modules/optionator": { "version": "0.9.4", "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.4.tgz", @@ -4264,6 +6226,63 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/ox": { + "version": "0.9.6", + "resolved": "https://registry.npmjs.org/ox/-/ox-0.9.6.tgz", + "integrity": "sha512-8SuCbHPvv2eZLYXrNmC0EC12rdzXQLdhnOMlHDW2wiCPLxBrOOJwX5L5E61by+UjTPOryqQiRSnjIKCI+GykKg==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@adraffy/ens-normalize": "^1.11.0", + "@noble/ciphers": "^1.3.0", + "@noble/curves": "1.9.1", + "@noble/hashes": "^1.8.0", + "@scure/bip32": "^1.7.0", + "@scure/bip39": "^1.6.0", + "abitype": "^1.0.9", + "eventemitter3": "5.0.1" + }, + "peerDependencies": { + "typescript": ">=5.4.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/ox/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/ox/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/p-limit": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", @@ -4294,6 +6313,15 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, "node_modules/package-json-from-dist": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/package-json-from-dist/-/package-json-from-dist-1.0.1.tgz", @@ -4316,7 +6344,6 @@ "version": "4.0.0", "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", - "dev": true, "engines": { "node": ">=8" } @@ -4361,7 +6388,6 @@ "version": "2.3.1", "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", - "dev": true, "engines": { "node": ">=8.6" }, @@ -4378,6 +6404,44 @@ "node": ">=0.10.0" } }, + "node_modules/pino": { + "version": "7.11.0", + "resolved": "https://registry.npmjs.org/pino/-/pino-7.11.0.tgz", + "integrity": "sha512-dMACeu63HtRLmCG8VKdy4cShCPKaYDR4youZqoSWLxl5Gu99HUw8bw75thbPv9Nip+H+QYX8o3ZJbTdVZZ2TVg==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0", + "fast-redact": "^3.0.0", + "on-exit-leak-free": "^0.2.0", + "pino-abstract-transport": "v0.5.0", + "pino-std-serializers": "^4.0.0", + "process-warning": "^1.0.0", + "quick-format-unescaped": "^4.0.3", + "real-require": "^0.1.0", + "safe-stable-stringify": "^2.1.0", + "sonic-boom": "^2.2.1", + "thread-stream": "^0.15.1" + }, + "bin": { + "pino": "bin.js" + } + }, + "node_modules/pino-abstract-transport": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-0.5.0.tgz", + "integrity": "sha512-+KAgmVeqXYbTtU2FScx1XS3kNyfZ5TrXY07V96QnUSFqo2gAqlvmaxH67Lj7SWazqsMabf+58ctdTcBgnOLUOQ==", + "license": "MIT", + "dependencies": { + "duplexify": "^4.1.2", + "split2": "^4.0.0" + } + }, + "node_modules/pino-std-serializers": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", + "integrity": "sha512-cK0pekc1Kjy5w9V2/n+8MkZwusa6EyyxfeQCB799CQRhRt/CqYKiWs5adeu8Shve2ZNffvfC/7J64A2PJo1W/Q==", + "license": "MIT" + }, "node_modules/pirates": { "version": "4.0.6", "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", @@ -4387,11 +6451,20 @@ "node": ">= 6" } }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "license": "MIT", + "engines": { + "node": ">=10.13.0" + } + }, "node_modules/possible-typed-array-names": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.0.0.tgz", "integrity": "sha512-d7Uw+eZoloe0EHDIYoe+bQ5WXnGMOpmiZFTuMWCwpjzzkL2nTjcKiAk4hh8TjnGye2TwWOk3UXucZ+3rbmBa8Q==", - "dev": true, + "devOptional": true, "engines": { "node": ">= 0.4" } @@ -4548,6 +6621,19 @@ "node": ">= 0.8.0" } }, + "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", + "optional": true + }, + "node_modules/process-warning": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/process-warning/-/process-warning-1.0.0.tgz", + "integrity": "sha512-du4wfLyj4yCZq1VupnVSZmRsPJsNuxoDQFdCFHLaYiEbFBD7QE0a+I4D7hOxrVnh78QE/YipFAj9lXHiXocV+Q==", + "license": "MIT" + }, "node_modules/prop-types": { "version": "15.8.1", "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz", @@ -4559,6 +6645,12 @@ "react-is": "^16.13.1" } }, + "node_modules/proxy-compare": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.1.tgz", + "integrity": "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==", + "license": "MIT" + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -4568,6 +6660,42 @@ "node": ">=6" } }, + "node_modules/qrcode": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", + "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==", + "license": "MIT", + "dependencies": { + "dijkstrajs": "^1.0.1", + "encode-utf8": "^1.0.3", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/query-string": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/query-string/-/query-string-7.1.3.tgz", + "integrity": "sha512-hh2WYhq4fi8+b+/2Kg9CEge4fDPvHS534aOOvOZeQ3+Vf2mCFsaFBYj0i+iXcAq6I9Vzp5fjMFBlONvayDC1qg==", + "license": "MIT", + "dependencies": { + "decode-uri-component": "^0.2.2", + "filter-obj": "^1.1.0", + "split-on-first": "^1.0.0", + "strict-uri-encode": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/queue-microtask": { "version": "1.2.3", "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", @@ -4588,6 +6716,28 @@ } ] }, + "node_modules/quick-format-unescaped": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/quick-format-unescaped/-/quick-format-unescaped-4.0.4.tgz", + "integrity": "sha512-tYC1Q1hgyRuHgloV/YXs2w15unPVh8qfu/qCTfhTYamaw7fyhumKa2yGpdSo87vY32rIclj+4fWYQXUMs9EHvg==", + "license": "MIT" + }, + "node_modules/radix3": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/radix3/-/radix3-1.1.2.tgz", + "integrity": "sha512-b484I/7b8rDEdSDKckSSBA8knMpcdsXudlE/LNL639wFoHKwLbEkQFZHWEYwDC0wa0FKUcCY+GAF73Z7wxNVFA==", + "license": "MIT" + }, + "node_modules/randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "^5.1.0" + } + }, "node_modules/react": { "version": "19.0.0", "resolved": "https://registry.npmjs.org/react/-/react-19.0.0.tgz", @@ -4622,6 +6772,36 @@ "pify": "^2.3.0" } }, + "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", + "optional": 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/readable-stream/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", + "optional": true + }, + "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", + "optional": true + }, "node_modules/readdirp": { "version": "3.6.0", "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", @@ -4634,6 +6814,15 @@ "node": ">=8.10.0" } }, + "node_modules/real-require": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/real-require/-/real-require-0.1.0.tgz", + "integrity": "sha512-r/H9MzAWtrv8aSVjPCMFpDMl5q66GqtmmRkRjpHTsp4zBAa+snZyiQNlMONiUmEJcsnaw0wCauJ2GWODr/aFkg==", + "license": "MIT", + "engines": { + "node": ">= 12.13.0" + } + }, "node_modules/reflect.getprototypeof": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/reflect.getprototypeof/-/reflect.getprototypeof-1.0.10.tgz", @@ -4676,6 +6865,21 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "license": "ISC" + }, "node_modules/resolve": { "version": "1.22.10", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.10.tgz", @@ -4724,12 +6928,18 @@ "node": ">=0.10.0" } }, - "node_modules/ripemd160-min": { - "version": "0.0.6", - "resolved": "https://registry.npmjs.org/ripemd160-min/-/ripemd160-min-0.0.6.tgz", - "integrity": "sha512-+GcJgQivhs6S9qvLogusiTcS9kQUfgR75whKuy5jIhuiOfQuJ8fjqxV6EGD5duH1Y/FawFUMtMhyeq3Fbnib8A==", + "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", + "optional": true, + "dependencies": { + "hash-base": "^3.1.2", + "inherits": "^2.0.4" + }, "engines": { - "node": ">=8" + "node": ">= 0.8" } }, "node_modules/run-parallel": { @@ -4791,7 +7001,9 @@ "type": "consulting", "url": "https://feross.org/support" } - ] + ], + "license": "MIT", + "optional": true }, "node_modules/safe-push-apply": { "version": "1.0.0", @@ -4826,6 +7038,15 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/safe-stable-stringify": { + "version": "2.5.0", + "resolved": "https://registry.npmjs.org/safe-stable-stringify/-/safe-stable-stringify-2.5.0.tgz", + "integrity": "sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/scheduler": { "version": "0.25.0", "resolved": "https://registry.npmjs.org/scheduler/-/scheduler-0.25.0.tgz", @@ -4835,15 +7056,32 @@ "version": "2.1.0", "resolved": "https://registry.npmjs.org/schema-inspector/-/schema-inspector-2.1.0.tgz", "integrity": "sha512-3bmQVhbA01/EW8cZin4vIpqlpNU2SIy4BhKCfCgogJ3T/L76dLx3QAE+++4o+dNT33sa+SN9vOJL7iHiHFjiNg==", + "license": "MIT", "dependencies": { "async": "~2.6.3" } }, + "node_modules/secp256k1": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/secp256k1/-/secp256k1-5.0.1.tgz", + "integrity": "sha512-lDFs9AAIaWP9UCdtWrotXWWF9t8PWgQDcxqgAnpM9rMqxb3Oaq2J0thzPVSxBwdJgyQtkU/sYtFtbM1RSt/iYA==", + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "dependencies": { + "elliptic": "^6.5.7", + "node-addon-api": "^5.0.0", + "node-gyp-build": "^4.2.0" + }, + "engines": { + "node": ">=18.0.0" + } + }, "node_modules/semver": { - "version": "7.6.3", - "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.3.tgz", - "integrity": "sha512-oVekP1cKtI+CTDvHWYFUcMtsK/00wmAEfyqKfNdARm8u1wNVhSgaX7A8d4UuIlUI5e84iEwOhs7ZPYRmzU9U6A==", - "devOptional": true, + "version": "7.7.2", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", + "integrity": "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA==", + "license": "ISC", "bin": { "semver": "bin/semver.js" }, @@ -4851,11 +7089,17 @@ "node": ">=10" } }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "license": "ISC" + }, "node_modules/set-function-length": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", - "dev": true, + "devOptional": true, "dependencies": { "define-data-property": "^1.1.4", "es-errors": "^1.3.0", @@ -4897,6 +7141,27 @@ "node": ">= 0.4" } }, + "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)", + "optional": 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/sharp": { "version": "0.33.5", "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", @@ -5050,6 +7315,15 @@ "is-arrayish": "^0.3.1" } }, + "node_modules/sonic-boom": { + "version": "2.8.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-2.8.0.tgz", + "integrity": "sha512-kuonw1YOYYNOve5iHdSahXPOK49GqwA+LZhI6Wz/l0rP57iKyXXIHaRagOBHAPmGwJC6od2Z9zgvZ5loSgMlVg==", + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, "node_modules/source-map-js": { "version": "1.2.1", "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.2.1.tgz", @@ -5058,12 +7332,36 @@ "node": ">=0.10.0" } }, + "node_modules/split-on-first": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/split-on-first/-/split-on-first-1.1.0.tgz", + "integrity": "sha512-43ZssAJaMusuKWL8sKUBQXHWOpq8d6CfN/u1p4gUzfJkM05C8rxTmYrkIPTXapZpORA6LkkzcUulJ8FqA7Uudw==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/split2": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/split2/-/split2-4.2.0.tgz", + "integrity": "sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==", + "license": "ISC", + "engines": { + "node": ">= 10.x" + } + }, "node_modules/stable-hash": { "version": "0.0.4", "resolved": "https://registry.npmjs.org/stable-hash/-/stable-hash-0.0.4.tgz", "integrity": "sha512-LjdcbuBeLcdETCrPn9i8AYAZ1eCtu4ECAWtP7UleOiZ9LzVxRzzUZEoZ8zB24nhkQnDWyET0I+3sWokSDS3E7g==", "dev": true }, + "node_modules/stream-shift": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/stream-shift/-/stream-shift-1.0.3.tgz", + "integrity": "sha512-76ORR0DO1o1hlKwTbi/DM3EXWGf3ZJYO8cXX5RJwnul2DEg2oyoZyjLNoQM8WsvZiFKCRfC1O0J7iCvie3RZmQ==", + "license": "MIT" + }, "node_modules/streamsearch": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", @@ -5072,6 +7370,30 @@ "node": ">=10.0.0" } }, + "node_modules/strict-uri-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strict-uri-encode/-/strict-uri-encode-2.0.0.tgz", + "integrity": "sha512-QwiXZgpRcKkhTj2Scnn++4PKtWsH0kpzZ62L2R6c/LUVYv7hVnZqcg2+sMuT6R7Jusu1vviK/MFsu6kNJfWlEQ==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/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" + }, "node_modules/string-width": { "version": "5.1.2", "resolved": "https://registry.npmjs.org/string-width/-/string-width-5.1.2.tgz", @@ -5453,10 +7775,34 @@ "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", "dev": true, "dependencies": { - "thenify": ">= 3.1.0 < 4" + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/thread-stream": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/thread-stream/-/thread-stream-0.15.2.tgz", + "integrity": "sha512-UkEhKIg2pD+fjkHQKyJO3yoIvAP3N6RlNFt2dUhcS1FGvCD1cQa1M/PGknCLFIyZdtJOWQjejp7bdNqmN7zwdA==", + "license": "MIT", + "dependencies": { + "real-require": "^0.1.0" + } + }, + "node_modules/to-buffer": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/to-buffer/-/to-buffer-1.2.2.tgz", + "integrity": "sha512-db0E3UJjcFhpDhAF4tLo03oli3pwl3dbnzXOUIlRKrp+ldk/VUxzpWYZENsw2SZiuBjHAk7DfB0VU7NKdpb6sw==", + "license": "MIT", + "optional": true, + "dependencies": { + "isarray": "^2.0.5", + "safe-buffer": "^5.2.1", + "typed-array-buffer": "^1.0.3" }, "engines": { - "node": ">=0.8" + "node": ">= 0.4" } }, "node_modules/to-regex-range": { @@ -5511,6 +7857,13 @@ "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==" }, + "node_modules/tweetnacl": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tweetnacl/-/tweetnacl-1.0.3.tgz", + "integrity": "sha512-6rt+RN7aOi1nGMyC4Xa5DdYiukl2UWCbcJft7YhxReBGQD7OAM8Pbxw6YMo4r2diNEA8FEmu32YOn9rhaiE5yw==", + "license": "Unlicense", + "optional": true + }, "node_modules/type-check": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", @@ -5527,7 +7880,7 @@ "version": "1.0.3", "resolved": "https://registry.npmjs.org/typed-array-buffer/-/typed-array-buffer-1.0.3.tgz", "integrity": "sha512-nAYYwfY3qnzX30IkA6AQZjVbtK6duGontcQm1WSG1MD94YLqK0515GNApXkoxKOWMusVssAHWLh9SeaoefYFGw==", - "dev": true, + "devOptional": true, "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", @@ -5597,11 +7950,18 @@ "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", + "optional": true + }, "node_modules/typescript": { "version": "5.7.3", "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.7.3.tgz", "integrity": "sha512-84MVSjMEHP+FQRPy3pX9sTVV/INIex71s9TL2Gm5FG/WG1SqXeKyZ0k7/blY/4FdOzI12CBy1vGc4og/eus0fw==", - "dev": true, + "devOptional": true, "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" @@ -5610,6 +7970,21 @@ "node": ">=14.17" } }, + "node_modules/ufo": { + "version": "1.6.1", + "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.6.1.tgz", + "integrity": "sha512-9a4/uxlTWJ4+a5i0ooc1rU7C7YOw3wT+UGqdeNNHWnOF9qcMBgLRS+4IYUqbczewFx4mLEig6gawh7X6mFlEkA==", + "license": "MIT" + }, + "node_modules/uint8arrays": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/uint8arrays/-/uint8arrays-3.1.1.tgz", + "integrity": "sha512-+QJa8QRnbdXVpHYjLoTpJIdCTiw9Ir62nocClWuXIq2JIh4Uta0cQsTSpFL678p2CN8B+XSApwcU+pQEqVpKWg==", + "license": "MIT", + "dependencies": { + "multiformats": "^9.4.2" + } + }, "node_modules/unbox-primitive": { "version": "1.1.0", "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.1.0.tgz", @@ -5628,11 +8003,141 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/uncrypto": { + "version": "0.1.3", + "resolved": "https://registry.npmjs.org/uncrypto/-/uncrypto-0.1.3.tgz", + "integrity": "sha512-Ql87qFHB3s/De2ClA9e0gsnS6zXG27SkTiSJwjCc9MebbfapQfuPzumMIUMi38ezPZVNFcHI9sUIepeQfw8J8Q==", + "license": "MIT" + }, "node_modules/undici-types": { "version": "6.19.8", "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-6.19.8.tgz", "integrity": "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==" }, + "node_modules/unstorage": { + "version": "1.17.1", + "resolved": "https://registry.npmjs.org/unstorage/-/unstorage-1.17.1.tgz", + "integrity": "sha512-KKGwRTT0iVBCErKemkJCLs7JdxNVfqTPc/85ae1XES0+bsHbc/sFBfVi5kJp156cc51BHinIH2l3k0EZ24vOBQ==", + "license": "MIT", + "dependencies": { + "anymatch": "^3.1.3", + "chokidar": "^4.0.3", + "destr": "^2.0.5", + "h3": "^1.15.4", + "lru-cache": "^10.4.3", + "node-fetch-native": "^1.6.7", + "ofetch": "^1.4.1", + "ufo": "^1.6.1" + }, + "peerDependencies": { + "@azure/app-configuration": "^1.8.0", + "@azure/cosmos": "^4.2.0", + "@azure/data-tables": "^13.3.0", + "@azure/identity": "^4.6.0", + "@azure/keyvault-secrets": "^4.9.0", + "@azure/storage-blob": "^12.26.0", + "@capacitor/preferences": "^6.0.3 || ^7.0.0", + "@deno/kv": ">=0.9.0", + "@netlify/blobs": "^6.5.0 || ^7.0.0 || ^8.1.0 || ^9.0.0 || ^10.0.0", + "@planetscale/database": "^1.19.0", + "@upstash/redis": "^1.34.3", + "@vercel/blob": ">=0.27.1", + "@vercel/functions": "^2.2.12 || ^3.0.0", + "@vercel/kv": "^1.0.1", + "aws4fetch": "^1.0.20", + "db0": ">=0.2.1", + "idb-keyval": "^6.2.1", + "ioredis": "^5.4.2", + "uploadthing": "^7.4.4" + }, + "peerDependenciesMeta": { + "@azure/app-configuration": { + "optional": true + }, + "@azure/cosmos": { + "optional": true + }, + "@azure/data-tables": { + "optional": true + }, + "@azure/identity": { + "optional": true + }, + "@azure/keyvault-secrets": { + "optional": true + }, + "@azure/storage-blob": { + "optional": true + }, + "@capacitor/preferences": { + "optional": true + }, + "@deno/kv": { + "optional": true + }, + "@netlify/blobs": { + "optional": true + }, + "@planetscale/database": { + "optional": true + }, + "@upstash/redis": { + "optional": true + }, + "@vercel/blob": { + "optional": true + }, + "@vercel/functions": { + "optional": true + }, + "@vercel/kv": { + "optional": true + }, + "aws4fetch": { + "optional": true + }, + "db0": { + "optional": true + }, + "idb-keyval": { + "optional": true + }, + "ioredis": { + "optional": true + }, + "uploadthing": { + "optional": true + } + } + }, + "node_modules/unstorage/node_modules/chokidar": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-4.0.3.tgz", + "integrity": "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA==", + "license": "MIT", + "dependencies": { + "readdirp": "^4.0.1" + }, + "engines": { + "node": ">= 14.16.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/unstorage/node_modules/readdirp": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-4.1.2.tgz", + "integrity": "sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==", + "license": "MIT", + "engines": { + "node": ">= 14.18.0" + }, + "funding": { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5645,17 +8150,120 @@ "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", - "dev": true + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==" + }, + "node_modules/valtio": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/valtio/-/valtio-2.1.5.tgz", + "integrity": "sha512-vsh1Ixu5mT0pJFZm+Jspvhga5GzHUTYv0/+Th203pLfh3/wbHwxhu/Z2OkZDXIgHfjnjBns7SN9HNcbDvPmaGw==", + "license": "MIT", + "dependencies": { + "proxy-compare": "^3.0.1" + }, + "engines": { + "node": ">=12.20.0" + }, + "peerDependencies": { + "@types/react": ">=18.0.0", + "react": ">=18.0.0" + }, + "peerDependenciesMeta": { + "@types/react": { + "optional": true + }, + "react": { + "optional": true + } + } }, "node_modules/varuint-bitcoin": { "version": "1.1.2", "resolved": "https://registry.npmjs.org/varuint-bitcoin/-/varuint-bitcoin-1.1.2.tgz", "integrity": "sha512-4EVb+w4rx+YfVM32HQX42AbbT7/1f5zwAYhIujKXKk8NQK+JfRVl3pqT3hjNn/L+RstigmGGKVwHA/P0wgITZw==", + "license": "MIT", + "optional": true, "dependencies": { "safe-buffer": "^5.1.1" } }, + "node_modules/viem": { + "version": "2.38.3", + "resolved": "https://registry.npmjs.org/viem/-/viem-2.38.3.tgz", + "integrity": "sha512-By2TutLv07iNHHtWqHHzjGipevYsfGqT7KQbGEmqLco1qTJxKnvBbSviqiu6/v/9REV6Q/FpmIxf2Z7/l5AbcQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/wevm" + } + ], + "license": "MIT", + "dependencies": { + "@noble/curves": "1.9.1", + "@noble/hashes": "1.8.0", + "@scure/bip32": "1.7.0", + "@scure/bip39": "1.6.0", + "abitype": "1.1.0", + "isows": "1.0.7", + "ox": "0.9.6", + "ws": "8.18.3" + }, + "peerDependencies": { + "typescript": ">=5.0.4" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/viem/node_modules/@noble/curves": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/@noble/curves/-/curves-1.9.1.tgz", + "integrity": "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA==", + "license": "MIT", + "dependencies": { + "@noble/hashes": "1.8.0" + }, + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/@noble/hashes": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", + "license": "MIT", + "engines": { + "node": "^14.21.3 || >=16" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + } + }, + "node_modules/viem/node_modules/ws": { + "version": "8.18.3", + "resolved": "https://registry.npmjs.org/ws/-/ws-8.18.3.tgz", + "integrity": "sha512-PEIGCY5tSlUt50cqyMXfCzX+oOPqN0vuGqWzbcJ2xvnkzkq46oOpz7dQaTDBdfICb4N14+GARUDw2XV2N4tvzg==", + "license": "MIT", + "engines": { + "node": ">=10.0.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": ">=5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, "node_modules/webidl-conversions": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", @@ -5749,11 +8357,17 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "license": "ISC" + }, "node_modules/which-typed-array": { "version": "1.1.18", "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.18.tgz", "integrity": "sha512-qEcY+KJYlWyLH9vNbsr6/5j59AXk5ni5aakf8ldzBvGde6Iz4sxZGkJyWSAueTG7QhOvNRYb1lDdFmL5Td0QKA==", - "dev": true, + "devOptional": true, "dependencies": { "available-typed-arrays": "^1.0.7", "call-bind": "^1.0.8", @@ -5769,6 +8383,48 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/wif": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz", + "integrity": "sha512-HIanZn1zmduSF+BQhkE+YXIbEiH0xPr1012QbFEGB0xsKqJii0/SqJjyn8dFv6y36kOznMgMB+LGcbZTJ1xACQ==", + "license": "MIT", + "optional": true, + "dependencies": { + "bs58check": "<3.0.0" + } + }, + "node_modules/wif/node_modules/base-x": { + "version": "3.0.11", + "resolved": "https://registry.npmjs.org/base-x/-/base-x-3.0.11.tgz", + "integrity": "sha512-xz7wQ8xDhdyP7tQxwdteLYeFfS68tSMNCZ/Y37WJ4bhGfKPpqEIlmIyueQHqOyoPhE6xNUqjzRr8ra0eF9VRvA==", + "license": "MIT", + "optional": true, + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/wif/node_modules/bs58": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/bs58/-/bs58-4.0.1.tgz", + "integrity": "sha512-Ok3Wdf5vOIlBrgCvTq96gBkJw+JUEzdBgyaza5HLtPm7yTHkjRy8+JzNyHF7BHa0bNWOQIp3m5YF0nnFcOIKLw==", + "license": "MIT", + "optional": true, + "dependencies": { + "base-x": "^3.0.2" + } + }, + "node_modules/wif/node_modules/bs58check": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/bs58check/-/bs58check-2.1.2.tgz", + "integrity": "sha512-0TS1jicxdU09dwJMNZtVAfzPi6Q6QeN0pM1Fkzrjn+XYHvzMKPU3pHVpva+769iNVSfIYWf7LJ6WR+BuuMf8cA==", + "license": "MIT", + "optional": true, + "dependencies": { + "bs58": "^4.0.0", + "create-hash": "^1.1.0", + "safe-buffer": "^5.1.2" + } + }, "node_modules/word-wrap": { "version": "1.2.5", "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.5.tgz", @@ -5866,6 +8522,39 @@ "url": "https://github.com/chalk/ansi-styles?sponsor=1" } }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/ws": { + "version": "7.5.10", + "resolved": "https://registry.npmjs.org/ws/-/ws-7.5.10.tgz", + "integrity": "sha512-+dbF1tHwZpXcbOJdVOkzLDxZP1ailvSxM6ZweXTegylPny803bFhA+vqBYw4s31NSAk4S2Qz+AKXK9a4wkdjcQ==", + "license": "MIT", + "engines": { + "node": ">=8.3.0" + }, + "peerDependencies": { + "bufferutil": "^4.0.1", + "utf-8-validate": "^5.0.2" + }, + "peerDependenciesMeta": { + "bufferutil": { + "optional": true + }, + "utf-8-validate": { + "optional": true + } + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "license": "ISC" + }, "node_modules/yaml": { "version": "2.7.0", "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.7.0.tgz", @@ -5878,6 +8567,134 @@ "node": ">= 14" } }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "license": "MIT", + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "license": "ISC", + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/yargs/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "license": "MIT", + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "license": "MIT", + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "license": "MIT", + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "license": "MIT", + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", @@ -5890,10 +8707,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/zod": { + "version": "3.22.4", + "resolved": "https://registry.npmjs.org/zod/-/zod-3.22.4.tgz", + "integrity": "sha512-iC+8Io04lddc+mVqQ9AZ7OQ2MrUKGN+oIQyq1vemgt46jwCwLfhq7/pwnBnNXXXZb8VTVLKwp9EDkx+ryxIWmg==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/colinhacks" + } + }, "node_modules/zone-file": { "version": "2.0.0-beta.3", "resolved": "https://registry.npmjs.org/zone-file/-/zone-file-2.0.0-beta.3.tgz", "integrity": "sha512-6tE3PSRcpN5lbTTLlkLez40WkNPc9vw/u1J2j6DBiy0jcVX48nCkWrx2EC+bWHqC2SLp069Xw4AdnYn/qp/W5g==", + "license": "ISC", "engines": { "node": ">=10" } diff --git a/frontend/package.json b/frontend/package.json index 0c392a3..b318527 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,7 +9,7 @@ "lint": "next lint" }, "dependencies": { - "@stacks/connect": "^7.10.0", + "@stacks/connect": "^8.2.0", "@stacks/network": "^7.0.2", "@stacks/transactions": "^7.0.2", "next": "15.1.5", From 73979d2e18013270b3f898030cc2c5ee073f1087 Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 18:47:32 +0100 Subject: [PATCH 04/24] fix: correct incorrect syntax --- contracts/tournament.clar | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/contracts/tournament.clar b/contracts/tournament.clar index 256b0d2..3138b13 100644 --- a/contracts/tournament.clar +++ b/contracts/tournament.clar @@ -164,8 +164,7 @@ ;; start tournament with updated values (let ( (round u1) - ;; TODO: start time should be stacks-block-timestamp - (started-meta (merge meta { status: STATUS_IN_PROGRESS, start-time: (some u0), current-round: round })) + (started-meta (merge meta { status: STATUS_IN_PROGRESS, start-time: (some stacks-block-height), current-round: round })) ) ;; update tournament mapping (map-set tournaments tid started-meta) @@ -187,24 +186,24 @@ (let ( (meta (try! (get-tournament-or-err tid))) (key { tournament-id: tid, round: round, match-number: match-number }) - (match (unwrap! (map-get? tournament-matches key) (err ERR_MATCH_NOT_FOUND))) + (match-data (unwrap! (map-get? tournament-matches key) (err ERR_MATCH_NOT_FOUND))) ) ;; assert caller is tic-tac-toe contract and verify match details (asserts! (is-eq contract-caller .tic-tac-toe) (err ERR_UNAUTHORIZED)) (asserts! (is-eq (get status meta) STATUS_IN_PROGRESS) (err ERR_NOT_IN_PROGRESS)) - (asserts! (is-eq (get completed match) false) (err ERR_MATCH_ALREADY_COMPLETED)) + (asserts! (is-eq (get completed match-data) false) (err ERR_MATCH_ALREADY_COMPLETED)) ;; fetch match players (let ( - (p1 (unwrap! (get player1 match) (err ERR_INVALID_PARAMS))) - (p2 (unwrap! (get player2 match) (err ERR_INVALID_PARAMS))) + (p1 (unwrap! (get player1 match-data) (err ERR_INVALID_PARAMS))) + (p2 (unwrap! (get player2 match-data) (err ERR_INVALID_PARAMS))) ) ;; verify winner is one of the two players (asserts! (or (is-eq winner p1) (is-eq winner p2)) (err ERR_INVALID_WINNER)) ;; update match with winner and mark as completed - (map-set tournament-matches key (merge match { winner: (some winner), completed: true })) + (map-set tournament-matches key (merge match-data { winner: (some winner), completed: true })) ;; emit event; return true (print { action: "match-complete", id: tid, round: round, match: match-number, winner: winner }) From 3def0aa762db6999bf3f512c723127fca4852cf8 Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 18:47:42 +0100 Subject: [PATCH 05/24] test: add tournament tests --- tests/tournament.test.ts | 287 +++++++++++++++++++++++++++++++++++++++ 1 file changed, 287 insertions(+) create mode 100644 tests/tournament.test.ts diff --git a/tests/tournament.test.ts b/tests/tournament.test.ts new file mode 100644 index 0000000..1517dca --- /dev/null +++ b/tests/tournament.test.ts @@ -0,0 +1,287 @@ +import { Cl } from "@stacks/transactions"; +import { describe, expect, it } from "vitest"; + +const accounts = simnet.getAccounts(); +const deployer = accounts.get("deployer")!; +const alice = accounts.get("wallet_1")!; +const bob = accounts.get("wallet_2")!; +const carol = accounts.get("wallet_3")!; +const dave = accounts.get("wallet_4")!; + +// Helper to set up tournament contract authorization +function setupTournamentAuth() { + simnet.callPublicFn( + "tic-tac-toe", + "set-tournament-contract", + [Cl.principal(`${deployer}.tournament`)], + deployer + ); +} + +describe("Tournament Contract - Trustless Architecture", () => { + + it("creates a tournament with valid params", () => { + const { result, events } = simnet.callPublicFn( + "tournament", + "create-tournament", + [Cl.stringUtf8("Test Cup"), Cl.uint(100), Cl.uint(4)], + alice + ); + + expect(result).toBeOk(Cl.uint(0)); + // One print event + expect(events.length).toBe(1); + + const meta = simnet.getMapEntry("tournament", "tournaments", Cl.uint(0)); + expect(meta).toBeSome( + Cl.tuple({ + creator: Cl.principal(alice), + name: Cl.stringUtf8("Test Cup"), + "entry-fee": Cl.uint(100), + "max-players": Cl.uint(4), + "prize-pool": Cl.uint(0), + status: Cl.uint(0), + "start-time": Cl.none(), + winner: Cl.none(), + "current-round": Cl.uint(0), + }) + ); + }); + + it("allows players to join until full and updates prize pool", () => { + // create another tournament + const createResult = simnet.callPublicFn( + "tournament", + "create-tournament", + [Cl.stringUtf8("Join Cup"), Cl.uint(250), Cl.uint(4)], + alice + ); + expect(createResult.result).toBeOk(Cl.uint(0)); // Tournament ID 0 (fresh simnet) + + // four joins (including creator is allowed) + const j1 = simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], alice); + expect(j1.result).toBeOk(Cl.uint(1)); + const j2 = simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], bob); + expect(j2.result).toBeOk(Cl.uint(2)); + const j3 = simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], carol); + expect(j3.result).toBeOk(Cl.uint(3)); + const j4 = simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], dave); + expect(j4.result).toBeOk(Cl.uint(4)); + + // now full; another join should fail + const j5 = simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], accounts.get("wallet_5")!); + expect(j5.result).toBeErr(Cl.uint(204)); // ERR_TOURNAMENT_FULL + + // prize pool should be 4 * 250 + const meta = simnet.getMapEntry("tournament", "tournaments", Cl.uint(0)); + expect(meta).toBeSome( + Cl.tuple({ + creator: Cl.principal(alice), + name: Cl.stringUtf8("Join Cup"), + "entry-fee": Cl.uint(250), + "max-players": Cl.uint(4), + "prize-pool": Cl.uint(1000), + status: Cl.uint(0), + "start-time": Cl.none(), + winner: Cl.none(), + "current-round": Cl.uint(0), + }) + ); + + const count = simnet.getMapEntry("tournament", "tournament-player-count", Cl.uint(0)); + expect(count).toBeSome(Cl.uint(4)); + }); + + it("can only be started by creator and only when full, creates matches automatically", () => { + // Set up tournament contract authorization first + setupTournamentAuth(); + + // create a 4 player tournament (use 4 to satisfy contract's power-of-two requirement) + const createResult = simnet.callPublicFn( + "tournament", + "create-tournament", + [Cl.stringUtf8("Start Cup"), Cl.uint(50), Cl.uint(4)], + alice + ); + expect(createResult.result).toBeOk(Cl.uint(0)); // Tournament ID 0 + + // joining one player only (not full) + simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], bob); + + // not full yet (needs 4), starting should fail + const notFull = simnet.callPublicFn( + "tournament", + "start-tournament", + [Cl.uint(0), Cl.contractPrincipal(deployer, "tic-tac-toe")], + alice + ); + expect(notFull.result).toBeErr(Cl.uint(204)); // ERR_TOURNAMENT_FULL + + // fill the tournament to 4 players (creator must also join) + simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], alice); + simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], carol); + simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], dave); + + // non-creator cannot start + const byNonCreator = simnet.callPublicFn( + "tournament", + "start-tournament", + [Cl.uint(0), Cl.contractPrincipal(deployer, "tic-tac-toe")], + bob + ); + expect(byNonCreator.result).toBeErr(Cl.uint(206)); // ERR_NOT_CREATOR + + // creator starts successfully with game contract trait + const started = simnet.callPublicFn( + "tournament", + "start-tournament", + [Cl.uint(0), Cl.contractPrincipal(deployer, "tic-tac-toe")], + alice + ); + expect(started.result).toBeOk(Cl.uint(0)); // Returns tournament ID + + const meta = simnet.getMapEntry("tournament", "tournaments", Cl.uint(0)); + expect(meta).toBeSome( + Cl.tuple({ + creator: Cl.principal(alice), + name: Cl.stringUtf8("Start Cup"), + "entry-fee": Cl.uint(50), + "max-players": Cl.uint(4), + "prize-pool": Cl.uint(200), + status: Cl.uint(1), // STATUS_IN_PROGRESS + "start-time": Cl.some(Cl.uint(simnet.blockHeight)), + winner: Cl.none(), + "current-round": Cl.uint(1), + }) + ); + // Verify that Round 1, Match 1 and Match 2 were created + const match1 = simnet.getMapEntry( + "tournament", + "tournament-matches", + Cl.tuple({ "tournament-id": Cl.uint(0), round: Cl.uint(1), "match-number": Cl.uint(1) }) + ); + expect(match1).toBeSome( + Cl.tuple({ + "game-id": Cl.some(Cl.uint(0)), // First game created + player1: Cl.some(Cl.principal(bob)), // Position 1 (bob joined first) + player2: Cl.some(Cl.principal(alice)), // Position 2 (creator joined later) + winner: Cl.none(), + completed: Cl.bool(false), + }) + ); + + const match2 = simnet.getMapEntry( + "tournament", + "tournament-matches", + Cl.tuple({ "tournament-id": Cl.uint(0), round: Cl.uint(1), "match-number": Cl.uint(2) }) + ); + expect(match2).toBeSome( + Cl.tuple({ + "game-id": Cl.some(Cl.uint(1)), // Second game created + player1: Cl.some(Cl.principal(carol)), + player2: Cl.some(Cl.principal(dave)), + winner: Cl.none(), + completed: Cl.bool(false), + }) + ); + }); + + it("creates multiple Round 1 matches for 4-player tournament", () => { + // Set up tournament contract authorization first + setupTournamentAuth(); + + // Create a 4-player tournament + const createResult = simnet.callPublicFn( + "tournament", + "create-tournament", + [Cl.stringUtf8("Four Cup"), Cl.uint(100), Cl.uint(4)], + alice + ); + expect(createResult.result).toBeOk(Cl.uint(0)); // Tournament ID 0 + + simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], alice); + simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], bob); + simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], carol); + simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], dave); + + // Start the tournament + const started = simnet.callPublicFn( + "tournament", + "start-tournament", + [Cl.uint(0), Cl.contractPrincipal(deployer, "tic-tac-toe")], + alice + ); + expect(started.result).toBeOk(Cl.uint(0)); // Returns tournament ID + + // Verify Match 1: positions 1 vs 2 (alice vs bob) + const match1 = simnet.getMapEntry( + "tournament", + "tournament-matches", + Cl.tuple({ "tournament-id": Cl.uint(0), round: Cl.uint(1), "match-number": Cl.uint(1) }) + ); + expect(match1).toBeSome( + Cl.tuple({ + "game-id": Cl.some(Cl.uint(0)), // First game + player1: Cl.some(Cl.principal(alice)), + player2: Cl.some(Cl.principal(bob)), + winner: Cl.none(), + completed: Cl.bool(false), + }) + ); + + // Verify Match 2: positions 3 vs 4 (carol vs dave) + const match2 = simnet.getMapEntry( + "tournament", + "tournament-matches", + Cl.tuple({ "tournament-id": Cl.uint(0), round: Cl.uint(1), "match-number": Cl.uint(2) }) + ); + expect(match2).toBeSome( + Cl.tuple({ + "game-id": Cl.some(Cl.uint(1)), // Second game + player1: Cl.some(Cl.principal(carol)), + player2: Cl.some(Cl.principal(dave)), + winner: Cl.none(), + completed: Cl.bool(false), + }) + ); + }); + + it("validates winner is a match participant when reporting results", () => { + // Set up tournament contract authorization first + setupTournamentAuth(); + + // Create a 4-player tournament and fill it so Round 1 matches are created + const createResult = simnet.callPublicFn( + "tournament", + "create-tournament", + [Cl.stringUtf8("Winner Cup"), Cl.uint(50), Cl.uint(4)], + alice + ); + expect(createResult.result).toBeOk(Cl.uint(0)); // Tournament ID 0 + + simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], alice); + simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], bob); + simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], carol); + simnet.callPublicFn("tournament", "join-tournament", [Cl.uint(0)], dave); + + simnet.callPublicFn( + "tournament", + "start-tournament", + [Cl.uint(0), Cl.contractPrincipal(deployer, "tic-tac-toe")], + alice + ); + + // Try to report an invalid winner (someone not in the match) by a non-contract caller + const invalidWinner = simnet.callPublicFn( + "tournament", + "report-game-winner", + [Cl.uint(0), Cl.uint(1), Cl.uint(1), Cl.principal(carol)], + deployer + ); + expect(invalidWinner.result).toBeErr(Cl.uint(200)); // ERR_UNAUTHORIZED (not called by tic-tac-toe contract) + + // NOTE: In production, report-game-winner should only be called by the tic-tac-toe contract + // For testing purposes, we would need to simulate a game completion that triggers the call + // For now, we'll test the authorization check which should fail since deployer is not the tic-tac-toe contract + }); +}); From c3134b16deeca208bafcea4f6d3e895207fb697f Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 19:08:25 +0100 Subject: [PATCH 06/24] feat: upgrade contracts for deployment --- ...ment-game-trait.clar => game-tournament-trait.clar} | 2 +- contracts/{tic-tac-toe.clar => tic-tac-toe-v2.clar} | 4 ++-- contracts/tournament.clar | 10 +++++----- 3 files changed, 8 insertions(+), 8 deletions(-) rename contracts/{tournament-game-trait.clar => game-tournament-trait.clar} (88%) rename contracts/{tic-tac-toe.clar => tic-tac-toe-v2.clar} (99%) diff --git a/contracts/tournament-game-trait.clar b/contracts/game-tournament-trait.clar similarity index 88% rename from contracts/tournament-game-trait.clar rename to contracts/game-tournament-trait.clar index 6d912fd..622b26b 100644 --- a/contracts/tournament-game-trait.clar +++ b/contracts/game-tournament-trait.clar @@ -1,7 +1,7 @@ ;; Trait for tournament game integration ;; This allows tournament contract to create games without circular dependency -(define-trait tournament-game-trait +(define-trait game-tournament-trait ( ;; Create a tournament game between two players (create-tournament-game (principal principal uint uint uint) (response uint uint)) diff --git a/contracts/tic-tac-toe.clar b/contracts/tic-tac-toe-v2.clar similarity index 99% rename from contracts/tic-tac-toe.clar rename to contracts/tic-tac-toe-v2.clar index a45b714..a9655fc 100644 --- a/contracts/tic-tac-toe.clar +++ b/contracts/tic-tac-toe-v2.clar @@ -5,8 +5,8 @@ (define-constant ERR_GAME_CANNOT_BE_JOINED u103) ;; Error thrown when a game cannot be joined, usually because it already has two players (define-constant ERR_NOT_YOUR_TURN u104) ;; Error thrown when a player tries to make a move when it is not their turn -;; Implement the tournament-game-trait -(impl-trait .tournament-game-trait.tournament-game-trait) +;; Implement the game-tournament-trait +(impl-trait .game-tournament-trait.game-tournament-trait) ;; The Game ID to use for the next game (define-data-var latest-game-id uint u0) diff --git a/contracts/tournament.clar b/contracts/tournament.clar index 3138b13..ef67805 100644 --- a/contracts/tournament.clar +++ b/contracts/tournament.clar @@ -14,7 +14,7 @@ ;; TRAITS -(use-trait tournament-game-trait .tournament-game-trait.tournament-game-trait) +(use-trait game-tournament-trait .game-tournament-trait.game-tournament-trait) ;; CONSTANTS @@ -149,7 +149,7 @@ ) ;; Start the tournament: only creator, only when full. Creates round 1 matches automatically. -(define-public (start-tournament (tid uint) (game-contract )) +(define-public (start-tournament (tid uint) (game-contract )) ;; fetch tournament metdata (let ( (meta (try! (get-tournament-or-err tid))) @@ -190,7 +190,7 @@ ) ;; assert caller is tic-tac-toe contract and verify match details - (asserts! (is-eq contract-caller .tic-tac-toe) (err ERR_UNAUTHORIZED)) + (asserts! (is-eq contract-caller .tic-tac-toe-v2) (err ERR_UNAUTHORIZED)) (asserts! (is-eq (get status meta) STATUS_IN_PROGRESS) (err ERR_NOT_IN_PROGRESS)) (asserts! (is-eq (get completed match-data) false) (err ERR_MATCH_ALREADY_COMPLETED)) @@ -248,7 +248,7 @@ ;; PRIVATE HELPER FUNCTIONS ;; Create round 1 matches by pairing adjacent players and creating tic-tac-toe games -(define-private (create-round-one-matches (tid uint) (player-count uint) (game-contract )) +(define-private (create-round-one-matches (tid uint) (player-count uint) (game-contract )) (begin ;; For a 4-player tournament: create 2 matches (1v2, 3v4) ;; For an 8-player tournament: create 4 matches (1v2, 3v4, 5v6, 7v8), etc. @@ -273,7 +273,7 @@ ) ;; Create a single match: look up players by position, create game, store match -(define-private (create-match (tid uint) (round uint) (match-num uint) (p1-pos uint) (p2-pos uint) (game-contract )) +(define-private (create-match (tid uint) (round uint) (match-num uint) (p1-pos uint) (p2-pos uint) (game-contract )) ;; fetch player details and game id (let ( (p1 (unwrap! (get-player-at tid p1-pos) (err ERR_INVALID_PARAMS))) From 54892f7fdb7f036c53c0e75eaf7cdbdb3466d87b Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 19:13:36 +0100 Subject: [PATCH 07/24] deploy: deploy contracts to tesnet --- Clarinet.toml | 16 ++++++++++++++-- deployments/default.simnet-plan.yaml | 10 ++++++++++ deployments/default.testnet-plan.yaml | 22 ++++++++++++++++++---- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/Clarinet.toml b/Clarinet.toml index 997f92c..2de141c 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -5,10 +5,22 @@ authors = [] telemetry = false cache_dir = './.cache' requirements = [] -[contracts.tic-tac-toe] -path = 'contracts/tic-tac-toe.clar' + +[contracts.tic-tac-toe-v2] +path = 'contracts/tic-tac-toe-v2.clar' +clarity_version = 3 +epoch = 3.0 + +[contracts.game-tournament-trait] +path = 'contracts/game-tournament-trait.clar' clarity_version = 3 epoch = 3.0 + +[contracts.tournament] +path = 'contracts/tournament.clar' +clarity_version = 3 +epoch = 3.0 + [repl.analysis] passes = ['check_checker'] diff --git a/deployments/default.simnet-plan.yaml b/deployments/default.simnet-plan.yaml index 3c90280..eca2bbf 100644 --- a/deployments/default.simnet-plan.yaml +++ b/deployments/default.simnet-plan.yaml @@ -49,9 +49,19 @@ plan: batches: - id: 0 transactions: + - emulated-contract-publish: + contract-name: tournament-game-trait + emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + path: contracts/tournament-game-trait.clar + clarity-version: 3 - emulated-contract-publish: contract-name: tic-tac-toe emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM path: contracts/tic-tac-toe.clar clarity-version: 3 + - emulated-contract-publish: + contract-name: tournament + emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + path: contracts/tournament.clar + clarity-version: 3 epoch: "3.0" diff --git a/deployments/default.testnet-plan.yaml b/deployments/default.testnet-plan.yaml index fba5452..ce9040a 100644 --- a/deployments/default.testnet-plan.yaml +++ b/deployments/default.testnet-plan.yaml @@ -9,10 +9,24 @@ plan: - id: 0 transactions: - contract-publish: - contract-name: tic-tac-toe - expected-sender: ST3P49R8XXQWG69S66MZASYPTTGNDKK0WW32RRJDN - cost: 90730 - path: contracts/tic-tac-toe.clar + contract-name: game-tournament-trait + expected-sender: ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88 + cost: 3060 + path: contracts/game-tournament-trait.clar + anchor-block-only: true + clarity-version: 3 + - contract-publish: + contract-name: tic-tac-toe-v2 + expected-sender: ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88 + cost: 110140 + path: contracts/tic-tac-toe-v2.clar + anchor-block-only: true + clarity-version: 3 + - contract-publish: + contract-name: tournament + expected-sender: ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88 + cost: 130690 + path: contracts/tournament.clar anchor-block-only: true clarity-version: 3 epoch: "3.0" From c69a4dab5b8baf97f5be629b1e1698103463fe83 Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 19:39:46 +0100 Subject: [PATCH 08/24] deploy: remove deprecated function from tournament contract, rename, and deploy --- Clarinet.toml | 4 +-- ...ament.clar => tic-tac-toe-tournament.clar} | 32 ------------------- deployments/default.devnet-plan.yaml | 32 +++++++++++++++++++ deployments/default.testnet-plan.yaml | 12 +++---- 4 files changed, 40 insertions(+), 40 deletions(-) rename contracts/{tournament.clar => tic-tac-toe-tournament.clar} (88%) create mode 100644 deployments/default.devnet-plan.yaml diff --git a/Clarinet.toml b/Clarinet.toml index 2de141c..c546df9 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -16,8 +16,8 @@ path = 'contracts/game-tournament-trait.clar' clarity_version = 3 epoch = 3.0 -[contracts.tournament] -path = 'contracts/tournament.clar' +[contracts.tic-tac-toe-tournament] +path = 'contracts/tic-tac-toe-tournament.clar' clarity_version = 3 epoch = 3.0 diff --git a/contracts/tournament.clar b/contracts/tic-tac-toe-tournament.clar similarity index 88% rename from contracts/tournament.clar rename to contracts/tic-tac-toe-tournament.clar index ef67805..db4c11b 100644 --- a/contracts/tournament.clar +++ b/contracts/tic-tac-toe-tournament.clar @@ -212,38 +212,6 @@ ) ) -;; ;; Old manual reporting function - DEPRECATED, kept for backwards compatibility but should not be used -;; ;; Report a match result. For phase 1, restrict to creator for manual advancement. -;; (define-public (report-match-result (tid uint) (round uint) (match-number uint) (winner principal)) -;; ;; fetch tournament data -;; (let ( -;; (meta (try! (get-tournament-or-err tid))) -;; (key { tournament-id: tid, round: round, match-number: match-number }) -;; (match (unwrap! (map-get? tournament-matches key) (err ERR_MATCH_NOT_FOUND))) -;; ) - -;; ;; assert only tournament creator can report results! -;; (asserts! (is-eq (get creator meta) tx-sender) (err ERR_UNAUTHORIZED)) -;; (asserts! (is-eq (get status meta) STATUS_IN_PROGRESS) (err ERR_NOT_IN_PROGRESS)) -;; (asserts! (is-eq (get completed match) false) (err ERR_MATCH_ALREADY_COMPLETED)) - -;; ;; get match players -;; (let ( -;; (p1 (default-to 'SP000000000000000000002Q6VF78 (get player1 match))) -;; (p2 (default-to 'SP000000000000000000002Q6VF78 (get player2 match))) -;; ) - -;; ;; confirm winner is verified player; update match record -;; (asserts! (or (is-eq winner p1) (is-eq winner p2)) (err ERR_INVALID_WINNER)) -;; (map-set tournament-matches key (merge match { winner: (some winner), completed: true })) - -;; ;; emit event; return true -;; (print { action: "match-complete", id: tid, round: round, match: match-number, winner: winner }) -;; (ok true) -;; ) -;; ) -;; ) - ;; PRIVATE HELPER FUNCTIONS diff --git a/deployments/default.devnet-plan.yaml b/deployments/default.devnet-plan.yaml new file mode 100644 index 0000000..d395f2b --- /dev/null +++ b/deployments/default.devnet-plan.yaml @@ -0,0 +1,32 @@ +--- +id: 0 +name: Devnet deployment +network: devnet +stacks-node: "http://localhost:20443" +bitcoin-node: "http://devnet:devnet@localhost:18443" +plan: + batches: + - id: 0 + transactions: + - contract-publish: + contract-name: game-tournament-trait + expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + cost: 3060 + path: contracts/game-tournament-trait.clar + anchor-block-only: true + clarity-version: 3 + - contract-publish: + contract-name: tic-tac-toe-tournament + expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + cost: 130690 + path: contracts/tic-tac-toe-tournament.clar + anchor-block-only: true + clarity-version: 3 + - contract-publish: + contract-name: tic-tac-toe-v2 + expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM + cost: 110140 + path: contracts/tic-tac-toe-v2.clar + anchor-block-only: true + clarity-version: 3 + epoch: "3.0" diff --git a/deployments/default.testnet-plan.yaml b/deployments/default.testnet-plan.yaml index ce9040a..3f14ab6 100644 --- a/deployments/default.testnet-plan.yaml +++ b/deployments/default.testnet-plan.yaml @@ -16,17 +16,17 @@ plan: anchor-block-only: true clarity-version: 3 - contract-publish: - contract-name: tic-tac-toe-v2 + contract-name: tic-tac-toe-tournament expected-sender: ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88 - cost: 110140 - path: contracts/tic-tac-toe-v2.clar + cost: 115510 + path: contracts/tic-tac-toe-tournament.clar anchor-block-only: true clarity-version: 3 - contract-publish: - contract-name: tournament + contract-name: tic-tac-toe-v2 expected-sender: ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88 - cost: 130690 - path: contracts/tournament.clar + cost: 110140 + path: contracts/tic-tac-toe-v2.clar anchor-block-only: true clarity-version: 3 epoch: "3.0" From c7901b868bf486fd3ca567bec969346057c29cc3 Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 19:40:26 +0100 Subject: [PATCH 09/24] feat: modify existing contract hooks --- frontend/hooks/use-stacks.ts | 10 ++- frontend/lib/contract.ts | 123 ++++++++++++++++------------- frontend/package-lock.json | 145 +++++++++++++++++++++++++++++++++++ frontend/package.json | 1 + 4 files changed, 224 insertions(+), 55 deletions(-) diff --git a/frontend/hooks/use-stacks.ts b/frontend/hooks/use-stacks.ts index c25de3d..fbff5b3 100644 --- a/frontend/hooks/use-stacks.ts +++ b/frontend/hooks/use-stacks.ts @@ -25,6 +25,7 @@ export function useStacks() { const [userData, setUserData] = useState(null); const [stxBalance, setStxBalance] = useState(0); const [userAddress, setUserAddress] = useState(null); + const [connected, setConnected] = useState(false); async function connectWallet() { try { @@ -185,6 +186,13 @@ export function useStacks() { } }, [userAddress]); + useEffect(() => { + // Check connection status only on client side + if (typeof window !== 'undefined') { + setConnected(isConnected() || userSession.isUserSignedIn()); + } + }, [userData]); + return { userData, userAddress, @@ -194,6 +202,6 @@ export function useStacks() { handleCreateGame, handleJoinGame, handlePlayGame, - isConnected: isConnected() || userSession.isUserSignedIn(), + isConnected: connected, }; } \ No newline at end of file diff --git a/frontend/lib/contract.ts b/frontend/lib/contract.ts index ab626ad..d160aff 100644 --- a/frontend/lib/contract.ts +++ b/frontend/lib/contract.ts @@ -11,8 +11,8 @@ import { UIntCV, } from "@stacks/transactions"; -const CONTRACT_ADDRESS = "ST3P49R8XXQWG69S66MZASYPTTGNDKK0WW32RRJDN"; -const CONTRACT_NAME = "tic-tac-toe"; +const CONTRACT_ADDRESS = "ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88"; +const CONTRACT_NAME = "tic-tac-toe-v2"; type GameCV = { "player-one": PrincipalCV; @@ -52,62 +52,77 @@ export const EMPTY_BOARD = [ ]; export async function getAllGames() { - // Fetch the latest-game-id from the contract - const latestGameIdCV = (await fetchCallReadOnlyFunction({ - contractAddress: CONTRACT_ADDRESS, - contractName: CONTRACT_NAME, - functionName: "get-latest-game-id", - functionArgs: [], - senderAddress: CONTRACT_ADDRESS, - network: STACKS_TESTNET, - })) as UIntCV; - - // Convert the uintCV to a JS/TS number type - const latestGameId = parseInt(latestGameIdCV.value.toString()); - - // Loop from 0 to latestGameId-1 and fetch the game details for each game - const games: Game[] = []; - for (let i = 0; i < latestGameId; i++) { - const game = await getGame(i); - if (game) games.push(game); + try { + // Use the get-latest-game-id read only function to fetch the latest game id + const latestGameIdCV = (await fetchCallReadOnlyFunction({ + contractAddress: CONTRACT_ADDRESS, + contractName: CONTRACT_NAME, + functionName: "get-latest-game-id", + functionArgs: [], + senderAddress: CONTRACT_ADDRESS, + network: STACKS_TESTNET, + })) as UIntCV; + + // Convert the uintCV to a JS/TS number type + const latestGameId = parseInt(latestGameIdCV.value.toString()); + + // Loop from 0 to latestGameId-1 and fetch the game details for each game + // Limit to last 10 games to avoid rate limiting + const startId = Math.max(0, latestGameId - 10); + const games: Game[] = []; + + for (let i = startId; i < latestGameId; i++) { + const game = await getGame(i); + if (game) games.push(game); + // Add small delay to avoid rate limiting + await new Promise(resolve => setTimeout(resolve, 100)); + } + return games; + } catch (error) { + console.error("Error fetching games:", error); + return []; } - return games; } export async function getGame(gameId: number) { - // Use the get-game read only function to fetch the game details for the given gameId - const gameDetails = await fetchCallReadOnlyFunction({ - contractAddress: CONTRACT_ADDRESS, - contractName: CONTRACT_NAME, - functionName: "get-game", - functionArgs: [uintCV(gameId)], - senderAddress: CONTRACT_ADDRESS, - network: STACKS_TESTNET, - }); - - const responseCV = gameDetails as OptionalCV>; - // If we get back a none, then the game does not exist and we return null - if (responseCV.type === "none") return null; - // If we get back a value that is not a tuple, something went wrong and we return null - if (responseCV.value.type !== "tuple") return null; - - // If we got back a GameCV tuple, we can convert it to a Game object - const gameCV = responseCV.value.value; - - const game: Game = { - id: gameId, - "player-one": gameCV["player-one"].value, - "player-two": - gameCV["player-two"].type === "some" - ? gameCV["player-two"].value.value - : null, - "is-player-one-turn": cvToValue(gameCV["is-player-one-turn"]), - "bet-amount": parseInt(gameCV["bet-amount"].value.toString()), - board: gameCV["board"].value.map((cell) => parseInt(cell.value.toString())), - winner: - gameCV["winner"].type === "some" ? gameCV["winner"].value.value : null, - }; - return game; + try { + // Use the get-game read only function to fetch the game details for the given gameId + const gameDetails = await fetchCallReadOnlyFunction({ + contractAddress: CONTRACT_ADDRESS, + contractName: CONTRACT_NAME, + functionName: "get-game", + functionArgs: [uintCV(gameId)], + senderAddress: CONTRACT_ADDRESS, + network: STACKS_TESTNET, + }); + + const responseCV = gameDetails as OptionalCV>; + // If we get back a none, then the game does not exist and we return null + if (responseCV.type === "none") return null; + // If we get back a value that is not a tuple, something went wrong and we return null + if (responseCV.value.type !== "tuple") return null; + + // If we got back a GameCV tuple, we can convert it to a Game object + const gameCV = responseCV.value.value; + + const game: Game = { + id: gameId, + "player-one": gameCV["player-one"].value, + "player-two": + gameCV["player-two"].type === "some" + ? gameCV["player-two"].value.value + : null, + "is-player-one-turn": cvToValue(gameCV["is-player-one-turn"]), + "bet-amount": parseInt(gameCV["bet-amount"].value.toString()), + board: gameCV["board"].value.map((cell) => parseInt(cell.value.toString())), + winner: + gameCV["winner"].type === "some" ? gameCV["winner"].value.value : null, + }; + return game; + } catch (error) { + console.error(`Error fetching game ${gameId}:`, error); + return null; + } } export async function createNewGame( diff --git a/frontend/package-lock.json b/frontend/package-lock.json index e079382..ecdff19 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -22,6 +22,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.1.5", + "pino-pretty": "^13.1.2", "postcss": "^8", "tailwindcss": "^3.4.1", "typescript": "^5" @@ -3383,6 +3384,13 @@ "simple-swizzle": "^0.2.2" } }, + "node_modules/colorette": { + "version": "2.0.20", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-2.0.20.tgz", + "integrity": "sha512-IfEDxwoWIjkeXL1eXcDiow4UbKjhLdq6/EuSVR9GMN7KVH3r9gQ83e73hsz1Nd1T3ijd5xv1wcWRYO+D6kCI2w==", + "dev": true, + "license": "MIT" + }, "node_modules/commander": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", @@ -3546,6 +3554,16 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/dateformat": { + "version": "4.6.3", + "resolved": "https://registry.npmjs.org/dateformat/-/dateformat-4.6.3.tgz", + "integrity": "sha512-2P0p0pFGzHS5EMnhdxQi7aJN+iMheud0UhG4dlE1DLAlvL8JHjJJTX/CSm4JXwV0Ka5nGk3zC5mcb5bUQUxxMA==", + "dev": true, + "license": "MIT", + "engines": { + "node": "*" + } + }, "node_modules/dayjs": { "version": "1.11.13", "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.13.tgz", @@ -4453,6 +4471,13 @@ "safe-buffer": "^5.1.1" } }, + "node_modules/fast-copy": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/fast-copy/-/fast-copy-3.0.2.tgz", + "integrity": "sha512-dl0O9Vhju8IrcLndv2eU4ldt1ftXMqqfgN4H1cpmGV7P6jeB9FwpN9a2c8DPGE1Ys88rNUJVYDHq73CGAGOPfQ==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", @@ -4508,6 +4533,13 @@ "node": ">=6" } }, + "node_modules/fast-safe-stringify": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/fast-safe-stringify/-/fast-safe-stringify-2.1.1.tgz", + "integrity": "sha512-W+KJc2dmILlPplD/H4K9l9LcAHAfPtP6BY84uVLXQ6Evcz9Lcg33Y2z1IVblT6xdY54PXYVHEv+0Wpq8Io6zkA==", + "dev": true, + "license": "MIT" + }, "node_modules/fast-sha256": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/fast-sha256/-/fast-sha256-1.3.0.tgz", @@ -4990,6 +5022,13 @@ "node": ">= 0.4" } }, + "node_modules/help-me": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/help-me/-/help-me-5.0.0.tgz", + "integrity": "sha512-7xgomUX6ADmcYzFik0HzAxh/73YlKR9bmFzf51CZwR+b6YtzU2m0u49hQCqV6SvlqIqsaxovfwdvbnsw3b/zpg==", + "dev": true, + "license": "MIT" + }, "node_modules/hmac-drbg": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/hmac-drbg/-/hmac-drbg-1.0.1.tgz", @@ -5547,6 +5586,16 @@ "jiti": "bin/jiti.js" } }, + "node_modules/joycon": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/joycon/-/joycon-3.1.1.tgz", + "integrity": "sha512-34wB/Y7MW7bzjKRjUKTa46I2Z7eV62Rkhva+KkopW7Qvv/OSWBqvkSY7vusOPrNuZcUG3tApvdVgNB8POj3SPw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=10" + } + }, "node_modules/js-tokens": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", @@ -6436,6 +6485,74 @@ "split2": "^4.0.0" } }, + "node_modules/pino-pretty": { + "version": "13.1.2", + "resolved": "https://registry.npmjs.org/pino-pretty/-/pino-pretty-13.1.2.tgz", + "integrity": "sha512-3cN0tCakkT4f3zo9RXDIhy6GTvtYD6bK4CRBLN9j3E/ePqN1tugAXD5rGVfoChW6s0hiek+eyYlLNqc/BG7vBQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "colorette": "^2.0.7", + "dateformat": "^4.6.3", + "fast-copy": "^3.0.2", + "fast-safe-stringify": "^2.1.1", + "help-me": "^5.0.0", + "joycon": "^3.1.1", + "minimist": "^1.2.6", + "on-exit-leak-free": "^2.1.0", + "pino-abstract-transport": "^2.0.0", + "pump": "^3.0.0", + "secure-json-parse": "^4.0.0", + "sonic-boom": "^4.0.1", + "strip-json-comments": "^5.0.2" + }, + "bin": { + "pino-pretty": "bin.js" + } + }, + "node_modules/pino-pretty/node_modules/on-exit-leak-free": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/on-exit-leak-free/-/on-exit-leak-free-2.1.2.tgz", + "integrity": "sha512-0eJJY6hXLGf1udHwfNftBqH+g73EU4B504nZeKpz1sYRKafAghwxEJunB2O7rDZkL4PGfsMVnTXZ2EjibbqcsA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.0.0" + } + }, + "node_modules/pino-pretty/node_modules/pino-abstract-transport": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pino-abstract-transport/-/pino-abstract-transport-2.0.0.tgz", + "integrity": "sha512-F63x5tizV6WCh4R6RHyi2Ml+M70DNRXt/+HANowMflpgGFMAym/VKm6G7ZOQRjqN7XbGxK1Lg9t6ZrtzOaivMw==", + "dev": true, + "license": "MIT", + "dependencies": { + "split2": "^4.0.0" + } + }, + "node_modules/pino-pretty/node_modules/sonic-boom": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/sonic-boom/-/sonic-boom-4.2.0.tgz", + "integrity": "sha512-INb7TM37/mAcsGmc9hyyI6+QR3rR1zVRu36B0NeGXKnOOLiZOfER5SA+N7X7k3yUYRzLWafduTDvJAfDswwEww==", + "dev": true, + "license": "MIT", + "dependencies": { + "atomic-sleep": "^1.0.0" + } + }, + "node_modules/pino-pretty/node_modules/strip-json-comments": { + "version": "5.0.3", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-5.0.3.tgz", + "integrity": "sha512-1tB5mhVo7U+ETBKNf92xT4hrQa3pm0MZ0PQvuDnWgAAGHDsfp4lPSpiS6psrSiet87wyGPh9ft6wmhOMQ0hDiw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=14.16" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/pino-std-serializers": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/pino-std-serializers/-/pino-std-serializers-4.0.0.tgz", @@ -6651,6 +6768,17 @@ "integrity": "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q==", "license": "MIT" }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "dev": true, + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, "node_modules/punycode": { "version": "2.3.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz", @@ -7077,6 +7205,23 @@ "node": ">=18.0.0" } }, + "node_modules/secure-json-parse": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/secure-json-parse/-/secure-json-parse-4.1.0.tgz", + "integrity": "sha512-l4KnYfEyqYJxDwlNVyRfO2E4NTHfMKAWdUuA8J0yve2Dz/E/PdBepY03RvyJpssIpRFwJoCD55wA+mEDs6ByWA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/fastify" + }, + { + "type": "opencollective", + "url": "https://opencollective.com/fastify" + } + ], + "license": "BSD-3-Clause" + }, "node_modules/semver": { "version": "7.7.2", "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.2.tgz", diff --git a/frontend/package.json b/frontend/package.json index b318527..f750d15 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -23,6 +23,7 @@ "@types/react-dom": "^19", "eslint": "^9", "eslint-config-next": "15.1.5", + "pino-pretty": "^13.1.2", "postcss": "^8", "tailwindcss": "^3.4.1", "typescript": "^5" From ba6a57ef2925ea1b372ee2c160e88b4730bd2cc3 Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 19:44:12 +0100 Subject: [PATCH 10/24] feat: add tournamennt hooks --- frontend/hooks/use-tournaments.ts | 228 +++++++++++++++++++++ frontend/lib/tournament-contract.ts | 303 ++++++++++++++++++++++++++++ 2 files changed, 531 insertions(+) create mode 100644 frontend/hooks/use-tournaments.ts create mode 100644 frontend/lib/tournament-contract.ts diff --git a/frontend/hooks/use-tournaments.ts b/frontend/hooks/use-tournaments.ts new file mode 100644 index 0000000..09ea81b --- /dev/null +++ b/frontend/hooks/use-tournaments.ts @@ -0,0 +1,228 @@ +import { + createTournament, + joinTournament, + startTournament, + getTournament, + getAllTournaments, + getTournamentParticipant, + getTournamentMatch, + type Tournament, + type TournamentMatch, + type TournamentParticipant, +} from "@/lib/tournament-contract"; +import { openContractCall } from "@stacks/connect"; +import { PostConditionMode } from "@stacks/transactions"; +import { useEffect, useState } from "react"; + +const appDetails = { + name: "Tic Tac Toe - Tournaments", + icon: "https://cryptologos.cc/logos/stacks-stx-logo.png", +}; + +export function useTournaments(userAddress: string | null) { + const [tournaments, setTournaments] = useState([]); + const [loading, setLoading] = useState(false); + const [refreshKey, setRefreshKey] = useState(0); + + /** + * Create a new tournament + */ + async function handleCreateTournament( + name: string, + entryFee: number, + maxPlayers: number + ) { + if (typeof window === "undefined") return; + + try { + if (!userAddress) throw new Error("User not connected"); + + const txOptions = await createTournament(name, entryFee, maxPlayers); + await openContractCall({ + ...txOptions, + appDetails, + onFinish: (data) => { + console.log("Tournament created:", data); + window.alert("Tournament created successfully!"); + // Refresh tournaments list + setRefreshKey((prev) => prev + 1); + }, + onCancel: () => { + console.log("Transaction cancelled"); + }, + postConditionMode: PostConditionMode.Allow, + }); + } catch (_err) { + const err = _err as Error; + console.error(err); + window.alert(err.message); + } + } + + /** + * Join an existing tournament + */ + async function handleJoinTournament(tournamentId: number) { + if (typeof window === "undefined") return; + + try { + if (!userAddress) throw new Error("User not connected"); + + const txOptions = await joinTournament(tournamentId); + await openContractCall({ + ...txOptions, + appDetails, + onFinish: (data) => { + console.log("Joined tournament:", data); + window.alert("Joined tournament successfully!"); + // Refresh tournaments list + setRefreshKey((prev) => prev + 1); + }, + onCancel: () => { + console.log("Transaction cancelled"); + }, + postConditionMode: PostConditionMode.Allow, + }); + } catch (_err) { + const err = _err as Error; + console.error(err); + window.alert(err.message); + } + } + + /** + * Start a tournament (creator only) + */ + async function handleStartTournament(tournamentId: number) { + if (typeof window === "undefined") return; + + try { + if (!userAddress) throw new Error("User not connected"); + + const txOptions = await startTournament(tournamentId); + await openContractCall({ + ...txOptions, + appDetails, + onFinish: (data) => { + console.log("Tournament started:", data); + window.alert("Tournament started successfully!"); + // Refresh tournaments list + setRefreshKey((prev) => prev + 1); + }, + onCancel: () => { + console.log("Transaction cancelled"); + }, + postConditionMode: PostConditionMode.Allow, + }); + } catch (_err) { + const err = _err as Error; + console.error(err); + window.alert(err.message); + } + } + + /** + * Fetch a single tournament by ID + */ + async function fetchTournament(tournamentId: number): Promise { + try { + return await getTournament(tournamentId); + } catch (error) { + console.error("Failed to fetch tournament:", error); + return null; + } + } + + /** + * Fetch participant info for a tournament + */ + async function fetchParticipant( + tournamentId: number, + playerAddress: string + ): Promise { + try { + return await getTournamentParticipant(tournamentId, playerAddress); + } catch (error) { + console.error("Failed to fetch participant:", error); + return null; + } + } + + /** + * Fetch match info for a tournament + */ + async function fetchMatch( + tournamentId: number, + round: number, + matchNumber: number + ): Promise { + try { + return await getTournamentMatch(tournamentId, round, matchNumber); + } catch (error) { + console.error("Failed to fetch match:", error); + return null; + } + } + + /** + * Fetch all tournaments + */ + async function fetchAllTournaments() { + setLoading(true); + try { + const allTournaments = await getAllTournaments(); + setTournaments(allTournaments); + } catch (error) { + console.error("Failed to fetch tournaments:", error); + setTournaments([]); + } finally { + setLoading(false); + } + } + + /** + * Get matches for a specific round in a tournament + */ + async function fetchRoundMatches( + tournamentId: number, + round: number + ): Promise { + const matches: TournamentMatch[] = []; + + // Fetch tournament to get max players + const tournament = await fetchTournament(tournamentId); + if (!tournament) return matches; + + // Calculate number of matches in this round + // Round 1: maxPlayers / 2 matches + // Round 2: maxPlayers / 4 matches, etc. + const matchesInRound = tournament.maxPlayers / Math.pow(2, round); + + for (let i = 1; i <= matchesInRound; i++) { + const match = await fetchMatch(tournamentId, round, i); + if (match) { + matches.push(match); + } + } + + return matches; + } + + // Fetch tournaments on mount and when refresh key changes + useEffect(() => { + fetchAllTournaments(); + }, [refreshKey]); + + return { + tournaments, + loading, + handleCreateTournament, + handleJoinTournament, + handleStartTournament, + fetchTournament, + fetchParticipant, + fetchMatch, + fetchRoundMatches, + refreshTournaments: () => setRefreshKey((prev) => prev + 1), + }; +} diff --git a/frontend/lib/tournament-contract.ts b/frontend/lib/tournament-contract.ts new file mode 100644 index 0000000..b23eabc --- /dev/null +++ b/frontend/lib/tournament-contract.ts @@ -0,0 +1,303 @@ +import { STACKS_TESTNET } from "@stacks/network"; +import { + uintCV, + stringUtf8CV, + standardPrincipalCV, + contractPrincipalCV, + fetchCallReadOnlyFunction, + OptionalCV, + TupleCV, + UIntCV, + PrincipalCV, + BooleanCV, + StringUtf8CV, +} from "@stacks/transactions"; + +// Contract identifiers +const CONTRACT_ADDRESS = "ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88"; +const TOURNAMENT_CONTRACT_NAME = "tic-tac-toe-tournament"; +const TIC_TAC_TOE_CONTRACT_NAME = "tic-tac-toe-v2"; + +// Tournament status constants (matching contract) +export const TOURNAMENT_STATUS = { + OPEN: 0, + IN_PROGRESS: 1, + COMPLETED: 2, + CANCELLED: 3, +} as const; + +// Clarity value types +type TournamentCV = { + creator: PrincipalCV; + name: StringUtf8CV; + "entry-fee": UIntCV; + "max-players": UIntCV; + "prize-pool": UIntCV; + status: UIntCV; + "start-time": OptionalCV; + winner: OptionalCV; + "current-round": UIntCV; +}; + +type ParticipantCV = { + "registration-time": UIntCV; + "bracket-position": UIntCV; + eliminated: BooleanCV; +}; + +type MatchCV = { + "game-id": OptionalCV; + player1: OptionalCV; + player2: OptionalCV; + winner: OptionalCV; + completed: BooleanCV; +}; + +// Type definitions +export interface Tournament { + id: number; + creator: string; + name: string; + entryFee: number; + maxPlayers: number; + prizePool: number; + status: number; + startTime: number | null; + winner: string | null; + currentRound: number; +} + +export interface TournamentParticipant { + registrationTime: number; + bracketPosition: number; + eliminated: boolean; +} + +export interface TournamentMatch { + gameId: number | null; + player1: string | null; + player2: string | null; + winner: string | null; + completed: boolean; +} + +/** + * Create a new tournament - returns transaction options + */ +export async function createTournament( + name: string, + entryFee: number, + maxPlayers: number +) { + // Validate parameters + if (![4, 8, 16, 32].includes(maxPlayers)) { + throw new Error("Max players must be 4, 8, 16, or 32"); + } + if (entryFee <= 0) { + throw new Error("Entry fee must be greater than 0"); + } + if (name.length > 50) { + throw new Error("Tournament name must be 50 characters or less"); + } + + const txOptions = { + contractAddress: CONTRACT_ADDRESS, + contractName: TOURNAMENT_CONTRACT_NAME, + functionName: "create-tournament", + functionArgs: [ + stringUtf8CV(name), + uintCV(entryFee), + uintCV(maxPlayers), + ], + }; + + return txOptions; +} + +/** + * Join an existing tournament - returns transaction options + */ +export async function joinTournament(tournamentId: number) { + const txOptions = { + contractAddress: CONTRACT_ADDRESS, + contractName: TOURNAMENT_CONTRACT_NAME, + functionName: "join-tournament", + functionArgs: [uintCV(tournamentId)], + }; + + return txOptions; +} + +/** + * Start a tournament (creator only, must be full) - returns transaction options + */ +export async function startTournament(tournamentId: number) { + // Pass tic-tac-toe contract as trait parameter + const gameContractPrincipal = contractPrincipalCV( + CONTRACT_ADDRESS, + TIC_TAC_TOE_CONTRACT_NAME + ); + + const txOptions = { + contractAddress: CONTRACT_ADDRESS, + contractName: TOURNAMENT_CONTRACT_NAME, + functionName: "start-tournament", + functionArgs: [uintCV(tournamentId), gameContractPrincipal], + }; + + return txOptions; +} + +/** + * Fetch tournament metadata (read-only) + */ +export async function getTournament( + tournamentId: number +): Promise { + const tournamentCV = await fetchCallReadOnlyFunction({ + contractAddress: CONTRACT_ADDRESS, + contractName: TOURNAMENT_CONTRACT_NAME, + functionName: "get-tournament", + functionArgs: [uintCV(tournamentId)], + senderAddress: CONTRACT_ADDRESS, + network: STACKS_TESTNET, + }); + + const responseCV = tournamentCV as OptionalCV>; + + if (responseCV.type === "none") return null; + if (responseCV.value.type !== "tuple") return null; + + const tCV = responseCV.value.value; + + const tournament: Tournament = { + id: tournamentId, + creator: tCV.creator.value, + name: tCV.name.value, + entryFee: parseInt(tCV["entry-fee"].value.toString()), + maxPlayers: parseInt(tCV["max-players"].value.toString()), + prizePool: parseInt(tCV["prize-pool"].value.toString()), + status: parseInt(tCV.status.value.toString()), + startTime: + tCV["start-time"].type === "some" + ? parseInt(tCV["start-time"].value.value.toString()) + : null, + winner: + tCV.winner.type === "some" ? tCV.winner.value.value : null, + currentRound: parseInt(tCV["current-round"].value.toString()), + }; + + return tournament; +} + +/** + * Fetch participant info (read-only) + */ +export async function getTournamentParticipant( + tournamentId: number, + playerAddress: string +): Promise { + const participantCV = await fetchCallReadOnlyFunction({ + contractAddress: CONTRACT_ADDRESS, + contractName: TOURNAMENT_CONTRACT_NAME, + functionName: "get-participant", + functionArgs: [uintCV(tournamentId), standardPrincipalCV(playerAddress)], + senderAddress: CONTRACT_ADDRESS, + network: STACKS_TESTNET, + }); + + const responseCV = participantCV as OptionalCV>; + + if (responseCV.type === "none") return null; + if (responseCV.value.type !== "tuple") return null; + + const pCV = responseCV.value.value; + + const participant: TournamentParticipant = { + registrationTime: parseInt(pCV["registration-time"].value.toString()), + bracketPosition: parseInt(pCV["bracket-position"].value.toString()), + eliminated: pCV.eliminated.type === "true", + }; + + return participant; +} + +/** + * Fetch match details (read-only) + */ +export async function getTournamentMatch( + tournamentId: number, + round: number, + matchNumber: number +): Promise { + const matchCV = await fetchCallReadOnlyFunction({ + contractAddress: CONTRACT_ADDRESS, + contractName: TOURNAMENT_CONTRACT_NAME, + functionName: "get-match", + functionArgs: [uintCV(tournamentId), uintCV(round), uintCV(matchNumber)], + senderAddress: CONTRACT_ADDRESS, + network: STACKS_TESTNET, + }); + + const responseCV = matchCV as OptionalCV>; + + if (responseCV.type === "none") return null; + if (responseCV.value.type !== "tuple") return null; + + const mCV = responseCV.value.value; + + const match: TournamentMatch = { + gameId: + mCV["game-id"].type === "some" + ? parseInt(mCV["game-id"].value.value.toString()) + : null, + player1: + mCV.player1.type === "some" ? mCV.player1.value.value : null, + player2: + mCV.player2.type === "some" ? mCV.player2.value.value : null, + winner: mCV.winner.type === "some" ? mCV.winner.value.value : null, + completed: mCV.completed.type === "true", + }; + + return match; +} + +/** + * Fetch all tournaments by iterating through latest-tournament-id + */ +export async function getAllTournaments(): Promise { + try { + // Fetch the latest tournament ID from the contract + const latestIdCV = (await fetchCallReadOnlyFunction({ + contractAddress: CONTRACT_ADDRESS, + contractName: TOURNAMENT_CONTRACT_NAME, + functionName: "get-latest-tournament-id", + functionArgs: [], + senderAddress: CONTRACT_ADDRESS, + network: STACKS_TESTNET, + })) as UIntCV; + + const latestTournamentId = parseInt(latestIdCV.value.toString()); + + if (latestTournamentId === 0) { + return []; + } + + const tournaments: Tournament[] = []; + + // Fetch all tournaments from 0 to latestTournamentId - 1 + for (let i = 0; i < latestTournamentId; i++) { + const tournament = await getTournament(i); + if (tournament) { + tournaments.push(tournament); + } + // Add small delay to avoid rate limiting (100ms between requests) + await new Promise(resolve => setTimeout(resolve, 100)); + } + + return tournaments; + } catch (error) { + console.error("Error fetching all tournaments:", error); + return []; + } +} From df73d338fb264439ddd5eaf98b39e52f42414a17 Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 19:45:04 +0100 Subject: [PATCH 11/24] feat: add tournament creation and view pages --- .../app/tournaments/[tournamentId]/page.tsx | 172 ++++++++++++++++++ frontend/app/tournaments/create/page.tsx | 154 ++++++++++++++++ frontend/app/tournaments/page.tsx | 39 ++++ 3 files changed, 365 insertions(+) create mode 100644 frontend/app/tournaments/[tournamentId]/page.tsx create mode 100644 frontend/app/tournaments/create/page.tsx create mode 100644 frontend/app/tournaments/page.tsx diff --git a/frontend/app/tournaments/[tournamentId]/page.tsx b/frontend/app/tournaments/[tournamentId]/page.tsx new file mode 100644 index 0000000..62d01ce --- /dev/null +++ b/frontend/app/tournaments/[tournamentId]/page.tsx @@ -0,0 +1,172 @@ +"use client"; + +import { useEffect, useState } from "react"; +import { useParams } from "next/navigation"; +import { useStacks } from "@/hooks/use-stacks"; +import { useTournaments } from "@/hooks/use-tournaments"; +import { TournamentRegistration } from "@/components/tournament-registration"; +import { TournamentBracket } from "@/components/tournament-bracket"; +import { Tournament, TournamentMatch, TOURNAMENT_STATUS } from "@/lib/tournament-contract"; + +export default function TournamentDetailPage() { + const params = useParams(); + const tournamentId = parseInt(params.tournamentId as string); + + const { userAddress } = useStacks(); + const { + fetchTournament, + fetchParticipant, + fetchRoundMatches, + handleJoinTournament, + handleStartTournament, + } = useTournaments(userAddress); + + const [tournament, setTournament] = useState(null); + const [isParticipant, setIsParticipant] = useState(false); + const [loading, setLoading] = useState(true); + const [roundMatches, setRoundMatches] = useState([]); + + useEffect(() => { + async function loadTournament() { + setLoading(true); + const t = await fetchTournament(tournamentId); + setTournament(t); + + if (t && userAddress) { + const participant = await fetchParticipant(tournamentId, userAddress); + setIsParticipant(!!participant); + } + + // Load bracket data if tournament is in progress or completed + if (t && (t.status === TOURNAMENT_STATUS.IN_PROGRESS || t.status === TOURNAMENT_STATUS.COMPLETED)) { + const numRounds = Math.log2(t.maxPlayers); + const allRounds: TournamentMatch[][] = []; + + for (let round = 1; round <= numRounds; round++) { + const matches = await fetchRoundMatches(tournamentId, round); + allRounds.push(matches); + } + + setRoundMatches(allRounds); + } + + setLoading(false); + } + + if (!isNaN(tournamentId)) { + loadTournament(); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tournamentId, userAddress]); + + if (loading) { + return ( +
+
+
+
+
+ ); + } + + if (!tournament) { + return ( +
+
+

Tournament not found

+
+
+ ); + } + + const getStatusLabel = (status: number) => { + switch (status) { + case TOURNAMENT_STATUS.OPEN: + return "Open for Registration"; + case TOURNAMENT_STATUS.IN_PROGRESS: + return "In Progress"; + case TOURNAMENT_STATUS.COMPLETED: + return "Completed"; + case TOURNAMENT_STATUS.CANCELLED: + return "Cancelled"; + default: + return "Unknown"; + } + }; + + return ( +
+
+

{tournament.name}

+

{getStatusLabel(tournament.status)}

+ +
+
+

Entry Fee

+

+ {(tournament.entryFee / 1_000_000).toFixed(6)} STX +

+
+ +
+

Prize Pool

+

+ {(tournament.prizePool / 1_000_000).toFixed(6)} STX +

+
+ +
+

Max Players

+

+ {tournament.maxPlayers} +

+
+
+ +
+

Tournament Details

+
+
+ Created by: + + {tournament.creator.slice(0, 12)}...{tournament.creator.slice(-8)} + +
+
+ Current Round: + {tournament.currentRound || "Not started"} +
+ {tournament.winner && ( +
+ Winner: + + {tournament.winner.slice(0, 12)}...{tournament.winner.slice(-8)} + +
+ )} +
+
+ + + + {(tournament.status === TOURNAMENT_STATUS.IN_PROGRESS || + tournament.status === TOURNAMENT_STATUS.COMPLETED) && ( +
+

Tournament Bracket

+ +
+ )} +
+
+ ); +} diff --git a/frontend/app/tournaments/create/page.tsx b/frontend/app/tournaments/create/page.tsx new file mode 100644 index 0000000..1695af2 --- /dev/null +++ b/frontend/app/tournaments/create/page.tsx @@ -0,0 +1,154 @@ +"use client"; + +import { useState } from "react"; +import { useRouter } from "next/navigation"; +import { useStacks } from "@/hooks/use-stacks"; +import { useTournaments } from "@/hooks/use-tournaments"; + +export default function CreateTournamentPage() { + const router = useRouter(); + const { userAddress, isConnected } = useStacks(); + const { handleCreateTournament } = useTournaments(userAddress); + + const [name, setName] = useState(""); + const [entryFee, setEntryFee] = useState("0.1"); + const [maxPlayers, setMaxPlayers] = useState<4 | 8 | 16 | 32>(4); + const [isSubmitting, setIsSubmitting] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + + if (!isConnected) { + alert("Please connect your wallet first"); + return; + } + + if (!name.trim()) { + alert("Please enter a tournament name"); + return; + } + + const feeInMicroStx = Math.floor(parseFloat(entryFee) * 1_000_000); + if (feeInMicroStx <= 0) { + alert("Entry fee must be greater than 0"); + return; + } + + setIsSubmitting(true); + try { + await handleCreateTournament(name, feeInMicroStx, maxPlayers); + // On success, redirect to tournaments page + setTimeout(() => { + router.push("/tournaments"); + }, 2000); + } catch (error) { + console.error("Failed to create tournament:", error); + } finally { + setIsSubmitting(false); + } + }; + + if (!isConnected) { + return ( +
+
+

Connect your wallet to create a tournament

+
+
+ ); + } + + return ( +
+
+

Create Tournament

+ +
+
+ + setName(e.target.value)} + maxLength={50} + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + placeholder="e.g., Summer Championship 2025" + required + /> +

{name.length}/50 characters

+
+ +
+ + setEntryFee(e.target.value)} + step="0.000001" + min="0.000001" + className="w-full px-4 py-2 border border-gray-300 rounded-lg focus:ring-2 focus:ring-blue-500 focus:border-transparent" + required + /> +

+ Minimum: 0.000001 STX ({parseFloat(entryFee) * 1_000_000} microSTX) +

+
+ +
+ + +

+ Must be a power of 2 for bracket structure +

+
+ +
+

Tournament Summary

+
+

• Name: {name || "..."}

+

• Entry Fee: {entryFee} STX

+

• Max Players: {maxPlayers}

+

• Total Prize Pool: {(parseFloat(entryFee) * maxPlayers).toFixed(6)} STX

+

• Number of Rounds: {Math.log2(maxPlayers)}

+
+
+ +
+ + +
+
+
+
+ ); +} diff --git a/frontend/app/tournaments/page.tsx b/frontend/app/tournaments/page.tsx new file mode 100644 index 0000000..da2d761 --- /dev/null +++ b/frontend/app/tournaments/page.tsx @@ -0,0 +1,39 @@ +"use client"; + +import { useTournaments } from "@/hooks/use-tournaments"; +import { useStacks } from "@/hooks/use-stacks"; +import { TournamentList } from "@/components/tournament-list"; +import Link from "next/link"; + +export default function TournamentsPage() { + const { userAddress, isConnected } = useStacks(); + const { tournaments, loading } = useTournaments(userAddress); + + return ( +
+
+
+

Tournaments

+

Compete in single-elimination tournaments

+
+ + {isConnected && ( + + Create Tournament + + )} +
+ + {!isConnected && ( +
+

Connect your wallet to view and join tournaments

+
+ )} + + +
+ ); +} From 4db97215ae90468f4fa528721d50772f1ecae5bc Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 19:45:55 +0100 Subject: [PATCH 12/24] feat: add tournament components --- frontend/components/navbar.tsx | 3 + frontend/components/play-game.tsx | 41 +++++- frontend/components/tournament-bracket.tsx | 138 ++++++++++++++++++ frontend/components/tournament-list.tsx | 120 +++++++++++++++ .../components/tournament-registration.tsx | 112 ++++++++++++++ 5 files changed, 412 insertions(+), 2 deletions(-) create mode 100644 frontend/components/tournament-bracket.tsx create mode 100644 frontend/components/tournament-list.tsx create mode 100644 frontend/components/tournament-registration.tsx diff --git a/frontend/components/navbar.tsx b/frontend/components/navbar.tsx index c0ab649..b899a3e 100644 --- a/frontend/components/navbar.tsx +++ b/frontend/components/navbar.tsx @@ -20,6 +20,9 @@ export function Navbar() { Create Game + + Tournaments +
diff --git a/frontend/components/play-game.tsx b/frontend/components/play-game.tsx index 07f17ae..b7db953 100644 --- a/frontend/components/play-game.tsx +++ b/frontend/components/play-game.tsx @@ -5,16 +5,32 @@ import { GameBoard } from "./game-board"; import { abbreviateAddress, explorerAddress, formatStx } from "@/lib/stx-utils"; import Link from "next/link"; import { useStacks } from "@/hooks/use-stacks"; -import { useState } from "react"; +import { useState, useEffect } from "react"; +import { useTournaments } from "@/hooks/use-tournaments"; interface PlayGameProps { game: Game; + tournamentId?: number; + round?: number; + matchNumber?: number; } -export function PlayGame({ game }: PlayGameProps) { +export function PlayGame({ game, tournamentId, round, matchNumber }: PlayGameProps) { const { userData, handleJoinGame, handlePlayGame } = useStacks(); + const { fetchTournament } = useTournaments(userData?.profile.stxAddress.testnet); const [board, setBoard] = useState(game.board); const [playedMoveIndex, setPlayedMoveIndex] = useState(-1); + const [tournamentName, setTournamentName] = useState(null); + + useEffect(() => { + if (tournamentId) { + fetchTournament(tournamentId).then((t) => { + if (t) setTournamentName(t.name); + }); + } + // eslint-disable-next-line react-hooks/exhaustive-deps + }, [tournamentId]); + if (!userData) return null; const isPlayerOne = @@ -39,6 +55,27 @@ export function PlayGame({ game }: PlayGameProps) { return (
+ {/* Tournament Banner */} + {tournamentId && ( +
+
+
+
Tournament Match
+
{tournamentName || `Tournament #${tournamentId}`}
+ {round && matchNumber && ( +
Round {round} • Match {matchNumber}
+ )} +
+ + View Bracket → + +
+
+ )} + +

No matches available yet

+
+ ); + } + + return ( +
+
+ {rounds.map((roundMatches, roundIndex) => ( +
+

+ {getRoundName(roundIndex + 1, numRounds)} +

+ + {roundMatches.map((match, matchIndex) => ( + + ))} +
+ ))} +
+
+ ); +} + +interface MatchCardProps { + match: TournamentMatch; + tournamentId: number; + round: number; + matchNumber: number; +} + +function MatchCard({ match, matchNumber }: MatchCardProps) { + const { player1, player2, winner, completed, gameId } = match; + + return ( +
+
+ Match {matchNumber} +
+ +
+ + +
VS
+ + +
+ + {gameId !== null && ( +
+ + View Game → + +
+ )} + + {completed && winner && ( +
+ āœ“ Complete +
+ )} +
+ ); +} + +interface PlayerSlotProps { + player: string | null; + isWinner: boolean; + label: string; +} + +function PlayerSlot({ player, isWinner, label }: PlayerSlotProps) { + if (!player) { + return ( +
+ {label}: TBD +
+ ); + } + + return ( +
+ {player.slice(0, 8)}...{player.slice(-6)} + {isWinner && šŸ‘‘} +
+ ); +} + +function getRoundName(round: number, totalRounds: number): string { + if (round === totalRounds) return "Finals"; + if (round === totalRounds - 1) return "Semi-Finals"; + if (round === totalRounds - 2) return "Quarter-Finals"; + return `Round ${round}`; +} diff --git a/frontend/components/tournament-list.tsx b/frontend/components/tournament-list.tsx new file mode 100644 index 0000000..892310d --- /dev/null +++ b/frontend/components/tournament-list.tsx @@ -0,0 +1,120 @@ +"use client"; + +import { Tournament, TOURNAMENT_STATUS } from "@/lib/tournament-contract"; +import Link from "next/link"; + +interface TournamentListProps { + tournaments: Tournament[]; + loading?: boolean; +} + +export function TournamentList({ tournaments, loading }: TournamentListProps) { + if (loading) { + return ( +
+
+
+ ); + } + + if (tournaments.length === 0) { + return ( +
+

No tournaments available

+

Create the first one!

+
+ ); + } + + return ( +
+ {tournaments.map((tournament) => ( + + ))} +
+ ); +} + +interface TournamentCardProps { + tournament: Tournament; +} + +function TournamentCard({ tournament }: TournamentCardProps) { + const getStatusBadge = (status: number) => { + switch (status) { + case TOURNAMENT_STATUS.OPEN: + return 🟢 Open; + case TOURNAMENT_STATUS.IN_PROGRESS: + return 🟔 In Progress; + case TOURNAMENT_STATUS.COMPLETED: + return šŸ”µ Completed; + case TOURNAMENT_STATUS.CANCELLED: + return šŸ”“ Cancelled; + default: + return Unknown; + } + }; + + // Calculate player count (would need to track this separately or query contract) + const playerCount = 0; // Placeholder - needs implementation + const progress = `${playerCount}/${tournament.maxPlayers}`; + + return ( + +
+
+

+ {tournament.name} +

+ {getStatusBadge(tournament.status)} +
+ +
+
+ Entry Fee: + {(tournament.entryFee / 1_000_000).toFixed(6)} STX +
+ +
+ Prize Pool: + + {(tournament.prizePool / 1_000_000).toFixed(6)} STX + +
+ +
+ Players: + {progress} +
+ +
+ Max Players: + {tournament.maxPlayers} +
+ + {tournament.currentRound > 0 && ( +
+ Current Round: + Round {tournament.currentRound} +
+ )} + + {tournament.winner && ( +
+
+ Winner: + + {tournament.winner.slice(0, 8)}...{tournament.winner.slice(-6)} + +
+
+ )} +
+ +
+
Created by: {tournament.creator.slice(0, 8)}...{tournament.creator.slice(-6)}
+
+
+ + ); +} diff --git a/frontend/components/tournament-registration.tsx b/frontend/components/tournament-registration.tsx new file mode 100644 index 0000000..1e2e94e --- /dev/null +++ b/frontend/components/tournament-registration.tsx @@ -0,0 +1,112 @@ +"use client"; + +import { Tournament, TOURNAMENT_STATUS } from "@/lib/tournament-contract"; +import { useState } from "react"; + +interface TournamentRegistrationProps { + tournament: Tournament; + userAddress: string | null; + onJoin: (tournamentId: number) => void; + onStart: (tournamentId: number) => void; + isParticipant: boolean; +} + +export function TournamentRegistration({ + tournament, + userAddress, + onJoin, + onStart, + isParticipant, +}: TournamentRegistrationProps) { + const [isProcessing, setIsProcessing] = useState(false); + + const isCreator = userAddress === tournament.creator; + const canJoin = tournament.status === TOURNAMENT_STATUS.OPEN && !isParticipant && userAddress; + const canStart = tournament.status === TOURNAMENT_STATUS.OPEN && isCreator; + + const handleJoin = async () => { + setIsProcessing(true); + try { + await onJoin(tournament.id); + } finally { + setIsProcessing(false); + } + }; + + const handleStart = async () => { + setIsProcessing(true); + try { + await onStart(tournament.id); + } finally { + setIsProcessing(false); + } + }; + + if (!userAddress) { + return ( +
+

Connect your wallet to participate

+
+ ); + } + + if (tournament.status === TOURNAMENT_STATUS.COMPLETED) { + return ( +
+

Tournament Completed

+ {tournament.winner && ( +

+ Winner: {tournament.winner.slice(0, 12)}...{tournament.winner.slice(-8)} +

+ )} +
+ ); + } + + if (tournament.status === TOURNAMENT_STATUS.IN_PROGRESS) { + return ( +
+

Tournament in Progress

+

Round {tournament.currentRound}

+
+ ); + } + + return ( +
+ {isParticipant && ( +
+

āœ“ You are registered

+
+ )} + + {canJoin && ( + + )} + + {canStart && ( + + )} + + {isCreator && !canStart && ( +
+

+ Waiting for {tournament.maxPlayers} players to join +

+
+ )} +
+ ); +} From 36d0e1b2f6022b191238668745559fd7e6bd89fe Mon Sep 17 00:00:00 2001 From: psychemist Date: Sun, 26 Oct 2025 19:55:31 +0100 Subject: [PATCH 13/24] fix: add function for fetching [all] tournament ids --- Clarinet.toml | 12 ++++++------ ...ournament.clar => tic-tac-toe-tournament-v2.clar} | 5 +++++ deployments/default.testnet-plan.yaml | 6 +++--- frontend/lib/tournament-contract.ts | 2 +- 4 files changed, 15 insertions(+), 10 deletions(-) rename contracts/{tic-tac-toe-tournament.clar => tic-tac-toe-tournament-v2.clar} (98%) diff --git a/Clarinet.toml b/Clarinet.toml index c546df9..c271665 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -6,18 +6,18 @@ telemetry = false cache_dir = './.cache' requirements = [] -[contracts.tic-tac-toe-v2] -path = 'contracts/tic-tac-toe-v2.clar' +[contracts.game-tournament-trait] +path = 'contracts/game-tournament-trait.clar' clarity_version = 3 epoch = 3.0 -[contracts.game-tournament-trait] -path = 'contracts/game-tournament-trait.clar' +[contracts.tic-tac-toe-tournament-v2] +path = 'contracts/tic-tac-toe-tournament-v2.clar' clarity_version = 3 epoch = 3.0 -[contracts.tic-tac-toe-tournament] -path = 'contracts/tic-tac-toe-tournament.clar' +[contracts.tic-tac-toe-v2] +path = 'contracts/tic-tac-toe-v2.clar' clarity_version = 3 epoch = 3.0 diff --git a/contracts/tic-tac-toe-tournament.clar b/contracts/tic-tac-toe-tournament-v2.clar similarity index 98% rename from contracts/tic-tac-toe-tournament.clar rename to contracts/tic-tac-toe-tournament-v2.clar index db4c11b..1e08c4b 100644 --- a/contracts/tic-tac-toe-tournament.clar +++ b/contracts/tic-tac-toe-tournament-v2.clar @@ -296,6 +296,11 @@ ;; READ-ONLY FUNCTIONS +;; get the latest tournament id (for fetching all tournaments easily) +(define-read-only (get-latest-tournament-id) + (var-get latest-tournament-id) +) + ;; public getter for tournament metadata (define-read-only (get-tournament (tid uint)) (map-get? tournaments tid) diff --git a/deployments/default.testnet-plan.yaml b/deployments/default.testnet-plan.yaml index 3f14ab6..7e21d7e 100644 --- a/deployments/default.testnet-plan.yaml +++ b/deployments/default.testnet-plan.yaml @@ -16,10 +16,10 @@ plan: anchor-block-only: true clarity-version: 3 - contract-publish: - contract-name: tic-tac-toe-tournament + contract-name: tic-tac-toe-tournament-v2 expected-sender: ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88 - cost: 115510 - path: contracts/tic-tac-toe-tournament.clar + cost: 117020 + path: contracts/tic-tac-toe-tournament-v2.clar anchor-block-only: true clarity-version: 3 - contract-publish: diff --git a/frontend/lib/tournament-contract.ts b/frontend/lib/tournament-contract.ts index b23eabc..bc2d553 100644 --- a/frontend/lib/tournament-contract.ts +++ b/frontend/lib/tournament-contract.ts @@ -15,7 +15,7 @@ import { // Contract identifiers const CONTRACT_ADDRESS = "ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88"; -const TOURNAMENT_CONTRACT_NAME = "tic-tac-toe-tournament"; +const TOURNAMENT_CONTRACT_NAME = "tic-tac-toe-tournament-v2"; const TIC_TAC_TOE_CONTRACT_NAME = "tic-tac-toe-v2"; // Tournament status constants (matching contract) From a1e2eb971e501009ad80a1e1934d544bda53f82f Mon Sep 17 00:00:00 2001 From: psychemist Date: Mon, 27 Oct 2025 11:18:08 +0100 Subject: [PATCH 14/24] fix: fix stack connection issues on leather and xverse wallets --- frontend/components/tournament-list.tsx | 8 +- frontend/hooks/use-stacks.ts | 31 ++++- frontend/hooks/use-tournaments.ts | 4 + frontend/lib/tournament-contract.ts | 176 +++++++++++++----------- 4 files changed, 131 insertions(+), 88 deletions(-) diff --git a/frontend/components/tournament-list.tsx b/frontend/components/tournament-list.tsx index 892310d..7a9b488 100644 --- a/frontend/components/tournament-list.tsx +++ b/frontend/components/tournament-list.tsx @@ -78,7 +78,13 @@ function TournamentCard({ tournament }: TournamentCardProps) {
Prize Pool: - {(tournament.prizePool / 1_000_000).toFixed(6)} STX + {tournament.prizePool === 0 ? ( + + 0.000000 STX + + ) : ( + `${(tournament.prizePool / 1_000_000).toFixed(6)} STX` + )}
diff --git a/frontend/hooks/use-stacks.ts b/frontend/hooks/use-stacks.ts index fbff5b3..5854172 100644 --- a/frontend/hooks/use-stacks.ts +++ b/frontend/hooks/use-stacks.ts @@ -10,6 +10,7 @@ import { type UserData, UserSession, } from "@stacks/connect"; +import { STACKS_TESTNET } from "@stacks/network"; import { PostConditionMode } from "@stacks/transactions"; import { useEffect, useState } from "react"; @@ -29,16 +30,18 @@ export function useStacks() { async function connectWallet() { try { - console.log("=== Connecting Wallet ==="); await connect(); - console.log("Wallet connected successfully"); - // Get user address after connection + // Get user address after connection; prioritize testnet let address = null; if (isConnected()) { const data = getLocalStorage(); if (data?.addresses?.stx && data.addresses.stx.length > 0) { - address = data.addresses.stx[0].address; + type StxAddress = { address: string; network?: string }; + const testnetAddr = data.addresses.stx.find((addr: StxAddress) => + addr.network === 'testnet' || addr.address.startsWith('ST') + ); + address = testnetAddr?.address || data.addresses.stx[0].address; setUserAddress(address); } } else if (userSession.isUserSignedIn()) { @@ -84,6 +87,7 @@ export function useStacks() { await openContractCall({ ...txOptions, appDetails, + network: STACKS_TESTNET, onFinish: (data) => { console.log(data); window.alert("Sent create game transaction"); @@ -110,6 +114,7 @@ export function useStacks() { await openContractCall({ ...txOptions, appDetails, + network: STACKS_TESTNET, onFinish: (data) => { console.log(data); window.alert("Sent join game transaction"); @@ -136,6 +141,7 @@ export function useStacks() { await openContractCall({ ...txOptions, appDetails, + network: STACKS_TESTNET, onFinish: (data) => { console.log(data); window.alert("Sent play game transaction"); @@ -154,14 +160,25 @@ export function useStacks() { if (isConnected()) { const data = getLocalStorage(); if (data?.addresses?.stx && data.addresses.stx.length > 0) { - const address = data.addresses.stx[0].address; + // Check if there's a testnet address in the array + type StxAddress = { address: string; network?: string }; + const testnetAddr = data.addresses.stx.find((addr: StxAddress) => + addr.network === 'testnet' || addr.address.startsWith('ST') + ); + const mainnetAddr = data.addresses.stx.find((addr: StxAddress) => + addr.network === 'mainnet' || addr.address.startsWith('SP') + ); + + // Use testnet address for our testnet app + const address = testnetAddr?.address || data.addresses.stx[0].address; setUserAddress(address); + // Create minimal userData for compatibility setUserData({ profile: { stxAddress: { - testnet: address, - mainnet: address, + testnet: testnetAddr?.address || address, + mainnet: mainnetAddr?.address || address, }, }, } as UserData); diff --git a/frontend/hooks/use-tournaments.ts b/frontend/hooks/use-tournaments.ts index 09ea81b..d15cc15 100644 --- a/frontend/hooks/use-tournaments.ts +++ b/frontend/hooks/use-tournaments.ts @@ -11,6 +11,7 @@ import { type TournamentParticipant, } from "@/lib/tournament-contract"; import { openContractCall } from "@stacks/connect"; +import { STACKS_TESTNET } from "@stacks/network"; import { PostConditionMode } from "@stacks/transactions"; import { useEffect, useState } from "react"; @@ -41,6 +42,7 @@ export function useTournaments(userAddress: string | null) { await openContractCall({ ...txOptions, appDetails, + network: STACKS_TESTNET, onFinish: (data) => { console.log("Tournament created:", data); window.alert("Tournament created successfully!"); @@ -72,6 +74,7 @@ export function useTournaments(userAddress: string | null) { await openContractCall({ ...txOptions, appDetails, + network: STACKS_TESTNET, onFinish: (data) => { console.log("Joined tournament:", data); window.alert("Joined tournament successfully!"); @@ -103,6 +106,7 @@ export function useTournaments(userAddress: string | null) { await openContractCall({ ...txOptions, appDetails, + network: STACKS_TESTNET, onFinish: (data) => { console.log("Tournament started:", data); window.alert("Tournament started successfully!"); diff --git a/frontend/lib/tournament-contract.ts b/frontend/lib/tournament-contract.ts index bc2d553..a4649ab 100644 --- a/frontend/lib/tournament-contract.ts +++ b/frontend/lib/tournament-contract.ts @@ -154,40 +154,45 @@ export async function startTournament(tournamentId: number) { export async function getTournament( tournamentId: number ): Promise { - const tournamentCV = await fetchCallReadOnlyFunction({ - contractAddress: CONTRACT_ADDRESS, - contractName: TOURNAMENT_CONTRACT_NAME, - functionName: "get-tournament", - functionArgs: [uintCV(tournamentId)], - senderAddress: CONTRACT_ADDRESS, - network: STACKS_TESTNET, - }); - - const responseCV = tournamentCV as OptionalCV>; - - if (responseCV.type === "none") return null; - if (responseCV.value.type !== "tuple") return null; - - const tCV = responseCV.value.value; - - const tournament: Tournament = { - id: tournamentId, - creator: tCV.creator.value, - name: tCV.name.value, - entryFee: parseInt(tCV["entry-fee"].value.toString()), - maxPlayers: parseInt(tCV["max-players"].value.toString()), - prizePool: parseInt(tCV["prize-pool"].value.toString()), - status: parseInt(tCV.status.value.toString()), - startTime: - tCV["start-time"].type === "some" - ? parseInt(tCV["start-time"].value.value.toString()) - : null, - winner: - tCV.winner.type === "some" ? tCV.winner.value.value : null, - currentRound: parseInt(tCV["current-round"].value.toString()), - }; + try { + const tournamentCV = await fetchCallReadOnlyFunction({ + contractAddress: CONTRACT_ADDRESS, + contractName: TOURNAMENT_CONTRACT_NAME, + functionName: "get-tournament", + functionArgs: [uintCV(tournamentId)], + senderAddress: CONTRACT_ADDRESS, + network: STACKS_TESTNET, + }); - return tournament; + const responseCV = tournamentCV as OptionalCV>; + + if (responseCV.type === "none") return null; + if (responseCV.value.type !== "tuple") return null; + + const tCV = responseCV.value.value; + + const tournament: Tournament = { + id: tournamentId, + creator: tCV.creator.value, + name: tCV.name.value, + entryFee: parseInt(tCV["entry-fee"].value.toString()), + maxPlayers: parseInt(tCV["max-players"].value.toString()), + prizePool: parseInt(tCV["prize-pool"].value.toString()), + status: parseInt(tCV.status.value.toString()), + startTime: + tCV["start-time"].type === "some" + ? parseInt(tCV["start-time"].value.value.toString()) + : null, + winner: + tCV.winner.type === "some" ? tCV.winner.value.value : null, + currentRound: parseInt(tCV["current-round"].value.toString()), + }; + + return tournament; + } catch (error) { + console.error(`Error fetching tournament ${tournamentId}:`, error); + return null; + } } /** @@ -197,29 +202,35 @@ export async function getTournamentParticipant( tournamentId: number, playerAddress: string ): Promise { - const participantCV = await fetchCallReadOnlyFunction({ - contractAddress: CONTRACT_ADDRESS, - contractName: TOURNAMENT_CONTRACT_NAME, - functionName: "get-participant", - functionArgs: [uintCV(tournamentId), standardPrincipalCV(playerAddress)], - senderAddress: CONTRACT_ADDRESS, - network: STACKS_TESTNET, - }); + try { + const participantCV = await fetchCallReadOnlyFunction({ + contractAddress: CONTRACT_ADDRESS, + contractName: TOURNAMENT_CONTRACT_NAME, + functionName: "get-participant", + functionArgs: [uintCV(tournamentId), standardPrincipalCV(playerAddress)], + senderAddress: CONTRACT_ADDRESS, + network: STACKS_TESTNET, + }); - const responseCV = participantCV as OptionalCV>; + const responseCV = participantCV as OptionalCV>; - if (responseCV.type === "none") return null; - if (responseCV.value.type !== "tuple") return null; + if (responseCV.type === "none") return null; + if (responseCV.value.type !== "tuple") return null; - const pCV = responseCV.value.value; + const pCV = responseCV.value.value; - const participant: TournamentParticipant = { - registrationTime: parseInt(pCV["registration-time"].value.toString()), - bracketPosition: parseInt(pCV["bracket-position"].value.toString()), - eliminated: pCV.eliminated.type === "true", - }; + const participant: TournamentParticipant = { + registrationTime: parseInt(pCV["registration-time"].value.toString()), + bracketPosition: parseInt(pCV["bracket-position"].value.toString()), + eliminated: pCV.eliminated.type === "true", + }; - return participant; + return participant; + } catch (error) { + console.error(`Error fetching participant for tournament ${tournamentId}:`, error); + // Return null instead of throwing - participant might not exist + return null; + } } /** @@ -230,36 +241,41 @@ export async function getTournamentMatch( round: number, matchNumber: number ): Promise { - const matchCV = await fetchCallReadOnlyFunction({ - contractAddress: CONTRACT_ADDRESS, - contractName: TOURNAMENT_CONTRACT_NAME, - functionName: "get-match", - functionArgs: [uintCV(tournamentId), uintCV(round), uintCV(matchNumber)], - senderAddress: CONTRACT_ADDRESS, - network: STACKS_TESTNET, - }); - - const responseCV = matchCV as OptionalCV>; - - if (responseCV.type === "none") return null; - if (responseCV.value.type !== "tuple") return null; - - const mCV = responseCV.value.value; - - const match: TournamentMatch = { - gameId: - mCV["game-id"].type === "some" - ? parseInt(mCV["game-id"].value.value.toString()) - : null, - player1: - mCV.player1.type === "some" ? mCV.player1.value.value : null, - player2: - mCV.player2.type === "some" ? mCV.player2.value.value : null, - winner: mCV.winner.type === "some" ? mCV.winner.value.value : null, - completed: mCV.completed.type === "true", - }; + try { + const matchCV = await fetchCallReadOnlyFunction({ + contractAddress: CONTRACT_ADDRESS, + contractName: TOURNAMENT_CONTRACT_NAME, + functionName: "get-match", + functionArgs: [uintCV(tournamentId), uintCV(round), uintCV(matchNumber)], + senderAddress: CONTRACT_ADDRESS, + network: STACKS_TESTNET, + }); + + const responseCV = matchCV as OptionalCV>; + + if (responseCV.type === "none") return null; + if (responseCV.value.type !== "tuple") return null; - return match; + const mCV = responseCV.value.value; + + const match: TournamentMatch = { + gameId: + mCV["game-id"].type === "some" + ? parseInt(mCV["game-id"].value.value.toString()) + : null, + player1: + mCV.player1.type === "some" ? mCV.player1.value.value : null, + player2: + mCV.player2.type === "some" ? mCV.player2.value.value : null, + winner: mCV.winner.type === "some" ? mCV.winner.value.value : null, + completed: mCV.completed.type === "true", + }; + + return match; + } catch (error) { + console.error(`Error fetching match for tournament ${tournamentId}, round ${round}, match ${matchNumber}:`, error); + return null; + } } /** From 29524542ae0208d5e759eacdc8fd0dadfc6208ce Mon Sep 17 00:00:00 2001 From: psychemist Date: Mon, 27 Oct 2025 17:20:40 +0100 Subject: [PATCH 15/24] feat: add caching to contract calls to avoid hitting Hiro's rate limits --- frontend/app/tournaments/page.tsx | 6 +++++ frontend/hooks/use-tournaments.ts | 10 ++++--- frontend/lib/stx-utils.ts | 39 +++++++++++++++++++++++---- frontend/lib/tournament-contract.ts | 41 ++++++++++++++++++++++++++--- 4 files changed, 85 insertions(+), 11 deletions(-) diff --git a/frontend/app/tournaments/page.tsx b/frontend/app/tournaments/page.tsx index da2d761..4eb1eb0 100644 --- a/frontend/app/tournaments/page.tsx +++ b/frontend/app/tournaments/page.tsx @@ -33,6 +33,12 @@ export default function TournamentsPage() {
)} + {loading && ( +
+

Loading tournaments... This may take a moment due to API rate limits.

+
+ )} +
); diff --git a/frontend/hooks/use-tournaments.ts b/frontend/hooks/use-tournaments.ts index d15cc15..c27372d 100644 --- a/frontend/hooks/use-tournaments.ts +++ b/frontend/hooks/use-tournaments.ts @@ -6,6 +6,7 @@ import { getAllTournaments, getTournamentParticipant, getTournamentMatch, + clearTournamentCache, type Tournament, type TournamentMatch, type TournamentParticipant, @@ -46,7 +47,8 @@ export function useTournaments(userAddress: string | null) { onFinish: (data) => { console.log("Tournament created:", data); window.alert("Tournament created successfully!"); - // Refresh tournaments list + // Clear cache and refresh tournaments list + clearTournamentCache(); setRefreshKey((prev) => prev + 1); }, onCancel: () => { @@ -78,7 +80,8 @@ export function useTournaments(userAddress: string | null) { onFinish: (data) => { console.log("Joined tournament:", data); window.alert("Joined tournament successfully!"); - // Refresh tournaments list + // Clear cache and refresh tournaments list + clearTournamentCache(tournamentId); setRefreshKey((prev) => prev + 1); }, onCancel: () => { @@ -110,7 +113,8 @@ export function useTournaments(userAddress: string | null) { onFinish: (data) => { console.log("Tournament started:", data); window.alert("Tournament started successfully!"); - // Refresh tournaments list + // Clear cache and refresh tournaments list + clearTournamentCache(tournamentId); setRefreshKey((prev) => prev + 1); }, onCancel: () => { diff --git a/frontend/lib/stx-utils.ts b/frontend/lib/stx-utils.ts index 54b3feb..9bc5379 100644 --- a/frontend/lib/stx-utils.ts +++ b/frontend/lib/stx-utils.ts @@ -10,13 +10,42 @@ export function explorerAddress(address: string) { return `https://explorer.hiro.so/address/${address}?chain=testnet`; } +// Cache for STX balances to avoid rate limiting +const balanceCache = new Map(); +const CACHE_TTL = 30000; // 30 seconds + export async function getStxBalance(address: string) { - const baseUrl = "https://api.testnet.hiro.so"; - const url = `${baseUrl}/extended/v1/address/${address}/stx`; + // Check cache first + const cached = balanceCache.get(address); + if (cached && Date.now() - cached.timestamp < CACHE_TTL) { + return cached.balance; + } + + try { + const baseUrl = "https://api.testnet.hiro.so"; + const url = `${baseUrl}/extended/v1/address/${address}/stx`; - const response = await fetch(url).then((res) => res.json()); - const balance = parseInt(response.balance); - return balance; + const response = await fetch(url).then((res) => { + if (!res.ok) { + throw new Error(`HTTP ${res.status}: ${res.statusText}`); + } + return res.json(); + }); + const balance = parseInt(response.balance); + + // Update cache + balanceCache.set(address, { balance, timestamp: Date.now() }); + + return balance; + } catch (error) { + console.error("Error fetching STX balance:", error); + // Return cached value if available, even if expired + if (cached) { + return cached.balance; + } + // Return 0 as fallback + return 0; + } } // Convert a raw STX amount to a human readable format by respecting the 6 decimal places diff --git a/frontend/lib/tournament-contract.ts b/frontend/lib/tournament-contract.ts index a4649ab..4c249f9 100644 --- a/frontend/lib/tournament-contract.ts +++ b/frontend/lib/tournament-contract.ts @@ -18,6 +18,10 @@ const CONTRACT_ADDRESS = "ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88"; const TOURNAMENT_CONTRACT_NAME = "tic-tac-toe-tournament-v2"; const TIC_TAC_TOE_CONTRACT_NAME = "tic-tac-toe-v2"; +// Cache for tournaments to reduce API calls +const tournamentsCache = new Map(); +const CACHE_TTL = 30000; // 30 seconds cache + // Tournament status constants (matching contract) export const TOURNAMENT_STATUS = { OPEN: 0, @@ -154,6 +158,12 @@ export async function startTournament(tournamentId: number) { export async function getTournament( tournamentId: number ): Promise { + // Check cache first + const cached = tournamentsCache.get(tournamentId); + if (cached && Date.now() - cached.timestamp < CACHE_TTL) { + return cached.tournament; + } + try { const tournamentCV = await fetchCallReadOnlyFunction({ contractAddress: CONTRACT_ADDRESS, @@ -188,9 +198,16 @@ export async function getTournament( currentRound: parseInt(tCV["current-round"].value.toString()), }; + // Update cache + tournamentsCache.set(tournamentId, { tournament, timestamp: Date.now() }); + return tournament; } catch (error) { console.error(`Error fetching tournament ${tournamentId}:`, error); + // Return cached value if available, even if expired + if (cached) { + return cached.tournament; + } return null; } } @@ -307,13 +324,31 @@ export async function getAllTournaments(): Promise { if (tournament) { tournaments.push(tournament); } - // Add small delay to avoid rate limiting (100ms between requests) - await new Promise(resolve => setTimeout(resolve, 100)); + // Increase delay to avoid rate limiting (300ms between requests) + if (i < latestTournamentId - 1) { + await new Promise(resolve => setTimeout(resolve, 300)); + } } return tournaments; } catch (error) { console.error("Error fetching all tournaments:", error); - return []; + // If rate limited, return cached tournaments + const cachedTournaments: Tournament[] = []; + tournamentsCache.forEach(({ tournament }) => { + cachedTournaments.push(tournament); + }); + return cachedTournaments; + } +} + +/** + * Clear tournament cache (useful after creating/joining/starting tournaments) + */ +export function clearTournamentCache(tournamentId?: number) { + if (tournamentId !== undefined) { + tournamentsCache.delete(tournamentId); + } else { + tournamentsCache.clear(); } } From 2edf617849f156f2837db9a28bdcb91919168475 Mon Sep 17 00:00:00 2001 From: psychemist Date: Mon, 27 Oct 2025 18:10:24 +0100 Subject: [PATCH 16/24] fix: fix bug where tournament participants could not see active match; calculate player count from prize pool --- .../app/tournaments/[tournamentId]/page.tsx | 76 +++++++++++++++++++ frontend/components/tournament-bracket.tsx | 4 +- frontend/hooks/use-tournaments.ts | 16 +++- frontend/lib/tournament-contract.ts | 22 ++++++ 4 files changed, 114 insertions(+), 4 deletions(-) diff --git a/frontend/app/tournaments/[tournamentId]/page.tsx b/frontend/app/tournaments/[tournamentId]/page.tsx index 62d01ce..ff7391f 100644 --- a/frontend/app/tournaments/[tournamentId]/page.tsx +++ b/frontend/app/tournaments/[tournamentId]/page.tsx @@ -2,6 +2,7 @@ import { useEffect, useState } from "react"; import { useParams } from "next/navigation"; +import Link from "next/link"; import { useStacks } from "@/hooks/use-stacks"; import { useTournaments } from "@/hooks/use-tournaments"; import { TournamentRegistration } from "@/components/tournament-registration"; @@ -30,6 +31,12 @@ export default function TournamentDetailPage() { async function loadTournament() { setLoading(true); const t = await fetchTournament(tournamentId); + + // Calculate player count from prize pool + if (t) { + t.playerCount = t.entryFee > 0 ? Math.floor(t.prizePool / t.entryFee) : 0; + } + setTournament(t); if (t && userAddress) { @@ -155,6 +162,75 @@ export default function TournamentDetailPage() { isParticipant={isParticipant} /> + {/* User's Active Match */} + {userAddress && isParticipant && tournament.status === TOURNAMENT_STATUS.IN_PROGRESS && ( +
+
+

šŸŽ® Your Match

+
+ {(() => { + // Find user's active match across all rounds + let activeMatch: { match: TournamentMatch; roundIndex: number } | null = null; + + for (let roundIndex = 0; roundIndex < roundMatches.length; roundIndex++) { + const matches = roundMatches[roundIndex]; + const userMatch = matches.find( + (match) => + match.player1 === userAddress || + match.player2 === userAddress + ); + + if (userMatch && !userMatch.completed) { + activeMatch = { match: userMatch, roundIndex }; + break; + } + } + + if (activeMatch) { + const { match, roundIndex } = activeMatch; + const opponent = match.player1 === userAddress ? match.player2 : match.player1; + + return ( +
+
+
+

Round {roundIndex + 1}

+

+ vs {opponent ? `${opponent.slice(0, 8)}...${opponent.slice(-6)}` : 'TBD'} +

+ {match.gameId === null && ( +

Waiting for game to be created...

+ )} +
+ {match.gameId !== null ? ( + + Play Now → + + ) : ( + + )} +
+
+ ); + } + + return ( +

+ No active matches at the moment. Check the bracket below for details. +

+ ); + })()} +
+ )} + {(tournament.status === TOURNAMENT_STATUS.IN_PROGRESS || tournament.status === TOURNAMENT_STATUS.COMPLETED) && (
diff --git a/frontend/components/tournament-bracket.tsx b/frontend/components/tournament-bracket.tsx index 87fb12f..b49e9a7 100644 --- a/frontend/components/tournament-bracket.tsx +++ b/frontend/components/tournament-bracket.tsx @@ -85,9 +85,9 @@ function MatchCard({ match, matchNumber }: MatchCardProps) {
- View Game → + {completed ? "View Game šŸ” →" : "Play Now šŸŽ®"}
)} diff --git a/frontend/hooks/use-tournaments.ts b/frontend/hooks/use-tournaments.ts index c27372d..e42adb8 100644 --- a/frontend/hooks/use-tournaments.ts +++ b/frontend/hooks/use-tournaments.ts @@ -179,7 +179,17 @@ export function useTournaments(userAddress: string | null) { setLoading(true); try { const allTournaments = await getAllTournaments(); - setTournaments(allTournaments); + + // Fetch player counts for each tournament + // Calculate from prize pool (prize pool = entry fee Ɨ player count) + const tournamentsWithCounts = allTournaments.map(tournament => ({ + ...tournament, + playerCount: tournament.entryFee > 0 + ? Math.floor(tournament.prizePool / tournament.entryFee) + : 0 + })); + + setTournaments(tournamentsWithCounts); } catch (error) { console.error("Failed to fetch tournaments:", error); setTournaments([]); @@ -199,7 +209,9 @@ export function useTournaments(userAddress: string | null) { // Fetch tournament to get max players const tournament = await fetchTournament(tournamentId); - if (!tournament) return matches; + if (!tournament) { + return matches; + } // Calculate number of matches in this round // Round 1: maxPlayers / 2 matches diff --git a/frontend/lib/tournament-contract.ts b/frontend/lib/tournament-contract.ts index 4c249f9..2bfffb3 100644 --- a/frontend/lib/tournament-contract.ts +++ b/frontend/lib/tournament-contract.ts @@ -69,6 +69,7 @@ export interface Tournament { startTime: number | null; winner: string | null; currentRound: number; + playerCount?: number; } export interface TournamentParticipant { @@ -342,6 +343,26 @@ export async function getAllTournaments(): Promise { } } +/** + * Get player count for a tournament by calculating from prize pool + * Prize pool = entry fee Ɨ number of players joined + */ +export async function getTournamentPlayerCount(tournamentId: number): Promise { + try { + const tournament = await getTournament(tournamentId); + if (!tournament || tournament.entryFee === 0) return 0; + + // Calculate player count from prize pool + // prizePool = entryFee * playerCount + const playerCount = Math.floor(tournament.prizePool / tournament.entryFee); + + return playerCount; + } catch (error) { + console.error(`Error calculating player count for tournament ${tournamentId}:`, error); + return 0; + } +} + /** * Clear tournament cache (useful after creating/joining/starting tournaments) */ @@ -352,3 +373,4 @@ export function clearTournamentCache(tournamentId?: number) { tournamentsCache.clear(); } } + From b3742fac43ebaf587e4cd4ec70b45912b6d0802a Mon Sep 17 00:00:00 2001 From: psychemist Date: Mon, 27 Oct 2025 18:10:36 +0100 Subject: [PATCH 17/24] feat: block new participants from joining tournament with full player list --- frontend/components/tournament-list.tsx | 10 ++++++++-- .../components/tournament-registration.tsx | 18 ++++++++++++++++-- 2 files changed, 24 insertions(+), 4 deletions(-) diff --git a/frontend/components/tournament-list.tsx b/frontend/components/tournament-list.tsx index 7a9b488..4665d41 100644 --- a/frontend/components/tournament-list.tsx +++ b/frontend/components/tournament-list.tsx @@ -41,8 +41,14 @@ interface TournamentCardProps { function TournamentCard({ tournament }: TournamentCardProps) { const getStatusBadge = (status: number) => { + // Check if tournament is full + const isFull = (tournament.playerCount || 0) >= tournament.maxPlayers; + switch (status) { case TOURNAMENT_STATUS.OPEN: + if (isFull) { + return 🟠 Full; + } return 🟢 Open; case TOURNAMENT_STATUS.IN_PROGRESS: return 🟔 In Progress; @@ -55,8 +61,8 @@ function TournamentCard({ tournament }: TournamentCardProps) { } }; - // Calculate player count (would need to track this separately or query contract) - const playerCount = 0; // Placeholder - needs implementation + // Use player count from tournament data (calculated from prize pool) + const playerCount = tournament.playerCount || 0; const progress = `${playerCount}/${tournament.maxPlayers}`; return ( diff --git a/frontend/components/tournament-registration.tsx b/frontend/components/tournament-registration.tsx index 1e2e94e..8a05eb7 100644 --- a/frontend/components/tournament-registration.tsx +++ b/frontend/components/tournament-registration.tsx @@ -21,7 +21,9 @@ export function TournamentRegistration({ const [isProcessing, setIsProcessing] = useState(false); const isCreator = userAddress === tournament.creator; - const canJoin = tournament.status === TOURNAMENT_STATUS.OPEN && !isParticipant && userAddress; + const playerCount = tournament.playerCount || 0; + const isFull = playerCount >= tournament.maxPlayers; + const canJoin = tournament.status === TOURNAMENT_STATUS.OPEN && !isParticipant && !isFull && userAddress; const canStart = tournament.status === TOURNAMENT_STATUS.OPEN && isCreator; const handleJoin = async () => { @@ -77,6 +79,18 @@ export function TournamentRegistration({ {isParticipant && (

āœ“ You are registered

+

+ Players: {playerCount}/{tournament.maxPlayers} +

+
+ )} + + {isFull && !isParticipant && tournament.status === TOURNAMENT_STATUS.OPEN && ( +
+

šŸ”’ Tournament Full

+

+ All {tournament.maxPlayers} slots have been filled +

)} @@ -103,7 +117,7 @@ export function TournamentRegistration({ {isCreator && !canStart && (

- Waiting for {tournament.maxPlayers} players to join + Waiting for {tournament.maxPlayers} players to join ({playerCount}/{tournament.maxPlayers})

)} From 587a465a57e60fb4a0752efde768564e52a5eae5 Mon Sep 17 00:00:00 2001 From: psychemist Date: Wed, 29 Oct 2025 13:52:42 +0100 Subject: [PATCH 18/24] feat: enhance tournament game functionality 1. add is-board-full private helper function 2. add logic for resolvig games that end in a tie 3. modify report-game-winner flow to prevent circular dependency --- contracts/tic-tac-toe-v2.clar | 252 ------------------- contracts/tic_tac_toe.clar | 456 ++++++++++++++++++++++++++++++++++ 2 files changed, 456 insertions(+), 252 deletions(-) delete mode 100644 contracts/tic-tac-toe-v2.clar create mode 100644 contracts/tic_tac_toe.clar diff --git a/contracts/tic-tac-toe-v2.clar b/contracts/tic-tac-toe-v2.clar deleted file mode 100644 index a9655fc..0000000 --- a/contracts/tic-tac-toe-v2.clar +++ /dev/null @@ -1,252 +0,0 @@ -(define-constant THIS_CONTRACT (as-contract tx-sender)) ;; The address of this contract itself -(define-constant ERR_MIN_BET_AMOUNT u100) ;; Error thrown when a player tries to create a game with a bet amount less than the minimum (0.0001 STX) -(define-constant ERR_INVALID_MOVE u101) ;; Error thrown when a move is invalid, i.e. not within range of the board or not an X or an O -(define-constant ERR_GAME_NOT_FOUND u102) ;; Error thrown when a game cannot be found given a Game ID, i.e. invalid Game ID -(define-constant ERR_GAME_CANNOT_BE_JOINED u103) ;; Error thrown when a game cannot be joined, usually because it already has two players -(define-constant ERR_NOT_YOUR_TURN u104) ;; Error thrown when a player tries to make a move when it is not their turn - -;; Implement the game-tournament-trait -(impl-trait .game-tournament-trait.game-tournament-trait) - -;; The Game ID to use for the next game -(define-data-var latest-game-id uint u0) - -;; tournament contract will be set after deployment -(define-data-var tournament-contract (optional principal) none) - -(define-map games - uint ;; Key (Game ID) - { ;; Value (Game Tuple) - player-one: principal, - player-two: (optional principal), - is-player-one-turn: bool, - - bet-amount: uint, - board: (list 9 uint), - - winner: (optional principal), - ;; Tournament integration - tournament-id: (optional uint), - tournament-round: (optional uint), - tournament-match: (optional uint) - } -) - -(define-public (create-game (bet-amount uint) (move-index uint) (move uint)) - (let ( - ;; Get the Game ID to use for creation of this new game - (game-id (var-get latest-game-id)) - ;; The initial starting board for the game with all cells empty - (starting-board (list u0 u0 u0 u0 u0 u0 u0 u0 u0)) - ;; Updated board with the starting move played by the game creator (X) - (game-board (unwrap! (replace-at? starting-board move-index move) (err ERR_INVALID_MOVE))) - ;; Create the game data tuple (player one address, bet amount, game board, and mark next turn to be player two's turn) - (game-data { - player-one: contract-caller, - player-two: none, - is-player-one-turn: false, - bet-amount: bet-amount, - board: game-board, - winner: none, - tournament-id: none, - tournament-round: none, - tournament-match: none - }) - ) - - ;; Ensure that user has put up a bet amount greater than the minimum - (asserts! (> bet-amount u0) (err ERR_MIN_BET_AMOUNT)) - ;; Ensure that the move being played is an `X`, not an `O` - (asserts! (is-eq move u1) (err ERR_INVALID_MOVE)) - ;; Ensure that the move meets validity requirements - (asserts! (validate-move starting-board move-index move) (err ERR_INVALID_MOVE)) - - ;; Transfer the bet amount STX from user to this contract - (try! (stx-transfer? bet-amount contract-caller THIS_CONTRACT)) - ;; Update the games map with the new game data - (map-set games game-id game-data) - ;; Increment the Game ID counter - (var-set latest-game-id (+ game-id u1)) - - ;; Log the creation of the new game - (print { action: "create-game", data: game-data}) - ;; Return the Game ID of the new game - (ok game-id) -)) - -(define-public (join-game (game-id uint) (move-index uint) (move uint)) - (let ( - ;; Load the game data for the game being joined, throw an error if Game ID is invalid - (original-game-data (unwrap! (map-get? games game-id) (err ERR_GAME_NOT_FOUND))) - ;; Get the original board from the game data - (original-board (get board original-game-data)) - - ;; Update the game board by placing the player's move at the specified index - (game-board (unwrap! (replace-at? original-board move-index move) (err ERR_INVALID_MOVE))) - ;; Update the copy of the game data with the updated board and marking the next turn to be player two's turn - (game-data (merge original-game-data { - board: game-board, - player-two: (some contract-caller), - is-player-one-turn: true - })) - ) - - ;; Ensure that the game being joined is able to be joined - ;; i.e. player-two is currently empty - (asserts! (is-none (get player-two original-game-data)) (err ERR_GAME_CANNOT_BE_JOINED)) - ;; Ensure that the move being played is an `O`, not an `X` - (asserts! (is-eq move u2) (err ERR_INVALID_MOVE)) - ;; Ensure that the move meets validity requirements - (asserts! (validate-move original-board move-index move) (err ERR_INVALID_MOVE)) - - ;; Transfer the bet amount STX from user to this contract - (try! (stx-transfer? (get bet-amount original-game-data) contract-caller THIS_CONTRACT)) - ;; Update the games map with the new game data - (map-set games game-id game-data) - - ;; Log the joining of the game - (print { action: "join-game", data: game-data}) - ;; Return the Game ID of the game - (ok game-id) -)) - -(define-public (play (game-id uint) (move-index uint) (move uint)) - (let ( - ;; Load the game data for the game being joined, throw an error if Game ID is invalid - (original-game-data (unwrap! (map-get? games game-id) (err ERR_GAME_NOT_FOUND))) - ;; Get the original board from the game data - (original-board (get board original-game-data)) - - ;; Is it player one's turn? - (is-player-one-turn (get is-player-one-turn original-game-data)) - ;; Get the player whose turn it currently is based on the is-player-one-turn flag - (player-turn (if is-player-one-turn (get player-one original-game-data) (unwrap! (get player-two original-game-data) (err ERR_GAME_NOT_FOUND)))) - ;; Get the expected move based on whose turn it is (X or O?) - (expected-move (if is-player-one-turn u1 u2)) - - ;; Update the game board by placing the player's move at the specified index - (game-board (unwrap! (replace-at? original-board move-index move) (err ERR_INVALID_MOVE))) - ;; Check if the game has been won now with this modified board - (is-now-winner (has-won game-board)) - ;; Merge the game data with the updated board and marking the next turn to be player two's turn - ;; Also mark the winner if the game has been won - (game-data (merge original-game-data { - board: game-board, - is-player-one-turn: (not is-player-one-turn), - winner: (if is-now-winner (some player-turn) none) - })) - ) - - ;; Ensure that the function is being called by the player whose turn it is - (asserts! (is-eq player-turn contract-caller) (err ERR_NOT_YOUR_TURN)) - ;; Ensure that the move being played is the correct move based on the current turn (X or O) - (asserts! (is-eq move expected-move) (err ERR_INVALID_MOVE)) - ;; Ensure that the move meets validity requirements - (asserts! (validate-move original-board move-index move) (err ERR_INVALID_MOVE)) - - ;; if the game has been won, transfer the (bet amount * 2 = both players bets) STX to the winner - (if is-now-winner (try! (as-contract (stx-transfer? (* u2 (get bet-amount game-data)) tx-sender player-turn))) false) - - ;; Update the games map with the new game data - (map-set games game-id game-data) - - ;; Log the action of a move being made - (print {action: "play", data: game-data}) - - ;; Return the Game ID of the game - (ok game-id) -)) - -(define-read-only (get-game (game-id uint)) - (map-get? games game-id) -) - -(define-read-only (get-latest-game-id) - (var-get latest-game-id) -) - -(define-private (validate-move (board (list 9 uint)) (move-index uint) (move uint)) - (let ( - ;; Validate that the move is being played within range of the board - (index-in-range (and (>= move-index u0) (< move-index u9))) - - ;; Validate that the move is either an X or an O - (x-or-o (or (is-eq move u1) (is-eq move u2))) - - ;; Validate that the cell the move is being played on is currently empty - (empty-spot (is-eq (unwrap! (element-at? board move-index) false) u0)) - ) - - ;; All three conditions must be true for the move to be valid - (and (is-eq index-in-range true) (is-eq x-or-o true) empty-spot) -)) - -;; Admin function to set the tournament contract address after deployment -(define-public (set-tournament-contract (contract principal)) - (begin - ;; In production, add authorization check here (e.g., only contract deployer can call this) - (var-set tournament-contract (some contract)) - (ok true) - ) -) - -;; create a game on behalf of the tournament contract with no bet transfers -(define-public (create-tournament-game (player-one principal) (player-two principal) (tid uint) (round uint) (match-num uint)) - (let ( - (game-id (var-get latest-game-id)) - (starting-board (list u0 u0 u0 u0 u0 u0 u0 u0 u0)) - (game-data { - player-one: player-one, - player-two: (some player-two), - is-player-one-turn: true, - bet-amount: u0, - board: starting-board, - winner: none, - tournament-id: (some tid), - tournament-round: (some round), - tournament-match: (some match-num) - }) - ) - - ;; assert caller is tournament contract - (asserts! (is-eq (some contract-caller) (var-get tournament-contract)) (err u999)) - - ;; update games mapping with tournament game - (map-set games game-id game-data) - (var-set latest-game-id (+ game-id u1)) - - (print { action: "create-tournament-game", game-id: game-id, tournament-id: tid, round: round, match: match-num }) - (ok game-id) - ) -) - -;; Given a board, return true if any possible three-in-a-row line has been completed -(define-private (has-won (board (list 9 uint))) - (or - (is-line board u0 u1 u2) ;; Row 1 - (is-line board u3 u4 u5) ;; Row 2 - (is-line board u6 u7 u8) ;; Row 3 - (is-line board u0 u3 u6) ;; Column 1 - (is-line board u1 u4 u7) ;; Column 2 - (is-line board u2 u5 u8) ;; Column 3 - (is-line board u0 u4 u8) ;; Left to Right Diagonal - (is-line board u2 u4 u6) ;; Right to Left Diagonal - ) -) - -;; Given a board and three cells to look at on the board -;; Return true if all three are not empty and are the same value (all X or all O) -;; Return false if any of the three is empty or a different value -(define-private (is-line (board (list 9 uint)) (a uint) (b uint) (c uint)) - (let ( - ;; Value of cell at index a - (a-val (unwrap! (element-at? board a) false)) - ;; Value of cell at index b - (b-val (unwrap! (element-at? board b) false)) - ;; Value of cell at index c - (c-val (unwrap! (element-at? board c) false)) - ) - - ;; a-val must equal b-val and must also equal c-val while not being empty (non-zero) - (and (is-eq a-val b-val) (is-eq a-val c-val) (not (is-eq a-val u0))) -)) \ No newline at end of file diff --git a/contracts/tic_tac_toe.clar b/contracts/tic_tac_toe.clar new file mode 100644 index 0000000..7b89263 --- /dev/null +++ b/contracts/tic_tac_toe.clar @@ -0,0 +1,456 @@ +;; Title: tic_tac_toe +;; Version: 4.0 +;; Summary: Onchain Tic Tac Toe game +;; Description: A simple on-chain Tic Tac Toe game with staking and tournament support + +;; Traits +;; Implement the game-tournament-trait +(impl-trait .game-tournament-trait.game-tournament-trait) + +;; Constants +(define-constant THIS_CONTRACT (as-contract tx-sender)) ;; The address of this contract itself +(define-constant ERR_MIN_BET_AMOUNT u100) ;; Error thrown when a player tries to create a game with a bet amount less than the minimum (0.0001 STX) +(define-constant ERR_INVALID_MOVE u101) ;; Error thrown when a move is invalid, i.e. not within range of the board or not an X or an O +(define-constant ERR_GAME_NOT_FOUND u102) ;; Error thrown when a game cannot be found given a Game ID, i.e. invalid Game ID +(define-constant ERR_GAME_CANNOT_BE_JOINED u103) ;; Error thrown when a game cannot be joined, usually because it already has two players +(define-constant ERR_NOT_YOUR_TURN u104) ;; Error thrown when a player tries to make a move when it is not their turn +(define-constant ERR_TOURNAMENT_ALREADY_SET u110) ;; Error thrown when tournament contract address is already set +(define-constant ERR_INVALID_CONTRACT_CALLER u111) ;; Error thrown when unauthorized contract tries to call create-tournament-game +(define-constant ERR_INVALID_PARAMS u112) ;; Error thrown when parameters provided to a function are invalid + +;; Data Vars +;; The Game ID to use for the next game +(define-data-var latest-game-id uint u0) + +;; tournament contract will be set after deployment +(define-data-var tournament-contract (optional principal) none) + +;; Data maps +(define-map games + uint ;; Key (Game ID) + { + ;; Value (Game Tuple) + player-one: principal, + player-two: (optional principal), + is-player-one-turn: bool, + bet-amount: uint, + board: (list 9 uint), + winner: (optional principal), + ;; Tournament integration + tournament-id: (optional uint), + tournament-round: (optional uint), + tournament-match: (optional uint), + } +) + +;; Public Functions +(define-public (create-game + (bet-amount uint) + (move-index uint) + (move uint) + ) + (let ( + ;; Get the Game ID to use for creation of this new game + (game-id (var-get latest-game-id)) + ;; The initial starting board for the game with all cells empty + (starting-board (list u0 u0 u0 u0 u0 u0 u0 u0 u0)) + ;; Updated board with the starting move played by the game creator (X) + (game-board (unwrap! (replace-at? starting-board move-index move) + (err ERR_INVALID_MOVE) + )) + ;; Create the game data tuple (player one address, bet amount, game board, and mark next turn to be player two's turn) + (game-data { + player-one: contract-caller, + player-two: none, + is-player-one-turn: false, + bet-amount: bet-amount, + board: game-board, + winner: none, + tournament-id: none, + tournament-round: none, + tournament-match: none, + }) + ) + ;; Ensure that user has put up a bet amount greater than the minimum + (asserts! (> bet-amount u0) (err ERR_MIN_BET_AMOUNT)) + ;; Ensure that the move being played is an `X`, not an `O` + (asserts! (is-eq move u1) (err ERR_INVALID_MOVE)) + ;; Ensure that the move meets validity requirements + (asserts! (validate-move starting-board move-index move) + (err ERR_INVALID_MOVE) + ) + + ;; Transfer the bet amount STX from user to this contract + (try! (stx-transfer? bet-amount contract-caller THIS_CONTRACT)) + ;; Update the games map with the new game data + (map-set games game-id game-data) + ;; Increment the Game ID counter + (var-set latest-game-id (+ game-id u1)) + + ;; Log the creation of the new game + (print { + action: "create-game", + data: game-data, + }) + ;; Return the Game ID of the new game + (ok game-id) + ) +) + +(define-public (join-game + (game-id uint) + (move-index uint) + (move uint) + ) + (let ( + ;; Load the game data for the game being joined, throw an error if Game ID is invalid + (original-game-data (unwrap! (map-get? games game-id) (err ERR_GAME_NOT_FOUND))) + ;; Get the original board from the game data + (original-board (get board original-game-data)) + ;; Update the game board by placing the player's move at the specified index + (game-board (unwrap! (replace-at? original-board move-index move) + (err ERR_INVALID_MOVE) + )) + ;; Update the copy of the game data with the updated board and marking the next turn to be player two's turn + (game-data (merge original-game-data { + board: game-board, + player-two: (some contract-caller), + is-player-one-turn: true, + })) + ) + ;; Ensure that the game being joined is able to be joined + ;; i.e. player-two is currently empty + (asserts! (is-none (get player-two original-game-data)) + (err ERR_GAME_CANNOT_BE_JOINED) + ) + ;; Ensure that the move being played is an `O`, not an `X` + (asserts! (is-eq move u2) (err ERR_INVALID_MOVE)) + ;; Ensure that the move meets validity requirements + (asserts! (validate-move original-board move-index move) + (err ERR_INVALID_MOVE) + ) + + ;; Transfer the bet amount STX from user to this contract + (try! (stx-transfer? (get bet-amount original-game-data) contract-caller + THIS_CONTRACT + )) + ;; Update the games map with the new game data + (map-set games game-id game-data) + + ;; Log the joining of the game + (print { + action: "join-game", + data: game-data, + }) + ;; Return the Game ID of the game + (ok game-id) + ) +) + +(define-public (play + (game-id uint) + (move-index uint) + (move uint) + ) + (let ( + ;; Load the game data for the game being joined, throw an error if Game ID is invalid + (original-game-data (unwrap! (map-get? games game-id) (err ERR_GAME_NOT_FOUND))) + ;; Get the original board from the game data + (original-board (get board original-game-data)) + ;; Is it player one's turn? + (is-player-one-turn (get is-player-one-turn original-game-data)) + ;; Get the player whose turn it currently is based on the is-player-one-turn flag + (player-turn (if is-player-one-turn + (get player-one original-game-data) + (unwrap! (get player-two original-game-data) + (err ERR_GAME_NOT_FOUND) + ) + )) + ;; Get the expected move based on whose turn it is (X or O?) + (expected-move (if is-player-one-turn + u1 + u2 + )) + ;; Update the game board by placing the player's move at the specified index + (game-board (unwrap! (replace-at? original-board move-index move) + (err ERR_INVALID_MOVE) + )) + ;; Check if the game has been won now with this modified board + (is-now-winner (has-won game-board)) + ;; Merge the game data with the updated board and marking the next turn to be player two's turn + ;; Also mark the winner if the game has been won + (game-data (merge original-game-data { + board: game-board, + is-player-one-turn: (not is-player-one-turn), + winner: (if is-now-winner + (some player-turn) + none + ), + })) + ) + ;; Ensure that the function is being called by the player whose turn it is + (asserts! (is-eq player-turn contract-caller) (err ERR_NOT_YOUR_TURN)) + ;; Ensure that the move being played is the correct move based on the current turn (X or O) + (asserts! (is-eq move expected-move) (err ERR_INVALID_MOVE)) + ;; Ensure that the move meets validity requirements + (asserts! (validate-move original-board move-index move) + (err ERR_INVALID_MOVE) + ) + ;; Check if board is now full (tie scenario) + (let ((is-tie (and (is-board-full game-board) (not is-now-winner)))) + ;; Handle winner based on game type (tournament vs regular) + (if is-now-winner + (begin + ;; Check if this is a tournament game + (match (get tournament-id game-data) + tid + (begin + ;; This is a tournament game - report winner to tournament contract + (let ( + (round (unwrap! (get tournament-round game-data) + (err ERR_INVALID_MOVE) + )) + (match-num (unwrap! (get tournament-match game-data) + (err ERR_INVALID_MOVE) + )) + ) + ;; Report winner to tournament contract + (try! (contract-call? .tic_tac_toe_tournament + report-game-winner tid round match-num + player-turn + )) + true + ) + ) + ;; Not a tournament game - transfer STX to winner + (try! (as-contract (stx-transfer? (* u2 (get bet-amount game-data)) + tx-sender player-turn + ))) + ) + ) + false + ) + ;; Handle tie scenario - automatically create rematch for tournament games + (if is-tie + (begin + (match (get tournament-id game-data) + tid + (begin + ;; This is a tournament game that ended in a tie - create rematch + (let ( + (round (unwrap! (get tournament-round game-data) + (err ERR_INVALID_MOVE) + )) + (match-num (unwrap! (get tournament-match game-data) + (err ERR_INVALID_MOVE) + )) + (p1 (get player-one game-data)) + (p2 (unwrap! (get player-two game-data) + (err ERR_GAME_NOT_FOUND) + )) + (new-game-id (var-get latest-game-id)) + (rematch-board (list u0 u0 u0 u0 u0 u0 u0 u0 u0)) + (rematch-data { + player-one: p1, + player-two: (some p2), + is-player-one-turn: true, + bet-amount: u0, + board: rematch-board, + winner: none, + tournament-id: (some tid), + tournament-round: (some round), + tournament-match: (some match-num), + }) + ) + ;; Create the rematch game + (map-set games new-game-id rematch-data) + (var-set latest-game-id (+ new-game-id u1)) + (print { + action: "tie-rematch-created", + old-game-id: game-id, + new-game-id: new-game-id, + tournament-id: tid, + round: round, + match: match-num, + }) + true + ) + ) + ;; Not a tournament game - just mark as tie (no rematch for regular games) + true + ) + ) + false + ) + ;; Update the games map with the new game data + (map-set games game-id game-data) + ;; Log the action of a move being made + (print { + action: "play", + data: game-data, + }) + + ;; Return the Game ID of the game + (ok game-id) + ) + ) +) + +;; create a game on behalf of the tournament contract with no bet transfers +(define-public (create-tournament-game + (player-one principal) + (player-two principal) + (tid uint) + (round uint) + (match-num uint) + ) + (let ( + (game-id (var-get latest-game-id)) + (starting-board (list u0 u0 u0 u0 u0 u0 u0 u0 u0)) + (game-data { + player-one: player-one, + player-two: (some player-two), + is-player-one-turn: true, + bet-amount: u0, + board: starting-board, + winner: none, + tournament-id: (some tid), + tournament-round: (some round), + tournament-match: (some match-num), + }) + ) + ;; assert caller is tournament contract + (asserts! (is-eq (some contract-caller) (var-get tournament-contract)) + (err ERR_INVALID_CONTRACT_CALLER) + ) + + ;; update games mapping with tournament game + (map-set games game-id game-data) + (var-set latest-game-id (+ game-id u1)) + + (print { + action: "create-tournament-game", + game-id: game-id, + tournament-id: tid, + round: round, + match: match-num, + }) + (ok game-id) + ) +) + +;; Admin function to set the tournament contract address after deployment +(define-public (set-tournament-contract (contract principal)) + (begin + ;; Allow setting the tournament contract only once. The deployment script (or deployer) should call this + ;; immediately after publishing both contracts. This prevents the tournament pointer from being changed later. + (asserts! (is-none (var-get tournament-contract)) + (err ERR_TOURNAMENT_ALREADY_SET) + ) + + ;; Additional validation: ensure the contract principal is valid (not empty) + (asserts! (is-some (some contract)) (err ERR_INVALID_PARAMS)) + + ;; set tournament contract in state + (var-set tournament-contract (some contract)) + + ;; Emit event for tracking + (print { + action: "set-tournament-contract", + contract: contract, + }) + (ok true) + ) +) + +;; Read-Only Functions +(define-read-only (get-game (game-id uint)) + (map-get? games game-id) +) + +(define-read-only (get-latest-game-id) + (var-get latest-game-id) +) + +;; Get the current tournament contract address (for verification) +(define-read-only (get-tournament-contract) + (var-get tournament-contract) +) + +;; Private Helper Functions +(define-private (validate-move + (board (list 9 uint)) + (move-index uint) + (move uint) + ) + (let ( + ;; Validate that the move is being played within range of the board + (index-in-range (and (>= move-index u0) (< move-index u9))) + ;; Validate that the move is either an X or an O + (x-or-o (or (is-eq move u1) (is-eq move u2))) + ;; Validate that the cell the move is being played on is currently empty + (empty-spot (is-eq (unwrap! (element-at? board move-index) false) u0)) + ) + ;; All three conditions must be true for the move to be valid + (and + (is-eq index-in-range true) + (is-eq x-or-o true) + empty-spot + ) + ) +) + +;; Given a board, return true if any possible three-in-a-row line has been completed +(define-private (has-won (board (list 9 uint))) + (or + (is-line board u0 u1 u2) ;; Row 1 + (is-line board u3 u4 u5) ;; Row 2 + (is-line board u6 u7 u8) ;; Row 3 + (is-line board u0 u3 u6) ;; Column 1 + (is-line board u1 u4 u7) ;; Column 2 + (is-line board u2 u5 u8) ;; Column 3 + (is-line board u0 u4 u8) ;; Left to Right Diagonal + (is-line board u2 u4 u6) ;; Right to Left Diagonal + ) +) + +;; Check if the board is full (all 9 spaces filled) - indicates a tie if no winner +(define-private (is-board-full (board (list 9 uint))) + (and + (not (is-eq (unwrap! (element-at? board u0) false) u0)) + (not (is-eq (unwrap! (element-at? board u1) false) u0)) + (not (is-eq (unwrap! (element-at? board u2) false) u0)) + (not (is-eq (unwrap! (element-at? board u3) false) u0)) + (not (is-eq (unwrap! (element-at? board u4) false) u0)) + (not (is-eq (unwrap! (element-at? board u5) false) u0)) + (not (is-eq (unwrap! (element-at? board u6) false) u0)) + (not (is-eq (unwrap! (element-at? board u7) false) u0)) + (not (is-eq (unwrap! (element-at? board u8) false) u0)) + ) +) + +;; Given a board and three cells to look at on the board +;; Return true if all three are not empty and are the same value (all X or all O) +;; Return false if any of the three is empty or a different value +(define-private (is-line + (board (list 9 uint)) + (a uint) + (b uint) + (c uint) + ) + (let ( + ;; Value of cell at index a + (a-val (unwrap! (element-at? board a) false)) + ;; Value of cell at index b + (b-val (unwrap! (element-at? board b) false)) + ;; Value of cell at index c + (c-val (unwrap! (element-at? board c) false)) + ) + ;; a-val must equal b-val and must also equal c-val while not being empty (non-zero) + (and + (is-eq a-val b-val) + (is-eq a-val c-val) + (not (is-eq a-val u0)) + ) + ) +) From 62d02559338c8c744fc29c97614ae0778c532fdd Mon Sep 17 00:00:00 2001 From: psychemist Date: Wed, 29 Oct 2025 13:57:33 +0100 Subject: [PATCH 19/24] feat: upgrade tournament contract 1. add automatic hook for reporting winner of a tournament game 2. handle creating matches in each round programmatically 3. add logic for advancing every tournament bracket 4. pay out STX proze pool to tournament winner --- contracts/tic-tac-toe-tournament-v2.clar | 317 -------- contracts/tic_tac_toe_tournament.clar | 917 +++++++++++++++++++++++ 2 files changed, 917 insertions(+), 317 deletions(-) delete mode 100644 contracts/tic-tac-toe-tournament-v2.clar create mode 100644 contracts/tic_tac_toe_tournament.clar diff --git a/contracts/tic-tac-toe-tournament-v2.clar b/contracts/tic-tac-toe-tournament-v2.clar deleted file mode 100644 index 1e08c4b..0000000 --- a/contracts/tic-tac-toe-tournament-v2.clar +++ /dev/null @@ -1,317 +0,0 @@ -;; ERRORS -(define-constant ERR_UNAUTHORIZED u200) -(define-constant ERR_INVALID_PARAMS u201) -(define-constant ERR_TOURNAMENT_NOT_FOUND u202) -(define-constant ERR_TOURNAMENT_NOT_OPEN u203) -(define-constant ERR_TOURNAMENT_FULL u204) -(define-constant ERR_ALREADY_REGISTERED u205) -(define-constant ERR_NOT_CREATOR u206) -(define-constant ERR_ALREADY_STARTED u207) -(define-constant ERR_NOT_IN_PROGRESS u208) -(define-constant ERR_INVALID_WINNER u209) -(define-constant ERR_MATCH_NOT_FOUND u210) -(define-constant ERR_MATCH_ALREADY_COMPLETED u211) - - -;; TRAITS -(use-trait game-tournament-trait .game-tournament-trait.game-tournament-trait) - - -;; CONSTANTS - -(define-constant THIS_CONTRACT (as-contract tx-sender)) - -;; status enum : 0=open, 1=in-progress, 2=completed, 3=cancelled -(define-constant STATUS_OPEN u0) -(define-constant STATUS_IN_PROGRESS u1) -(define-constant STATUS_COMPLETED u2) -(define-constant STATUS_CANCELLED u3) - - -;; GLOBAL VARIABLES -(define-data-var latest-tournament-id uint u0) - - -;; MAPPINGS - -;; tournaments: id -> metadata -(define-map tournaments - uint - { - creator: principal, - name: (string-utf8 50), - entry-fee: uint, - max-players: uint, - prize-pool: uint, - status: uint, - start-time: (optional uint), - winner: (optional principal), - current-round: uint - } -) - -;; player registry and ordering per tournament -(define-map tournament-participants - { tournament-id: uint, player: principal } - { registration-time: uint, bracket-position: uint, eliminated: bool } -) - -;; quick lookup by index (1-based) -(define-map tournament-player-index - { tournament-id: uint, index: uint } - principal -) - -;; count of players joined per tournament -(define-map tournament-player-count - uint ;; tournament-id - uint ;; count -) - -;; matches per round -(define-map tournament-matches - { tournament-id: uint, round: uint, match-number: uint } - { game-id: (optional uint), player1: (optional principal), player2: (optional principal), winner: (optional principal), completed: bool } -) - - -;; PUBLIC FUNCTIONS - -;; Create a new tournament, returns tournament-id -(define-public (create-tournament (name (string-utf8 50)) (entry-fee uint) (max-players uint)) - (begin - - ;; assert conditions for successful creation of tournament - (asserts! (> entry-fee u0) (err ERR_INVALID_PARAMS)) - (asserts! (is-power-of-two max-players) (err ERR_INVALID_PARAMS)) - - ;; create new tournament - (let ( - (tid (var-get latest-tournament-id)) - (meta { - creator: tx-sender, - name: name, - entry-fee: entry-fee, - max-players: max-players, - prize-pool: u0, - status: STATUS_OPEN, - start-time: none, - winner: none, - current-round: u0 - }) - ) - ;; update mappings in state with new values - (map-set tournaments tid meta) - (set-player-count tid u0) - (var-set latest-tournament-id (+ tid u1)) - - ;; emit event and return new tournament id - (print { action: "create-tournament", id: tid, data: meta }) - (ok tid) - ) - ) -) - -;; Join an open tournament: transfers entry fee and assigns bracket position -(define-public (join-tournament (tid uint)) - ;; fetch tournament data and player count - (let ( - (meta (try! (get-tournament-or-err tid))) - (count (get-player-count tid)) - ) - - ;; assert conditions for successful entry into tournament - (asserts! (is-eq (get status meta) STATUS_OPEN) (err ERR_TOURNAMENT_NOT_OPEN)) - (asserts! (< count (get max-players meta)) (err ERR_TOURNAMENT_FULL)) - (asserts! (is-none (map-get? tournament-participants { tournament-id: tid, player: tx-sender })) (err ERR_ALREADY_REGISTERED)) - - ;; transfer entry fee into this contract to build prize pool - (try! (stx-transfer? (get entry-fee meta) tx-sender THIS_CONTRACT)) - - ;; create participant record, add player entry into pool, assign player to next bracket position - (let ( - (new-index (+ count u1)) - (participant { registration-time: u0, bracket-position: new-index, eliminated: false }) - (updated-meta (merge meta { prize-pool: (+ (get prize-pool meta) (get entry-fee meta)) })) - ) - - ;; save player's registration; save update tournament meta to mapping - (map-set tournament-participants { tournament-id: tid, player: tx-sender } participant) - (set-player-at tid new-index tx-sender) - (set-player-count tid new-index) - (map-set tournaments tid updated-meta) - - ;; emit event for tracking; return new player's bracket position - (print { action: "join-tournament", id: tid, player: tx-sender, position: new-index }) - (ok new-index) - ) - ) -) - -;; Start the tournament: only creator, only when full. Creates round 1 matches automatically. -(define-public (start-tournament (tid uint) (game-contract )) - ;; fetch tournament metdata - (let ( - (meta (try! (get-tournament-or-err tid))) - (count (get-player-count tid)) - ) - - ;; assert tournament is open and ready to start by creator - (asserts! (is-eq (get creator meta) tx-sender) (err ERR_NOT_CREATOR)) - (asserts! (is-eq (get status meta) STATUS_OPEN) (err ERR_TOURNAMENT_NOT_OPEN)) - (asserts! (is-eq count (get max-players meta)) (err ERR_TOURNAMENT_FULL)) - - ;; start tournament with updated values - (let ( - (round u1) - (started-meta (merge meta { status: STATUS_IN_PROGRESS, start-time: (some stacks-block-height), current-round: round })) - ) - ;; update tournament mapping - (map-set tournaments tid started-meta) - - ;; create round 1 matches (pair adjacent players: 1v2, 3v4, etc.) - (unwrap-panic (create-round-one-matches tid count game-contract)) - - ;; emit event; return tourname nt id - (print { action: "start-tournament", id: tid, round: round }) - (ok tid) - ) - ) -) - - -;; automatically trigger trustless match result reporting when a tournament game finishes -(define-public (report-game-winner (tid uint) (round uint) (match-number uint) (winner principal)) - ;; fetch tournament data - (let ( - (meta (try! (get-tournament-or-err tid))) - (key { tournament-id: tid, round: round, match-number: match-number }) - (match-data (unwrap! (map-get? tournament-matches key) (err ERR_MATCH_NOT_FOUND))) - ) - - ;; assert caller is tic-tac-toe contract and verify match details - (asserts! (is-eq contract-caller .tic-tac-toe-v2) (err ERR_UNAUTHORIZED)) - (asserts! (is-eq (get status meta) STATUS_IN_PROGRESS) (err ERR_NOT_IN_PROGRESS)) - (asserts! (is-eq (get completed match-data) false) (err ERR_MATCH_ALREADY_COMPLETED)) - - ;; fetch match players - (let ( - (p1 (unwrap! (get player1 match-data) (err ERR_INVALID_PARAMS))) - (p2 (unwrap! (get player2 match-data) (err ERR_INVALID_PARAMS))) - ) - ;; verify winner is one of the two players - (asserts! (or (is-eq winner p1) (is-eq winner p2)) (err ERR_INVALID_WINNER)) - - ;; update match with winner and mark as completed - (map-set tournament-matches key (merge match-data { winner: (some winner), completed: true })) - - ;; emit event; return true - (print { action: "match-complete", id: tid, round: round, match: match-number, winner: winner }) - (ok true) - ) - ) -) - - -;; PRIVATE HELPER FUNCTIONS - -;; Create round 1 matches by pairing adjacent players and creating tic-tac-toe games -(define-private (create-round-one-matches (tid uint) (player-count uint) (game-contract )) - (begin - ;; For a 4-player tournament: create 2 matches (1v2, 3v4) - ;; For an 8-player tournament: create 4 matches (1v2, 3v4, 5v6, 7v8), etc. - (and (>= player-count u2) (is-ok (create-match tid u1 u1 u1 u2 game-contract))) - (and (>= player-count u4) (is-ok (create-match tid u1 u2 u3 u4 game-contract))) - (and (>= player-count u6) (is-ok (create-match tid u1 u3 u5 u6 game-contract))) - (and (>= player-count u8) (is-ok (create-match tid u1 u4 u7 u8 game-contract))) - (and (>= player-count u10) (is-ok (create-match tid u1 u5 u9 u10 game-contract))) - (and (>= player-count u12) (is-ok (create-match tid u1 u6 u11 u12 game-contract))) - (and (>= player-count u14) (is-ok (create-match tid u1 u7 u13 u14 game-contract))) - (and (>= player-count u16) (is-ok (create-match tid u1 u8 u15 u16 game-contract))) - (and (>= player-count u18) (is-ok (create-match tid u1 u9 u17 u18 game-contract))) - (and (>= player-count u20) (is-ok (create-match tid u1 u10 u19 u20 game-contract))) - (and (>= player-count u22) (is-ok (create-match tid u1 u11 u21 u22 game-contract))) - (and (>= player-count u24) (is-ok (create-match tid u1 u12 u23 u24 game-contract))) - (and (>= player-count u26) (is-ok (create-match tid u1 u13 u25 u26 game-contract))) - (and (>= player-count u28) (is-ok (create-match tid u1 u14 u27 u28 game-contract))) - (and (>= player-count u30) (is-ok (create-match tid u1 u15 u29 u30 game-contract))) - (and (>= player-count u32) (is-ok (create-match tid u1 u16 u31 u32 game-contract))) - (ok true) - ) -) - -;; Create a single match: look up players by position, create game, store match -(define-private (create-match (tid uint) (round uint) (match-num uint) (p1-pos uint) (p2-pos uint) (game-contract )) - ;; fetch player details and game id - (let ( - (p1 (unwrap! (get-player-at tid p1-pos) (err ERR_INVALID_PARAMS))) - (p2 (unwrap! (get-player-at tid p2-pos) (err ERR_INVALID_PARAMS))) - (game-id (try! (contract-call? game-contract create-tournament-game p1 p2 tid round match-num))) - ) - - ;; update tournament mapping with player values - (map-set tournament-matches - { tournament-id: tid, round: round, match-number: match-num } - { game-id: (some game-id), player1: (some p1), player2: (some p2), winner: none, completed: false }) - - ;; emit create match event; return game id - (print { action: "create-match", tournament: tid, round: round, match: match-num, game: game-id, p1: p1, p2: p2 }) - (ok game-id) - ) -) - -;; validates that n (no of players in tournament) is one of: 4, 8, 16, or 32 -(define-private (is-power-of-two (n uint)) - (or (is-eq n u4) (is-eq n u8) (is-eq n u16) (is-eq n u32)) -) - -;; fetches player count for a tournament -(define-private (get-player-count (tid uint)) - (default-to u0 (map-get? tournament-player-count tid)) -) - -;; updates the player count for a tournament -(define-private (set-player-count (tid uint) (n uint)) - (map-set tournament-player-count tid n) -) - -;; looks up which player is at a specific bracket position -(define-private (get-player-at (tid uint) (idx uint)) - (map-get? tournament-player-index { tournament-id: tid, index: idx }) -) - -;; assigns a player to a specific bracket position -(define-private (set-player-at (tid uint) (idx uint) (p principal)) - (map-set tournament-player-index { tournament-id: tid, index: idx } p) -) - -;; attempts to fetch tournament metadata -(define-private (get-tournament-or-err (tid uint)) - (match (map-get? tournaments tid) - meta (ok meta) - (err ERR_TOURNAMENT_NOT_FOUND) - ) -) - - -;; READ-ONLY FUNCTIONS - -;; get the latest tournament id (for fetching all tournaments easily) -(define-read-only (get-latest-tournament-id) - (var-get latest-tournament-id) -) - -;; public getter for tournament metadata -(define-read-only (get-tournament (tid uint)) - (map-get? tournaments tid) -) - -;; checks if a player is registered in a tournament -(define-read-only (get-participant (tid uint) (p principal)) - (map-get? tournament-participants { tournament-id: tid, player: p }) -) - -;; fetches details about a specific match -(define-read-only (get-match (tid uint) (round uint) (match-number uint)) - (map-get? tournament-matches { tournament-id: tid, round: round, match-number: match-number }) -) diff --git a/contracts/tic_tac_toe_tournament.clar b/contracts/tic_tac_toe_tournament.clar new file mode 100644 index 0000000..97c81f6 --- /dev/null +++ b/contracts/tic_tac_toe_tournament.clar @@ -0,0 +1,917 @@ +;; Title: tic_tac_toe_tournament +;; Version: 4.0 +;; Summary: Tic Tac Toe Tournament Contract +;; Description: Manages tournament gameplay for on-chain Tic Tac Toe games + +;; TRAITS +(use-trait game-tournament-trait .game-tournament-trait.game-tournament-trait) + +;; CONSTANTS +(define-constant THIS_CONTRACT (as-contract tx-sender)) +;; errors +(define-constant ERR_UNAUTHORIZED u200) +(define-constant ERR_INVALID_PARAMS u201) +(define-constant ERR_TOURNAMENT_NOT_FOUND u202) +(define-constant ERR_TOURNAMENT_NOT_OPEN u203) +(define-constant ERR_TOURNAMENT_FULL u204) +(define-constant ERR_ALREADY_REGISTERED u205) +(define-constant ERR_NOT_CREATOR u206) +(define-constant ERR_ALREADY_STARTED u207) +(define-constant ERR_NOT_IN_PROGRESS u208) +(define-constant ERR_INVALID_WINNER u209) +(define-constant ERR_MATCH_NOT_FOUND u210) +(define-constant ERR_MATCH_ALREADY_COMPLETED u211) +;; status enum : 0=open, 1=in-progress, 2=completed, 3=cancelled +(define-constant STATUS_OPEN u0) +(define-constant STATUS_IN_PROGRESS u1) +(define-constant STATUS_COMPLETED u2) +(define-constant STATUS_CANCELLED u3) + +;; GLOBAL VARIABLES +(define-data-var latest-tournament-id uint u0) + +;; MAPPINGS + +;; tournaments: id -> metadata +(define-map tournaments + uint + { + creator: principal, + name: (string-utf8 50), + entry-fee: uint, + max-players: uint, + prize-pool: uint, + status: uint, + start-time: (optional uint), + winner: (optional principal), + current-round: uint, + } +) + +;; player registry and ordering per tournament +(define-map tournament-participants + { + tournament-id: uint, + player: principal, + } + { + registration-time: uint, + bracket-position: uint, + eliminated: bool, + } +) + +;; quick lookup by index (1-based) +(define-map tournament-player-index + { + tournament-id: uint, + index: uint, + } + principal +) + +;; count of players joined per tournament +(define-map tournament-player-count + uint ;; tournament-id + uint ;; count +) + +;; matches per round +(define-map tournament-matches + { + tournament-id: uint, + round: uint, + match-number: uint, + } + { + game-id: (optional uint), + player1: (optional principal), + player2: (optional principal), + winner: (optional principal), + completed: bool, + } +) + +;; PUBLIC FUNCTIONS + +;; Create a new tournament, returns tournament-id +(define-public (create-tournament + (name (string-utf8 50)) + (entry-fee uint) + (max-players uint) + ) + (begin + ;; assert conditions for successful creation of tournament + (asserts! (> entry-fee u0) (err ERR_INVALID_PARAMS)) + (asserts! (is-power-of-two max-players) (err ERR_INVALID_PARAMS)) + + ;; create new tournament + (let ( + (tid (var-get latest-tournament-id)) + (meta { + creator: tx-sender, + name: name, + entry-fee: entry-fee, + max-players: max-players, + prize-pool: u0, + status: STATUS_OPEN, + start-time: none, + winner: none, + current-round: u0, + }) + ) + ;; update mappings in state with new values + (map-set tournaments tid meta) + (set-player-count tid u0) + (var-set latest-tournament-id (+ tid u1)) + + ;; emit event and return new tournament id + (print { + action: "create-tournament", + id: tid, + data: meta, + }) + (ok tid) + ) + ) +) + +;; Join an open tournament: transfers entry fee and assigns bracket position +(define-public (join-tournament (tid uint)) + ;; fetch tournament data and player count + (let ( + (meta (try! (get-tournament-or-err tid))) + (count (get-player-count tid)) + ) + ;; assert conditions for successful entry into tournament + (asserts! (is-eq (get status meta) STATUS_OPEN) + (err ERR_TOURNAMENT_NOT_OPEN) + ) + (asserts! (< count (get max-players meta)) (err ERR_TOURNAMENT_FULL)) + (asserts! + (is-none (map-get? tournament-participants { + tournament-id: tid, + player: tx-sender, + })) + (err ERR_ALREADY_REGISTERED) + ) + + ;; transfer entry fee into this contract to build prize pool + (try! (stx-transfer? (get entry-fee meta) tx-sender THIS_CONTRACT)) + + ;; create participant record, add player entry into pool, assign player to next bracket position + (let ( + (new-index (+ count u1)) + (participant { + registration-time: u0, + bracket-position: new-index, + eliminated: false, + }) + (updated-meta (merge meta { prize-pool: (+ (get prize-pool meta) (get entry-fee meta)) })) + ) + ;; save player's registration; save update tournament meta to mapping + (map-set tournament-participants { + tournament-id: tid, + player: tx-sender, + } + participant + ) + (set-player-at tid new-index tx-sender) + (set-player-count tid new-index) + (map-set tournaments tid updated-meta) + + ;; emit event for tracking; return new player's bracket position + (print { + action: "join-tournament", + id: tid, + player: tx-sender, + position: new-index, + }) + (ok new-index) + ) + ) +) + +;; Start the tournament: only creator, only when full. Creates round 1 matches automatically. +(define-public (start-tournament + (tid uint) + (game-contract ) + ) + ;; fetch tournament metdata + (let ( + (meta (try! (get-tournament-or-err tid))) + (count (get-player-count tid)) + ) + ;; assert tournament is open and ready to start by creator + (asserts! (is-eq (get creator meta) tx-sender) (err ERR_NOT_CREATOR)) + (asserts! (is-eq (get status meta) STATUS_OPEN) + (err ERR_TOURNAMENT_NOT_OPEN) + ) + (asserts! (is-eq count (get max-players meta)) (err ERR_TOURNAMENT_FULL)) + + ;; start tournament with updated values + (let ( + (round u1) + (started-meta (merge meta { + status: STATUS_IN_PROGRESS, + start-time: (some stacks-block-height), + current-round: round, + })) + ) + ;; update tournament mapping + (map-set tournaments tid started-meta) + + ;; create round 1 matches (pair adjacent players: 1v2, 3v4, etc.) + (unwrap-panic (create-round-one-matches tid count game-contract)) + + ;; emit event; return tourname nt id + (print { + action: "start-tournament", + id: tid, + round: round, + }) + (ok tid) + ) + ) +) + +;; automatically trigger trustless match result reporting when a tournament game finishes +(define-public (report-game-winner + (tid uint) + (round uint) + (match-number uint) + (winner principal) + ) + ;; fetch tournament data + (let ( + (meta (try! (get-tournament-or-err tid))) + (key { + tournament-id: tid, + round: round, + match-number: match-number, + }) + (match-data (unwrap! (map-get? tournament-matches key) (err ERR_MATCH_NOT_FOUND))) + ) + ;; assert caller is tic-tac-toe contract and verify match details + (asserts! (is-eq contract-caller .tic_tac_toe) (err ERR_UNAUTHORIZED)) + (asserts! (is-eq (get status meta) STATUS_IN_PROGRESS) + (err ERR_NOT_IN_PROGRESS) + ) + (asserts! (is-eq (get completed match-data) false) + (err ERR_MATCH_ALREADY_COMPLETED) + ) + + ;; fetch match players + (let ( + (p1 (unwrap! (get player1 match-data) (err ERR_INVALID_PARAMS))) + (p2 (unwrap! (get player2 match-data) (err ERR_INVALID_PARAMS))) + ) + ;; verify winner is one of the two players + (asserts! (or (is-eq winner p1) (is-eq winner p2)) + (err ERR_INVALID_WINNER) + ) + + ;; update match with winner and mark as completed + (map-set tournament-matches key + (merge match-data { + winner: (some winner), + completed: true, + }) + ) + + ;; emit event + (print { + action: "match-complete", + id: tid, + round: round, + match: match-number, + winner: winner, + }) + + (ok true) + ) + ) +) + +;; PRIVATE HELPER FUNCTIONS + +;; Create round 1 matches by pairing adjacent players and creating tic-tac-toe games +(define-private (create-round-one-matches + (tid uint) + (player-count uint) + (game-contract ) + ) + (begin + ;; For a 4-player tournament: create 2 matches (1v2, 3v4) + ;; For an 8-player tournament: create 4 matches (1v2, 3v4, 5v6, 7v8), etc. + (and (>= player-count u2) (is-ok (create-match tid u1 u1 u1 u2 game-contract))) + (and (>= player-count u4) (is-ok (create-match tid u1 u2 u3 u4 game-contract))) + (and (>= player-count u6) (is-ok (create-match tid u1 u3 u5 u6 game-contract))) + (and (>= player-count u8) (is-ok (create-match tid u1 u4 u7 u8 game-contract))) + (and (>= player-count u10) (is-ok (create-match tid u1 u5 u9 u10 game-contract))) + (and (>= player-count u12) (is-ok (create-match tid u1 u6 u11 u12 game-contract))) + (and (>= player-count u14) (is-ok (create-match tid u1 u7 u13 u14 game-contract))) + (and (>= player-count u16) (is-ok (create-match tid u1 u8 u15 u16 game-contract))) + (and (>= player-count u18) (is-ok (create-match tid u1 u9 u17 u18 game-contract))) + (and (>= player-count u20) (is-ok (create-match tid u1 u10 u19 u20 game-contract))) + (and (>= player-count u22) (is-ok (create-match tid u1 u11 u21 u22 game-contract))) + (and (>= player-count u24) (is-ok (create-match tid u1 u12 u23 u24 game-contract))) + (and (>= player-count u26) (is-ok (create-match tid u1 u13 u25 u26 game-contract))) + (and (>= player-count u28) (is-ok (create-match tid u1 u14 u27 u28 game-contract))) + (and (>= player-count u30) (is-ok (create-match tid u1 u15 u29 u30 game-contract))) + (and (>= player-count u32) (is-ok (create-match tid u1 u16 u31 u32 game-contract))) + (ok true) + ) +) + +;; Create a single match: look up players by position, create game, store match +(define-private (create-match + (tid uint) + (round uint) + (match-num uint) + (p1-pos uint) + (p2-pos uint) + (game-contract ) + ) + ;; fetch player details and game id + (let ( + (p1 (unwrap! (get-player-at tid p1-pos) (err ERR_INVALID_PARAMS))) + (p2 (unwrap! (get-player-at tid p2-pos) (err ERR_INVALID_PARAMS))) + (game-id (try! (contract-call? game-contract create-tournament-game p1 p2 tid round + match-num + ))) + ) + ;; update tournament mapping with player values + (map-set tournament-matches { + tournament-id: tid, + round: round, + match-number: match-num, + } { + game-id: (some game-id), + player1: (some p1), + player2: (some p2), + winner: none, + completed: false, + }) + + ;; emit create match event; return game id + (print { + action: "create-match", + tournament: tid, + round: round, + match: match-num, + game: game-id, + p1: p1, + p2: p2, + }) + (ok game-id) + ) +) + +;; number of matches in a given round based on player count (supports 4, 8, 16, 32) +(define-private (round-match-count + (player-count uint) + (round uint) + ) + ;; determine match count based on round and player count + (let ( + (r1 (if (>= player-count u32) + u16 + (if (>= player-count u16) + u8 + (if (>= player-count u8) + u4 + u2 + ) + ) + )) + (r2 (if (>= player-count u32) + u8 + (if (>= player-count u16) + u4 + (if (>= player-count u8) + u2 + u1 + ) + ) + )) + (r3 (if (>= player-count u32) + u4 + (if (>= player-count u16) + u2 + (if (>= player-count u8) + u1 + u0 + ) + ) + )) + (r4 (if (>= player-count u32) + u2 + (if (>= player-count u16) + u1 + u0 + ) + )) + (r5 (if (>= player-count u32) + u1 + u0 + )) + ) + (if (is-eq round u1) + r1 + (if (is-eq round u2) + r2 + (if (is-eq round u3) + r3 + (if (is-eq round u4) + r4 + r5 + ) + ) + ) + ) + ) +) + +;; check that every match 1..count in a round is completed (unrolled up to 16 matches) +(define-private (all-round-matches-complete + (tid uint) + (round uint) + (count uint) + ) + ;; unroll checks for matches 1..count + (let ((c count)) + (and + (or (<= c u0) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u1, + }) + ))) + (or (<= c u1) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u2, + }) + ))) + (or (<= c u2) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u3, + }) + ))) + (or (<= c u3) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u4, + }) + ))) + (or (<= c u4) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u5, + }) + ))) + (or (<= c u5) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u6, + }) + ))) + (or (<= c u6) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u7, + }) + ))) + (or (<= c u7) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u8, + }) + ))) + (or (<= c u8) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u9, + }) + ))) + (or (<= c u9) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u10, + }) + ))) + (or (<= c u10) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u11, + }) + ))) + (or (<= c u11) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u12, + }) + ))) + (or (<= c u12) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u13, + }) + ))) + (or (<= c u13) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u14, + }) + ))) + (or (<= c u14) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u15, + }) + ))) + (or (<= c u15) (get completed + (default-to { completed: false } + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u16, + }) + ))) + ) + ) +) + +;; advance if round complete; if final complete, set winner and completed +(define-public (advance-round-if-complete + (tid uint) + (round uint) + (game-contract ) + ) + ;; fetch player count and match count for round + (let ( + (player-count (get-player-count tid)) + (match-count (round-match-count player-count round)) + ) + ;; if all matches complete, either create next round or complete tournament + (if (and (> match-count u0) (all-round-matches-complete tid round match-count)) + ;; check if this was the final match + (if (is-eq match-count u1) + ;; tournament complete: fetch final winner and update tournament meta + (let ( + (final (unwrap! + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: u1, + }) + (err ERR_MATCH_NOT_FOUND) + )) + (final-winner (unwrap! (get winner final) (err ERR_INVALID_PARAMS))) + (meta (unwrap-panic (get-tournament-or-err tid))) + (completed-meta (merge meta { + status: STATUS_COMPLETED, + winner: (some final-winner), + })) + ) + + ;; transfer prize pool to winner + (try! (stx-transfer? (get prize-pool meta) final-winner THIS_CONTRACT)) + + ;; update tournament mapping + (map-set tournaments tid completed-meta) + + ;; emit tournament complete event + (print { + action: "tournament-complete", + id: tid, + winner: final-winner, + }) + + (ok true) + ) + + ;; otherwise, create next round + (create-next-round tid round (+ round u1) match-count + game-contract + ) + ) + + (ok true) + ) + ) +) + +;; create next round matches from winners of current round +(define-private (create-next-round + (tid uint) + (current-round uint) + (next-round uint) + (match-count uint) + (game-contract ) + ) + (begin + ;; fetch tournament metadata + (map-set tournaments tid + (merge (unwrap-panic (get-tournament-or-err tid)) { current-round: next-round }) + ) + + ;; unroll match creation for next round based on current round match count + (if (>= match-count u2) + (let ( + (w1 (unwrap! + (get winner + (unwrap! + (map-get? tournament-matches { + tournament-id: tid, + round: current-round, + match-number: u1, + }) + (err ERR_MATCH_NOT_FOUND) + )) + (err ERR_INVALID_PARAMS) + )) + (w2 (unwrap! + (get winner + (unwrap! + (map-get? tournament-matches { + tournament-id: tid, + round: current-round, + match-number: u2, + }) + (err ERR_MATCH_NOT_FOUND) + )) + (err ERR_INVALID_PARAMS) + )) + ) + (try! (create-next-round-match tid next-round u1 w1 w2 game-contract)) + true + ) + true + ) + + (if (>= match-count u4) + (let ( + (w3 (unwrap! + (get winner + (unwrap! + (map-get? tournament-matches { + tournament-id: tid, + round: current-round, + match-number: u3, + }) + (err ERR_MATCH_NOT_FOUND) + )) + (err ERR_INVALID_PARAMS) + )) + (w4 (unwrap! + (get winner + (unwrap! + (map-get? tournament-matches { + tournament-id: tid, + round: current-round, + match-number: u4, + }) + (err ERR_MATCH_NOT_FOUND) + )) + (err ERR_INVALID_PARAMS) + )) + ) + (try! (create-next-round-match tid next-round u2 w3 w4 game-contract)) + true + ) + true + ) + + (if (>= match-count u6) + (let ( + (w5 (unwrap! + (get winner + (unwrap! + (map-get? tournament-matches { + tournament-id: tid, + round: current-round, + match-number: u5, + }) + (err ERR_MATCH_NOT_FOUND) + )) + (err ERR_INVALID_PARAMS) + )) + (w6 (unwrap! + (get winner + (unwrap! + (map-get? tournament-matches { + tournament-id: tid, + round: current-round, + match-number: u6, + }) + (err ERR_MATCH_NOT_FOUND) + )) + (err ERR_INVALID_PARAMS) + )) + ) + (try! (create-next-round-match tid next-round u3 w5 w6 game-contract)) + true + ) + true + ) + + (if (>= match-count u8) + (let ( + (w7 (unwrap! + (get winner + (unwrap! + (map-get? tournament-matches { + tournament-id: tid, + round: current-round, + match-number: u7, + }) + (err ERR_MATCH_NOT_FOUND) + )) + (err ERR_INVALID_PARAMS) + )) + (w8 (unwrap! + (get winner + (unwrap! + (map-get? tournament-matches { + tournament-id: tid, + round: current-round, + match-number: u8, + }) + (err ERR_MATCH_NOT_FOUND) + )) + (err ERR_INVALID_PARAMS) + )) + ) + (try! (create-next-round-match tid next-round u4 w7 w8 game-contract)) + true + ) + true + ) + + (ok true) + ) +) + +;; create a next round match given two players +(define-private (create-next-round-match + (tid uint) + (round uint) + (match-num uint) + (p1 principal) + (p2 principal) + (game-contract ) + ) + ;; create game + (let ((game-id (try! (contract-call? game-contract create-tournament-game p1 p2 tid round + match-num + )))) + ;; update tournament mapping with player values + (map-set tournament-matches { + tournament-id: tid, + round: round, + match-number: match-num, + } { + game-id: (some game-id), + player1: (some p1), + player2: (some p2), + winner: none, + completed: false, + }) + + (print { + action: "create-next-round-match", + tournament: tid, + round: round, + match: match-num, + game: game-id, + p1: p1, + p2: p2, + }) + (ok true) + ) +) + +;; validates that n (no of players in tournament) is one of: 4, 8, 16, or 32 +(define-private (is-power-of-two (n uint)) + (or + (is-eq n u4) + (is-eq n u8) + (is-eq n u16) + (is-eq n u32) + ) +) + +;; fetches player count for a tournament +(define-private (get-player-count (tid uint)) + (default-to u0 (map-get? tournament-player-count tid)) +) + +;; updates the player count for a tournament +(define-private (set-player-count + (tid uint) + (n uint) + ) + (map-set tournament-player-count tid n) +) + +;; looks up which player is at a specific bracket position +(define-private (get-player-at + (tid uint) + (idx uint) + ) + (map-get? tournament-player-index { + tournament-id: tid, + index: idx, + }) +) + +;; assigns a player to a specific bracket position +(define-private (set-player-at + (tid uint) + (idx uint) + (p principal) + ) + (map-set tournament-player-index { + tournament-id: tid, + index: idx, + } + p + ) +) + +;; attempts to fetch tournament metadata +(define-private (get-tournament-or-err (tid uint)) + (match (map-get? tournaments tid) + meta (ok meta) + (err ERR_TOURNAMENT_NOT_FOUND) + ) +) + +;; READ-ONLY FUNCTIONS + +;; get the latest tournament id (for fetching all tournaments easily) +(define-read-only (get-latest-tournament-id) + (var-get latest-tournament-id) +) + +;; public getter for tournament metadata +(define-read-only (get-tournament (tid uint)) + (map-get? tournaments tid) +) + +;; checks if a player is registered in a tournament +(define-read-only (get-participant + (tid uint) + (p principal) + ) + (map-get? tournament-participants { + tournament-id: tid, + player: p, + }) +) + +;; fetches details about a specific match +(define-read-only (get-match + (tid uint) + (round uint) + (match-number uint) + ) + (map-get? tournament-matches { + tournament-id: tid, + round: round, + match-number: match-number, + }) +) From b0417d21d41f96a3be665af2b1ca4c90dbd6745e Mon Sep 17 00:00:00 2001 From: psychemist Date: Wed, 29 Oct 2025 14:03:53 +0100 Subject: [PATCH 20/24] deploy: deploy version 4 contracts to devnet and testnet --- Clarinet.toml | 8 ++-- deployments/default.devnet-plan.yaml | 12 ++--- deployments/default.simnet-plan.yaml | 67 --------------------------- deployments/default.testnet-plan.yaml | 12 ++--- frontend/lib/contract.ts | 4 +- 5 files changed, 18 insertions(+), 85 deletions(-) delete mode 100644 deployments/default.simnet-plan.yaml diff --git a/Clarinet.toml b/Clarinet.toml index c271665..2f66948 100644 --- a/Clarinet.toml +++ b/Clarinet.toml @@ -11,13 +11,13 @@ path = 'contracts/game-tournament-trait.clar' clarity_version = 3 epoch = 3.0 -[contracts.tic-tac-toe-tournament-v2] -path = 'contracts/tic-tac-toe-tournament-v2.clar' +[contracts.tic_tac_toe] +path = 'contracts/tic_tac_toe.clar' clarity_version = 3 epoch = 3.0 -[contracts.tic-tac-toe-v2] -path = 'contracts/tic-tac-toe-v2.clar' +[contracts.tic_tac_toe_tournament] +path = 'contracts/tic_tac_toe_tournament.clar' clarity_version = 3 epoch = 3.0 diff --git a/deployments/default.devnet-plan.yaml b/deployments/default.devnet-plan.yaml index d395f2b..19d006a 100644 --- a/deployments/default.devnet-plan.yaml +++ b/deployments/default.devnet-plan.yaml @@ -16,17 +16,17 @@ plan: anchor-block-only: true clarity-version: 3 - contract-publish: - contract-name: tic-tac-toe-tournament + contract-name: tic_tac_toe expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM - cost: 130690 - path: contracts/tic-tac-toe-tournament.clar + cost: 188270 + path: contracts/tic_tac_toe.clar anchor-block-only: true clarity-version: 3 - contract-publish: - contract-name: tic-tac-toe-v2 + contract-name: tic_tac_toe_tournament expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM - cost: 110140 - path: contracts/tic-tac-toe-v2.clar + cost: 302720 + path: contracts/tic_tac_toe_tournament.clar anchor-block-only: true clarity-version: 3 epoch: "3.0" diff --git a/deployments/default.simnet-plan.yaml b/deployments/default.simnet-plan.yaml deleted file mode 100644 index eca2bbf..0000000 --- a/deployments/default.simnet-plan.yaml +++ /dev/null @@ -1,67 +0,0 @@ ---- -id: 0 -name: "Simulated deployment, used as a default for `clarinet console`, `clarinet test` and `clarinet check`" -network: simnet -genesis: - wallets: - - name: deployer - address: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM - balance: "100000000000000" - - name: faucet - address: STNHKEPYEPJ8ET55ZZ0M5A34J0R3N5FM2CMMMAZ6 - balance: "100000000000000" - - name: wallet_1 - address: ST1SJ3DTE5DN7X54YDH5D64R3BCB6A2AG2ZQ8YPD5 - balance: "100000000000000" - - name: wallet_2 - address: ST2CY5V39NHDPWSXMW9QDT3HC3GD6Q6XX4CFRK9AG - balance: "100000000000000" - - name: wallet_3 - address: ST2JHG361ZXG51QTKY2NQCVBPPRRE2KZB1HR05NNC - balance: "100000000000000" - - name: wallet_4 - address: ST2NEB84ASENDXKYGJPQW86YXQCEFEX2ZQPG87ND - balance: "100000000000000" - - name: wallet_5 - address: ST2REHHS5J3CERCRBEPMGH7921Q6PYKAADT7JP2VB - balance: "100000000000000" - - name: wallet_6 - address: ST3AM1A56AK2C1XAFJ4115ZSV26EB49BVQ10MGCS0 - balance: "100000000000000" - - name: wallet_7 - address: ST3PF13W7Z0RRM42A8VZRVFQ75SV1K26RXEP8YGKJ - balance: "100000000000000" - - name: wallet_8 - address: ST3NBRSFKX28FQ2ZJ1MAKX58HKHSDGNV5N7R21XCP - balance: "100000000000000" - contracts: - - costs - - pox - - pox-2 - - pox-3 - - pox-4 - - lockup - - costs-2 - - costs-3 - - cost-voting - - bns -plan: - batches: - - id: 0 - transactions: - - emulated-contract-publish: - contract-name: tournament-game-trait - emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM - path: contracts/tournament-game-trait.clar - clarity-version: 3 - - emulated-contract-publish: - contract-name: tic-tac-toe - emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM - path: contracts/tic-tac-toe.clar - clarity-version: 3 - - emulated-contract-publish: - contract-name: tournament - emulated-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM - path: contracts/tournament.clar - clarity-version: 3 - epoch: "3.0" diff --git a/deployments/default.testnet-plan.yaml b/deployments/default.testnet-plan.yaml index 7e21d7e..ed63c8f 100644 --- a/deployments/default.testnet-plan.yaml +++ b/deployments/default.testnet-plan.yaml @@ -16,17 +16,17 @@ plan: anchor-block-only: true clarity-version: 3 - contract-publish: - contract-name: tic-tac-toe-tournament-v2 + contract-name: tic_tac_toe expected-sender: ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88 - cost: 117020 - path: contracts/tic-tac-toe-tournament-v2.clar + cost: 188270 + path: contracts/tic_tac_toe.clar anchor-block-only: true clarity-version: 3 - contract-publish: - contract-name: tic-tac-toe-v2 + contract-name: tic_tac_toe_tournament expected-sender: ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88 - cost: 110140 - path: contracts/tic-tac-toe-v2.clar + cost: 302720 + path: contracts/tic_tac_toe_tournament.clar anchor-block-only: true clarity-version: 3 epoch: "3.0" diff --git a/frontend/lib/contract.ts b/frontend/lib/contract.ts index d160aff..91b295c 100644 --- a/frontend/lib/contract.ts +++ b/frontend/lib/contract.ts @@ -12,7 +12,7 @@ import { } from "@stacks/transactions"; const CONTRACT_ADDRESS = "ST16XCPGV6CVM7D5M1H3BGT0VKDGNFKPRDSQXRW88"; -const CONTRACT_NAME = "tic-tac-toe-v2"; +const CONTRACT_NAME = "tic_tac_toe"; type GameCV = { "player-one": PrincipalCV; @@ -70,7 +70,7 @@ export async function getAllGames() { // Limit to last 10 games to avoid rate limiting const startId = Math.max(0, latestGameId - 10); const games: Game[] = []; - + for (let i = startId; i < latestGameId; i++) { const game = await getGame(i); if (game) games.push(game); From 46a0382efef641e6b7e4b036d644103f16e03b95 Mon Sep 17 00:00:00 2001 From: psychemist Date: Wed, 29 Oct 2025 14:09:27 +0100 Subject: [PATCH 21/24] feat: update tournament hooks & components 1. add logic to for manually advancing each round after all games are won (only creator) 2. enhance game winner display on frontend --- .../app/tournaments/[tournamentId]/page.tsx | 2 ++ frontend/components/play-game.tsx | 2 +- .../components/tournament-registration.tsx | 24 ++++++++++++- frontend/hooks/use-tournaments.ts | 35 ++++++++++++++++++ frontend/lib/tournament-contract.ts | 36 ++++++++++++++----- 5 files changed, 89 insertions(+), 10 deletions(-) diff --git a/frontend/app/tournaments/[tournamentId]/page.tsx b/frontend/app/tournaments/[tournamentId]/page.tsx index ff7391f..7ad287b 100644 --- a/frontend/app/tournaments/[tournamentId]/page.tsx +++ b/frontend/app/tournaments/[tournamentId]/page.tsx @@ -18,6 +18,7 @@ export default function TournamentDetailPage() { fetchTournament, fetchParticipant, fetchRoundMatches, + handleAdvanceRound, handleJoinTournament, handleStartTournament, } = useTournaments(userAddress); @@ -159,6 +160,7 @@ export default function TournamentDetailPage() { userAddress={userAddress} onJoin={handleJoinTournament} onStart={handleStartTournament} + onAdvanceRound={handleAdvanceRound} isParticipant={isParticipant} /> diff --git a/frontend/components/play-game.tsx b/frontend/components/play-game.tsx index b7db953..84685e7 100644 --- a/frontend/components/play-game.tsx +++ b/frontend/components/play-game.tsx @@ -121,7 +121,7 @@ export function PlayGame({ game, tournamentId, round, matchNumber }: PlayGamePro {abbreviateAddress(game["winner"])} diff --git a/frontend/components/tournament-registration.tsx b/frontend/components/tournament-registration.tsx index 8a05eb7..dbdbd2f 100644 --- a/frontend/components/tournament-registration.tsx +++ b/frontend/components/tournament-registration.tsx @@ -8,6 +8,7 @@ interface TournamentRegistrationProps { userAddress: string | null; onJoin: (tournamentId: number) => void; onStart: (tournamentId: number) => void; + onAdvanceRound: (tournamentId: number, round: number) => void; isParticipant: boolean; } @@ -16,6 +17,7 @@ export function TournamentRegistration({ userAddress, onJoin, onStart, + onAdvanceRound, isParticipant, }: TournamentRegistrationProps) { const [isProcessing, setIsProcessing] = useState(false); @@ -25,6 +27,8 @@ export function TournamentRegistration({ const isFull = playerCount >= tournament.maxPlayers; const canJoin = tournament.status === TOURNAMENT_STATUS.OPEN && !isParticipant && !isFull && userAddress; const canStart = tournament.status === TOURNAMENT_STATUS.OPEN && isCreator; + const canAdvance = (tournament.status === TOURNAMENT_STATUS.IN_PROGRESS || tournament.status === TOURNAMENT_STATUS.COMPLETED) && isCreator; + const alreadyStarted = tournament.status === TOURNAMENT_STATUS.IN_PROGRESS || tournament.status === TOURNAMENT_STATUS.COMPLETED; const handleJoin = async () => { setIsProcessing(true); @@ -44,6 +48,15 @@ export function TournamentRegistration({ } }; + const handleAdvanceRound = async () => { + setIsProcessing(true); + try { + await onAdvanceRound(tournament.id, tournament.currentRound); + } finally { + setIsProcessing(false); + } + }; + if (!userAddress) { return (
@@ -70,6 +83,15 @@ export function TournamentRegistration({

Tournament in Progress

Round {tournament.currentRound}

+ {canAdvance && ( + + )}
); } @@ -107,7 +129,7 @@ export function TournamentRegistration({ {canStart && (