diff --git a/Makefile b/Makefile index e6e3590766..dae289754a 100644 --- a/Makefile +++ b/Makefile @@ -39,8 +39,11 @@ BACKEND_ONLY_PKGS := packages/txm,apps/randomness,apps/faucet # packages needed to build the backend services BACKEND_PKGS := support/common,$(BACKEND_ONLY_PKGS) +# all the swarm leaderboard packages +LEADERBOARD_PKGS := apps/leaderboard-backend + # all typescript packages, excluding docs -TS_PKGS := $(ACCOUNT_PKGS),$(DEMOS_PKGS),${BACKEND_ONLY_PKGS} +TS_PKGS := $(ACCOUNT_PKGS),$(DEMOS_PKGS),${BACKEND_ONLY_PKGS},apps/leaderboard-backend # all packages that have a package.json NPM_PKGS := $(TS_PKGS),apps/docs,contracts,support/configs @@ -169,6 +172,20 @@ submitter.prod: submitter.build cd apps/submitter && make prod; .PHONY: submitter.prod +leaderboard-backend.dev: setup.ts shared.dev + cd apps/leaderboard-backend && make migrate; + cd apps/leaderboard-backend && make dev; +.PHONY: leaderboard-backend.dev + +leaderboard-backend.build: shared.build + cd apps/leaderboard-backend && make build; +.PHONY: leaderboard-backend.build + +leaderboard-backend.prod: leaderboard-backend.build + cd apps/leaderboard-backend && make migrate; + cd apps/leaderboard-backend && make prod; +.PHONY: leaderboard-backend.prod + iframe.dev: shared.dev sdk.dev ## Serves the wallet iframe at http://localhost:5160 cd apps/iframe && make dev .PHONY: iframe.dev diff --git a/apps/leaderboard-backend/.env.example b/apps/leaderboard-backend/.env.example new file mode 100644 index 0000000000..33415c6eb9 --- /dev/null +++ b/apps/leaderboard-backend/.env.example @@ -0,0 +1,3 @@ +PORT=4545 +LEADERBOARD_DB_URL="leaderboard-backend.sqlite" +DATABASE_MIGRATE_DIR="migrations" diff --git a/apps/leaderboard-backend/Makefile b/apps/leaderboard-backend/Makefile new file mode 100644 index 0000000000..f6b8d8e415 --- /dev/null +++ b/apps/leaderboard-backend/Makefile @@ -0,0 +1,34 @@ +include ../../makefiles/lib.mk +include ../../makefiles/bundling.mk +include ../../makefiles/formatting.mk +include ../../makefiles/help.mk + +# include .env file and export its env vars +# (-include to ignore error if it does not exist) +-include .env + +dev: node_modules reset-dev + @NODE_ENV=development bun --bun --hot src/index.ts; +.PHONY: dev + +prod: build ## Start the local dev server for development + @NODE_ENV=production bun --bun run build/index.es.js; +.PHONY: prod + +# Migration targets for leaderboard-backend (using Bun) + +migrate: ## Runs latest migrations + @echo "Running latest migrations..." + @bun run src/db/migrate.ts +.PHONY: migrate + +migrate-fresh: ## Deletes database and starts fresh + @if [ -f ${LEADERBOARD_DB_URL} ]; then rm ${LEADERBOARD_DB_URL}; fi; + @echo "Running latest migrations on fresh DB..." + @bun run src/db/migrate.ts +.PHONY: migrate-fresh + +# NOTE: migrate-rollback is not implemented in migrate.ts; add support if needed +migrate-rollback: ## Rollsback latest migrations (not implemented) + @echo "Rollback not implemented in migrate.ts. Implement if needed." +.PHONY: migrate-rollback \ No newline at end of file diff --git a/apps/leaderboard-backend/README.md b/apps/leaderboard-backend/README.md new file mode 100644 index 0000000000..d2f3bed966 --- /dev/null +++ b/apps/leaderboard-backend/README.md @@ -0,0 +1,11 @@ +# leaderboard-backend + +## Quickstart + +- `make setup` — Install dependencies +- `make dev` — Start the dev server with hot reload +- `make build` — Build the service for production +- `make prod` — Run the built app in production mode +- `make migrate` — Run database migrations (customize as needed) +- `make migrate-fresh` — Reset and re-run migrations +- `make migrate-rollback` — Roll back the last migration diff --git a/apps/leaderboard-backend/biome.jsonc b/apps/leaderboard-backend/biome.jsonc new file mode 100644 index 0000000000..676b35581d --- /dev/null +++ b/apps/leaderboard-backend/biome.jsonc @@ -0,0 +1,4 @@ +{ + "$schema": "../../node_modules/@biomejs/biome/configuration_schema.json", + "extends": ["../../support/configs/biome.jsonc"] +} diff --git a/apps/leaderboard-backend/build.config.ts b/apps/leaderboard-backend/build.config.ts new file mode 100644 index 0000000000..3f42ace28a --- /dev/null +++ b/apps/leaderboard-backend/build.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "@happy.tech/happybuild" + +export default defineConfig({ + bunConfig: { + target: "bun", + minify: false, + }, +}) diff --git a/apps/leaderboard-backend/package.json b/apps/leaderboard-backend/package.json new file mode 100644 index 0000000000..bfacfa0ea8 --- /dev/null +++ b/apps/leaderboard-backend/package.json @@ -0,0 +1,36 @@ +{ + "version": "0.1.0", + "name": "@happy.tech/leaderboard-backend", + "private": true, + "main": "./dist/index.es.js", + "module": "./dist/index.es.js", + "type": "module", + "types": "./dist/index.es.d.ts", + "exports": { + ".": { + "types": "./dist/index.es.d.ts", + "default": "./dist/index.es.js" + } + }, + "devDependencies": { + "@happy.tech/common": "workspace:0.1.0", + "@happy.tech/configs": "workspace:0.1.0", + "@happy.tech/happybuild": "workspace:0.1.1", + "@types/bun": "latest" + }, + "peerDependencies": { + "typescript": "^5.0.0" + }, + "dependencies": { + "@hono/zod-openapi": "^0.19.6", + "@hono/zod-validator": "^0.4.3", + "@scalar/hono-api-reference": "^0.8.8", + "hono": "^4.7.2", + "hono-openapi": "^0.4.4", + "kysely": "^0.27.5", + "kysely-bun-sqlite": "^0.3.2", + "viem": "^2.21.53", + "zod": "^3.23.8", + "zod-openapi": "^4.2.3" + } +} diff --git a/apps/leaderboard-backend/public/gameicon.png b/apps/leaderboard-backend/public/gameicon.png new file mode 100644 index 0000000000..6c1db48087 Binary files /dev/null and b/apps/leaderboard-backend/public/gameicon.png differ diff --git a/apps/leaderboard-backend/src/db/driver.ts b/apps/leaderboard-backend/src/db/driver.ts new file mode 100644 index 0000000000..54591fc7ab --- /dev/null +++ b/apps/leaderboard-backend/src/db/driver.ts @@ -0,0 +1,13 @@ +import { Database as BunDatabase } from "bun:sqlite" +import { Kysely } from "kysely" +import { BunSqliteDialect } from "kysely-bun-sqlite" +import type { Database } from "./types" + +import { env } from "../env" + +const dbPath = env.LEADERBOARD_DB_URL || ":memory:" + +export const db = new Kysely({ + dialect: new BunSqliteDialect({ database: new BunDatabase(dbPath) }), + // Add plugins here if needed +}) diff --git a/apps/leaderboard-backend/src/db/migrate.ts b/apps/leaderboard-backend/src/db/migrate.ts new file mode 100644 index 0000000000..fdf7a6e036 --- /dev/null +++ b/apps/leaderboard-backend/src/db/migrate.ts @@ -0,0 +1,37 @@ +import { type Migration, type MigrationProvider, Migrator } from "kysely" +import { db } from "./driver" +import { migrations } from "./migrations" + +class ObjectMigrationProvider implements MigrationProvider { + constructor(private migrations: Record) {} + async getMigrations(): Promise> { + return this.migrations + } +} + +const migrator = new Migrator({ + db, + provider: new ObjectMigrationProvider(migrations), +}) + +async function migrateToLatest() { + const { error, results } = await migrator.migrateToLatest() + + results?.forEach((it) => { + if (it.status === "Success") { + console.log(`migration "${it.migrationName}" was executed successfully`) + } else if (it.status === "Error") { + console.error(`failed to execute migration "${it.migrationName}"`) + } + }) + + if (error) { + console.error("failed to migrate") + console.error(error) + process.exit(1) + } + + await db.destroy() +} + +migrateToLatest() diff --git a/apps/leaderboard-backend/src/db/migrations/1745907000000_create_all_tables.ts b/apps/leaderboard-backend/src/db/migrations/1745907000000_create_all_tables.ts new file mode 100644 index 0000000000..064ef1070a --- /dev/null +++ b/apps/leaderboard-backend/src/db/migrations/1745907000000_create_all_tables.ts @@ -0,0 +1,89 @@ +import type { Kysely } from "kysely" +import { sql } from "kysely" + +// biome-ignore lint/suspicious/noExplicitAny: `any` is required here since migrations should be frozen in time. alternatively, keep a "snapshot" db interface. +export async function up(db: Kysely): Promise { + // Users table + await db.schema + .createTable("users") + .addColumn("id", "integer", (col) => col.primaryKey().autoIncrement()) + .addColumn("primary_wallet", "text", (col) => col.notNull().unique()) + .addColumn("username", "text", (col) => col.notNull().unique()) + .addColumn("created_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()) + .addColumn("updated_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()) + .execute() + + // User wallets table (multiple wallets per user) + await db.schema + .createTable("user_wallets") + .addColumn("id", "integer", (col) => col.primaryKey().autoIncrement()) + .addColumn("user_id", "integer", (col) => col.notNull()) + .addColumn("wallet_address", "text", (col) => col.notNull().unique()) + .addColumn("is_primary", "boolean", (col) => col.notNull().defaultTo(false)) + .addColumn("created_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()) + .addForeignKeyConstraint("user_wallets_user_id_fk", ["user_id"], "users", ["id"]) + .execute() + + // Guilds table + await db.schema + .createTable("guilds") + .addColumn("id", "integer", (col) => col.primaryKey().autoIncrement()) + .addColumn("name", "text", (col) => col.notNull().unique()) + .addColumn("icon_url", "text", (col) => col) + .addColumn("creator_id", "integer", (col) => col.notNull()) + .addColumn("created_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()) + .addColumn("updated_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()) + .addForeignKeyConstraint("guilds_creator_id_fk", ["creator_id"], "users", ["id"]) + .execute() + + // Guild members table (many-to-many users <-> guilds) + await db.schema + .createTable("guild_members") + .addColumn("id", "integer", (col) => col.primaryKey().autoIncrement()) + .addColumn("guild_id", "integer", (col) => col.notNull()) + .addColumn("user_id", "integer", (col) => col.notNull()) + .addColumn("is_admin", "boolean", (col) => col.notNull().defaultTo(false)) + .addColumn("joined_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()) + .addForeignKeyConstraint("guild_members_guild_id_fk", ["guild_id"], "guilds", ["id"]) + .addForeignKeyConstraint("guild_members_user_id_fk", ["user_id"], "users", ["id"]) + .addUniqueConstraint("guild_members_unique", ["guild_id", "user_id"]) + .execute() + + // Games table + await db.schema + .createTable("games") + .addColumn("id", "integer", (col) => col.primaryKey().autoIncrement()) + .addColumn("name", "text", (col) => col.notNull().unique()) + .addColumn("icon_url", "text", (col) => col) + .addColumn("description", "text", (col) => col) + .addColumn("admin_id", "integer", (col) => col.notNull()) + .addColumn("created_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()) + .addColumn("updated_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()) + .addForeignKeyConstraint("games_admin_id_fk", ["admin_id"], "users", ["id"]) + .execute() + + // User game scores table + await db.schema + .createTable("user_game_scores") + .addColumn("id", "integer", (col) => col.primaryKey().autoIncrement()) + .addColumn("user_id", "integer", (col) => col.notNull()) + .addColumn("game_id", "integer", (col) => col.notNull()) + .addColumn("score", "integer", (col) => col.notNull()) + .addColumn("metadata", "text", (col) => col) + .addColumn("created_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()) + .addColumn("updated_at", "text", (col) => col.defaultTo(sql`CURRENT_TIMESTAMP`).notNull()) + .addForeignKeyConstraint("user_game_scores_user_id_fk", ["user_id"], "users", ["id"]) + .addForeignKeyConstraint("user_game_scores_game_id_fk", ["game_id"], "games", ["id"]) + .addUniqueConstraint("user_game_scores_unique", ["user_id", "game_id"]) + .execute() +} + +// biome-ignore lint/suspicious/noExplicitAny: `any` is required here since migrations should be frozen in time. alternatively, keep a "snapshot" db interface. +export async function down(db: Kysely): Promise { + await db.schema.dropTable("user_game_scores").execute() + await db.schema.dropTable("games").execute() + await db.schema.dropTable("guild_members").execute() + await db.schema.dropTable("guilds").execute() + await db.schema.dropTable("user_wallets").execute() + await db.schema.dropTable("users").execute() +} diff --git a/apps/leaderboard-backend/src/db/migrations/index.ts b/apps/leaderboard-backend/src/db/migrations/index.ts new file mode 100644 index 0000000000..a2e90c05ab --- /dev/null +++ b/apps/leaderboard-backend/src/db/migrations/index.ts @@ -0,0 +1,5 @@ +import * as M_1745907000000_create_all_tables from "./1745907000000_create_all_tables" + +export const migrations = { + "1745907000000_create_all_tables": M_1745907000000_create_all_tables, +} diff --git a/apps/leaderboard-backend/src/db/types.ts b/apps/leaderboard-backend/src/db/types.ts new file mode 100644 index 0000000000..ab74e4f5c5 --- /dev/null +++ b/apps/leaderboard-backend/src/db/types.ts @@ -0,0 +1,139 @@ +import type { Address } from "@happy.tech/common" +import type { ColumnType, Generated, Insertable, Selectable, Updateable } from "kysely" + +// --- Branded ID types for strong nominal typing --- +export type UserTableId = number & { _brand: "users_id" } +export type GuildTableId = number & { _brand: "guilds_id" } +export type GameTableId = number & { _brand: "games_id" } +export type ScoreTableId = number & { _brand: "scores_id" } +export type GuildMemberTableId = number & { _brand: "guild_members_id" } + +// Main Kysely database schema definition +export interface Database { + users: UserTable + user_wallets: UserWalletTable + guilds: GuildTable + guild_members: GuildMemberTable + games: GameTable + user_game_scores: UserGameScoreTable +} + +// Registered users +export interface UserTable { + id: Generated + primary_wallet: Address // Primary wallet for the user + username: string + created_at: ColumnType + updated_at: ColumnType +} + +// User wallet addresses (allows multiple wallets per user) +export interface UserWalletTable { + id: Generated + user_id: UserTableId // FK to users + wallet_address: Address + is_primary: boolean // If this is the user's primary wallet + created_at: ColumnType +} + +// Guilds (groups of users) +export interface GuildTable { + id: Generated + name: string + icon_url: string | null + creator_id: UserTableId // FK to users, original creator + created_at: ColumnType + updated_at: ColumnType +} + +// Guild membership JOIN table (users in guilds with role) +export interface GuildMemberTable { + id: Generated + guild_id: GuildTableId // FK to guilds + user_id: UserTableId // FK to users + is_admin: boolean // Whether user is an admin of this guild + joined_at: ColumnType +} + +// Games available on the platform +export interface GameTable { + id: Generated + name: string + icon_url: string | null + description: string | null + admin_id: UserTableId // FK to users, creator/admin of the game + created_at: ColumnType + updated_at: ColumnType +} + +// User scores in games +export interface UserGameScoreTable { + id: Generated + user_id: UserTableId // FK to users + game_id: GameTableId // FK to games + score: number // The actual score + metadata: string | null // JSON string for any additional game-specific data + created_at: ColumnType + updated_at: ColumnType +} + +// Kysely helper types +export type User = Selectable +export type NewUser = Insertable +export type UpdateUser = Updateable + +export type UserWallet = Selectable +export type NewUserWallet = Insertable +export type UpdateUserWallet = Updateable + +export type Guild = Selectable +export type NewGuild = Insertable +export type UpdateGuild = Updateable + +export type GuildMember = Selectable +export type NewGuildMember = Insertable +export type UpdateGuildMember = Updateable +export type GuildMemberWithUser = GuildMember & { + username: string + primary_wallet: Address +} + +export type Game = Selectable +export type NewGame = Insertable +export type UpdateGame = Updateable + +export type UserGameScore = Selectable +export type NewUserGameScore = Insertable +export type UpdateUserGameScore = Updateable + +export interface GlobalLeaderboardEntry { + user_id: UserTableId + username: string + primary_wallet: Address + total_score: number +} + +export interface GuildLeaderboardEntry { + guild_id: GuildTableId + guild_name: string + icon_url: string | null + total_score: number + member_count: number +} + +export interface GameLeaderboardEntry { + game_id: GameTableId + user_id: UserTableId + username: string + primary_wallet: Address + score: number +} + +export interface GameGuildLeaderboardEntry { + game_id: GameTableId + guild_id: GuildTableId + guild_name: string + icon_url: string | null + total_score: number + member_count: number +} diff --git a/apps/leaderboard-backend/src/env.ts b/apps/leaderboard-backend/src/env.ts new file mode 100644 index 0000000000..a31f3b06ad --- /dev/null +++ b/apps/leaderboard-backend/src/env.ts @@ -0,0 +1,16 @@ +import { z } from "zod" + +const envSchema = z.object({ + LEADERBOARD_DB_URL: z.string().trim().optional(), + PORT: z.string().trim().optional(), + DATABASE_MIGRATE_DIR: z.string().trim().optional(), +}) + +const parsedEnv = envSchema.safeParse(process.env) + +if (!parsedEnv.success) { + const formatted = parsedEnv.error.format() + throw new Error("❌ Invalid environment variables in leaderboard-backend:\n" + JSON.stringify(formatted, null, 2)) +} + +export const env = parsedEnv.data diff --git a/apps/leaderboard-backend/src/index.ts b/apps/leaderboard-backend/src/index.ts new file mode 100644 index 0000000000..1b0e362c93 --- /dev/null +++ b/apps/leaderboard-backend/src/index.ts @@ -0,0 +1,9 @@ +import { env } from "./env" +import { type AppType, app } from "./server" + +export type { AppType } + +export default { + port: Number(env.PORT) || 4545, + fetch: app.fetch, +} diff --git a/apps/leaderboard-backend/src/middlewares/isValidSignature.ts b/apps/leaderboard-backend/src/middlewares/isValidSignature.ts new file mode 100644 index 0000000000..1745787dbe --- /dev/null +++ b/apps/leaderboard-backend/src/middlewares/isValidSignature.ts @@ -0,0 +1,14 @@ +// TODO: Reserved for another PR, this is just stub, ignore this file for now + +import { createMiddleware } from "hono/factory" + +const isValidSignature = createMiddleware(async (c, next) => { + console.log(c.req) + console.log("TODO: import wagmi, make call to SCA.isValidSignature()") + await next() + // !Optional, modify response after it comes back from the handler + // c.res = undefined + // c.res = new Response('New Response') +}) + +export { isValidSignature } diff --git a/apps/leaderboard-backend/src/repositories/GamesRepository.ts b/apps/leaderboard-backend/src/repositories/GamesRepository.ts new file mode 100644 index 0000000000..86de3a24e4 --- /dev/null +++ b/apps/leaderboard-backend/src/repositories/GamesRepository.ts @@ -0,0 +1,176 @@ +import type { Kysely } from "kysely" +import type { Database, Game, GameTableId, NewGame, UpdateGame, UserGameScore, UserTableId } from "../db/types" + +export class GameRepository { + constructor(private db: Kysely) {} + + async findById(id: GameTableId): Promise { + return await this.db.selectFrom("games").where("id", "=", id).selectAll().executeTakeFirst() + } + + async findByNameLike(name: string): Promise { + return await this.db.selectFrom("games").where("name", "like", `%${name}%`).selectAll().execute() + } + + async findByExactName(name: string): Promise { + return await this.db.selectFrom("games").where("name", "=", name).selectAll().executeTakeFirst() + } + + async findByAdmin(adminId: UserTableId): Promise { + return await this.db.selectFrom("games").where("admin_id", "=", adminId).selectAll().execute() + } + + async find(criteria: { + name?: string + admin_id?: UserTableId + }): Promise { + const { name, admin_id } = criteria + + return await this.db + .selectFrom("games") + .$if(typeof name === "string" && name.length > 0, (qb) => qb.where("name", "like", `%${name}%`)) + .$if(typeof admin_id === "number", (qb) => qb.where("admin_id", "=", admin_id as UserTableId)) + .selectAll() + .execute() + } + + async create(game: NewGame): Promise { + const now = new Date().toISOString() + return await this.db + .insertInto("games") + .values({ + ...game, + created_at: now, + updated_at: now, + }) + .returningAll() + .executeTakeFirstOrThrow() + } + + async update(id: GameTableId, updateWith: UpdateGame): Promise { + await this.db + .updateTable("games") + .set({ ...updateWith, updated_at: new Date().toISOString() }) + .where("id", "=", id) + .execute() + + return this.findById(id) + } + + async delete(id: GameTableId): Promise { + return await this.db.transaction().execute(async (trx) => { + // Get game before deletion + const game = await trx.selectFrom("games").where("id", "=", id).selectAll().executeTakeFirst() + + if (!game) { + return undefined + } + + await trx.deleteFrom("user_game_scores").where("game_id", "=", id).execute() + await trx.deleteFrom("games").where("id", "=", id).execute() + + return game + }) + } +} + +export class GameScoreRepository { + constructor(private db: Kysely) {} + + async findUserGameScore(userId: UserTableId, gameId: GameTableId): Promise { + return await this.db + .selectFrom("user_game_scores") + .where("user_id", "=", userId) + .where("game_id", "=", gameId) + .selectAll() + .executeTakeFirst() + } + + async findUserScores( + userId: UserTableId, + gameId?: GameTableId, + ): Promise<(UserGameScore & { game_name: string })[]> { + return await this.db + .selectFrom("user_game_scores") + .innerJoin("games", "games.id", "user_game_scores.game_id") + .where("user_game_scores.user_id", "=", userId) + .$if(typeof gameId === "number", (qb) => qb.where("user_game_scores.game_id", "=", gameId as GameTableId)) + .select([ + "user_game_scores.id", + "user_game_scores.game_id", + "user_game_scores.user_id", + "user_game_scores.score", + "user_game_scores.metadata", + "user_game_scores.created_at", + "user_game_scores.updated_at", + "games.name as game_name", + ]) + .execute() + } + + async findGameScores(gameId: GameTableId, limit = 50): Promise<(UserGameScore & { username: string })[]> { + return await this.db + .selectFrom("user_game_scores") + .innerJoin("users", "users.id", "user_game_scores.user_id") + .where("user_game_scores.game_id", "=", gameId) + .select([ + "user_game_scores.id", + "user_game_scores.game_id", + "user_game_scores.user_id", + "user_game_scores.score", + "user_game_scores.metadata", + "user_game_scores.created_at", + "user_game_scores.updated_at", + "users.username", + ]) + .orderBy("user_game_scores.score", "desc") + .limit(limit) + .execute() + } + + async submitScore( + userId: UserTableId, + gameId: GameTableId, + score: number, + metadata?: string, + ): Promise { + const existingScore = await this.findUserGameScore(userId, gameId) + const now = new Date().toISOString() + + if (existingScore) { + const newScore = existingScore.score + score + return await this.db + .updateTable("user_game_scores") + .set({ + score: newScore, + metadata: metadata !== undefined ? metadata : existingScore.metadata, + updated_at: now, + }) + .where("id", "=", existingScore.id) + .returningAll() + .executeTakeFirstOrThrow() + } else { + return await this.db + .insertInto("user_game_scores") + .values({ + user_id: userId, + game_id: gameId, + score, + metadata, + created_at: now, + updated_at: now, + }) + .returningAll() + .executeTakeFirstOrThrow() + } + } + + async deleteScore(userId: UserTableId, gameId: GameTableId): Promise { + return await this.db + .deleteFrom("user_game_scores") + .where("user_id", "=", userId) + .where("game_id", "=", gameId) + .returningAll() + .executeTakeFirst() + } +} diff --git a/apps/leaderboard-backend/src/repositories/GuildsRepository.ts b/apps/leaderboard-backend/src/repositories/GuildsRepository.ts new file mode 100644 index 0000000000..668087665c --- /dev/null +++ b/apps/leaderboard-backend/src/repositories/GuildsRepository.ts @@ -0,0 +1,250 @@ +import type { Kysely } from "kysely" +import type { + Database, + Guild, + GuildMember, + GuildMemberWithUser, + GuildTableId, + NewGuild, + UpdateGuild, + UserTableId, +} from "../db/types" + +export class GuildRepository { + constructor(private db: Kysely) {} + + async findById( + id: GuildTableId, + includeMembers = false, + ): Promise<(Guild & { members: GuildMember[] }) | undefined> { + const guild = await this.db.selectFrom("guilds").where("id", "=", id).selectAll().executeTakeFirst() + + if (guild && includeMembers) { + const members = await this.getGuildMembersWithUserDetails(id) + return { ...guild, members } + } + + return guild as Guild & { members: GuildMember[] } + } + + async findByName(name: string, includeMembers = false): Promise<(Guild & { members: GuildMember[] })[]> { + const guilds = await this.db.selectFrom("guilds").where("name", "like", `%${name}%`).selectAll().execute() + + if (includeMembers && guilds.length > 0) { + const guildsWithMembers = await Promise.all( + guilds.map(async (guild) => { + const members = await this.getGuildMembersWithUserDetails(guild.id) + return { ...guild, members } + }), + ) + return guildsWithMembers + } + + return guilds as (Guild & { members: GuildMember[] })[] + } + + async findByCreator( + creatorId: UserTableId, + includeMembers = false, + ): Promise<(Guild & { members: GuildMember[] })[]> { + const guilds = await this.db.selectFrom("guilds").where("creator_id", "=", creatorId).selectAll().execute() + + if (includeMembers && guilds.length > 0) { + const guildsWithMembers = await Promise.all( + guilds.map(async (guild) => { + const members = await this.getGuildMembersWithUserDetails(guild.id) + return { ...guild, members } + }), + ) + return guildsWithMembers + } + + return guilds as (Guild & { members: GuildMember[] })[] + } + + async find(criteria: { + name?: string + creator_id?: UserTableId + includeMembers?: boolean + }): Promise<(Guild & { members: GuildMember[] })[]> { + const { name, creator_id, includeMembers = false } = criteria + + const guilds = await this.db + .selectFrom("guilds") + .$if(typeof name === "string" && name.length > 0, (qb) => qb.where("name", "like", `%${name}%`)) + .$if(typeof creator_id === "number", (qb) => qb.where("creator_id", "=", creator_id as UserTableId)) + .selectAll() + .execute() + + if (includeMembers && guilds.length > 0) { + const guildsWithMembers = await Promise.all( + guilds.map(async (guild) => { + const members = await this.getGuildMembersWithUserDetails(guild.id) + return { ...guild, members } + }), + ) + return guildsWithMembers + } + + return guilds as (Guild & { members: GuildMember[] })[] + } + + async create(guild: NewGuild): Promise { + const now = new Date().toISOString() + return await this.db.transaction().execute(async (trx) => { + const newGuild = await trx + .insertInto("guilds") + .values({ + ...guild, + created_at: now, + updated_at: now, + }) + .returningAll() + .executeTakeFirstOrThrow() + + await trx + .insertInto("guild_members") + .values({ + guild_id: newGuild.id, + user_id: newGuild.creator_id, + is_admin: true, + joined_at: now, + }) + .execute() + + return newGuild + }) + } + + async update(id: GuildTableId, updateWith: UpdateGuild): Promise { + await this.db + .updateTable("guilds") + .set({ + ...updateWith, + updated_at: new Date().toISOString(), + }) + .where("id", "=", id) + .execute() + + return this.findById(id) + } + + async delete(id: GuildTableId): Promise { + return await this.db.transaction().execute(async (trx) => { + const guild = await trx.selectFrom("guilds").where("id", "=", id).selectAll().executeTakeFirst() + + if (!guild) { + return undefined + } + + await trx.deleteFrom("guild_members").where("guild_id", "=", id).execute() + await trx.deleteFrom("guilds").where("id", "=", id).execute() + + return guild + }) + } + + async getGuildMembersWithUserDetails(guildId: GuildTableId): Promise { + return await this.db + .selectFrom("guild_members") + .innerJoin("users", "users.id", "guild_members.user_id") + .where("guild_members.guild_id", "=", guildId) + .select([ + "guild_members.id", + "guild_members.guild_id", + "guild_members.user_id", + "guild_members.is_admin", + "guild_members.joined_at", + "users.username", + "users.primary_wallet", + ]) + .execute() + } + + async getUserGuilds(userId: UserTableId): Promise { + return await this.db + .selectFrom("guild_members") + .innerJoin("guilds", "guilds.id", "guild_members.guild_id") + .where("guild_members.user_id", "=", userId) + .select([ + "guilds.id", + "guilds.name", + "guilds.icon_url", + "guilds.creator_id", + "guilds.created_at", + "guilds.updated_at", + ]) + .execute() + } + + async findGuildMember(guildId: GuildTableId, userId: UserTableId): Promise { + return await this.db + .selectFrom("guild_members") + .where("guild_id", "=", guildId) + .where("user_id", "=", userId) + .selectAll() + .executeTakeFirst() + } + + async addMember(guildId: GuildTableId, userId: UserTableId, isAdmin = false): Promise { + const existing = await this.db + .selectFrom("guild_members") + .where("guild_id", "=", guildId) + .where("user_id", "=", userId) + .select(["id"]) + .executeTakeFirst() + + if (existing) { + return undefined + } + + await this.db + .insertInto("guild_members") + .values({ + guild_id: guildId, + user_id: userId, + is_admin: isAdmin, + joined_at: new Date().toISOString(), + }) + .execute() + + return await this.db + .selectFrom("guild_members") + .where("guild_id", "=", guildId) + .where("user_id", "=", userId) + .select(["id", "guild_id", "user_id", "is_admin", "joined_at"]) + .executeTakeFirst() + } + + async updateMemberRole( + guildId: GuildTableId, + userId: UserTableId, + isAdmin: boolean, + ): Promise { + return await this.db + .updateTable("guild_members") + .set({ is_admin: isAdmin }) + .where("guild_id", "=", guildId) + .where("user_id", "=", userId) + .returningAll() + .executeTakeFirst() + } + + async removeMember(guildId: GuildTableId, userId: UserTableId): Promise { + const guild = await this.findById(guildId) + if (!guild) { + return undefined + } + + if (guild.creator_id === userId) { + return undefined + } + + return await this.db + .deleteFrom("guild_members") + .where("guild_id", "=", guildId) + .where("user_id", "=", userId) + .returningAll() + .executeTakeFirst() + } +} diff --git a/apps/leaderboard-backend/src/repositories/LeaderBoardRepository.ts b/apps/leaderboard-backend/src/repositories/LeaderBoardRepository.ts new file mode 100644 index 0000000000..43f7697113 --- /dev/null +++ b/apps/leaderboard-backend/src/repositories/LeaderBoardRepository.ts @@ -0,0 +1,105 @@ +import type { Kysely } from "kysely" +import type { + Database, + GameGuildLeaderboardEntry, + GameLeaderboardEntry, + GameTableId, + GlobalLeaderboardEntry, + GuildLeaderboardEntry, +} from "../db/types" + +export class LeaderBoardRepository { + constructor(private db: Kysely) {} + + // Global leaderboard: top users by total score across all games + async getGlobalLeaderboard(limit = 50): Promise { + return await this.db + .selectFrom("users") + .innerJoin("user_game_scores", "users.id", "user_game_scores.user_id") + .select([ + "users.id as user_id", + "users.username", + "users.primary_wallet", + this.db.fn.sum("user_game_scores.score").as("total_score"), + ]) + .groupBy(["users.id", "users.username", "users.primary_wallet"]) + .orderBy("total_score", "desc") + .limit(limit) + .execute() + } + + // Guild leaderboard: top guilds by total score of all members + async getGuildLeaderboard(limit = 50): Promise { + const leaderboardRows = await this.db + .selectFrom("guilds") + .innerJoin("guild_members", "guilds.id", "guild_members.guild_id") + .innerJoin("user_game_scores", "guild_members.user_id", "user_game_scores.user_id") + .select([ + "guilds.id as guild_id", + "guilds.name as guild_name", + "guilds.icon_url", + this.db.fn.sum("user_game_scores.score").as("total_score"), + ]) + .groupBy(["guilds.id", "guilds.name", "guilds.icon_url"]) + .orderBy("total_score", "desc") + .limit(limit) + .execute() + + // Get member counts for all guilds in the leaderboard + const guildIds = leaderboardRows.map((row) => row.guild_id) + let memberCounts: Record = {} + if (guildIds.length > 0) { + const memberCountRows = await this.db + .selectFrom("guild_members") + .select(["guild_id", this.db.fn.count("user_id").as("member_count")]) + .where("guild_id", "in", guildIds) + .groupBy(["guild_id"]) + .execute() + memberCounts = Object.fromEntries(memberCountRows.map((row) => [row.guild_id, row.member_count])) + } + + return leaderboardRows.map((row) => ({ + ...row, + member_count: memberCounts[row.guild_id] ?? 0, + })) + } + + // Game-specific leaderboard: top users by score in a game + async getGameLeaderboard(gameId: GameTableId, limit = 50): Promise { + return await this.db + .selectFrom("user_game_scores") + .innerJoin("users", "users.id", "user_game_scores.user_id") + .where("user_game_scores.game_id", "=", gameId) + .select([ + "user_game_scores.game_id", + "users.id as user_id", + "users.username", + "users.primary_wallet", + "user_game_scores.score", + ]) + .orderBy("user_game_scores.score", "desc") + .limit(limit) + .execute() + } + + // Game-specific guild leaderboard: top guilds by total score of members in a game + async getGameGuildLeaderboard(gameId: GameTableId, limit = 50): Promise { + return await this.db + .selectFrom("guilds") + .innerJoin("guild_members", "guilds.id", "guild_members.guild_id") + .innerJoin("user_game_scores", "guild_members.user_id", "user_game_scores.user_id") + .where("user_game_scores.game_id", "=", gameId) + .select([ + "user_game_scores.game_id", + "guilds.id as guild_id", + "guilds.name as guild_name", + "guilds.icon_url", + this.db.fn.sum("user_game_scores.score").as("total_score"), + this.db.fn.count("guild_members.id").as("member_count"), + ]) + .groupBy(["user_game_scores.game_id", "guilds.id", "guilds.name", "guilds.icon_url"]) + .orderBy("total_score", "desc") + .limit(limit) + .execute() + } +} diff --git a/apps/leaderboard-backend/src/repositories/UsersRepository.ts b/apps/leaderboard-backend/src/repositories/UsersRepository.ts new file mode 100644 index 0000000000..75aff47178 --- /dev/null +++ b/apps/leaderboard-backend/src/repositories/UsersRepository.ts @@ -0,0 +1,226 @@ +import type { Address } from "@happy.tech/common" +import type { Kysely } from "kysely" +import type { Database, NewUser, UpdateUser, User, UserTableId, UserWallet } from "../db/types" + +export class UserRepository { + constructor(private db: Kysely) {} + + async findById(id: UserTableId, includeWallets = false): Promise<(User & { wallets: UserWallet[] }) | undefined> { + const user = await this.db.selectFrom("users").where("id", "=", id).selectAll().executeTakeFirst() + + if (user && includeWallets) { + const wallets = await this.getUserWallets(id) + return { ...user, wallets } + } + + return user as User & { wallets: UserWallet[] } + } + + async findByWalletAddress( + walletAddress: Address, + includeWallets = false, + ): Promise<(User & { wallets: UserWallet[] }) | undefined> { + let user = await this.db + .selectFrom("users") + .where("primary_wallet", "=", walletAddress) + .selectAll() + .executeTakeFirst() + + if (!user) { + const walletEntry = await this.db + .selectFrom("user_wallets") + .where("wallet_address", "=", walletAddress) + .selectAll() + .executeTakeFirst() + + if (walletEntry) { + user = await this.findById(walletEntry.user_id) + } + } + + if (user && includeWallets) { + const wallets = await this.getUserWallets(user.id) + return { ...user, wallets } + } + + return user as User & { wallets: UserWallet[] } + } + + async findByUsername( + username: string, + includeWallets = false, + ): Promise<(User & { wallets: UserWallet[] }) | undefined> { + const user = await this.db.selectFrom("users").where("username", "=", username).selectAll().executeTakeFirst() + + if (user && includeWallets) { + const wallets = await this.getUserWallets(user.id) + return { ...user, wallets } + } + + return user as User & { wallets: UserWallet[] } + } + + async getUserWallets(userId: UserTableId): Promise { + return await this.db.selectFrom("user_wallets").where("user_id", "=", userId).selectAll().execute() + } + + async find(criteria: { + primary_wallet?: Address + username?: string + includeWallets?: boolean + }): Promise<(User & { wallets: UserWallet[] })[]> { + const { primary_wallet, username, includeWallets = false } = criteria + + if (primary_wallet) { + const user = await this.findByWalletAddress(primary_wallet, includeWallets) + return user ? [user] : [] + } + + if (username) { + const users = await this.db.selectFrom("users").where("username", "=", username).selectAll().execute() + + if (includeWallets && users.length > 0) { + const usersWithWallets = await Promise.all( + users.map(async (user) => { + const wallets = await this.getUserWallets(user.id) + return { ...user, wallets } + }), + ) + return usersWithWallets + } + + return users as (User & { wallets: UserWallet[] })[] + } + + return [] + } + + async create(user: NewUser): Promise { + const now = new Date().toISOString() + return await this.db.transaction().execute(async (trx) => { + const createdUser = await trx + .insertInto("users") + .values({ + ...user, + created_at: now, + updated_at: now, + }) + .returningAll() + .executeTakeFirstOrThrow() + + await trx + .insertInto("user_wallets") + .values({ + user_id: createdUser.id, + wallet_address: createdUser.primary_wallet, + is_primary: true, + created_at: now, + }) + .execute() + + return createdUser + }) + } + + async update(id: UserTableId, updateWith: UpdateUser): Promise { + await this.db + .updateTable("users") + .set({ + ...updateWith, + updated_at: new Date().toISOString(), + }) + .where("id", "=", id) + .execute() + + return this.findById(id) + } + + async addWallet(userId: UserTableId, walletAddress: Address): Promise { + // Check if wallet already exists for any user + const existingWallet = await this.db + .selectFrom("user_wallets") + .select("id") + .where("wallet_address", "=", walletAddress) + .executeTakeFirst() + if (existingWallet) { + return false + } + // Insert as secondary wallet + await this.db + .insertInto("user_wallets") + .values({ + user_id: userId, + wallet_address: walletAddress, + is_primary: false, + created_at: new Date().toISOString(), + }) + .execute() + return true + } + + async setWalletAsPrimary(userId: UserTableId, walletAddress: Address): Promise { + const wallet = await this.db + .selectFrom("user_wallets") + .where("user_id", "=", userId) + .where("wallet_address", "=", walletAddress) + .executeTakeFirst() + + if (!wallet) { + return false + } + + return await this.db.transaction().execute(async (trx) => { + await trx + .updateTable("users") + .set({ + primary_wallet: walletAddress, + updated_at: new Date().toISOString(), + }) + .where("id", "=", userId) + .execute() + + await trx.updateTable("user_wallets").set({ is_primary: false }).where("user_id", "=", userId).execute() + + await trx + .updateTable("user_wallets") + .set({ is_primary: true }) + .where("user_id", "=", userId) + .where("wallet_address", "=", walletAddress) + .execute() + + return true + }) + } + + async removeWallet(userId: UserTableId, walletAddress: Address): Promise { + const user = await this.findById(userId) + if (!user || user.primary_wallet === walletAddress) { + return false + } + + const result = await this.db + .deleteFrom("user_wallets") + .where("user_id", "=", userId) + .where("wallet_address", "=", walletAddress) + .execute() + + return result.length > 0 + } + + async delete(id: UserTableId): Promise { + return await this.db.transaction().execute(async (trx) => { + const user = await trx.selectFrom("users").where("id", "=", id).selectAll().executeTakeFirst() + + if (!user) { + return undefined + } + + await trx.deleteFrom("user_wallets").where("user_id", "=", id).execute() + await trx.deleteFrom("guild_members").where("user_id", "=", id).execute() + await trx.deleteFrom("user_game_scores").where("user_id", "=", id).execute() + await trx.deleteFrom("users").where("id", "=", id).execute() + + return user + }) + } +} diff --git a/apps/leaderboard-backend/src/repositories/index.ts b/apps/leaderboard-backend/src/repositories/index.ts new file mode 100644 index 0000000000..1dca81dfed --- /dev/null +++ b/apps/leaderboard-backend/src/repositories/index.ts @@ -0,0 +1,21 @@ +import { db } from "../db/driver" +import { GameRepository, GameScoreRepository } from "./GamesRepository" +import { GuildRepository } from "./GuildsRepository" +import { LeaderBoardRepository } from "./LeaderBoardRepository" +import { UserRepository } from "./UsersRepository" + +export type Repositories = { + userRepo: UserRepository + guildRepo: GuildRepository + leaderboardRepo: LeaderBoardRepository + gameRepo: GameRepository + gameScoreRepo: GameScoreRepository +} + +export const repositories: Repositories = { + userRepo: new UserRepository(db), + guildRepo: new GuildRepository(db), + leaderboardRepo: new LeaderBoardRepository(db), + gameRepo: new GameRepository(db), + gameScoreRepo: new GameScoreRepository(db), +} diff --git a/apps/leaderboard-backend/src/repositories/utils.ts b/apps/leaderboard-backend/src/repositories/utils.ts new file mode 100644 index 0000000000..4e2854ce70 --- /dev/null +++ b/apps/leaderboard-backend/src/repositories/utils.ts @@ -0,0 +1,21 @@ +import type { GameTableId, GuildTableId, UserTableId } from "../db/types" +import type { GameRepository } from "./GamesRepository" +import type { GuildRepository } from "./GuildsRepository" + +export async function isUserGameAdminById( + gameRepo: GameRepository, + userId: UserTableId, + gameId: GameTableId, +): Promise { + const game = await gameRepo.findById(gameId) + return game ? game.admin_id === userId : false +} + +export async function isUserGuildAdminById( + guildRepo: GuildRepository, + userId: UserTableId, + guildId: GuildTableId, +): Promise { + const member = await guildRepo.findGuildMember(guildId, userId) + return !!member && member.is_admin === true +} diff --git a/apps/leaderboard-backend/src/routes/api/gamesRoutes.ts b/apps/leaderboard-backend/src/routes/api/gamesRoutes.ts new file mode 100644 index 0000000000..75825aeb69 --- /dev/null +++ b/apps/leaderboard-backend/src/routes/api/gamesRoutes.ts @@ -0,0 +1,268 @@ +import type { Address } from "@happy.tech/common" +import { Hono } from "hono" +import type { GameTableId, UserTableId } from "../../db/types" +import { + AdminWalletParamValidation, + GameCreateDescription, + GameCreateValidation, + GameGetByIdDescription, + GameIdParamValidation, + GameListByAdminDescription, + GameQueryDescription, + GameQueryValidation, + GameScoresListDescription, + GameScoresQueryValidation, + GameUpdateDescription, + GameUpdateValidation, + ScoreSubmitDescription, + ScoreSubmitValidation, + UserGameScoresListDescription, + UserWalletParamValidation, +} from "../../validation/games" + +export default new Hono() + + // ==================================================================================================== + // Game Collection Routes + + /** + * List all games (optionally filter by name, admin, etc) + * GET /games + */ + .get("/", GameQueryDescription, GameQueryValidation, async (c) => { + try { + const { name, admin_id } = c.req.valid("query") + const { gameRepo } = c.get("repos") + + const games = await gameRepo.find({ + name: name ? name : undefined, + admin_id: admin_id ? (admin_id as UserTableId) : undefined, + }) + + return c.json({ ok: true, data: games }, 200) + } catch (err) { + console.error("Error listing games:", err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + /** + * Get a game by ID + * GET /games/:id + */ + .get("/:id", GameGetByIdDescription, GameIdParamValidation, async (c) => { + try { + const { id } = c.req.valid("param") + const { gameRepo } = c.get("repos") + + const gameId = Number.parseInt(id, 10) as GameTableId + const game = await gameRepo.findById(gameId) + if (!game) { + return c.json({ ok: false, error: "Game not found" }, 404) + } + + return c.json({ ok: true, data: game }, 200) + } catch (err) { + console.error(`Error fetching game ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + // ==================================================================================================== + // Game Listing by Admin + + /** + * List all games for a given admin wallet + * GET /games/admin/:admin_wallet + */ + .get("/admin/:admin_wallet", GameListByAdminDescription, AdminWalletParamValidation, async (c) => { + try { + const { admin_wallet } = c.req.valid("param") + const { gameRepo, userRepo } = c.get("repos") + + // Find user by wallet + const admin = await userRepo.findByWalletAddress(admin_wallet as Address) + if (!admin) { + return c.json({ ok: false, error: "Admin user not found for provided wallet address" }, 404) + } + const games = await gameRepo.findByAdmin(admin.id) + return c.json({ ok: true, data: games }, 200) + } catch (err) { + console.error("Error listing games by admin:", err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + /** + * Create a new game (admin required) + * POST /games + */ + .post("/", GameCreateDescription, GameCreateValidation, async (c) => { + try { + const gameData = c.req.valid("json") + const { gameRepo, userRepo } = c.get("repos") + + // Check if game name already exists + const existingGame = await gameRepo.findByExactName(gameData.name) + if (existingGame) { + return c.json({ ok: false, error: "Game name already exists" }, 409) + } + + // Check if admin user exists by wallet address + const admin = await userRepo.findByWalletAddress(gameData.admin_wallet as Address) + if (!admin) { + return c.json({ ok: false, error: "Admin user not found for provided wallet address" }, 404) + } + + const newGame = await gameRepo.create({ + name: gameData.name, + icon_url: gameData.icon_url || null, + description: gameData.description || null, + admin_id: admin.id, + }) + + return c.json({ ok: true, data: newGame }, 201) + } catch (err) { + console.error("Error creating game:", err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + // ==================================================================================================== + // Individual Game Routes + + /** + * Update game details (admin only) + * PATCH /games/:id + */ + .patch("/:id", GameUpdateDescription, GameIdParamValidation, GameUpdateValidation, async (c) => { + try { + const { id } = c.req.valid("param") + const updateData = c.req.valid("json") + const { gameRepo } = c.get("repos") + + // Check if game exists + const gameId = Number.parseInt(id, 10) as GameTableId + const game = await gameRepo.findById(gameId) + if (!game) { + return c.json({ ok: false, error: "Game not found" }, 404) + } + + // Check if name is being changed and is unique + if (updateData.name && updateData.name !== game.name) { + const existingGame = await gameRepo.findByExactName(updateData.name) + if (existingGame) { + return c.json({ ok: false, error: "Game name already exists" }, 409) + } + } + + const updatedGame = await gameRepo.update(gameId, updateData) + return c.json({ ok: true, data: updatedGame }, 200) + } catch (err) { + console.error(`Error updating game ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + // ==================================================================================================== + // Game Scores Routes + + /** + * Submit a new score for a game + * POST /games/:id/scores + */ + .post("/:id/scores", ScoreSubmitDescription, GameIdParamValidation, ScoreSubmitValidation, async (c) => { + try { + const { id } = c.req.valid("param") + const { user_wallet, score, metadata } = c.req.valid("json") + const { gameRepo, userRepo } = c.get("repos") + + // Check if game exists + const gameId = Number.parseInt(id, 10) as GameTableId + const game = await gameRepo.findById(gameId) + if (!game) { + return c.json({ ok: false, error: "Game not found" }, 404) + } + + // Check if user exists + const user = await userRepo.findByWalletAddress(user_wallet) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + // Submit score + const { gameScoreRepo } = c.get("repos") + const userId = user.id as UserTableId + const newScore = await gameScoreRepo.submitScore(userId, gameId, score, metadata) + + return c.json({ ok: true, data: newScore }, 201) + } catch (err) { + console.error(`Error submitting score for game ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + /** + * Get all scores for a game + * GET /games/:id/scores + */ + .get("/:id/scores", GameScoresListDescription, GameIdParamValidation, GameScoresQueryValidation, async (c) => { + try { + const { id } = c.req.valid("param") + const { top } = c.req.valid("query") + const { gameRepo } = c.get("repos") + + // Check if game exists + const gameId = Number.parseInt(id, 10) as GameTableId + const game = await gameRepo.findById(gameId) + if (!game) { + return c.json({ ok: false, error: "Game not found" }, 404) + } + + // Get scores for the game + const { gameScoreRepo } = c.get("repos") + const scores = await gameScoreRepo.findGameScores(gameId, top) + return c.json({ ok: true, data: scores }, 200) + } catch (err) { + console.error(`Error fetching scores for game ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + /** + * Get all scores for a user in a game + * GET /games/:id/scores/user/:user_wallet + */ + .get( + "/:id/scores/user/:user_wallet", + UserGameScoresListDescription, + GameIdParamValidation, + UserWalletParamValidation, + async (c) => { + try { + const { id, user_wallet } = c.req.valid("param") + const { gameRepo, userRepo } = c.get("repos") + + // Check if game exists + const gameId = Number.parseInt(id, 10) as GameTableId + const game = await gameRepo.findById(gameId) + if (!game) { + return c.json({ ok: false, error: "Game not found" }, 404) + } + + // Find user by wallet address + const user = await userRepo.findByWalletAddress(user_wallet as Address) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + // Get user's scores for the game + const { gameScoreRepo } = c.get("repos") + const scores = await gameScoreRepo.findUserScores(user.id, gameId) + return c.json({ ok: true, data: scores }, 200) + } catch (err) { + console.error(`Error fetching user ${c.req.param("userId")} scores for game ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }, + ) diff --git a/apps/leaderboard-backend/src/routes/api/guildsRoutes.ts b/apps/leaderboard-backend/src/routes/api/guildsRoutes.ts new file mode 100644 index 0000000000..766e1fb565 --- /dev/null +++ b/apps/leaderboard-backend/src/routes/api/guildsRoutes.ts @@ -0,0 +1,291 @@ +import { Hono } from "hono" +import type { GuildTableId, UserTableId } from "../../db/types" +import { + GuildCreateDescription, + GuildCreateValidation, + GuildGetByIdDescription, + GuildIdParamValidation, + GuildListMembersDescription, + GuildMemberAddDescription, + GuildMemberAddValidation, + GuildMemberDeleteDescription, + GuildMemberIdParamValidation, + GuildMemberUpdateDescription, + GuildMemberUpdateValidation, + GuildQueryDescription, + GuildQueryValidation, + GuildUpdateDescription, + GuildUpdateValidation, +} from "../../validation/guilds" + +export default new Hono() + + // ==================================================================================================== + // Guild Collection Routes + + /** + * List all guilds (optionally filter by name, creator, or include members). + * GET /guilds + */ + .get("/", GuildQueryDescription, GuildQueryValidation, async (c) => { + try { + const { name, creator_id, include_members } = c.req.valid("query") + const { guildRepo } = c.get("repos") + + const guilds = await guildRepo.find({ + name: name ? name : undefined, + creator_id: creator_id ? (creator_id as UserTableId) : undefined, + includeMembers: include_members ? include_members : false, + }) + + return c.json(guilds, 200) + } catch (err) { + console.error("Error listing guilds:", err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + /** + * Create a new guild (creator becomes admin). + * POST /guilds + */ + .post("/", GuildCreateDescription, GuildCreateValidation, async (c) => { + try { + const guildData = c.req.valid("json") + const { guildRepo } = c.get("repos") + + // Check if guild name already exists + const existingGuilds = await guildRepo.findByName(guildData.name) + if (existingGuilds.length > 0) { + return c.json({ ok: false, error: "Guild name already exists" }, 409) + } + + const newGuild = await guildRepo.create({ + name: guildData.name, + icon_url: guildData.icon_url || null, + creator_id: guildData.creator_id as UserTableId, + }) + + return c.json(newGuild, 201) + } catch (err) { + console.error("Error creating guild:", err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + /** + * Get a guild by ID (optionally include members). + * GET /guilds/:id + */ + .get("/:id", GuildGetByIdDescription, GuildIdParamValidation, async (c) => { + try { + const { id } = c.req.valid("param") + + const { guildRepo } = c.get("repos") + const includeMembers = c.req.query("include_members") === "true" + + const guildId = Number.parseInt(id, 10) as GuildTableId + const guild = await guildRepo.findById(guildId, includeMembers) + if (!guild) { + return c.json({ ok: false, error: "Guild not found" }, 404) + } + + return c.json(guild, 200) + } catch (err) { + console.error(`Error fetching guild ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + /** + * Update guild details (admin only). + * PATCH /guilds/:id + */ + .patch("/:id", GuildUpdateDescription, GuildIdParamValidation, GuildUpdateValidation, async (c) => { + try { + const { id } = c.req.valid("param") + + const updateData = c.req.valid("json") + const { guildRepo } = c.get("repos") + + // Check if guild exists + const guildId = Number.parseInt(id, 10) as GuildTableId + const guild = await guildRepo.findById(guildId) + if (!guild) { + return c.json({ ok: false, error: "Guild not found" }, 404) + } + + // Check if name is being changed and is unique + if (updateData.name && updateData.name !== guild.name) { + const existingGuilds = await guildRepo.findByName(updateData.name) + if (existingGuilds.length > 0) { + return c.json({ ok: false, error: "Guild name already exists" }, 409) + } + } + + const updatedGuild = await guildRepo.update(guildId, updateData) + return c.json(updatedGuild, 200) + } catch (err) { + console.error(`Error updating guild ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + // ==================================================================================================== + // Guild Member Routes + + /** + * List all members of a guild. + * GET /guilds/:id/members + */ + .get("/:id/members", GuildListMembersDescription, GuildIdParamValidation, async (c) => { + try { + const { id } = c.req.valid("param") + + const { guildRepo } = c.get("repos") + + // Check if guild exists + const guildId = Number.parseInt(id, 10) as GuildTableId + const guild = await guildRepo.findById(guildId) + if (!guild) { + return c.json({ ok: false, error: "Guild not found" }, 404) + } + + const members = await guildRepo.getGuildMembersWithUserDetails(guildId) + return c.json(members, 200) + } catch (err) { + console.error(`Error fetching members for guild ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + /** + * Add a member to a guild (admin only). + * POST /guilds/:id/members + */ + .post("/:id/members", GuildMemberAddDescription, GuildIdParamValidation, GuildMemberAddValidation, async (c) => { + try { + const { id } = c.req.valid("param") + let { user_id, username, is_admin } = c.req.valid("json") + const { guildRepo, userRepo } = c.get("repos") + + // Ensure guild exists + const guildId = Number.parseInt(id, 10) as GuildTableId + const guild = await guildRepo.findById(guildId) + if (!guild) { + return c.json({ ok: false, error: "Guild not found" }, 404) + } + + // Resolve user_id from username if needed + if (!user_id && username) { + const userByName = await userRepo.findByUsername(username) + if (!userByName) { + return c.json({ ok: false, error: "User not found by username" }, 404) + } + user_id = userByName.id + } + + if (!user_id) { + return c.json({ ok: false, error: "User ID or username required" }, 400) + } + + // Ensure user exists + const user = await userRepo.findById(user_id as UserTableId) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + // Add member to guild + const member = await guildRepo.addMember(guildId, user_id as UserTableId, is_admin || false) + if (!member) { + return c.json({ ok: false, error: "User is already a member of this guild" }, 409) + } + + return c.json(member, 201) + } catch (err) { + console.error(`Error adding member to guild ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + /** + * Update a guild member's role (admin only). + * PATCH /guilds/:id/members/:member_id + */ + .patch( + "/:id/members/:member_id", + GuildMemberUpdateDescription, + GuildIdParamValidation, + GuildMemberIdParamValidation, + GuildMemberUpdateValidation, + async (c) => { + try { + const { id, member_id } = c.req.valid("param") + + const { is_admin } = c.req.valid("json") + const { guildRepo } = c.get("repos") + + // Check if guild exists + const guildId = Number.parseInt(id, 10) as GuildTableId + const userId = Number.parseInt(member_id, 10) as UserTableId + const guild = await guildRepo.findById(guildId) + if (!guild) { + return c.json({ ok: false, error: "Guild not found" }, 404) + } + + // Update member role + const updatedMember = await guildRepo.updateMemberRole(guildId, userId, is_admin) + if (!updatedMember) { + return c.json({ ok: false, error: "Member not found in guild" }, 404) + } + + return c.json(updatedMember, 200) + } catch (err) { + console.error(`Error updating member role in guild ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }, + ) + + /** + * Remove a member from a guild (admin only). + * DELETE /guilds/:id/members/:member_id + */ + .delete( + "/:id/members/:member_id", + GuildMemberDeleteDescription, + GuildIdParamValidation, + GuildMemberIdParamValidation, + async (c) => { + try { + const { id, member_id } = c.req.valid("param") + + const { guildRepo } = c.get("repos") + + // Check if guild exists + const guildId = Number.parseInt(id, 10) as GuildTableId + const userId = Number.parseInt(member_id, 10) as UserTableId + const guild = await guildRepo.findById(guildId) + if (!guild) { + return c.json({ ok: false, error: "Guild not found" }, 404) + } + + // Remove member from guild + const removedMember = await guildRepo.removeMember(guildId, userId) + if (!removedMember) { + return c.json( + { + ok: false, + error: "Cannot remove member: user may be the guild creator or not a member", + }, + 400, + ) + } + + return c.json({ removed: true }, 200) + } catch (err) { + console.error(`Error removing member from guild ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }, + ) diff --git a/apps/leaderboard-backend/src/routes/api/leaderboardRoutes.ts b/apps/leaderboard-backend/src/routes/api/leaderboardRoutes.ts new file mode 100644 index 0000000000..3be4f01cf1 --- /dev/null +++ b/apps/leaderboard-backend/src/routes/api/leaderboardRoutes.ts @@ -0,0 +1,124 @@ +import { Hono } from "hono" +import type { GameTableId } from "../../db/types" +import { + GameGuildLeaderboardDescription, + GameGuildLeaderboardParamValidation, + GameGuildLeaderboardQueryValidation, + GameLeaderboardDescription, + GameLeaderboardParamValidation, + GameLeaderboardQueryValidation, + GlobalLeaderboardDescription, + GlobalLeaderboardValidation, + GuildLeaderboardDescription, + GuildLeaderboardValidation, +} from "../../validation/leaderboard" + +export default new Hono() + + /** + * GET /leaderboards/global - Global leaderboard (top users across all games) + * Returns users ranked by their total score across all games + */ + .get("/global", GlobalLeaderboardDescription, GlobalLeaderboardValidation, async (c) => { + try { + const { limit } = c.req.valid("query") + const { leaderboardRepo } = c.get("repos") + + const leaderboard = await leaderboardRepo.getGlobalLeaderboard(Number.parseInt(limit, 10)) + return c.json({ + ok: true, + data: leaderboard, + }) + } catch (err) { + console.error("Error fetching global leaderboard:", err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + /** + * GET /leaderboards/guilds - Guild leaderboard (top guilds) + * Returns guilds ranked by their members' total score across all games + */ + .get("/guilds", GuildLeaderboardDescription, GuildLeaderboardValidation, async (c) => { + try { + const { limit } = c.req.valid("query") + const { leaderboardRepo } = c.get("repos") + + const leaderboard = await leaderboardRepo.getGuildLeaderboard(Number.parseInt(limit, 10)) + return c.json({ + ok: true, + data: leaderboard, + }) + } catch (err) { + console.error("Error fetching guild leaderboard:", err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) + + /** + * GET /leaderboards/games/:id - Game-specific leaderboard (top users in a game) + * Returns users ranked by their score in a specific game + */ + .get( + "/games/:id", + GameLeaderboardDescription, + GameLeaderboardParamValidation, + GameLeaderboardQueryValidation, + async (c) => { + try { + const { id } = c.req.valid("param") + const { limit } = c.req.valid("query") + const { leaderboardRepo, gameRepo } = c.get("repos") + + // Check if game exists + const gameId = Number.parseInt(id, 10) as GameTableId + const game = await gameRepo.findById(gameId) + if (!game) { + return c.json({ ok: false, error: "Game not found" }, 404) + } + + const leaderboard = await leaderboardRepo.getGameLeaderboard(gameId, Number.parseInt(limit, 10)) + return c.json({ + ok: true, + data: leaderboard, + }) + } catch (err) { + console.error(`Error fetching leaderboard for game ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }, + ) + + /** + * GET /leaderboards/games/:id/guilds - Game-specific guild leaderboard + * Returns guilds ranked by their members' total score in a specific game + */ + .get( + "/games/:id/guilds", + GameGuildLeaderboardDescription, + GameGuildLeaderboardParamValidation, + GameGuildLeaderboardQueryValidation, + async (c) => { + try { + const { id } = c.req.valid("param") + const { limit } = c.req.valid("query") + const { leaderboardRepo, gameRepo } = c.get("repos") + + // Check if game exists + const gameId = Number.parseInt(id, 10) as GameTableId + const game = await gameRepo.findById(gameId) + if (!game) { + return c.json({ ok: false, error: "Game not found" }, 404) + } + + const leaderboard = await leaderboardRepo.getGameGuildLeaderboard(gameId, Number.parseInt(limit, 10)) + return c.json({ + ok: true, + data: leaderboard, + }) + } catch (err) { + console.error(`Error fetching guild leaderboard for game ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }, + ) diff --git a/apps/leaderboard-backend/src/routes/api/usersRoutes.ts b/apps/leaderboard-backend/src/routes/api/usersRoutes.ts new file mode 100644 index 0000000000..12800a027b --- /dev/null +++ b/apps/leaderboard-backend/src/routes/api/usersRoutes.ts @@ -0,0 +1,520 @@ +import { Hono } from "hono" +import type { UserTableId } from "../../db/types" +import { + PrimaryWalletParamValidation, + UserCreateDescription, + UserCreateValidation, + UserDeleteByIdDescription, + UserDeleteByPrimaryWalletDescription, + UserGetByIdDescription, + UserGetByPrimaryWalletDescription, + UserGuildsListDescription, + UserIdParamValidation, + UserQueryDescription, + UserQueryValidation, + UserUpdateByIdDescription, + UserUpdateByPrimaryWalletDescription, + UserUpdateValidation, + UserWalletAddByIdDescription, + UserWalletAddByPrimaryWalletDescription, + UserWalletRemoveByIdDescription, + UserWalletRemoveByPrimaryWalletDescription, + UserWalletSetPrimaryByIdDescription, + UserWalletSetPrimaryByPrimaryWalletDescription, + UserWalletValidation, + UserWalletsGetByIdDescription, + UserWalletsGetByPrimaryWalletDescription, +} from "../../validation/users" + +export default new Hono() + + // ==================================================================================================== + // User Collection Routes + + /** + * List users with optional filters (by primary_wallet or username). + * GET /users + */ + .get("/", UserQueryDescription, UserQueryValidation, async (c) => { + const query = c.req.valid("query") + const { userRepo } = c.get("repos") + + const users = await userRepo.find({ + primary_wallet: query.primary_wallet, + username: query.username, + includeWallets: query.include_wallets, + }) + + return c.json(users, 200) + }) + + /** + * Create a new user. + * POST /users + */ + .post("/", UserCreateDescription, UserCreateValidation, async (c) => { + const userData = c.req.valid("json") + const { userRepo } = c.get("repos") + + // Check if user with this wallet or username already exists + const existingByWallet = await userRepo.findByWalletAddress(userData.primary_wallet) + if (existingByWallet) { + return c.json({ ok: false, error: "Wallet address already registered" }, 409) + } + + const existingByUsername = await userRepo.findByUsername(userData.username) + if (existingByUsername) { + return c.json({ ok: false, error: "Username already taken" }, 409) + } + + const newUser = await userRepo.create({ + primary_wallet: userData.primary_wallet, + username: userData.username, + }) + + return c.json(newUser, 201) + }) + + // ==================================================================================================== + // User Resource Routes (by ID) + + /** + * Get user details by user ID. + * GET /users/:id + */ + .get("/:id", UserGetByIdDescription, UserIdParamValidation, async (c) => { + const { id } = c.req.valid("param") + const { userRepo } = c.get("repos") + + const userTableId = Number.parseInt(id, 10) as UserTableId + const user = await userRepo.findById(userTableId, true) // Include wallets + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + return c.json(user, 200) + }) + + /** + * Update user details by user ID. + * PATCH /users/:id + */ + .patch("/:id", UserUpdateByIdDescription, UserIdParamValidation, UserUpdateValidation, async (c) => { + const { id } = c.req.valid("param") + const updateData = c.req.valid("json") + const { userRepo } = c.get("repos") + + // Check if user exists + const userId = Number.parseInt(id, 10) as UserTableId + const user = await userRepo.findById(userId) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + // Check if username is being changed and is unique + if (updateData.username && updateData.username !== user.username) { + const existingUser = await userRepo.findByUsername(updateData.username) + if (existingUser) { + return c.json({ ok: false, error: "Username already taken" }, 409) + } + } + + const updatedUser = await userRepo.update(userId, updateData) + return c.json(updatedUser, 200) + }) + + /** + * Delete a user by user ID and all associated data. + * DELETE /users/:id + */ + .delete("/:id", UserDeleteByIdDescription, UserIdParamValidation, async (c) => { + const { id } = c.req.valid("param") + const { userRepo } = c.get("repos") + + // Check if user exists + const userId = Number.parseInt(id, 10) as UserTableId + const user = await userRepo.findById(userId) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + const success = await userRepo.delete(userId) + if (!success) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + return c.json(user, 200) + }) + + // ==================================================================================================== + // User Resource Routes (by Primary Wallet Address) + + /** + * Get user details by primary wallet address. + * GET /users/pw/:primary_wallet + */ + .get("/pw/:primary_wallet", UserGetByPrimaryWalletDescription, PrimaryWalletParamValidation, async (c) => { + const { primary_wallet } = c.req.valid("param") + const { userRepo } = c.get("repos") + + const user = await userRepo.findByWalletAddress(primary_wallet, true) // Include wallets + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + return c.json(user, 200) + }) + + /** + * Update user details by primary wallet address. + * PATCH /users/pw/:primary_wallet + */ + .patch( + "/pw/:primary_wallet", + UserUpdateByPrimaryWalletDescription, + PrimaryWalletParamValidation, + UserUpdateValidation, + async (c) => { + const { primary_wallet } = c.req.valid("param") + const updateData = c.req.valid("json") + const { userRepo } = c.get("repos") + + // Check if user exists + const user = await userRepo.findByWalletAddress(primary_wallet) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + // Check if username is being changed and is unique + if (updateData.username && updateData.username !== user.username) { + const existingUser = await userRepo.findByUsername(updateData.username) + if (existingUser) { + return c.json({ ok: false, error: "Username already taken" }, 409) + } + } + + const updatedUser = await userRepo.update(user.id, updateData) + return c.json(updatedUser, 200) + }, + ) + + /** + * Delete a user by primary wallet address and all associated wallets, guild memberships, and scores. + * DELETE /users/pw/:primary_wallet + */ + .delete("/pw/:primary_wallet", UserDeleteByPrimaryWalletDescription, PrimaryWalletParamValidation, async (c) => { + const { primary_wallet } = c.req.valid("param") + const { userRepo } = c.get("repos") + + // Check if user exists + const user = await userRepo.findByWalletAddress(primary_wallet) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + const success = await userRepo.delete(user.id) + if (!success) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + return c.json(user, 200) + }) + + // ==================================================================================================== + // User Wallets Collection Routes + + /** + * Get all wallets for a user by user ID. + * GET /users/:id/wallets + */ + .get("/:id/wallets", UserWalletsGetByIdDescription, UserIdParamValidation, async (c) => { + const { id } = c.req.valid("param") + const { userRepo } = c.get("repos") + + // Check if user exists + const userId = Number.parseInt(id, 10) as UserTableId + const user = await userRepo.findById(userId) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + const wallets = await userRepo.getUserWallets(userId) + return c.json(wallets, 200) + }) + + /** + * Get all wallets for a user by primary wallet address. + * GET /users/pw/:primary_wallet/wallets + */ + .get( + "/pw/:primary_wallet/wallets", + UserWalletsGetByPrimaryWalletDescription, + PrimaryWalletParamValidation, + async (c) => { + const { primary_wallet } = c.req.valid("param") + const { userRepo } = c.get("repos") + + // Check if user exists + const user = await userRepo.findByWalletAddress(primary_wallet) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + const wallets = await userRepo.getUserWallets(user.id) + return c.json(wallets, 200) + }, + ) + + /** + * Add a wallet to a user by user ID. + * POST /users/:id/wallets + */ + .post("/:id/wallets", UserWalletAddByIdDescription, UserIdParamValidation, UserWalletValidation, async (c) => { + const { id } = c.req.valid("param") + const { wallet_address } = c.req.valid("json") + const { userRepo } = c.get("repos") + + // Check if user exists + const userId = Number.parseInt(id, 10) as UserTableId + const user = await userRepo.findById(userId) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + // Check if wallet already belongs to another user + const existingUser = await userRepo.findByWalletAddress(wallet_address) + if (existingUser && existingUser.id !== userId) { + return c.json({ ok: false, error: "Wallet already registered to another user" }, 409) + } + + const success = await userRepo.addWallet(userId, wallet_address) + if (!success) { + return c.json({ ok: false, error: "Wallet already exists for this user" }, 409) + } + + // Get updated user with wallets + const updatedUser = await userRepo.findById(userId, true) + return c.json(updatedUser, 200) + }) + + /** + * Add a wallet to a user by primary wallet address. + * POST /users/pw/:primary_wallet/wallets + */ + .post( + "/pw/:primary_wallet/wallets", + UserWalletAddByPrimaryWalletDescription, + PrimaryWalletParamValidation, + UserWalletValidation, + async (c) => { + const { primary_wallet } = c.req.valid("param") + const { wallet_address } = c.req.valid("json") + const { userRepo } = c.get("repos") + + if (primary_wallet === wallet_address) { + return c.json({ ok: false, error: "Primary wallet already exists for this user" }, 400) + } + + // Check if user exists + const user = await userRepo.findByWalletAddress(primary_wallet) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + // Check if wallet already belongs to another user + const existingUser = await userRepo.findByWalletAddress(wallet_address) + if (existingUser && existingUser.id !== user.id) { + return c.json({ ok: false, error: "Wallet already registered to another user" }, 409) + } + + const success = await userRepo.addWallet(user.id, wallet_address) + if (!success) { + return c.json({ ok: false, error: "Wallet already exists for this user" }, 409) + } + + // Get updated user with wallets + const updatedUser = await userRepo.findById(user.id, true) + return c.json(updatedUser, 200) + }, + ) + + // ==================================================================================================== + // User Wallet Resource Routes (Set Primary, Remove) + + /** + * Set a wallet as primary for a user by user ID. + * PATCH /users/:id/wallets + */ + .patch( + "/:id/wallets", + UserWalletSetPrimaryByIdDescription, + UserIdParamValidation, + UserWalletValidation, + async (c) => { + const { id } = c.req.valid("param") + const { wallet_address } = c.req.valid("json") + const { userRepo } = c.get("repos") + + // Check if user exists + const userId = Number.parseInt(id, 10) as UserTableId + const user = await userRepo.findById(userId) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + if (user.primary_wallet === wallet_address) { + return c.json({ ok: false, error: "Wallet is already primary" }, 400) + } + + const success = await userRepo.setWalletAsPrimary(userId, wallet_address) + if (!success) { + return c.json({ ok: false, error: "Wallet not found for this user" }, 404) + } + + // Get updated user with wallets + const updatedUser = await userRepo.findById(userId, true) + return c.json(updatedUser, 200) + }, + ) + + /** + * Set a wallet as primary for a user by primary wallet address. + * PATCH /users/pw/:primary_wallet/wallets + */ + .patch( + "/pw/:primary_wallet/wallets", + UserWalletSetPrimaryByPrimaryWalletDescription, + PrimaryWalletParamValidation, + UserWalletValidation, + async (c) => { + const { primary_wallet } = c.req.valid("param") + const { wallet_address } = c.req.valid("json") + const { userRepo } = c.get("repos") + + if (primary_wallet === wallet_address) { + return c.json({ ok: false, error: "Primary wallet cannot be set as primary" }, 400) + } + + // Check if user exists + const user = await userRepo.findByWalletAddress(primary_wallet) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + if (user.primary_wallet === wallet_address) { + return c.json({ ok: false, error: "Wallet is already primary" }, 400) + } + + const success = await userRepo.setWalletAsPrimary(user.id, wallet_address) + if (!success) { + return c.json({ ok: false, error: "Wallet not found for this user" }, 404) + } + + // Get updated user with wallets + const updatedUser = await userRepo.findById(user.id, true) + return c.json(updatedUser, 200) + }, + ) + + /** + * Remove a wallet from a user by user ID. + * DELETE /users/:id/wallets + */ + .delete("/:id/wallets", UserWalletRemoveByIdDescription, UserIdParamValidation, UserWalletValidation, async (c) => { + const { id } = c.req.valid("param") + const { wallet_address } = c.req.valid("json") + const { userRepo } = c.get("repos") + + // Check if user exists + const userId = Number.parseInt(id, 10) as UserTableId + const user = await userRepo.findById(userId) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + if (user.primary_wallet === wallet_address) { + return c.json({ ok: false, error: "Cannot remove primary wallet" }, 400) + } + + const success = await userRepo.removeWallet(userId, wallet_address) + if (!success) { + return c.json( + { + ok: false, + error: "Cannot remove wallet: it may be the primary wallet or not found", + }, + 400, + ) + } + + // Get updated user with wallets + const updatedUser = await userRepo.findById(userId, true) + return c.json(updatedUser, 200) + }) + + /** + * Remove a wallet from a user by primary wallet address. + * DELETE /users/pw/:primary_wallet/wallets + */ + .delete( + "/pw/:primary_wallet/wallets", + UserWalletRemoveByPrimaryWalletDescription, + PrimaryWalletParamValidation, + UserWalletValidation, + async (c) => { + const { primary_wallet } = c.req.valid("param") + const { wallet_address } = c.req.valid("json") + const { userRepo } = c.get("repos") + + if (primary_wallet === wallet_address) { + return c.json({ ok: false, error: "Cannot remove primary wallet" }, 400) + } + + // Check if user exists + const user = await userRepo.findByWalletAddress(primary_wallet) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + if (user.primary_wallet === wallet_address) { + return c.json({ ok: false, error: "Cannot remove primary wallet" }, 400) + } + + const success = await userRepo.removeWallet(user.id, wallet_address) + if (!success) { + return c.json( + { + ok: false, + error: "Cannot remove wallet: it may be the primary wallet or not found", + }, + 400, + ) + } + + // Get updated user with wallets + const updatedUser = await userRepo.findById(user.id, true) + return c.json(updatedUser, 200) + }, + ) + + /** + * Get all guilds a user belongs to. + * GET /users/:id/guilds + */ + .get("/:id/guilds", UserGuildsListDescription, UserIdParamValidation, async (c) => { + try { + const { id } = c.req.valid("param") + const { guildRepo, userRepo } = c.get("repos") + + // Check if user exists + const userId = Number.parseInt(id, 10) as UserTableId + const user = await userRepo.findById(userId) + if (!user) { + return c.json({ ok: false, error: "User not found" }, 404) + } + + const guilds = await guildRepo.getUserGuilds(userId) + return c.json(guilds, 200) + } catch (err) { + console.error(`Error fetching guilds for user ${c.req.param("id")}:`, err) + return c.json({ ok: false, error: "Internal Server Error" }, 500) + } + }) diff --git a/apps/leaderboard-backend/src/server.ts b/apps/leaderboard-backend/src/server.ts new file mode 100644 index 0000000000..d96185801b --- /dev/null +++ b/apps/leaderboard-backend/src/server.ts @@ -0,0 +1,104 @@ +import { Scalar } from "@scalar/hono-api-reference" +import { Hono } from "hono" +import { openAPISpecs } from "hono-openapi" +import { every } from "hono/combine" +import { cors } from "hono/cors" +import { HTTPException } from "hono/http-exception" +import { logger as loggerMiddleware } from "hono/logger" +import { prettyJSON as prettyJSONMiddleware } from "hono/pretty-json" +import { requestId as requestIdMiddleware } from "hono/request-id" +import { timeout as timeoutMiddleware } from "hono/timeout" +import { timing as timingMiddleware } from "hono/timing" +import { ZodError } from "zod" +import { env } from "./env" +import { type Repositories, repositories } from "./repositories" +import gamesApi from "./routes/api/gamesRoutes" +import guildsApi from "./routes/api/guildsRoutes" +import leaderboardApi from "./routes/api/leaderboardRoutes" +import usersApi from "./routes/api/usersRoutes" + +declare module "hono" { + interface ContextVariableMap { + repos: Repositories + } +} + +const app = new Hono() + .use(requestIdMiddleware()) + .use(cors()) + .use("*", async (c, next) => { + c.set("repos", repositories) + await next() + }) + .use( + every( + timingMiddleware(), // measure response times + loggerMiddleware(), // log all calls to the console + prettyJSONMiddleware(), // add '?pretty' to any json endpoint to prettyprint + ), + ) + .use(timeoutMiddleware(5000)) + // Landing Page + .get("/", (c) => + c.html( + `

Leaderboard API

+

Visit the Happy Docs for more information, or the Open API Spec

`, + ), + ) + .route("/users", usersApi) + .route("/guilds", guildsApi) + .route("/games", gamesApi) + .route("/leaderboards", leaderboardApi) + +// Serve OpenAPI JSON at /docs/openapi.json +app.get( + "/docs/openapi.json", + openAPISpecs(app, { + documentation: { + info: { + title: "Leaderboard API", + version: "0.1.0", + description: "Leaderboard backend for HappyChain", + }, + servers: [ + { + url: `http://localhost:${env.PORT || 4545}`, + description: "Local server", + }, + ], + }, + }), +) + +// Adds Scalar UI for API docs +app.get( + "/docs", + // https://github.com/scalar/scalar/blob/main/documentation/configuration.md + Scalar({ + pageTitle: "Leaderboard API Reference - HappyChain", + theme: "kepler", + url: "/docs/openapi.json", + showSidebar: true, + hideSearch: false, + }), +) + +app.notFound((c) => c.json({ message: "Not Found", ok: false, requestId: c.get("requestId"), url: c.req.url }, 404)) +app.onError(async (err, c) => { + if (err instanceof HTTPException && err.cause instanceof ZodError) { + const error = err.cause.issues.map((i) => ({ path: i.path.join("."), message: i.message })) + return c.json({ error, requestId: c.get("requestId"), url: c.req.url }, 422) + } + if (err instanceof HTTPException) return err.getResponse() + return c.json( + { + error: err.message, + requestId: c.get("requestId"), + url: c.req.url, + }, + 500, + ) +}) + +export type AppType = typeof app +export { app } diff --git a/apps/leaderboard-backend/src/validation/games/gameRouteDescriptions.ts b/apps/leaderboard-backend/src/validation/games/gameRouteDescriptions.ts new file mode 100644 index 0000000000..9a0e42d360 --- /dev/null +++ b/apps/leaderboard-backend/src/validation/games/gameRouteDescriptions.ts @@ -0,0 +1,299 @@ +import { describeRoute } from "hono-openapi" +import { + ErrorResponseSchemaObj, + GameListResponseSchemaObj, + GameResponseSchemaObj, + UserGameScoreResponseSchemaObj, +} from "./gameSchemas" + +// ==================================================================================================== +// Game Collection + +export const GameQueryDescription = describeRoute({ + validateResponse: false, + description: "Get a list of games, optionally filtered by name or admin.", + responses: { + 200: { + description: "Successfully retrieved games.", + content: { + "application/json": { + schema: GameListResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid query parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const GameCreateDescription = describeRoute({ + validateResponse: false, + description: "Create a new game (admin required).", + requestBody: { + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 201: { + description: "Game created successfully.", + content: { + "application/json": { + schema: GameResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid request body.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 409: { + description: "Game name already exists.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +// ==================================================================================================== +// Individual Game + +export const GameGetByIdDescription = describeRoute({ + validateResponse: false, + description: "Get a game by its ID.", + responses: { + 200: { + description: "Successfully retrieved game.", + content: { + "application/json": { + schema: GameResponseSchemaObj, + }, + }, + }, + 404: { + description: "Game not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const GameUpdateDescription = describeRoute({ + validateResponse: false, + description: "Update details of a game (admin only).", + requestBody: { + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 200: { + description: "Game updated successfully.", + content: { + "application/json": { + schema: GameResponseSchemaObj, + }, + }, + }, + 404: { + description: "Game not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 409: { + description: "Game name already exists.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid request body or parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +// ==================================================================================================== +// Game Listing by Admin + +export const GameListByAdminDescription = describeRoute({ + validateResponse: false, + description: "List all games for a given admin wallet.", + responses: { + 200: { + description: "Successfully retrieved games for admin.", + content: { + "application/json": { + schema: GameListResponseSchemaObj, + }, + }, + }, + 404: { + description: "Admin user not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid admin wallet parameter.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +// ==================================================================================================== +// Game Scores + +export const ScoreSubmitDescription = describeRoute({ + validateResponse: false, + description: "Submit a new score for a game.", + requestBody: { + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 201: { + description: "Score submitted successfully.", + content: { + "application/json": { + schema: UserGameScoreResponseSchemaObj, + }, + }, + }, + 404: { + description: "Game or user not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid request body or parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 409: { + description: "Duplicate score or conflict.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const GameScoresListDescription = describeRoute({ + validateResponse: false, + description: "Get all scores for a game.", + responses: { + 200: { + description: "Successfully retrieved scores.", + content: { + "application/json": { + schema: GameListResponseSchemaObj, + }, + }, + }, + 404: { + description: "Game not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserGameScoresListDescription = describeRoute({ + validateResponse: false, + description: "Get all scores for a user in a game.", + responses: { + 200: { + description: "Successfully retrieved user's scores for the game.", + content: { + "application/json": { + schema: GameListResponseSchemaObj, + }, + }, + }, + 404: { + description: "Game or user not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) diff --git a/apps/leaderboard-backend/src/validation/games/gameRouteValidations.ts b/apps/leaderboard-backend/src/validation/games/gameRouteValidations.ts new file mode 100644 index 0000000000..5caada879e --- /dev/null +++ b/apps/leaderboard-backend/src/validation/games/gameRouteValidations.ts @@ -0,0 +1,20 @@ +import { validator as zValidator } from "hono-openapi/zod" +import { + AdminWalletParamSchema, + GameCreateRequestSchema, + GameIdParamSchema, + GameQuerySchema, + GameScoresQuerySchema, + GameUpdateRequestSchema, + ScoreSubmitRequestSchema, + UserWalletParamSchema, +} from "./gameSchemas" + +export const GameQueryValidation = zValidator("query", GameQuerySchema) +export const GameCreateValidation = zValidator("json", GameCreateRequestSchema) +export const GameIdParamValidation = zValidator("param", GameIdParamSchema) +export const AdminWalletParamValidation = zValidator("param", AdminWalletParamSchema) +export const GameUpdateValidation = zValidator("json", GameUpdateRequestSchema) +export const ScoreSubmitValidation = zValidator("json", ScoreSubmitRequestSchema) +export const GameScoresQueryValidation = zValidator("query", GameScoresQuerySchema) +export const UserWalletParamValidation = zValidator("param", UserWalletParamSchema) diff --git a/apps/leaderboard-backend/src/validation/games/gameSchemas.ts b/apps/leaderboard-backend/src/validation/games/gameSchemas.ts new file mode 100644 index 0000000000..95c70dc82c --- /dev/null +++ b/apps/leaderboard-backend/src/validation/games/gameSchemas.ts @@ -0,0 +1,184 @@ +import { z } from "@hono/zod-openapi" +import { resolver } from "hono-openapi/zod" +import { isHex } from "viem" + +// ==================================================================================================== +// Response Schemas + +export const GameResponseSchema = z + .object({ + id: z.number().int(), + name: z.string().min(3).max(50), + icon_url: z.string().nullable(), + description: z.string().nullable(), + admin_id: z.number().int(), + created_at: z.string(), + updated_at: z.string(), + }) + .strict() + .openapi({ + example: { + id: 1, + name: "Crypto Racer", + icon_url: "https://example.com/game-icon.png", + description: "Race to earn the most crypto", + admin_id: 1, + created_at: "2023-01-01T00:00:00.000Z", + updated_at: "2023-01-01T00:00:00.000Z", + }, + }) + +export const UserGameScoreResponseSchema = z + .object({ + id: z.number().int(), + user_id: z.number().int(), + game_id: z.number().int(), + score: z.number().int(), + metadata: z.string().optional(), + created_at: z.string(), + updated_at: z.string(), + username: z.string().optional(), + game_name: z.string().optional(), + }) + .strict() + .openapi({ + example: { + id: 1, + user_id: 2, + game_id: 3, + score: 1000, + metadata: '{"level": 5, "items": ["sword", "shield"]}', + created_at: "2023-01-01T00:00:00.000Z", + updated_at: "2023-01-01T00:00:00.000Z", + username: "player1", + game_name: "Crypto Racer", + }, + }) + +export const GameResponseSchemaObj = resolver(GameResponseSchema) + +export const GameListResponseSchemaObj = resolver(z.array(GameResponseSchema)) + +export const UserGameScoreResponseSchemaObj = resolver(UserGameScoreResponseSchema) + +// Generic error schema +export const ErrorResponseSchemaObj = resolver(z.object({ ok: z.literal(false), error: z.string() })) + +// ==================================================================================================== +// Request Body Schemas + +export const GameQuerySchema = z + .object({ + name: z.string().min(3).max(50).optional(), + admin_id: z.number().int().optional(), + }) + .strict() + .refine((data) => data.name !== undefined || data.admin_id !== undefined, { + message: "At least one filter (name or admin_id) must be provided", + }) + .openapi({ + example: { + name: "Crypto", + admin_id: 1, + }, + }) + +export const GameCreateRequestSchema = z + .object({ + name: z.string().min(3).max(50), + icon_url: z.string().url().nullable().optional(), + description: z.string().nullable().optional(), + admin_wallet: z.string().refine(isHex), + }) + .strict() + .openapi({ + example: { + name: "Crypto Racer", + icon_url: "https://example.com/game-icon.png", + description: "Race to earn the most crypto", + admin_wallet: "0xBC5F85819B9b970c956f80c1Ab5EfbE73c818eaa", + }, + }) + +export const GameUpdateRequestSchema = z + .object({ + name: z.string().min(3).max(50).optional(), + icon_url: z.string().url().nullable().optional(), + description: z.string().nullable().optional(), + }) + .strict() + .refine((data) => data.name !== undefined || data.icon_url !== undefined || data.description !== undefined, { + message: "At least one field must be provided", + }) + .openapi({ + example: { + name: "Crypto Racing", + icon_url: "https://example.com/updated-icon.png", + description: "Updated game description", + }, + }) + +export const ScoreSubmitRequestSchema = z + .object({ + user_wallet: z.string().refine(isHex, { message: "User wallet must be a valid hex string" }), + score: z.number().int().positive(), + metadata: z.string().optional(), + }) + .strict() + .openapi({ + example: { + user_wallet: "0xBC5F85819B9b970c956f80c1Ab5EfbE73c818eaa", + score: 1000, + metadata: '{"level": 5, "items": ["sword", "shield"]}', + }, + }) + +export const GameScoresQuerySchema = z + .object({ + top: z.number().int().positive().optional().default(50), + }) + .strict() + .openapi({ + example: { + top: 10, + }, + }) + +// ==================================================================================================== +// Request Param Schemas + +export const GameIdParamSchema = z + .object({ + id: z.string().regex(/^\d+$/, { message: "Must be a positive integer string" }), + }) + .strict() + .openapi({ + example: { id: "1" }, + }) + +export const AdminIdParamSchema = z + .object({ + admin_id: z.string().regex(/^\d+$/, { message: "Must be a positive integer string" }), + }) + .strict() + .openapi({ + example: { admin_id: "1" }, + }) + +export const AdminWalletParamSchema = z + .object({ + admin_wallet: z.string().refine(isHex, { message: "Admin wallet must be a valid hex string" }), + }) + .strict() + .openapi({ + example: { admin_wallet: "0xBC5F85819B9b970c956f80c1Ab5EfbE73c818eaa" }, + }) + +export const UserWalletParamSchema = z + .object({ + user_wallet: z.string().refine(isHex, { message: "User wallet must be a valid hex string" }), + }) + .strict() + .openapi({ + example: { user_wallet: "0xBC5F85819B9b970c956f80c1Ab5EfbE73c818eaa" }, + }) diff --git a/apps/leaderboard-backend/src/validation/games/index.ts b/apps/leaderboard-backend/src/validation/games/index.ts new file mode 100644 index 0000000000..4240381d92 --- /dev/null +++ b/apps/leaderboard-backend/src/validation/games/index.ts @@ -0,0 +1,3 @@ +export * from "./gameRouteDescriptions" +export * from "./gameRouteValidations" +export * from "./gameSchemas" diff --git a/apps/leaderboard-backend/src/validation/guilds/guildRouteDescriptions.ts b/apps/leaderboard-backend/src/validation/guilds/guildRouteDescriptions.ts new file mode 100644 index 0000000000..6ed6681229 --- /dev/null +++ b/apps/leaderboard-backend/src/validation/guilds/guildRouteDescriptions.ts @@ -0,0 +1,277 @@ +import { describeRoute } from "hono-openapi" +import { ErrorResponseSchemaObj, GuildListResponseSchemaObj, GuildResponseSchemaObj } from "./guildSchemas" + +// ==================================================================================================== +// Guild Collection + +export const GuildQueryDescription = describeRoute({ + validateResponse: false, + description: "Get a list of guilds, optionally filtered by name or creator_id.", + responses: { + 200: { + description: "Successfully retrieved guilds.", + content: { + "application/json": { + schema: GuildListResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid query parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const GuildCreateDescription = describeRoute({ + validateResponse: false, + description: "Create a new guild.", + requestBody: { + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 201: { + description: "Guild created successfully.", + content: { + "application/json": { + schema: GuildResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid request body.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const GuildGetByIdDescription = describeRoute({ + validateResponse: false, + description: "Get a guild by its ID (optionally include members).", + responses: { + 200: { + description: "Successfully retrieved guild.", + content: { + "application/json": { + schema: GuildResponseSchemaObj, + }, + }, + }, + 404: { + description: "Guild not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const GuildUpdateDescription = describeRoute({ + validateResponse: false, + description: "Update details of a guild (admin only).", + requestBody: { + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 200: { + description: "Guild updated successfully.", + content: { + "application/json": { + schema: GuildResponseSchemaObj, + }, + }, + }, + 404: { + description: "Guild not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid request body or parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const GuildListMembersDescription = describeRoute({ + validateResponse: false, + description: "List all members of a guild.", + responses: { + 200: { + description: "Successfully retrieved guild members.", + content: { + "application/json": { + schema: {}, + }, + }, + }, + 404: { + description: "Guild not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const GuildMemberAddDescription = describeRoute({ + validateResponse: false, + description: "Add a member to a guild (admin only).", + requestBody: { + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 201: { + description: "Member added successfully.", + content: { + "application/json": { + schema: {}, + }, + }, + }, + 404: { + description: "Guild or user not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 409: { + description: "User is already a member of the guild.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid request body or parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const GuildMemberUpdateDescription = describeRoute({ + validateResponse: false, + description: "Update a guild member's role (admin only).", + requestBody: { + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 200: { + description: "Member updated successfully.", + content: { + "application/json": { + schema: {}, + }, + }, + }, + 404: { + description: "Guild or member not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid request body or parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const GuildMemberDeleteDescription = describeRoute({ + validateResponse: false, + description: "Remove a member from a guild (admin only).", + responses: { + 200: { + description: "Member removed successfully.", + content: { + "application/json": { + schema: {}, + }, + }, + }, + 404: { + description: "Guild or member not found, or user is the creator.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid parameters or removal not allowed.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) diff --git a/apps/leaderboard-backend/src/validation/guilds/guildRouteValidations.ts b/apps/leaderboard-backend/src/validation/guilds/guildRouteValidations.ts new file mode 100644 index 0000000000..592473e514 --- /dev/null +++ b/apps/leaderboard-backend/src/validation/guilds/guildRouteValidations.ts @@ -0,0 +1,24 @@ +import { validator as zValidator } from "hono-openapi/zod" +import { + GuildCreateRequestSchema, + GuildIdParamSchema, + GuildMemberAddRequestSchema, + GuildMemberIdParamSchema, + GuildMemberUpdateRequestSchema, + GuildQuerySchema, + GuildUpdateRequestSchema, +} from "./guildSchemas" + +export const GuildQueryValidation = zValidator("query", GuildQuerySchema) + +export const GuildCreateValidation = zValidator("json", GuildCreateRequestSchema) + +export const GuildUpdateValidation = zValidator("json", GuildUpdateRequestSchema) + +export const GuildMemberAddValidation = zValidator("json", GuildMemberAddRequestSchema) + +export const GuildMemberUpdateValidation = zValidator("json", GuildMemberUpdateRequestSchema) + +export const GuildIdParamValidation = zValidator("param", GuildIdParamSchema) + +export const GuildMemberIdParamValidation = zValidator("param", GuildMemberIdParamSchema) diff --git a/apps/leaderboard-backend/src/validation/guilds/guildSchemas.ts b/apps/leaderboard-backend/src/validation/guilds/guildSchemas.ts new file mode 100644 index 0000000000..59d051f022 --- /dev/null +++ b/apps/leaderboard-backend/src/validation/guilds/guildSchemas.ts @@ -0,0 +1,169 @@ +import { z } from "@hono/zod-openapi" +import { resolver } from "hono-openapi/zod" +import { type Address, isHex } from "viem" + +// ==================================================================================================== +// Response Schemas + +const GuildResponseSchema = z + .object({ + id: z.number().int(), + name: z.string(), + icon_url: z.string().nullable(), + creator_id: z.number().int(), + created_at: z.string(), + updated_at: z.string(), + }) + .strict() + .openapi({ + example: { + id: 1, + name: "Alpha Guild", + icon_url: "https://example.com/icon.png", + creator_id: 1, + created_at: "2023-01-01T00:00:00.000Z", + updated_at: "2023-01-01T00:00:00.000Z", + }, + }) + +const GuildMemberResponseSchema = z + .object({ + id: z.number().int(), + guild_id: z.number().int(), + user_id: z.number().int(), + is_admin: z.boolean(), + joined_at: z.string(), + // Extended properties when including user details + username: z.string().optional(), + primary_wallet: z + .string() + .refine(isHex) + .transform((val) => val as Address) + .optional(), + }) + .strict() + .openapi({ + example: { + id: 1, + guild_id: 1, + user_id: 1, + is_admin: true, + joined_at: "2023-01-01T00:00:00.000Z", + username: "player1", + primary_wallet: "0xBC5F85819B9b970c956f80c1Ab5EfbE73c818eaa", + }, + }) + +export const GuildResponseSchemaObj = resolver(GuildResponseSchema) + +export const GuildListResponseSchemaObj = resolver(z.array(GuildResponseSchema)) + +export const GuildMemberResponseSchemaObj = resolver(GuildMemberResponseSchema) + +export const GuildMemberListResponseSchemaObj = resolver(z.array(GuildMemberResponseSchema)) + +// Generic error schema +export const ErrorResponseSchemaObj = resolver(z.object({ ok: z.literal(false), error: z.string() })) + +// ==================================================================================================== +// Request Body Schemas + +export const GuildQuerySchema = z + .object({ + name: z.string().optional(), + creator_id: z.number().int().optional(), + include_members: z.boolean().default(false).optional(), + }) + .strict() + .openapi({ + example: { + name: "Alpha", + creator_id: 1, + include_members: true, + }, + }) + +export const GuildCreateRequestSchema = z + .object({ + name: z.string().min(3).max(50), + icon_url: z.string().url().nullable().optional(), + creator_id: z.number().int(), + }) + .strict() + .openapi({ + example: { + name: "Alpha Guild", + icon_url: "https://example.com/icon.png", + creator_id: 1, + }, + }) + +export const GuildUpdateRequestSchema = z + .object({ + name: z.string().min(3).max(50).optional(), + icon_url: z.string().url().nullable().optional(), + }) + .strict() + .refine((data) => data.name !== undefined || data.icon_url !== undefined, { + message: "At least one of name or icon_url must be provided", + }) + .openapi({ + example: { + name: "Beta Guild", + icon_url: "https://example.com/new-icon.png", + }, + }) + +export const GuildMemberAddRequestSchema = z + .object({ + user_id: z.number().int().optional(), + username: z.string().min(1).optional(), + is_admin: z.boolean().default(false).optional(), + }) + .strict() + .refine((data) => data.user_id !== undefined || data.username !== undefined, { + message: "Either user_id or username must be provided", + }) + .openapi({ + example: { + username: "aryan", + is_admin: false, + }, + }) + +// Guild member update request schema (for PATCH /guilds/:id/members/:userId) +export const GuildMemberUpdateRequestSchema = z + .object({ + is_admin: z.boolean(), + }) + .strict() + .openapi({ + example: { + is_admin: true, + }, + }) + +// ==================================================================================================== +// Request Param Schemas + +export const GuildIdParamSchema = z + .object({ + id: z.string().regex(/^\d+$/, { message: "Must be a positive integer string" }), + }) + .strict() + .openapi({ + example: { + id: "1", + }, + }) + +export const GuildMemberIdParamSchema = z + .object({ + member_id: z.string().regex(/^\d+$/, { message: "Must be a positive integer string" }), + }) + .strict() + .openapi({ + example: { + member_id: "1", + }, + }) diff --git a/apps/leaderboard-backend/src/validation/guilds/index.ts b/apps/leaderboard-backend/src/validation/guilds/index.ts new file mode 100644 index 0000000000..4f8ccbc6c0 --- /dev/null +++ b/apps/leaderboard-backend/src/validation/guilds/index.ts @@ -0,0 +1,3 @@ +export * from "./guildRouteDescriptions" +export * from "./guildRouteValidations" +export * from "./guildSchemas" diff --git a/apps/leaderboard-backend/src/validation/leaderboard/index.ts b/apps/leaderboard-backend/src/validation/leaderboard/index.ts new file mode 100644 index 0000000000..8a5b0a436a --- /dev/null +++ b/apps/leaderboard-backend/src/validation/leaderboard/index.ts @@ -0,0 +1,3 @@ +export * from "./leaderBoardRouteDescriptions" +export * from "./leaderBoardRouteValidations" +export * from "./leaderBoardSchema" diff --git a/apps/leaderboard-backend/src/validation/leaderboard/leaderBoardRouteDescriptions.ts b/apps/leaderboard-backend/src/validation/leaderboard/leaderBoardRouteDescriptions.ts new file mode 100644 index 0000000000..d10a09adf2 --- /dev/null +++ b/apps/leaderboard-backend/src/validation/leaderboard/leaderBoardRouteDescriptions.ts @@ -0,0 +1,67 @@ +import { describeRoute } from "hono-openapi" +import { + GameGuildLeaderBoardResponseObj, + GameLeaderBoardResponseObj, + GlobalLeaderBoardResponseObj, + GuildLeaderBoardResponseObj, +} from "./leaderBoardSchema" + +export const GlobalLeaderboardDescription = describeRoute({ + validateResponse: false, + description: "Returns users ranked by their total score across all games.", + responses: { + 200: { + description: "Global leaderboard data", + content: { + "application/json": { + schema: GlobalLeaderBoardResponseObj, + }, + }, + }, + }, +}) + +export const GuildLeaderboardDescription = describeRoute({ + validateResponse: false, + description: "Returns guilds ranked by their members' total score across all games.", + responses: { + 200: { + description: "Guild leaderboard data", + content: { + "application/json": { + schema: GuildLeaderBoardResponseObj, + }, + }, + }, + }, +}) + +export const GameLeaderboardDescription = describeRoute({ + validateResponse: false, + description: "Returns users ranked by their score in a specific game.", + responses: { + 200: { + description: "Game leaderboard data", + content: { + "application/json": { + schema: GameLeaderBoardResponseObj, + }, + }, + }, + }, +}) + +export const GameGuildLeaderboardDescription = describeRoute({ + validateResponse: false, + description: "Returns guilds ranked by their members' total score in a specific game.", + responses: { + 200: { + description: "Game-specific guild leaderboard data", + content: { + "application/json": { + schema: GameGuildLeaderBoardResponseObj, + }, + }, + }, + }, +}) diff --git a/apps/leaderboard-backend/src/validation/leaderboard/leaderBoardRouteValidations.ts b/apps/leaderboard-backend/src/validation/leaderboard/leaderBoardRouteValidations.ts new file mode 100644 index 0000000000..1e9dc7b300 --- /dev/null +++ b/apps/leaderboard-backend/src/validation/leaderboard/leaderBoardRouteValidations.ts @@ -0,0 +1,9 @@ +import { validator as zValidator } from "hono-openapi/zod" +import { LeaderboardGameIdParamSchema, LeaderboardLimitQuerySchema } from "./leaderBoardSchema" + +export const GlobalLeaderboardValidation = zValidator("query", LeaderboardLimitQuerySchema) +export const GuildLeaderboardValidation = zValidator("query", LeaderboardLimitQuerySchema) +export const GameLeaderboardParamValidation = zValidator("param", LeaderboardGameIdParamSchema) +export const GameLeaderboardQueryValidation = zValidator("query", LeaderboardLimitQuerySchema) +export const GameGuildLeaderboardParamValidation = zValidator("param", LeaderboardGameIdParamSchema) +export const GameGuildLeaderboardQueryValidation = zValidator("query", LeaderboardLimitQuerySchema) diff --git a/apps/leaderboard-backend/src/validation/leaderboard/leaderBoardSchema.ts b/apps/leaderboard-backend/src/validation/leaderboard/leaderBoardSchema.ts new file mode 100644 index 0000000000..b3e5bd870e --- /dev/null +++ b/apps/leaderboard-backend/src/validation/leaderboard/leaderBoardSchema.ts @@ -0,0 +1,101 @@ +import { z } from "@hono/zod-openapi" +import { resolver } from "hono-openapi/zod" +import { isHex } from "viem" + +// ==================================================================================================== +// Response Schemas + +const GlobalLeaderboardEntrySchema = z + .object({ + user_id: z.number().int(), + username: z.string(), // is unique (enforced in db) + primary_wallet: z.string().refine(isHex), + total_score: z.number().int(), + }) + .strict() + .openapi({ + example: { + user_id: 1, + username: "player1", + primary_wallet: "0xBC5F85819B9b970c956f80c1Ab5EfbE73c818eaa", + total_score: 5000, + }, + }) + +const GuildLeaderboardEntrySchema = z + .object({ + guild_id: z.number().int(), + guild_name: z.string(), + icon_url: z.string().nullable(), + total_score: z.number().int(), + member_count: z.number().int(), + }) + .strict() + .openapi({ + example: { + guild_id: 1, + guild_name: "Alpha Guild", + icon_url: "https://example.com/icon.png", + total_score: 12000, + member_count: 5, + }, + }) + +const GameLeaderboardEntrySchema = z + .object({ + game_id: z.number().int(), + user_id: z.number().int(), + username: z.string(), + primary_wallet: z.string().refine(isHex), + score: z.number().int(), + }) + .strict() + .openapi({ + example: { + game_id: 42, + user_id: 2, + username: "player2", + primary_wallet: "0x1234567890abcdef1234567890abcdef12345678", + score: 3000, + }, + }) + +const GameGuildLeaderboardEntrySchema = z + .object({ + game_id: z.number().int(), + guild_id: z.number().int(), + guild_name: z.string(), + icon_url: z.string().nullable(), + total_score: z.number().int(), + member_count: z.number().int(), + }) + .strict() + .openapi({ + example: { + game_id: 42, + guild_id: 2, + guild_name: "Beta Guild", + icon_url: "https://example.com/beta-icon.png", + total_score: 8000, + member_count: 3, + }, + }) + +export const GlobalLeaderBoardResponseObj = resolver(z.array(GlobalLeaderboardEntrySchema)) + +export const GuildLeaderBoardResponseObj = resolver(z.array(GuildLeaderboardEntrySchema)) + +export const GameLeaderBoardResponseObj = resolver(z.array(GameLeaderboardEntrySchema)) + +export const GameGuildLeaderBoardResponseObj = resolver(z.array(GameGuildLeaderboardEntrySchema)) + +// ==================================================================================================== +// Request Param/Query Schemas + +export const LeaderboardLimitQuerySchema = z.object({ + limit: z.string().regex(/^\d+$/, { message: "Must be a positive integer string" }).default("50"), +}) + +export const LeaderboardGameIdParamSchema = z.object({ + id: z.string().regex(/^\d+$/, { message: "Must be a positive integer string" }), +}) diff --git a/apps/leaderboard-backend/src/validation/users/index.ts b/apps/leaderboard-backend/src/validation/users/index.ts new file mode 100644 index 0000000000..a8f6b6f153 --- /dev/null +++ b/apps/leaderboard-backend/src/validation/users/index.ts @@ -0,0 +1,3 @@ +export * from "./userRouteDescriptions" +export * from "./userRouteValidations" +export * from "./userSchemas" diff --git a/apps/leaderboard-backend/src/validation/users/userRouteDescriptions.ts b/apps/leaderboard-backend/src/validation/users/userRouteDescriptions.ts new file mode 100644 index 0000000000..ce7711c0ea --- /dev/null +++ b/apps/leaderboard-backend/src/validation/users/userRouteDescriptions.ts @@ -0,0 +1,566 @@ +import { describeRoute } from "hono-openapi" +import { + ErrorResponseSchemaObj, + UserListResponseSchemaObj, + UserResponseSchemaObj, + UserWalletListResponseSchemaObj, +} from "./userSchemas" + +// ==================================================================================================== +// User Collection + +export const UserQueryDescription = describeRoute({ + validateResponse: false, + description: "Get a list of users, optionally filtered by primary_wallet or username.", + responses: { + 200: { + description: "Successfully retrieved users.", + content: { + "application/json": { + schema: UserListResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid query parameters.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserCreateDescription = describeRoute({ + validateResponse: false, + description: "Create a new user.", + requestBody: { + description: "User creation request body.", + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 201: { + description: "User created successfully.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid user creation data.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +// ==================================================================================================== +// User Resource by ID + +export const UserGetByIdDescription = describeRoute({ + validateResponse: false, + description: "Get user details by user ID.", + responses: { + 200: { + description: "User found.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 404: { + description: "User not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserUpdateByIdDescription = describeRoute({ + validateResponse: false, + description: "Update user details by user ID.", + requestBody: { + description: "User update request body.", + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 200: { + description: "User updated successfully.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid update data.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 404: { + description: "User not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserDeleteByIdDescription = describeRoute({ + validateResponse: false, + description: "Delete a user by user ID and all associated data.", + responses: { + 200: { + description: "User deleted successfully.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 404: { + description: "User not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +// ==================================================================================================== +// User Resource by Primary Wallet + +export const UserGetByPrimaryWalletDescription = describeRoute({ + validateResponse: false, + description: "Get user details by primary wallet address.", + responses: { + 200: { + description: "User found.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 404: { + description: "User not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserUpdateByPrimaryWalletDescription = describeRoute({ + validateResponse: false, + description: "Update user details by primary wallet address.", + requestBody: { + description: "User update request body.", + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 200: { + description: "User updated successfully.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid update data.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 404: { + description: "User not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserDeleteByPrimaryWalletDescription = describeRoute({ + validateResponse: false, + description: "Delete a user by primary wallet address and all associated data.", + responses: { + 200: { + description: "User deleted successfully.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 404: { + description: "User not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +// ==================================================================================================== +// Wallet Collection (by User ID and Primary Wallet) + +export const UserWalletsGetByIdDescription = describeRoute({ + validateResponse: false, + description: "Get all wallets for a user by user ID.", + responses: { + 200: { + description: "Wallets retrieved successfully.", + content: { + "application/json": { + schema: UserWalletListResponseSchemaObj, + }, + }, + }, + 404: { + description: "User not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserWalletsGetByPrimaryWalletDescription = describeRoute({ + validateResponse: false, + description: "Get all wallets for a user by primary wallet address.", + responses: { + 200: { + description: "Wallets retrieved successfully.", + content: { + "application/json": { + schema: UserWalletListResponseSchemaObj, + }, + }, + }, + 404: { + description: "User not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserWalletAddByIdDescription = describeRoute({ + validateResponse: false, + description: "Add a wallet to a user by user ID.", + requestBody: { + description: "Wallet add request body.", + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 201: { + description: "Wallet added successfully.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid wallet data.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 404: { + description: "User not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserWalletAddByPrimaryWalletDescription = describeRoute({ + validateResponse: false, + description: "Add a wallet to a user by primary wallet address.", + requestBody: { + description: "Wallet add request body.", + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 201: { + description: "Wallet added successfully.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid wallet data.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 404: { + description: "User not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +// ==================================================================================================== +// Wallet Resource (Set Primary, Remove) + +export const UserWalletSetPrimaryByIdDescription = describeRoute({ + validateResponse: false, + description: "Set a wallet as primary for a user by user ID.", + requestBody: { + description: "Set primary wallet request body.", + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 200: { + description: "Primary wallet set successfully.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid wallet data.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 404: { + description: "User or wallet not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserWalletSetPrimaryByPrimaryWalletDescription = describeRoute({ + validateResponse: false, + description: "Set a wallet as primary for a user by primary wallet address.", + requestBody: { + description: "Set primary wallet request body.", + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 200: { + description: "Primary wallet set successfully.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid wallet data.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 404: { + description: "User or wallet not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserWalletRemoveByIdDescription = describeRoute({ + validateResponse: false, + description: "Remove a wallet from a user by user ID.", + requestBody: { + description: "Remove wallet request body.", + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 200: { + description: "Wallet removed successfully.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid wallet data.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 404: { + description: "User or wallet not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserWalletRemoveByPrimaryWalletDescription = describeRoute({ + validateResponse: false, + description: "Remove a wallet from a user by primary wallet address.", + requestBody: { + description: "Remove wallet request body.", + required: true, + content: { + "application/json": { + schema: {}, + }, + }, + }, + responses: { + 200: { + description: "Wallet removed successfully.", + content: { + "application/json": { + schema: UserResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid wallet data.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 404: { + description: "User or wallet not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) + +export const UserGuildsListDescription = describeRoute({ + validateResponse: false, + description: "Get all guilds a user belongs to.", + responses: { + 200: { + description: "Successfully retrieved user guilds.", + content: { + "application/json": { + schema: {}, + }, + }, + }, + 404: { + description: "User not found.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + 400: { + description: "Invalid user ID parameter.", + content: { + "application/json": { + schema: ErrorResponseSchemaObj, + }, + }, + }, + }, +}) diff --git a/apps/leaderboard-backend/src/validation/users/userRouteValidations.ts b/apps/leaderboard-backend/src/validation/users/userRouteValidations.ts new file mode 100644 index 0000000000..2720656a6d --- /dev/null +++ b/apps/leaderboard-backend/src/validation/users/userRouteValidations.ts @@ -0,0 +1,21 @@ +import { validator as zValidator } from "hono-openapi/zod" +import { + PrimaryWalletParamSchema, + UserCreateRequestSchema, + UserIdParamSchema, + UserQuerySchema, + UserUpdateRequestSchema, + UserWalletRequestSchema, +} from "./userSchemas" + +export const UserQueryValidation = zValidator("query", UserQuerySchema) + +export const UserCreateValidation = zValidator("json", UserCreateRequestSchema) + +export const UserUpdateValidation = zValidator("json", UserUpdateRequestSchema) + +export const UserWalletValidation = zValidator("json", UserWalletRequestSchema) + +export const UserIdParamValidation = zValidator("param", UserIdParamSchema) + +export const PrimaryWalletParamValidation = zValidator("param", PrimaryWalletParamSchema) diff --git a/apps/leaderboard-backend/src/validation/users/userSchemas.ts b/apps/leaderboard-backend/src/validation/users/userSchemas.ts new file mode 100644 index 0000000000..c5e770cd94 --- /dev/null +++ b/apps/leaderboard-backend/src/validation/users/userSchemas.ts @@ -0,0 +1,152 @@ +import { z } from "@hono/zod-openapi" +import { resolver } from "hono-openapi/zod" +import { isHex } from "viem" + +// ==================================================================================================== +// Response Schemas + +const UserWalletSchema = z + .object({ + id: z.number().int(), + user_id: z.number().int(), + wallet_address: z.string().refine(isHex), + is_primary: z.boolean(), + created_at: z.string(), + }) + .strict() + .openapi({ + example: { + id: 1, + user_id: 1, + wallet_address: "0xBC5F85819B9b970c956f80c1Ab5EfbE73c818eaa", + is_primary: true, + created_at: "2023-01-01T00:00:00.000Z", + }, + }) + +const UserResponseSchema = z + .object({ + id: z.number().int(), + primary_wallet: z.string().refine(isHex), + username: z.string(), + created_at: z.string(), + updated_at: z.string(), + wallets: z.array(UserWalletSchema).optional(), + }) + .strict() + .openapi({ + example: { + id: 1, + primary_wallet: "0xBC5F85819B9b970c956f80c1Ab5EfbE73c818eaa", + username: "username", + created_at: "2023-01-01T00:00:00.000Z", + updated_at: "2023-01-01T00:00:00.000Z", + wallets: [ + { + id: 1, + user_id: 1, + wallet_address: "0xBC5F85819B9b970c956f80c1Ab5EfbE73c818eaa", + is_primary: true, + created_at: "2023-01-01T00:00:00.000Z", + }, + ], + }, + }) + +export const UserResponseSchemaObj = resolver(UserResponseSchema) + +export const UserListResponseSchemaObj = resolver(z.array(UserResponseSchema)) + +export const UserWalletListResponseSchemaObj = resolver(z.array(UserWalletSchema)) + +// Generic error schema +export const ErrorResponseSchemaObj = resolver(z.object({ ok: z.literal(false), error: z.string() })) + +// ==================================================================================================== +// Request Body Schemas + +export const UserQuerySchema = z + .object({ + primary_wallet: z.string().refine(isHex).optional(), + username: z.string().optional(), + include_wallets: z.boolean().optional().default(false), + }) + .strict() + .refine((data) => data.primary_wallet !== undefined || data.username !== undefined, { + message: "At least one filter (primary_wallet or username) must be provided", + }) + .openapi({ + example: { + primary_wallet: "0xBC5F85819B9b970c956f80c1Ab5EfbE73c818eaa", + username: "username", + include_wallets: true, + }, + }) + +export const UserCreateRequestSchema = z + .object({ + primary_wallet: z.string().refine(isHex), + username: z.string(), + }) + .strict() + .openapi({ + example: { + primary_wallet: "0xBC5F85819B9b970c956f80c1Ab5EfbE73c818eaa", + username: "username", + }, + }) + +export const UserUpdateRequestSchema = z + .object({ + username: z.string().optional(), + }) + .strict() + .openapi({ + example: { + username: "new_username", + }, + }) + +export const UserWalletRequestSchema = z + .object({ + wallet_address: z.string().refine(isHex), + }) + .strict() + .openapi({ + example: { + wallet_address: "0x1a2b3c4d5e6f7A8B9C0D1E2F3a4b5c6d7e8f9a0b", + }, + }) + +// ==================================================================================================== +// Request Param Schemas + +export const UserIdParamSchema = z + .object({ + id: z.string().regex(/^\d+$/, { message: "Must be a positive integer string" }), + }) + .strict() + .openapi({ + param: { + name: "id", + in: "path", + }, + example: { + id: "1", + }, + }) + +export const PrimaryWalletParamSchema = z + .object({ + primary_wallet: z.string().refine(isHex, { message: "Primary wallet address must be a valid hex string" }), + }) + .strict() + .openapi({ + param: { + name: "primary_wallet", + in: "path", + }, + example: { + primary_wallet: "0xBC5F85819B9b970c956f80c1Ab5EfbE73c818eaa", + }, + }) diff --git a/apps/leaderboard-backend/tsconfig.build.json b/apps/leaderboard-backend/tsconfig.build.json new file mode 100644 index 0000000000..5391b3f5e4 --- /dev/null +++ b/apps/leaderboard-backend/tsconfig.build.json @@ -0,0 +1,4 @@ +{ + "extends": ["../../support/configs/tsconfig.base.json", "../../support/configs/tsconfig.types.json"], + "include": ["src"] +} diff --git a/apps/leaderboard-backend/tsconfig.json b/apps/leaderboard-backend/tsconfig.json new file mode 100644 index 0000000000..626aea6852 --- /dev/null +++ b/apps/leaderboard-backend/tsconfig.json @@ -0,0 +1,4 @@ +{ + "extends": ["../../support/configs/tsconfig.base.json"], + "include": ["*.ts", "src"] +} diff --git a/bun.lock b/bun.lock index ee8412cf11..689e4f2c6c 100644 --- a/bun.lock +++ b/bun.lock @@ -125,6 +125,31 @@ "abitype": "^1.0.0", }, }, + "apps/leaderboard-backend": { + "name": "@happy.tech/leaderboard-backend", + "version": "0.1.0", + "dependencies": { + "@hono/zod-openapi": "^0.19.6", + "@hono/zod-validator": "^0.4.3", + "@scalar/hono-api-reference": "^0.8.8", + "hono": "^4.7.2", + "hono-openapi": "^0.4.4", + "kysely": "^0.27.5", + "kysely-bun-sqlite": "^0.3.2", + "viem": "^2.21.53", + "zod": "^3.23.8", + "zod-openapi": "^4.2.3", + }, + "devDependencies": { + "@happy.tech/common": "workspace:0.1.0", + "@happy.tech/configs": "workspace:0.1.0", + "@happy.tech/happybuild": "workspace:0.1.1", + "@types/bun": "latest", + }, + "peerDependencies": { + "typescript": "^5.0.0", + }, + }, "apps/randomness": { "name": "@happy.tech/randomness", "version": "0.1.0", @@ -493,6 +518,8 @@ "@ark-ui/react": ["@ark-ui/react@4.9.2", "", { "dependencies": { "@internationalized/date": "3.7.0", "@zag-js/accordion": "0.82.2", "@zag-js/anatomy": "0.82.2", "@zag-js/auto-resize": "0.82.2", "@zag-js/avatar": "0.82.2", "@zag-js/carousel": "0.82.2", "@zag-js/checkbox": "0.82.2", "@zag-js/clipboard": "0.82.2", "@zag-js/collapsible": "0.82.2", "@zag-js/collection": "0.82.2", "@zag-js/color-picker": "0.82.2", "@zag-js/color-utils": "0.82.2", "@zag-js/combobox": "0.82.2", "@zag-js/core": "0.82.2", "@zag-js/date-picker": "0.82.2", "@zag-js/date-utils": "0.82.2", "@zag-js/dialog": "0.82.2", "@zag-js/dom-query": "0.82.2", "@zag-js/editable": "0.82.2", "@zag-js/file-upload": "0.82.2", "@zag-js/file-utils": "0.82.2", "@zag-js/focus-trap": "0.82.2", "@zag-js/highlight-word": "0.82.2", "@zag-js/hover-card": "0.82.2", "@zag-js/i18n-utils": "0.82.2", "@zag-js/menu": "0.82.2", "@zag-js/number-input": "0.82.2", "@zag-js/pagination": "0.82.2", "@zag-js/pin-input": "0.82.2", "@zag-js/popover": "0.82.2", "@zag-js/presence": "0.82.2", "@zag-js/progress": "0.82.2", "@zag-js/qr-code": "0.82.2", "@zag-js/radio-group": "0.82.2", "@zag-js/rating-group": "0.82.2", "@zag-js/react": "0.82.2", "@zag-js/select": "0.82.2", "@zag-js/signature-pad": "0.82.2", "@zag-js/slider": "0.82.2", "@zag-js/splitter": "0.82.2", "@zag-js/steps": "0.82.2", "@zag-js/switch": "0.82.2", "@zag-js/tabs": "0.82.2", "@zag-js/tags-input": "0.82.2", "@zag-js/time-picker": "0.82.2", "@zag-js/timer": "0.82.2", "@zag-js/toast": "0.82.2", "@zag-js/toggle-group": "0.82.2", "@zag-js/tooltip": "0.82.2", "@zag-js/tour": "0.82.2", "@zag-js/tree-view": "0.82.2", "@zag-js/types": "0.82.2" }, "peerDependencies": { "react": ">=18.0.0", "react-dom": ">=18.0.0" } }, "sha512-LJnz8nwXgGRszlkU2AiH3yLsAeXiXeQl4JBjMA7d8klZJBiBUp7URwLhBSWmoAIWRH7bW6fSPjhRAEkJLmD8gA=="], + "@asteasolutions/zod-to-openapi": ["@asteasolutions/zod-to-openapi@7.3.0", "", { "dependencies": { "openapi3-ts": "^4.1.2" }, "peerDependencies": { "zod": "^3.20.2" } }, "sha512-7tE/r1gXwMIvGnXVUdIqUhCU1RevEFC4Jk6Bussa0fk1ecbnnINkZzj1EOAJyE/M3AI25DnHT/zKQL1/FPFi8Q=="], + "@babel/code-frame": ["@babel/code-frame@7.27.1", "", { "dependencies": { "@babel/helper-validator-identifier": "^7.27.1", "js-tokens": "^4.0.0", "picocolors": "^1.1.1" } }, "sha512-cjQ7ZlQ0Mv3b47hABuTevyTuYN4i+loJKGeV9flcCgIK37cCXRh+L1bd3iBHlynerhQ7BhCkn2BPbQUL+rGqFg=="], "@babel/compat-data": ["@babel/compat-data@7.27.2", "", {}, "sha512-TUtMJYRPyUb/9aU8f3K0mjmjf6M9N5Woshn2CS6nqJSeJtTtQcpLUXjGt9vbF8ZGff0El99sWkLgzwW3VXnxZQ=="], @@ -885,6 +912,8 @@ "@happy.tech/iframe": ["@happy.tech/iframe@workspace:apps/iframe"], + "@happy.tech/leaderboard-backend": ["@happy.tech/leaderboard-backend@workspace:apps/leaderboard-backend"], + "@happy.tech/randomness": ["@happy.tech/randomness@workspace:apps/randomness"], "@happy.tech/randomness-monitor": ["@happy.tech/randomness-monitor@workspace:apps/randomness-monitor"], @@ -909,6 +938,8 @@ "@hono/node-server": ["@hono/node-server@1.14.2", "", { "peerDependencies": { "hono": "^4" } }, "sha512-GHjpOeHYbr9d1vkID2sNUYkl5IxumyhDrUJB7wBp7jvqYwPFt+oNKsAPBRcdSbV7kIrXhouLE199ks1QcK4r7A=="], + "@hono/zod-openapi": ["@hono/zod-openapi@0.19.6", "", { "dependencies": { "@asteasolutions/zod-to-openapi": "^7.3.0", "@hono/zod-validator": "^0.5.0" }, "peerDependencies": { "hono": ">=4.3.6", "zod": "3.*" } }, "sha512-qD2I0i5Ksry8gf47rXR6tuUAfv5S/wRZPRUNn+y8vOkgArDtIs30Ha3KGHeuGhcMk773D197IlPUppSCbHt6iQ=="], + "@hono/zod-validator": ["@hono/zod-validator@0.4.3", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-xIgMYXDyJ4Hj6ekm9T9Y27s080Nl9NXHcJkOvkXPhubOLj8hZkOL8pDnnXfvCf5xEE8Q4oMFenQUZZREUY2gqQ=="], "@humanwhocodes/config-array": ["@humanwhocodes/config-array@0.13.0", "", { "dependencies": { "@humanwhocodes/object-schema": "^2.0.3", "debug": "^4.3.1", "minimatch": "^3.0.5" } }, "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw=="], @@ -945,8 +976,6 @@ "@jridgewell/trace-mapping": ["@jridgewell/trace-mapping@0.3.9", "", { "dependencies": { "@jridgewell/resolve-uri": "^3.0.3", "@jridgewell/sourcemap-codec": "^1.4.10" } }, "sha512-3Belt6tdc8bPgAtbcmdtNJlirVoTmEb5e2gC94PnkwEW9jI6CAHUeoG85tjWP5WquqfavoMtMwiG4P926ZKKuQ=="], - "@js-sdsl/ordered-map": ["@js-sdsl/ordered-map@4.4.2", "", {}, "sha512-iUKgm52T8HOE/makSxjqoWhe95ZJA1/G1sYsGev2JDKUSS14KAgg1LHb+Ba+IPow0xflbnSkOsZcO08C7w1gYw=="], - "@jsdevtools/ono": ["@jsdevtools/ono@7.1.3", "", {}, "sha512-4JQNk+3mVzK3xh2rqd6RB4J46qUR19azEHBneZyTZM+c456qOrbbM/5xcR8huNCCcbVt7+UmizG6GuUvPvKUYg=="], "@kevincharm/bls-bn254": ["@kevincharm/bls-bn254@2.0.0", "", { "peerDependencies": { "ethers": "^6.8.0", "mcl-wasm": "^1.4.0" } }, "sha512-Y6Jk8oE6Re4v3rDkb51mF3rHfDakxCTGptVr/LX2iwtbA7qu3DLNBNgdBU3heYFA8t22JiX3BgnMTbBgm3AZMA=="], @@ -1225,101 +1254,101 @@ "@radix-ui/primitive": ["@radix-ui/primitive@1.1.2", "", {}, "sha512-XnbHrrprsNqZKQhStrSwgRUQzoCI1glLzdw79xiZPoofhGICeZRSQ3dIxAKH1gb3OHfNf4d6f+vAv3kil2eggA=="], - "@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.6", "", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Eh+3JK1ApmX7DYGMquj6gctxmbLX4JD+5kn1Pi/VlFGdHvod+dtoFoAGEkz3Muy/E+MVC7P77MPC5zqAaxrHxg=="], + "@radix-ui/react-accessible-icon": ["@radix-ui/react-accessible-icon@1.1.7", "", { "dependencies": { "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-XM+E4WXl0OqUJFovy6GjmxxFyx9opfCAIUku4dlKRd5YEPqt4kALOkQOp0Of6reHuUkJuiPBEc5k0o4z4lTC8A=="], - "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collapsible": "1.1.10", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-x+URzV1siKmeXPSUIQ22L81qp2eOhjpy3tgteF+zOr4d1u0qJnFuyBF4MoQRhmKP6ivDxlvDAvqaF77gh7DOIw=="], + "@radix-ui/react-accordion": ["@radix-ui/react-accordion@1.2.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collapsible": "1.1.11", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-l3W5D54emV2ues7jjeG1xcyN7S3jnK3zE2zHqgn0CmMsy9lNJwmgcrmaxS+7ipw15FAivzKNzH3d5EcGoFKw0A=="], - "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.13", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/uPs78OwxGxslYOG5TKeUsv9fZC0vo376cXSADdKirTmsLJU2au6L3n34c3p6W26rFDDDze/hwy4fYeNd0qdGA=="], + "@radix-ui/react-alert-dialog": ["@radix-ui/react-alert-dialog@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dialog": "1.1.14", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IOZfZ3nPvN6lXpJTBCunFQPRSvK8MDgSc1FB85xnIpUKOw9en0dJj8JmCAxV7BiZdtYlUpmrQjoTFkVYtdoWzQ=="], - "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.6", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-2JMfHJf/eVnwq+2dewT3C0acmCWD3XiVA1Da+jTDqo342UlU13WvXtqHhG+yJw5JeQmu4ue2eMy6gcEArLBlcw=="], + "@radix-ui/react-arrow": ["@radix-ui/react-arrow@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F+M1tLhO+mlQaOWspE8Wstg+z6PwxwRd8oQ8IXceWz92kfAmalTRf0EjrouQeo7QssEPfCn05B4Ihs1K9WQ/7w=="], - "@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.6", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-cZvNiIKqWQjf3DsQk1+wktF3DD73kUbWQ2E/XSh8m2IcpFGwg4IiIvGlVNdovxuozK/9+4QXd2zVlzUMiexSDg=="], + "@radix-ui/react-aspect-ratio": ["@radix-ui/react-aspect-ratio@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Yq6lvO9HQyPwev1onK1daHCHqXVLzPhSVjmsNjCa2Zcxy2f7uJD2itDtxknv6FzAKCwD1qQkeVDmX/cev13n/g=="], - "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.9", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-10tQokfvZdFvnvDkcOJPjm2pWiP8A0R4T83MoD7tb15bC/k2GU7B1YBuzJi8lNQ8V1QqhP8ocNqp27ByZaNagQ=="], + "@radix-ui/react-avatar": ["@radix-ui/react-avatar@1.1.10", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-V8piFfWapM5OmNCXTzVQY+E1rDa53zY+MQ4Y7356v4fFz6vqCyUtIz2rUD44ZEdwg78/jKmMJHj07+C/Z/rcog=="], - "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-xTaLKAO+XXMPK/BpVTSaAAhlefmvMSACjIhK9mGsImvX2ljcTDm8VGR1CuS1uYcNdR5J+oiOhoJZc5un6bh3VQ=="], + "@radix-ui/react-checkbox": ["@radix-ui/react-checkbox@1.3.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-yd+dI56KZqawxKZrJ31eENUwqc1QSqg4OZ15rybGjF2ZNwMO+wCyHzAVLRp9qoYJf7kYy0YpZ2b0JCzJ42HZpA=="], - "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-O2mcG3gZNkJ/Ena34HurA3llPOEA/M4dJtIRMa6y/cknRDC8XY5UZBInKTsUwW5cUue9A4k0wi1XU5fKBzKe1w=="], + "@radix-ui/react-collapsible": ["@radix-ui/react-collapsible@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-2qrRsVGSCYasSz1RFOorXwl0H7g7J1frQtgpQgYrt+MOidtPAINHn9CPovQXb83r8ahapdx3Tu0fa/pdFFSdPg=="], - "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.6", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-PbhRFK4lIEw9ADonj48tiYWzkllz81TM7KVYyyMMw2cwHO7D5h4XKEblL8NlaRisTK3QTe6tBEhDccFUryxHBQ=="], + "@radix-ui/react-collection": ["@radix-ui/react-collection@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Fh9rGN0MoI4ZFUNyfFVNU4y9LUz93u9/0K+yLgA2bwRojxM8JU1DyvvMBabnZPBgMWREAJvU2jjVzq+LrFUglw=="], "@radix-ui/react-compose-refs": ["@radix-ui/react-compose-refs@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-z4eqJvfiNnFMHIIvXP3CY57y2WJs5g2v3X0zm9mEJkrkNv4rDxu+sg9Jh8EkXyeqBkB7SOcboo9dMVqhyrACIg=="], "@radix-ui/react-context": ["@radix-ui/react-context@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-jCi/QKUM2r1Ju5a3J64TH2A5SpKAgh0LpknyqdQ4m6DCV0xJ2HG1xARRwNGPQfi1SLdLWZ1OJz6F4OMBBNiGJA=="], - "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.14", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-RUHvrJE2qKAd9pQ50HZZsePio4SMWEh8v6FWQwg/4t6K1fuxfb4Ec40VEVvni6V7nFxmj9srU4UZc7aYp8x0LQ=="], + "@radix-ui/react-context-menu": ["@radix-ui/react-context-menu@2.2.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-menu": "2.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-UsQUMjcYTsBjTSXw0P3GO0werEQvUY2plgRQuKoCTtkNr45q1DiL51j4m7gxhABzZ0BadoXNsIbg7F3KwiUBbw=="], - "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.9", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.6", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.8", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-slot": "1.2.2", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ARFmqUyhIVS3+riWzwGTe7JLjqwqgnODBUZdqpWar/z1WFs9z76fuOs/2BOWCR+YboRn4/WN9aoaGVwqNRr8VA=="], + "@radix-ui/react-dialog": ["@radix-ui/react-dialog@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-+CpweKjqpzTmwRwcYECQcNYbI8V9VSQt0SNFKeEBLgfucbsLssU6Ppq7wUdNXEGb573bMjFhVjKVll8rmV6zMw=="], "@radix-ui/react-direction": ["@radix-ui/react-direction@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-1UEWRX6jnOA2y4H5WczZ44gOOjTEmlqv1uNW4GAJEO5+bauCBhv8snY65Iw5/VOS/ghKN9gr2KjnLKxrsvoMVw=="], - "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.9", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-way197PiTvNp+WBP7svMJasHl+vibhWGQDb6Mgf5mhEWJkgb85z7Lfl9TUdkqpWsf8GRNmoopx9ZxCyDzmgRMQ=="], + "@radix-ui/react-dismissable-layer": ["@radix-ui/react-dismissable-layer@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-escape-keydown": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IM1zzRV4W3HtVgftdQiiOmA0AdJlCtMLe00FXaHwgt3rAnNsIyDqshvkIW3hj/iu5hu8ERP7KIYki6NkqDxAwQ=="], - "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.14", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-lzuyNjoWOoaMFE/VC5FnAAYM16JmQA8ZmucOXtlhm2kKR5TSU95YLAueQ4JYuRmUJmBvSqXaVFGIfuukybwZJQ=="], + "@radix-ui/react-dropdown-menu": ["@radix-ui/react-dropdown-menu@2.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-mIBnOjgwo9AH3FyKaSWoSu/dYj6VdhJ7frEPiGTeXCdUFHjl9h3mFh2wwhEtINOmYXWhdpf1rY2minFsmaNgVQ=="], "@radix-ui/react-focus-guards": ["@radix-ui/react-focus-guards@1.1.2", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-fyjAACV62oPV925xFCrH8DR5xWhg9KYtJT4s3u54jxp+L/hbpTY2kIeEFFbFe+a/HCE94zGQMZLIpVTPVZDhaA=="], - "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.6", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-r9zpYNUQY+2jWHWZGyddQLL9YHkM/XvSFHVcWs7bdVuxMAnCwTAuy6Pf47Z4nw7dYcUou1vg/VgjjrrH03VeBw=="], + "@radix-ui/react-focus-scope": ["@radix-ui/react-focus-scope@1.1.7", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-t2ODlkXBQyn7jkl6TNaw/MtVEVvIGelJDCG41Okq/KwUsJBwQ4XVZsHAVUkK4mBv3ewiAS3PGuUWuY2BoK4ZUw=="], - "@radix-ui/react-form": ["@radix-ui/react-form@0.1.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-label": "2.1.6", "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7AMSeVvepeJU8dIUSDlR92Pm8mScmqWBaiYw0oIAcN8wU/H5muJGcZdU/sYRHNws3b7eCoHyq4FTLrstVtCacQ=="], + "@radix-ui/react-form": ["@radix-ui/react-form@0.1.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IXLKFnaYvFg/KkeV5QfOX7tRnwHXp127koOFUjLWMTrRv5Rny3DQcAtIFFeA/Cli4HHM8DuJCXAUsgnFVJndlw=="], - "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.9", "@radix-ui/react-popper": "1.2.6", "@radix-ui/react-portal": "1.1.8", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Wtjvx0d/6Bgd/jAYS1mW6IPSUQ25y0hkUSOS1z5/4+U8+DJPwKroqJlM/AlVFl3LywGoruiPmcvB9Aks9mSOQw=="], + "@radix-ui/react-hover-card": ["@radix-ui/react-hover-card@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-CPYZ24Mhirm+g6D8jArmLzjYu4Eyg3TTUHswR26QgzXBHBe64BO/RHOJKzmF/Dxb4y4f9PKyJdwm/O/AhNkb+Q=="], "@radix-ui/react-icons": ["@radix-ui/react-icons@1.3.2", "", { "peerDependencies": { "react": "^16.x || ^17.x || ^18.x || ^19.0.0 || ^19.0.0-rc" } }, "sha512-fyQIhGDhzfc9pK2kH6Pl9c4BDJGfMkPqkyIgYDthyNYoNg3wVhoJMMh19WS4Up/1KMPFVpNsT2q3WmXn2N1m6g=="], "@radix-ui/react-id": ["@radix-ui/react-id@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-kGkGegYIdQsOb4XjsfM97rXsiHaBwco+hFI66oO4s9LU+PLAC5oJ7khdOVFxkhsmlbpUqDAvXw11CluXP+jkHg=="], - "@radix-ui/react-label": ["@radix-ui/react-label@2.1.6", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-S/hv1mTlgcPX2gCTJrWuTjSXf7ER3Zf7zWGtOprxhIIY93Qin3n5VgNA0Ez9AgrK/lEtlYgzLd4f5x6AVar4Yw=="], + "@radix-ui/react-label": ["@radix-ui/react-label@2.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YT1GqPSL8kJn20djelMX7/cTRp/Y9w5IZHvfxQTVHrOqa2yMl7i/UfMqKRU5V7mEyKTrUVgJXhNQPVCG8PBLoQ=="], - "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.9", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.6", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.6", "@radix-ui/react-portal": "1.1.8", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-roving-focus": "1.1.9", "@radix-ui/react-slot": "1.2.2", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0zSiBAIFq9GSKoSH5PdEaQeRB3RnEGxC+H2P0egtnKoKKLNBH8VBHyVO6/jskhjAezhOIplyRUj7U2lds9A+Yg=="], + "@radix-ui/react-menu": ["@radix-ui/react-menu@2.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-tVlmA3Vb9n8SZSd+YSbuFR66l87Wiy4du+YE+0hzKQEANA+7cWKH1WgqcEX4pXqxUFQKrWQGHdvEfw00TjFiew=="], - "@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.14", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-roving-focus": "1.1.9", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-nWLOS7EG3iYhT/zlE/Pbip17rrMnV/0AS7ueb3pKHTSAnpA6/N9rXQYowulZw4owZ9P+qSilHsFzSx/kU7yplQ=="], + "@radix-ui/react-menubar": ["@radix-ui/react-menubar@1.1.15", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-menu": "2.1.15", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Z71C7LGD+YDYo3TV81paUs8f3Zbmkvg6VLRQpKYfzioOE6n7fOhA3ApK/V/2Odolxjoc4ENk8AYCjohCNayd5A=="], - "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.9", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-iExvawdu7n6DidDJRU5pMTdi+Z3DaVPN4UZbAGuTs7nJA8P4RvvkEz+XYI2UJjb/Hh23RrH19DakgZNLdaq9Bw=="], + "@radix-ui/react-navigation-menu": ["@radix-ui/react-navigation-menu@1.2.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-WG8wWfDiJlSF5hELjwfjSGOXcBR/ZMhBFCGYe8vERpC39CQYZeq1PQ2kaYHdye3V95d06H89KGMsVCIE4LWo3g=="], - "@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.6", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-roving-focus": "1.1.9", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-hLjEmrZ7Ld++eL/hUOqfmBA4pEk78Sf7iXvEWs9t3aAuvWmtI24FuEfiMYbiXVJuUjzpo3vND6eUTAPFvG44Gg=="], + "@radix-ui/react-one-time-password-field": ["@radix-ui/react-one-time-password-field@0.1.7", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-w1vm7AGI8tNXVovOK7TYQHrAGpRF7qQL+ENpT1a743De5Zmay2RbWGKAiYDKIyIuqptns+znCKwNztE2xl1n0Q=="], - "@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-p5IJUTuyknUMv5VPGEa3fZvjb77cPzCK9w+Em/xHLaTqCVfIhykvdzAe8+X5BmboE9NwxDEBmbWnceFVw4tDdg=="], + "@radix-ui/react-password-toggle-field": ["@radix-ui/react-password-toggle-field@0.1.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-is-hydrated": "0.1.0" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-F90uYnlBsLPU1UbSLciLsWQmk8+hdWa6SFw4GXaIdNWxFxI5ITKVdAG64f+Twaa9ic6xE7pqxPyUmodrGjT4pQ=="], - "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.9", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.6", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.6", "@radix-ui/react-portal": "1.1.8", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-slot": "1.2.2", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-84uqQV3omKDR076izYgcha6gdpN8m3z6w/AeJ83MSBJYVG/AbOHdLjAgsPZkeC/kt+k64moXFCnio8BbqXszlw=="], + "@radix-ui/react-popover": ["@radix-ui/react-popover@1.1.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ODz16+1iIbGUfFEfKx2HTPKizg2MN39uIOV8MXeHnmdd3i/N9Wt7vU46wbHsqA0xoaQyXVcs0KIlBdOA2Y95bw=="], - "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.6", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-7iqXaOWIjDBfIG7aq8CUEeCSsQMLFdn7VEE8TaFz704DtEzpPHR7w/uuzRflvKgltqSAImgcmxQ7fFX3X7wasg=="], + "@radix-ui/react-popper": ["@radix-ui/react-popper@1.2.7", "", { "dependencies": { "@floating-ui/react-dom": "^2.0.0", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-rect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/rect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-IUFAccz1JyKcf/RjB552PlWwxjeCJB8/4KxT7EhBHOJM+mN7LdW+B3kacJXILm32xawcMMjb2i0cIZpo+f9kiQ=="], - "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.8", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-hQsTUIn7p7fxCPvao/q6wpbxmCwgLrlz+nOrJgC+RwfZqWY/WN+UMqkXzrtKbPrF82P43eCTl3ekeKuyAQbFeg=="], + "@radix-ui/react-portal": ["@radix-ui/react-portal@1.1.9", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-bpIxvq03if6UNwXZ+HTK71JLh4APvnXntDc6XOX8UVq4XQOVl7lwok0AvIl+b8zgCw3fSaVTZMpAPPagXbKmHQ=="], "@radix-ui/react-presence": ["@radix-ui/react-presence@1.1.4", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ueDqRbdc4/bkaQT3GIpLQssRlFgWaL/U2z/S31qRwwLWoxHLgry3SIfCwhxeQNbirEUXFa+lq3RL3oBYXtcmIA=="], - "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.2", "", { "dependencies": { "@radix-ui/react-slot": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-uHa+l/lKfxuDD2zjN/0peM/RhhSmRjr5YWdk/37EnSv1nJ88uvG85DPexSm8HdFQROd2VdERJ6ynXbkCFi+APw=="], + "@radix-ui/react-primitive": ["@radix-ui/react-primitive@2.1.3", "", { "dependencies": { "@radix-ui/react-slot": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-m9gTwRkhy2lvCPe6QJp4d3G1TYEUHn/FzJUtq9MjH46an1wJU+GdoGC5VLof8RX8Ft/DlpshApkhswDLZzHIcQ=="], - "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.6", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-QzN9a36nKk2eZKMf9EBCia35x3TT+SOgZuzQBVIHyRrmYYi73VYBRK3zKwdJ6az/F5IZ6QlacGJBg7zfB85liA=="], + "@radix-ui/react-progress": ["@radix-ui/react-progress@1.1.7", "", { "dependencies": { "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-vPdg/tF6YC/ynuBIJlk1mm7Le0VgW6ub6J2UWnTQ7/D23KXcPI1qy+0vBkgKgd38RCMJavBXpB83HPNFMTb0Fg=="], - "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.3.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-roving-focus": "1.1.9", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-1tfTAqnYZNVwSpFhCT273nzK8qGBReeYnNTPspCggqk1fvIrfVxJekIuBFidNivzpdiMqDwVGnQvHqXrRPM4Og=="], + "@radix-ui/react-radio-group": ["@radix-ui/react-radio-group@1.3.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-9w5XhD0KPOrm92OTTE0SysH3sYzHsSTHNvZgUBo/VZ80VdYyB5RneDbc0dKpURS24IxkoFRu/hI0i4XyfFwY6g=="], - "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.9", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ZzrIFnMYHHCNqSNCsuN6l7wlewBEq0O0BCSBkabJMFXVO51LRUTq71gLP1UxFvmrXElqmPjA5VX7IqC9VpazAQ=="], + "@radix-ui/react-roving-focus": ["@radix-ui/react-roving-focus@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-dT9aOXUen9JSsxnMPv/0VqySQf5eDQ6LCk5Sw28kamz8wSOW2bJdlX2Bg5VUIIcV+6XlHpWTIuTPCf/UNIyq8Q=="], - "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.8", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-K5h1RkYA6M0Sn61BV5LQs686zqBsSC0sGzL4/Gw4mNnjzrQcGSc6YXfC6CRFNaGydSdv5+M8cb0eNsOGo0OXtQ=="], + "@radix-ui/react-scroll-area": ["@radix-ui/react-scroll-area@1.2.9", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-YSjEfBXnhUELsO2VzjdtYYD4CfQjvao+lhhrX5XsHD7/cyUNzljF1FHEbgTPN7LH2MClfwRMIsYlqTYpKTTe2A=="], - "@radix-ui/react-select": ["@radix-ui/react-select@2.2.4", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.9", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.6", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.6", "@radix-ui/react-portal": "1.1.8", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-slot": "1.2.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.2", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-/OOm58Gil4Ev5zT8LyVzqfBcij4dTHYdeyuF5lMHZ2bIp0Lk9oETocYiJ5QC0dHekEQnK6L/FNJCceeb4AkZ6Q=="], + "@radix-ui/react-select": ["@radix-ui/react-select@2.2.5", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3", "aria-hidden": "^1.2.4", "react-remove-scroll": "^2.6.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-HnMTdXEVuuyzx63ME0ut4+sEMYW6oouHWNGUZc7ddvUWIcfCva/AMoqEW/3wnEllriMWBa0RHspCYnfCWJQYmA=="], - "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.6", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Izof3lPpbCfTM7WDta+LRkz31jem890VjEvpVRoWQNKpDUMMVffuyq854XPGP1KYGWWmjmYvHvPFeocWhFCy1w=="], + "@radix-ui/react-separator": ["@radix-ui/react-separator@1.1.7", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-0HEb8R9E8A+jZjvmFCy/J4xhbXy3TV+9XSnGJ3KvTtjlIUy/YQ/p6UYZvi7YbeoeXdyU9+Y3scizK6hkY37baA=="], - "@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.4", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Cp6hEmQtRJFci285vkdIJ+HCDLTRDk+25VhFwa1fcubywjMUE3PynBgtN5RLudOgSCYMlT4jizCXdmV+8J7Y2w=="], + "@radix-ui/react-slider": ["@radix-ui/react-slider@1.3.5", "", { "dependencies": { "@radix-ui/number": "1.1.1", "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-rkfe2pU2NBAYfGaxa3Mqosi7VZEWX5CxKaanRv0vZd4Zhl9fvQrg0VM93dv3xGLGfrHuoTRF3JXH8nb9g+B3fw=="], - "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.2", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-y7TBO4xN4Y94FvcWIOIh18fM4R1A8S4q1jhoz4PNzOoHsFcN8pogcFmZrTYAm4F9VRUrWP/Mw7xSKybIeRI+CQ=="], + "@radix-ui/react-slot": ["@radix-ui/react-slot@1.2.3", "", { "dependencies": { "@radix-ui/react-compose-refs": "1.1.2" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-aeNmHnBxbi2St0au6VBVC7JXFlhLlOnvIIlePNniyUNAClzmtAUEY8/pBiK3iHjufOlwA+c20/8jngo7xcrg8A=="], - "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.4", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-yZCky6XZFnR7pcGonJkr9VyNRu46KcYAbyg1v/gVVCZUr8UJ4x+RpncC27hHtiZ15jC+3WS8Yg/JSgyIHnYYsQ=="], + "@radix-ui/react-switch": ["@radix-ui/react-switch@1.2.5", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-previous": "1.1.1", "@radix-ui/react-use-size": "1.1.1" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-5ijLkak6ZMylXsaImpZ8u4Rlf5grRmoc0p0QeX9VJtlrM4f5m3nCTX8tWga/zOA8PZYIR/t0p2Mnvd7InrJ6yQ=="], - "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.11", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-roving-focus": "1.1.9", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-4FiKSVoXqPP/KfzlB7lwwqoFV6EPwkrrqGp9cUYXjwDYHhvpnqq79P+EPHKcdoTE7Rl8w/+6s9rTlsfXHES9GA=="], + "@radix-ui/react-tabs": ["@radix-ui/react-tabs@1.1.12", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-GTVAlRVrQrSw3cEARM0nAx73ixrWDPNZAruETn3oHCNP6SbZ/hNxdxp+u7VkIEv3/sFoLq1PfcHrl7Pnp0CDpw=="], - "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.13", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.9", "@radix-ui/react-portal": "1.1.8", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-e/e43mQAwgYs8BY4y9l99xTK6ig1bK2uXsFLOMn9IZ16lAgulSTsotcPHVT2ZlSb/ye6Sllq7IgyDB8dGhpeXQ=="], + "@radix-ui/react-toast": ["@radix-ui/react-toast@1.2.14", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-nAP5FBxBJGQ/YfUB+r+O6USFVkWq3gAInkxyEnmvEV5jtSbfDhfa4hwX8CraCnbjMLsE7XSf/K75l9xXY7joWg=="], - "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.8", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-hrpa59m3zDnsa35LrTOH5s/a3iGv/VD+KKQjjiCTo/W4r0XwPpiWQvAv6Xl1nupSoaZeNNxW6sJH9ZydsjKdYQ=="], + "@radix-ui/react-toggle": ["@radix-ui/react-toggle@1.1.9", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ZoFkBBz9zv9GWer7wIjvdRxmh2wyc2oKWw6C6CseWd6/yq1DK/l5lJ+wnsmFwJZbBYqr02mrf8A2q/CVCuM3ZA=="], - "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.9", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-roving-focus": "1.1.9", "@radix-ui/react-toggle": "1.1.8", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-HJ6gXdYVN38q/5KDdCcd+JTuXUyFZBMJbwXaU/82/Gi+V2ps6KpiZ2sQecAeZCV80POGRfkUBdUIj6hIdF6/MQ=="], + "@radix-ui/react-toggle-group": ["@radix-ui/react-toggle-group@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-toggle": "1.1.9", "@radix-ui/react-use-controllable-state": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-kiU694Km3WFLTC75DdqgM/3Jauf3rD9wxeS9XtyWFKsBUeZA337lC+6uUazT7I1DhanZ5gyD5Stf8uf2dbQxOQ=="], - "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.9", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-roving-focus": "1.1.9", "@radix-ui/react-separator": "1.1.6", "@radix-ui/react-toggle-group": "1.1.9" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-qqGkE9h018CSbpO4ag4rR6ZuOc/A9wM3dUv2jHrkfwUqspuvZmPegBPElVimH0FPWrYn4Alt4QTOptRjbwJnKw=="], + "@radix-ui/react-toolbar": ["@radix-ui/react-toolbar@1.1.10", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-toggle-group": "1.1.10" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-jiwQsduEL++M4YBIurjSa+voD86OIytCod0/dbIxFZDLD8NfO1//keXYMfsW8BPcfqwoNjt+y06XcJqAb4KR7A=="], - "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.6", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.9", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.6", "@radix-ui/react-portal": "1.1.8", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-slot": "1.2.2", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-zYb+9dc9tkoN2JjBDIIPLQtk3gGyz8FMKoqYTb8EMVQ5a5hBcdHPECrsZVI4NpPAUOixhkoqg7Hj5ry5USowfA=="], + "@radix-ui/react-tooltip": ["@radix-ui/react-tooltip@1.2.7", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-id": "1.1.1", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-Ap+fNYwKTYJ9pzqW+Xe2HtMRbQ/EeWkj2qykZ6SuEV4iS/o1bZI5ssJbk4D2r8XuDuOBVz/tIx2JObtuqU+5Zw=="], "@radix-ui/react-use-callback-ref": ["@radix-ui/react-use-callback-ref@1.1.1", "", { "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-FkBMwD+qbGQeMu1cOHnuGB6x4yzPjho8ap5WtbEJ26umhgqVXbhekKUQO+hZEL1vU92a3wHwdp0HAcqAUF5iDg=="], @@ -1339,7 +1368,7 @@ "@radix-ui/react-use-size": ["@radix-ui/react-use-size@1.1.1", "", { "dependencies": { "@radix-ui/react-use-layout-effect": "1.1.1" }, "peerDependencies": { "@types/react": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react"] }, "sha512-ewrXRDTAqAXlkl6t/fkXWNAhFX9I+CkKlw6zjEwk86RSPKwZr3xpBRso655aqYafwtnbpHLj6toFzmd6xdVptQ=="], - "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.2", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-ORCmRUbNiZIv6uV5mhFrhsIKw4UX/N3syZtyqvry61tbGm4JlgQuSn0hk5TwCARsCjkcnuRkSdCE3xfb+ADHew=="], + "@radix-ui/react-visually-hidden": ["@radix-ui/react-visually-hidden@1.2.3", "", { "dependencies": { "@radix-ui/react-primitive": "2.1.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-pzJq12tEaaIhqjbzpCuv/OypJY/BPavOofm+dbab+MHLajy277+1lLm6JFcGgF5eskJ6mquGirhXY2GD/8u8Ug=="], "@radix-ui/rect": ["@radix-ui/rect@1.1.1", "", {}, "sha512-HPwpGIzkl28mWyZqG52jiqDJ12waP11Pa1lGoiyUkIEuMLBP0oeK/C89esbXrxsky5we7dfd8U58nm0SgAWpVw=="], @@ -1733,7 +1762,7 @@ "@types/json5": ["@types/json5@0.0.29", "", {}, "sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ=="], - "@types/lodash": ["@types/lodash@4.17.16", "", {}, "sha512-HX7Em5NYQAXKW+1T+FiuG27NGwzJfCX3s1GjOa7ujxZa52kjJLOr4FUxT+giF6Tgxv1e+/czV/iTtBw27WTU9g=="], + "@types/lodash": ["@types/lodash@4.17.17", "", {}, "sha512-RRVJ+J3J+WmyOTqnz3PiBLA501eKwXl2noseKOrNo/6+XEHjTAxO4xHvxQB6QuNm+s4WRbn6rSiap8+EA+ykFQ=="], "@types/lru-cache": ["@types/lru-cache@5.1.1", "", {}, "sha512-ssE3Vlrys7sdIzs5LOxCzTVMsU7i9oa/IaW92wF32JFb3CVczqOkru2xspuKczHEbG3nvmPY7IFqVmGGHdNbYw=="], @@ -1747,7 +1776,7 @@ "@types/ms": ["@types/ms@2.1.0", "", {}, "sha512-GsCCIZDE/p3i96vtEqx+7dBUGXrc7zeSK3wwPHIaRThS+9OhWIXRqzs4d6k1SVU8g91DrNRWxWUGhp5KXQb2VA=="], - "@types/node": ["@types/node@22.15.19", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-3vMNr4TzNQyjHcRZadojpRaD9Ofr6LsonZAoQ+HMUa/9ORTPoxVIw0e0mpqWpdjj8xybyCM+oKOUH2vwFu/oEw=="], + "@types/node": ["@types/node@22.15.21", "", { "dependencies": { "undici-types": "~6.21.0" } }, "sha512-EV/37Td6c+MgKAbkcLG6vqZ2zEYHD7bvSrzqqs2RIhbA6w3x+Dqz8MZM3sP6kGTeLrdoOgKZe+Xja7tUB2DNkQ=="], "@types/pbkdf2": ["@types/pbkdf2@3.1.2", "", { "dependencies": { "@types/node": "*" } }, "sha512-uRwJqmiXmh9++aSu1VNEn3iIxWOhd8AHXNSdlaLfdAAdSTY9jYVeGWnzejM3dvrkbqE3/hyQkQQ29IFATEGlew=="], @@ -2541,7 +2570,7 @@ "eastasianwidth": ["eastasianwidth@0.2.0", "", {}, "sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA=="], - "eciesjs": ["eciesjs@0.4.14", "", { "dependencies": { "@ecies/ciphers": "^0.2.2", "@noble/ciphers": "^1.0.0", "@noble/curves": "^1.6.0", "@noble/hashes": "^1.5.0" } }, "sha512-eJAgf9pdv214Hn98FlUzclRMYWF7WfoLlkS9nWMTm1qcCwn6Ad4EGD9lr9HXMBfSrZhYQujRE+p0adPRkctC6A=="], + "eciesjs": ["eciesjs@0.4.15", "", { "dependencies": { "@ecies/ciphers": "^0.2.3", "@noble/ciphers": "^1.3.0", "@noble/curves": "^1.9.1", "@noble/hashes": "^1.8.0" } }, "sha512-r6kEJXDKecVOCj2nLMuXK/FCPeurW33+3JRpfXVbjLja3XUYFfD9I/JBreH6sUyzcm3G/YQboBjMla6poKeSdA=="], "ee-first": ["ee-first@1.1.1", "", {}, "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow=="], @@ -2831,7 +2860,7 @@ "get-symbol-description": ["get-symbol-description@1.1.0", "", { "dependencies": { "call-bound": "^1.0.3", "es-errors": "^1.3.0", "get-intrinsic": "^1.2.6" } }, "sha512-w9UMqWwJxHNOvoNzSJ2oPF5wvYcvP7jUvYzhp67yEhTi17ZDBBC1z9pTdGuzjD+EFIqLSYRweZjqfiPzQ06Ebg=="], - "get-tsconfig": ["get-tsconfig@4.10.0", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-kGzZ3LWWQcGIAmg6iWvXn0ei6WDtV26wzHRMwDSzmAbcXrTEXxHy6IehI6/4eT6VRKyMP1eF1VqwrVUmE/LR7A=="], + "get-tsconfig": ["get-tsconfig@4.10.1", "", { "dependencies": { "resolve-pkg-maps": "^1.0.0" } }, "sha512-auHyJ4AgMz7vgS8Hp3N6HXSmlMdUyhSUrfBF16w153rxtLIEOE+HGqaBppczZvnHLqQJfiHotCYpNhl0lUROFQ=="], "ghost-testrpc": ["ghost-testrpc@0.0.2", "", { "dependencies": { "chalk": "^2.4.2", "node-emoji": "^1.10.0" }, "bin": { "testrpc-sc": "./index.js" } }, "sha512-i08dAEgJ2g8z5buJIrCTduwPIhih3DP+hOCTyyryikfV8T0bNvHnGXO67i0DD1H4GBDETTclPy9njZbfluQYrQ=="], @@ -3271,7 +3300,7 @@ "lowlight": ["lowlight@3.3.0", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.0.0", "highlight.js": "~11.11.0" } }, "sha512-0JNhgFoPvP6U6lE/UdVsSq99tn6DhjjpAj5MxG49ewd2mOBVtwWYIT8ClyABhq198aXXODMU6Ox8DrGy/CpTZQ=="], - "lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], "lru_map": ["lru_map@0.3.3", "", {}, "sha512-Pn9cox5CsMYngeDbmChANltQl+5pi6XmTrraMSzhPmMBbmgcxmqWry0U3PGapCU1yB4/LqCcom7qhHZiF/jGfQ=="], @@ -3581,6 +3610,8 @@ "openapi-types": ["openapi-types@12.1.3", "", {}, "sha512-N4YtSYJqghVu4iek2ZUvcN/0aqH1kRDuNqzcycDxhOUpg7GdvLa2F3DgS6yBNhInhv2r/6I0Flkn7CqL8+nIcw=="], + "openapi3-ts": ["openapi3-ts@4.4.0", "", { "dependencies": { "yaml": "^2.5.0" } }, "sha512-9asTNB9IkKEzWMcHmVZE7Ts3kC9G7AFHfs8i7caD8HbI76gEjdkId4z/AkP83xdZsH7PLAnnbl47qZkXuxpArw=="], + "optionator": ["optionator@0.8.3", "", { "dependencies": { "deep-is": "~0.1.3", "fast-levenshtein": "~2.0.6", "levn": "~0.3.0", "prelude-ls": "~1.1.2", "type-check": "~0.3.2", "word-wrap": "~1.2.3" } }, "sha512-+IW9pACdk3XWmmTXG8m3upGUJst5XRGzxMRjXzAuJ1XnIFNvfhjjIuYkDvysnPQ7qzqVzLt78BCruntqRhWQbA=="], "ora": ["ora@7.0.1", "", { "dependencies": { "chalk": "^5.3.0", "cli-cursor": "^4.0.0", "cli-spinners": "^2.9.0", "is-interactive": "^2.0.0", "is-unicode-supported": "^1.3.0", "log-symbols": "^5.1.0", "stdin-discarder": "^0.1.0", "string-width": "^6.1.0", "strip-ansi": "^7.1.0" } }, "sha512-0TUxTiFJWv+JnjWm4o9yvuskpEJLXTcng8MJuKd+SzAzp2o+OP3HWqNhB4OdJRt1Vsd9/mR0oyaEYlOnL7XIRw=="], @@ -3729,7 +3760,7 @@ "proto-list": ["proto-list@1.2.4", "", {}, "sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA=="], - "protobufjs": ["protobufjs@7.5.2", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-f2ls6rpO6G153Cy+o2XQ+Y0sARLOZ17+OGVLHrc3VUKcLHYKEKWbkSujdBWQXM7gKn5NTfp0XnRPZn1MIu8n9w=="], + "protobufjs": ["protobufjs@7.4.0", "", { "dependencies": { "@protobufjs/aspromise": "^1.1.2", "@protobufjs/base64": "^1.1.2", "@protobufjs/codegen": "^2.0.4", "@protobufjs/eventemitter": "^1.1.0", "@protobufjs/fetch": "^1.1.0", "@protobufjs/float": "^1.0.2", "@protobufjs/inquire": "^1.1.0", "@protobufjs/path": "^1.1.2", "@protobufjs/pool": "^1.1.0", "@protobufjs/utf8": "^1.1.0", "@types/node": ">=13.7.0", "long": "^5.0.0" } }, "sha512-mRUWCc3KUU4w1jU8sGxICXH/gNS94DvI1gxqDvBzhj1JpcsimQkYiOJfwsPUykUI5ZaspFbSgmBLER8IrQ3tqw=="], "proxy-compare": ["proxy-compare@3.0.1", "", {}, "sha512-V9plBAt3qjMlS1+nC8771KNf6oJ12gExvaxnNzN/9yVRLdTv/lc+oJlnSzrdYDAvBfTStPCoiaCOTmTs0adv7Q=="], @@ -3759,7 +3790,7 @@ "quick-lru": ["quick-lru@5.1.1", "", {}, "sha512-WuyALRjWPDGtt/wzJiadO5AXY+8hZ80hVpe6MyivgraREW751X3SbhRvG3eLKOYN+8VEvqLcf3wdnt44Z4S4SA=="], - "radix-ui": ["radix-ui@1.4.1", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-accessible-icon": "1.1.6", "@radix-ui/react-accordion": "1.2.10", "@radix-ui/react-alert-dialog": "1.1.13", "@radix-ui/react-arrow": "1.1.6", "@radix-ui/react-aspect-ratio": "1.1.6", "@radix-ui/react-avatar": "1.1.9", "@radix-ui/react-checkbox": "1.3.1", "@radix-ui/react-collapsible": "1.1.10", "@radix-ui/react-collection": "1.1.6", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.14", "@radix-ui/react-dialog": "1.1.13", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.9", "@radix-ui/react-dropdown-menu": "2.1.14", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.6", "@radix-ui/react-form": "0.1.6", "@radix-ui/react-hover-card": "1.1.13", "@radix-ui/react-label": "2.1.6", "@radix-ui/react-menu": "2.1.14", "@radix-ui/react-menubar": "1.1.14", "@radix-ui/react-navigation-menu": "1.2.12", "@radix-ui/react-one-time-password-field": "0.1.6", "@radix-ui/react-password-toggle-field": "0.1.1", "@radix-ui/react-popover": "1.1.13", "@radix-ui/react-popper": "1.2.6", "@radix-ui/react-portal": "1.1.8", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.2", "@radix-ui/react-progress": "1.1.6", "@radix-ui/react-radio-group": "1.3.6", "@radix-ui/react-roving-focus": "1.1.9", "@radix-ui/react-scroll-area": "1.2.8", "@radix-ui/react-select": "2.2.4", "@radix-ui/react-separator": "1.1.6", "@radix-ui/react-slider": "1.3.4", "@radix-ui/react-slot": "1.2.2", "@radix-ui/react-switch": "1.2.4", "@radix-ui/react-tabs": "1.1.11", "@radix-ui/react-toast": "1.2.13", "@radix-ui/react-toggle": "1.1.8", "@radix-ui/react-toggle-group": "1.1.9", "@radix-ui/react-toolbar": "1.1.9", "@radix-ui/react-tooltip": "1.2.6", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.2" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-xG1aeAgvAiVglxHXMpHyk7RqLGnc8VnDUZvzpE8rZ8GAhuGeNm/+7YbIwCV+rKKRpsSgxdnvfUObQidK2EnTzw=="], + "radix-ui": ["radix-ui@1.4.2", "", { "dependencies": { "@radix-ui/primitive": "1.1.2", "@radix-ui/react-accessible-icon": "1.1.7", "@radix-ui/react-accordion": "1.2.11", "@radix-ui/react-alert-dialog": "1.1.14", "@radix-ui/react-arrow": "1.1.7", "@radix-ui/react-aspect-ratio": "1.1.7", "@radix-ui/react-avatar": "1.1.10", "@radix-ui/react-checkbox": "1.3.2", "@radix-ui/react-collapsible": "1.1.11", "@radix-ui/react-collection": "1.1.7", "@radix-ui/react-compose-refs": "1.1.2", "@radix-ui/react-context": "1.1.2", "@radix-ui/react-context-menu": "2.2.15", "@radix-ui/react-dialog": "1.1.14", "@radix-ui/react-direction": "1.1.1", "@radix-ui/react-dismissable-layer": "1.1.10", "@radix-ui/react-dropdown-menu": "2.1.15", "@radix-ui/react-focus-guards": "1.1.2", "@radix-ui/react-focus-scope": "1.1.7", "@radix-ui/react-form": "0.1.7", "@radix-ui/react-hover-card": "1.1.14", "@radix-ui/react-label": "2.1.7", "@radix-ui/react-menu": "2.1.15", "@radix-ui/react-menubar": "1.1.15", "@radix-ui/react-navigation-menu": "1.2.13", "@radix-ui/react-one-time-password-field": "0.1.7", "@radix-ui/react-password-toggle-field": "0.1.2", "@radix-ui/react-popover": "1.1.14", "@radix-ui/react-popper": "1.2.7", "@radix-ui/react-portal": "1.1.9", "@radix-ui/react-presence": "1.1.4", "@radix-ui/react-primitive": "2.1.3", "@radix-ui/react-progress": "1.1.7", "@radix-ui/react-radio-group": "1.3.7", "@radix-ui/react-roving-focus": "1.1.10", "@radix-ui/react-scroll-area": "1.2.9", "@radix-ui/react-select": "2.2.5", "@radix-ui/react-separator": "1.1.7", "@radix-ui/react-slider": "1.3.5", "@radix-ui/react-slot": "1.2.3", "@radix-ui/react-switch": "1.2.5", "@radix-ui/react-tabs": "1.1.12", "@radix-ui/react-toast": "1.2.14", "@radix-ui/react-toggle": "1.1.9", "@radix-ui/react-toggle-group": "1.1.10", "@radix-ui/react-toolbar": "1.1.10", "@radix-ui/react-tooltip": "1.2.7", "@radix-ui/react-use-callback-ref": "1.1.1", "@radix-ui/react-use-controllable-state": "1.2.2", "@radix-ui/react-use-effect-event": "0.0.2", "@radix-ui/react-use-escape-keydown": "1.1.1", "@radix-ui/react-use-is-hydrated": "0.1.0", "@radix-ui/react-use-layout-effect": "1.1.1", "@radix-ui/react-use-size": "1.1.1", "@radix-ui/react-visually-hidden": "1.2.3" }, "peerDependencies": { "@types/react": "*", "@types/react-dom": "*", "react": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc", "react-dom": "^16.8 || ^17.0 || ^18.0 || ^19.0 || ^19.0.0-rc" }, "optionalPeers": ["@types/react", "@types/react-dom"] }, "sha512-fT/3YFPJzf2WUpqDoQi005GS8EpCi+53VhcLaHUj5fwkPYiZAjk1mSxFvbMA8Uq71L03n+WysuYC+mlKkXxt/Q=="], "radix-vue": ["radix-vue@1.9.17", "", { "dependencies": { "@floating-ui/dom": "^1.6.7", "@floating-ui/vue": "^1.1.0", "@internationalized/date": "^3.5.4", "@internationalized/number": "^3.5.3", "@tanstack/vue-virtual": "^3.8.1", "@vueuse/core": "^10.11.0", "@vueuse/shared": "^10.11.0", "aria-hidden": "^1.2.4", "defu": "^6.1.4", "fast-deep-equal": "^3.1.3", "nanoid": "^5.0.7" }, "peerDependencies": { "vue": ">= 3.2.0" } }, "sha512-mVCu7I2vXt1L2IUYHTt0sZMz7s1K2ZtqKeTIxG3yC5mMFfLBG4FtE1FDeRMpDd+Hhg/ybi9+iXmAP1ISREndoQ=="], @@ -3945,7 +3976,7 @@ "secp256k1": ["secp256k1@4.0.4", "", { "dependencies": { "elliptic": "^6.5.7", "node-addon-api": "^5.0.0", "node-gyp-build": "^4.2.0" } }, "sha512-6JfvwvjUOn8F/jUoBY2Q1v5WY5XS+rj8qSe0v8Y4ezH4InLgTEeOOPQsRll9OV429Pvo6BCHGavIyJfr3TAhsw=="], - "semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="], + "semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], "send": ["send@0.19.0", "", { "dependencies": { "debug": "2.6.9", "depd": "2.0.0", "destroy": "1.2.0", "encodeurl": "~1.0.2", "escape-html": "~1.0.3", "etag": "~1.8.1", "fresh": "0.5.2", "http-errors": "2.0.0", "mime": "1.6.0", "ms": "2.1.3", "on-finished": "2.4.1", "range-parser": "~1.2.1", "statuses": "2.0.1" } }, "sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw=="], @@ -4485,7 +4516,7 @@ "zksync-web3": ["zksync-web3@0.14.4", "", { "peerDependencies": { "ethers": "^5.7.0" } }, "sha512-kYehMD/S6Uhe1g434UnaMN+sBr9nQm23Ywn0EUP5BfQCsbjcr3ORuS68PosZw8xUTu3pac7G6YMSnNHk+fwzvg=="], - "zod": ["zod@3.25.7", "", {}, "sha512-YGdT1cVRmKkOg6Sq7vY7IkxdphySKnXhaUmFI4r4FcuFVNgpCb9tZfNwXbT6BPjD5oz0nubFsoo9pIqKrDcCvg=="], + "zod": ["zod@3.25.16", "", {}, "sha512-3lOav31WLa1MstEvkM0QlcsFjKmJ2TI9IFYlIVpLE3upguhaeiRfPOzqqtisS/Hetk4ri2fLLC3OtW15lS5jxQ=="], "zod-openapi": ["zod-openapi@4.2.4", "", { "peerDependencies": { "zod": "^3.21.4" } }, "sha512-tsrQpbpqFCXqVXUzi3TPwFhuMtLN3oNZobOtYnK6/5VkXsNdnIgyNr4r8no4wmYluaxzN3F7iS+8xCW8BmMQ8g=="], @@ -4497,8 +4528,6 @@ "@arethetypeswrong/cli/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], - "@arethetypeswrong/core/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "@arethetypeswrong/core/typescript": ["typescript@5.6.1-rc", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-E3b2+1zEFu84jB0YQi9BORDjz9+jGbwwy1Zi3G0LUNw7a7cePUrHMRNy8aPh53nXpkFGVHSxIZo5vKTfYaFiBQ=="], "@babel/core/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], @@ -4513,8 +4542,6 @@ "@clack/prompts/is-unicode-supported": ["is-unicode-supported@1.3.0", "", { "bundled": true }, "sha512-43r2mRvz+8JRIKnWJ+3j8JtjRKZ6GmjzfaE/qiBJnikNnYv/6bagRJ1kUhNk8R5EX/GkobD+r+sfxCPJsiKBLQ=="], - "@coinbase/wallet-sdk/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - "@coinbase/wallet-sdk/clsx": ["clsx@1.2.1", "", {}, "sha512-EcR6r5a8bj6pu3ycsa/E/cKVGuTgZJZdsyUYHOksG/UHIiKfjxzRxYJpyVBwYaQeOvghal9fcc4PidlgzugAQg=="], "@eslint/eslintrc/ajv": ["ajv@6.12.6", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "fast-json-stable-stringify": "^2.0.0", "json-schema-traverse": "^0.4.1", "uri-js": "^4.2.2" } }, "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g=="], @@ -4539,6 +4566,8 @@ "@happy.tech/txm/vitest": ["vitest@3.1.4", "", { "dependencies": { "@vitest/expect": "3.1.4", "@vitest/mocker": "3.1.4", "@vitest/pretty-format": "^3.1.4", "@vitest/runner": "3.1.4", "@vitest/snapshot": "3.1.4", "@vitest/spy": "3.1.4", "@vitest/utils": "3.1.4", "chai": "^5.2.0", "debug": "^4.4.0", "expect-type": "^1.2.1", "magic-string": "^0.30.17", "pathe": "^2.0.3", "std-env": "^3.9.0", "tinybench": "^2.9.0", "tinyexec": "^0.3.2", "tinyglobby": "^0.2.13", "tinypool": "^1.0.2", "tinyrainbow": "^2.0.0", "vite": "^5.0.0 || ^6.0.0", "vite-node": "3.1.4", "why-is-node-running": "^2.3.0" }, "peerDependencies": { "@edge-runtime/vm": "*", "@types/debug": "^4.1.12", "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "@vitest/browser": "3.1.4", "@vitest/ui": "3.1.4", "happy-dom": "*", "jsdom": "*" }, "optionalPeers": ["@edge-runtime/vm", "@types/debug", "@types/node", "@vitest/browser", "@vitest/ui", "happy-dom", "jsdom"], "bin": { "vitest": "vitest.mjs" } }, "sha512-Ta56rT7uWxCSJXlBtKgIlApJnT6e6IGmTYxYcmxjJ4ujuZDI59GUQgVDObXXJujOmPDBYXHK1qmaGtneu6TNIQ=="], + "@hono/zod-openapi/@hono/zod-validator": ["@hono/zod-validator@0.5.0", "", { "peerDependencies": { "hono": ">=3.9.0", "zod": "^3.19.1" } }, "sha512-ds5bW6DCgAnNHP33E3ieSbaZFd5dkV52ZjyaXtGoR06APFrCtzAsKZxTHwOrJNBdXsi0e5wNwo5L4nVEVnJUdg=="], + "@humanwhocodes/config-array/minimatch": ["minimatch@3.1.2", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw=="], "@hyperjump/json-schema/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], @@ -4587,26 +4616,18 @@ "@microsoft/api-extractor/minimatch": ["minimatch@3.0.8", "", { "dependencies": { "brace-expansion": "^1.1.7" } }, "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q=="], + "@microsoft/api-extractor/semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="], + "@microsoft/api-extractor/typescript": ["typescript@5.8.2", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-aJn6wq13/afZp/jT9QZmwEjDqqvSGp1VT5GVg+f/t6/oVyrgXM6BY1h9BRh/O5p3PlUPAe+WuiEZOmb/49RqoQ=="], "@microsoft/tsdoc-config/ajv": ["ajv@8.12.0", "", { "dependencies": { "fast-deep-equal": "^3.1.1", "json-schema-traverse": "^1.0.0", "require-from-string": "^2.0.2", "uri-js": "^4.2.2" } }, "sha512-sRu1kpcO9yLtYxBKvqfTeh9KzZEwO3STyX1HT+4CaDzC6HpTGYhIhPIzj9XuKU7KYDwnaeh5hcOwjy1QuJzBPA=="], - "@motionone/easing/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "@motionone/generators/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "@nomiclabs/hardhat-etherscan/cbor": ["cbor@5.2.0", "", { "dependencies": { "bignumber.js": "^9.0.1", "nofilter": "^1.0.4" } }, "sha512-5IMhi9e1QU76ppa5/ajP1BmMWZ2FHkhAhjeVKQ/EFCgYSEaeVaoGtL7cxJskf9oCCk+XjzaIdc3IuU/dbA/o2A=="], "@nomiclabs/hardhat-etherscan/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], - "@opentelemetry/exporter-logs-otlp-grpc/@grpc/grpc-js": ["@grpc/grpc-js@1.13.3", "", { "dependencies": { "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-FTXHdOoPbZrBjlVLHuKbDZnsTxXv2BlHF57xw6LuThXacXvtkahEPED0CKMk6obZDf65Hv4k3z62eyPNpvinIg=="], - - "@opentelemetry/exporter-metrics-otlp-grpc/@grpc/grpc-js": ["@grpc/grpc-js@1.13.3", "", { "dependencies": { "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-FTXHdOoPbZrBjlVLHuKbDZnsTxXv2BlHF57xw6LuThXacXvtkahEPED0CKMk6obZDf65Hv4k3z62eyPNpvinIg=="], - - "@opentelemetry/exporter-trace-otlp-grpc/@grpc/grpc-js": ["@grpc/grpc-js@1.13.3", "", { "dependencies": { "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-FTXHdOoPbZrBjlVLHuKbDZnsTxXv2BlHF57xw6LuThXacXvtkahEPED0CKMk6obZDf65Hv4k3z62eyPNpvinIg=="], - - "@opentelemetry/otlp-grpc-exporter-base/@grpc/grpc-js": ["@grpc/grpc-js@1.13.3", "", { "dependencies": { "@grpc/proto-loader": "^0.7.13", "@js-sdsl/ordered-map": "^4.4.2" } }, "sha512-FTXHdOoPbZrBjlVLHuKbDZnsTxXv2BlHF57xw6LuThXacXvtkahEPED0CKMk6obZDf65Hv4k3z62eyPNpvinIg=="], - "@openzeppelin/upgrades-core/chalk": ["chalk@4.1.2", "", { "dependencies": { "ansi-styles": "^4.1.0", "supports-color": "^7.1.0" } }, "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA=="], "@pnpm/network.ca-file/graceful-fs": ["graceful-fs@4.2.10", "", {}, "sha512-9ByhssR2fPVsNZj478qUUbKfmL0+t5BDVyjShtyZZLiK7ZDAArFFfopyOTj0M05wE2tJPisA4iTnnXl2YoPvOA=="], @@ -4631,6 +4652,8 @@ "@rushstack/node-core-library/fs-extra": ["fs-extra@11.3.0", "", { "dependencies": { "graceful-fs": "^4.2.0", "jsonfile": "^6.0.1", "universalify": "^2.0.0" } }, "sha512-Z4XaCL6dUDHfP/jT25jJKMmtxvuwbkrD1vNSMFlo9lNLY2c5FHYSQgHPRZUjAB26TpDEoW9HCOgplrdbaPV/ew=="], + "@rushstack/node-core-library/semver": ["semver@7.5.4", "", { "dependencies": { "lru-cache": "^6.0.0" }, "bin": { "semver": "bin/semver.js" } }, "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA=="], + "@rushstack/ts-command-line/argparse": ["argparse@1.0.10", "", { "dependencies": { "sprintf-js": "~1.0.2" } }, "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg=="], "@scalar/api-client/nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="], @@ -4651,7 +4674,7 @@ "@scalar/core/@scalar/types": ["@scalar/types@0.0.40", "", { "dependencies": { "@scalar/openapi-types": "0.1.9", "@unhead/schema": "^1.11.11", "zod": "^3.23.8" } }, "sha512-0J6o+yZzgZEvl3KhvLTAGiXXyrCeEPKvs9gUWQDf1Rb5NfFxF0lA10ougCQCwVJIguWNEzZfOUiSoAFzGy2EqQ=="], - "@scalar/icons/@types/node": ["@types/node@20.17.48", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-KpSfKOHPsiSC4IkZeu2LsusFwExAIVGkhG1KkbaBMLwau0uMhj0fCrvyg9ddM2sAvd+gtiBJLir4LAw1MNMIaw=="], + "@scalar/icons/@types/node": ["@types/node@20.17.50", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A=="], "@scalar/oas-utils/nanoid": ["nanoid@5.1.5", "", { "bin": { "nanoid": "bin/nanoid.js" } }, "sha512-Ir/+ZpE9fDsNH0hQ3C68uyThDXzYcim2EqcZ8zn8Chtt1iylPT9xXJB0kPCnqzgcEGikO9RxSrh63MsmVCU7Fw=="], @@ -4709,8 +4732,6 @@ "@tailwindcss/vite/tailwindcss": ["tailwindcss@4.0.7", "", {}, "sha512-yH5bPPyapavo7L+547h3c4jcBXcrKwybQRjwdEIVAd9iXRvy/3T1CC6XSQEgZtRySjKfqvo3Cc0ZF1DTheuIdA=="], - "@tanstack/react-store/use-sync-external-store": ["use-sync-external-store@1.5.0", "", { "peerDependencies": { "react": "^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0" } }, "sha512-Rb46I4cGGVBmjamjphe8L/UnvJD+uPPtTkNvX5mZgqdbavhI4EbgIWJiIHXJ8bc/i9EQGPRh4DwEURJ552Do0A=="], - "@tanstack/router-generator/prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], "@tanstack/router-utils/diff": ["diff@7.0.0", "", {}, "sha512-PJWHUb1RFevKCwaFA9RlG5tCd+FO5iRh9A8HEtkmBH2Li03iJriB6m6JIN4rGz3K3JLawI7/veA1xzRKP6ISBw=="], @@ -4749,14 +4770,10 @@ "@typescript-eslint/typescript-estree/globby": ["globby@11.1.0", "", { "dependencies": { "array-union": "^2.1.0", "dir-glob": "^3.0.1", "fast-glob": "^3.2.9", "ignore": "^5.2.0", "merge2": "^1.4.1", "slash": "^3.0.0" } }, "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g=="], - "@typescript-eslint/typescript-estree/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], - "@vanilla-extract/compiler/vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="], "@vanilla-extract/compiler/vite-node": ["vite-node@3.1.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA=="], - "@vanilla-extract/css/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "@vanilla-extract/integration/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], "@vanilla-extract/integration/find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], @@ -4877,8 +4894,6 @@ "eslint/optionator": ["optionator@0.9.4", "", { "dependencies": { "deep-is": "^0.1.3", "fast-levenshtein": "^2.0.6", "levn": "^0.4.1", "prelude-ls": "^1.2.1", "type-check": "^0.4.0", "word-wrap": "^1.2.5" } }, "sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g=="], - "eslint-compat-utils/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], - "eslint-config-airbnb-base/semver": ["semver@6.3.1", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA=="], "eslint-import-resolver-node/debug": ["debug@3.2.7", "", { "dependencies": { "ms": "^2.1.1" } }, "sha512-CFjzYYAi4ThfiQvizrFQevTTXHtnCqWfe7x1AhgEscTz6ZbLbfoLRLPugTQyBth6f8ZERVUSyWHFD/7Wu4t1XQ=="], @@ -4897,8 +4912,6 @@ "eslint-plugin-n/globals": ["globals@15.15.0", "", {}, "sha512-7ACyT3wmyp3I61S4fG682L0VA2RGD9otkqGJIwNUMF1SWUombIIk+af1unuDYgMm082aHYwD+mzJvv9Iu8dsgg=="], - "eslint-plugin-n/semver": ["semver@7.7.2", "", { "bin": { "semver": "bin/semver.js" } }, "sha512-RF0Fw+rO5AMf9MAyaRXI4AV0Ulj5lMHqVxxdSgiVbixSCXoEmmX/jk0CuJw4+3SqroYO9VoUh+HcuJivvtJemA=="], - "eslint-plugin-prettier/prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="], "eslint-plugin-tsdoc/@microsoft/tsdoc": ["@microsoft/tsdoc@0.15.0", "", {}, "sha512-HZpPoABogPvjeJOdzCOSJsXeL/SMCBgBZMVC3X3d7YYp2gf31MfxhUoYUNwf1ERPJOnQc0wkFn9trqI6ZEdZuA=="], @@ -5047,13 +5060,11 @@ "lightningcss/detect-libc": ["detect-libc@2.0.4", "", {}, "sha512-3UDv+G9CsCKO1WKMGw9fwq/SWJYbI0c5Y7LU1AXYoDdbhE2AHQ6N6Nb34sG8Fj7T5APy8qXDCKuuIHd1BR0tVA=="], - "lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], - "marked-terminal/ansi-regex": ["ansi-regex@6.1.0", "", {}, "sha512-7HSX4QQb4CspciLpVFwyRe79O3xsIZDDLER21kERQ71oaPodF8jL725AgJMFAYbooIqolJoRLuM81SpeUkpkvA=="], "marked-terminal/node-emoji": ["node-emoji@2.2.0", "", { "dependencies": { "@sindresorhus/is": "^4.6.0", "char-regex": "^1.0.2", "emojilib": "^2.4.0", "skin-tone": "^2.0.0" } }, "sha512-Z3lTE9pLaJF47NyMhd4ww1yFTAP8YhYI8SleJiHzM46Fgpm5cnNzSl9XfzFNqbaz+VlJrIj3fXQ4DeN1Rjm6cw=="], - "mcl-wasm/@types/node": ["@types/node@20.17.48", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-KpSfKOHPsiSC4IkZeu2LsusFwExAIVGkhG1KkbaBMLwau0uMhj0fCrvyg9ddM2sAvd+gtiBJLir4LAw1MNMIaw=="], + "mcl-wasm/@types/node": ["@types/node@20.17.50", "", { "dependencies": { "undici-types": "~6.19.2" } }, "sha512-Mxiq0ULv/zo1OzOhwPqOA13I81CV/W3nvd3ChtQZRT5Cwz3cr0FKo/wMSsbTqL3EXpaBAEQhva2B8ByRkOIh9A=="], "mdast-util-directive/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], @@ -5109,20 +5120,12 @@ "ora/strip-ansi": ["strip-ansi@7.1.0", "", { "dependencies": { "ansi-regex": "^6.0.1" } }, "sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ=="], - "ox/@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.0", "", {}, "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg=="], - "ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], "ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - "ox/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], - - "ox/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], - "parse5-htmlparser2-tree-adapter/parse5": ["parse5@6.0.1", "", {}, "sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw=="], - "path-scurry/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "pkg-dir/find-up": ["find-up@5.0.0", "", { "dependencies": { "locate-path": "^6.0.0", "path-exists": "^4.0.0" } }, "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng=="], "pkg-types/pathe": ["pathe@2.0.3", "", {}, "sha512-WUjGcAqP1gQacoQe+OBJsFA7Ld4DyXuUIjZ5cc75cLHvJ7dtNsTugphxIADwspS+AraAUePCKrSVtPLFj/F88w=="], @@ -5145,16 +5148,10 @@ "react-json-tree/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], - "react-remove-scroll/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - - "react-remove-scroll-bar/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "react-resizable-panels/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], "react-resizable-panels/react-dom": ["react-dom@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0", "scheduler": "^0.23.2" }, "peerDependencies": { "react": "^18.3.1" } }, "sha512-5m4nQKp+rZRb09LNH59GM4BxTh9251/ylbKIbpe7TpGxfJ+9kv6BLkLBXIjjspbgbnIBNqlI23tRnTWT0snUIw=="], - "react-style-singleton/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "read-cache/pify": ["pify@2.3.0", "", {}, "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog=="], "read-yaml-file/js-yaml": ["js-yaml@3.14.1", "", { "dependencies": { "argparse": "^1.0.7", "esprima": "^4.0.0" }, "bin": { "js-yaml": "bin/js-yaml.js" } }, "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g=="], @@ -5191,6 +5188,8 @@ "send/encodeurl": ["encodeurl@1.0.2", "", {}, "sha512-TPJXq8JqFaVYm2CWmPvnP2Iyo4ZSM7/QKcSmuMLDObfpH5fi7RUGmd/rTDf+rut/saiDiQEeVTNgAmJEdAOx0w=="], + "sha.js/safe-buffer": ["safe-buffer@5.1.2", "", {}, "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g=="], + "shelljs/glob": ["glob@7.2.3", "", { "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", "inherits": "2", "minimatch": "^3.1.1", "once": "^1.3.0", "path-is-absolute": "^1.0.0" } }, "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q=="], "shiki/@shikijs/engine-oniguruma": ["@shikijs/engine-oniguruma@1.29.2", "", { "dependencies": { "@shikijs/types": "1.29.2", "@shikijs/vscode-textmate": "^10.0.1" } }, "sha512-7iiOx3SG8+g1MnlzZVDYiaeHe7Ez2Kf2HrJzdmGwkRisT7r4rak0e655AcM/tF9JG/kg5fMNYlLLKglbN7gBqA=="], @@ -5265,12 +5264,8 @@ "unstorage/chokidar": ["chokidar@4.0.3", "", { "dependencies": { "readdirp": "^4.0.1" } }, "sha512-Qgzu8kfBvo+cA4962jnP1KkS6Dop5NS6g7R5LFYJr4b8Ub94PPQXUksCw9PvXoeXPRRddRNC5C1JQUR2SMGtnA=="], - "unstorage/lru-cache": ["lru-cache@10.4.3", "", {}, "sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ=="], - "uri-js/punycode": ["punycode@2.3.1", "", {}, "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg=="], - "use-callback-ref/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "use-sidecar/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], "valtio/proxy-compare": ["proxy-compare@2.6.0", "", {}, "sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw=="], @@ -5301,8 +5296,6 @@ "web3-eth-contract/@ethereumjs/rlp": ["@ethereumjs/rlp@5.0.2", "", { "bin": { "rlp": "bin/rlp.cjs" } }, "sha512-DziebCdg4JpGlEqEdGgXmjqcFoJi+JGulUXwEjsZGAscAQ7MyD/7LE/GVCP29vEQxKc7AAwjT3A2ywHp2xfoCA=="], - "web3-eth-ens/@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.0", "", {}, "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg=="], - "web3-utils/ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="], "web3-validator/ethereum-cryptography": ["ethereum-cryptography@2.2.1", "", { "dependencies": { "@noble/curves": "1.4.2", "@noble/hashes": "1.4.0", "@scure/bip32": "1.4.0", "@scure/bip39": "1.3.0" } }, "sha512-r/W8lkHSiTLxUxW8Rf3u4HGB0xQweG2RyETjywylKZSzLWoWAijRz8WCuOtJ6wah+avllXBqZuk29HCCvhEIRg=="], @@ -5363,8 +5356,6 @@ "@happy.tech/txm/vitest/tinyrainbow": ["tinyrainbow@2.0.0", "", {}, "sha512-op4nsTR47R6p0vMUUoYl/a+ljLFVtlfaXkLQmqfLR1qHma1h/ysYk4hEXZ880bf2CYgTskvTa/e196Vd5dDQXw=="], - "@happy.tech/txm/vitest/vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="], - "@happy.tech/txm/vitest/vite-node": ["vite-node@3.1.4", "", { "dependencies": { "cac": "^6.7.14", "debug": "^4.4.0", "es-module-lexer": "^1.7.0", "pathe": "^2.0.3", "vite": "^5.0.0 || ^6.0.0" }, "bin": { "vite-node": "vite-node.mjs" } }, "sha512-6enNwYnpyDo4hEgytbmc6mYWHXDHYEn0D1/rw4Q+tnHUGtKTJsn8T1YkX6Q18wI5LCrS8CTYlBaiCqxOy2kvUA=="], "@humanwhocodes/config-array/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], @@ -5391,6 +5382,8 @@ "@microsoft/api-extractor/minimatch/brace-expansion": ["brace-expansion@1.1.11", "", { "dependencies": { "balanced-match": "^1.0.0", "concat-map": "0.0.1" } }, "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA=="], + "@microsoft/api-extractor/semver/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "@nomiclabs/hardhat-etherscan/cbor/nofilter": ["nofilter@1.0.4", "", {}, "sha512-N8lidFp+fCz+TD51+haYdbDGrcBWwuHX40F5+z0qkUjMJ5Tp+rdSuAkMJ9N9eoolDlEVTf6u5icM+cNKkKW2mA=="], "@openzeppelin/upgrades-core/chalk/supports-color": ["supports-color@7.2.0", "", { "dependencies": { "has-flag": "^4.0.0" } }, "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw=="], @@ -5417,6 +5410,8 @@ "@rushstack/node-core-library/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], + "@rushstack/node-core-library/semver/lru-cache": ["lru-cache@6.0.0", "", { "dependencies": { "yallist": "^4.0.0" } }, "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA=="], + "@scalar/code-highlight/unist-util-visit/@types/unist": ["@types/unist@3.0.3", "", {}, "sha512-ko/gIFJRv177XgZsZcBwnqJN5x/Gien8qNOn0D5bQU/zAzVf9Zt3BlcUiLqhV9y4ARk0GbT3tnUiPNgnTXzc/Q=="], "@scalar/code-highlight/unist-util-visit/unist-util-is": ["unist-util-is@6.0.0", "", { "dependencies": { "@types/unist": "^3.0.0" } }, "sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw=="], @@ -5613,8 +5608,6 @@ "eslint/optionator/type-check": ["type-check@0.4.0", "", { "dependencies": { "prelude-ls": "^1.2.1" } }, "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew=="], - "eth-json-rpc-filters/async-mutex/tslib": ["tslib@2.8.1", "", {}, "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w=="], - "ethers/@types/node/undici-types": ["undici-types@6.19.8", "", {}, "sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw=="], "ghost-testrpc/chalk/ansi-styles": ["ansi-styles@3.2.1", "", { "dependencies": { "color-convert": "^1.9.0" } }, "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA=="], @@ -5835,16 +5828,10 @@ "valtio/use-sync-external-store/react": ["react@18.3.1", "", { "dependencies": { "loose-envify": "^1.1.0" } }, "sha512-wS+hAgJShR0KhEvPJArfuPVN1+Hz1t0Y6n5jLrGQbkb4urgPE/0Rve+1kMB1v/oWgHgm4WIcV+i7F2pTVj+2iQ=="], - "viem/ox/@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.0", "", {}, "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg=="], - "viem/ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], "viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - "viem/ox/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], - - "viem/ox/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], - "vocs/fs-extra/jsonfile": ["jsonfile@6.1.0", "", { "dependencies": { "universalify": "^2.0.0" }, "optionalDependencies": { "graceful-fs": "^4.1.6" } }, "sha512-5dgndWOriYSm5cnYaJNhalLNDKOqFwyDB/rr1E9ZsGciGvKPs8R2xYGCacuf3z6K1YKDz182fd+fY3cn3pMqXQ=="], "vocs/fs-extra/universalify": ["universalify@2.0.1", "", {}, "sha512-gptHNQghINnc/vTGIk0SOFGFNXw7JVrlRUtConJRlvaw6DuX0wO5Jeko9sWrMBhh+PsYAZ7oXAiOnf/UKogyiw=="], @@ -5899,12 +5886,14 @@ "@happy.tech/txm/vitest/@vitest/mocker/estree-walker": ["estree-walker@3.0.3", "", { "dependencies": { "@types/estree": "^1.0.0" } }, "sha512-7RUKfXgSMMkzt6ZuXmqapOurLGPPfgj6l9uRZ7lRGolvk0y2yocc35LdcxKC5PQZdn2DMqioAQ2NoWcrTKmm6g=="], - "@happy.tech/txm/vitest/vite/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], + "@happy.tech/txm/vitest/vite-node/vite": ["vite@6.3.5", "", { "dependencies": { "esbuild": "^0.25.0", "fdir": "^6.4.4", "picomatch": "^4.0.2", "postcss": "^8.5.3", "rollup": "^4.34.9", "tinyglobby": "^0.2.13" }, "optionalDependencies": { "fsevents": "~2.3.3" }, "peerDependencies": { "@types/node": "^18.0.0 || ^20.0.0 || >=22.0.0", "jiti": ">=1.21.0", "less": "*", "lightningcss": "^1.21.0", "sass": "*", "sass-embedded": "*", "stylus": "*", "sugarss": "*", "terser": "^5.16.0", "tsx": "^4.8.1", "yaml": "^2.4.2" }, "optionalPeers": ["@types/node", "jiti", "less", "lightningcss", "sass", "sass-embedded", "stylus", "sugarss", "terser", "tsx", "yaml"], "bin": { "vite": "bin/vite.js" } }, "sha512-cZn6NDFE7wdTpINgs++ZJ4N49W2vRp8LCKrn3Ob1kYNtOo21vfDoaV5GzBfLU4MovSAB8uNRm4jgzVQZ+mBzPQ=="], "@metamask/eth-json-rpc-provider/@metamask/json-rpc-engine/@metamask/utils/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], "@metamask/eth-json-rpc-provider/@metamask/json-rpc-engine/@metamask/utils/uuid": ["uuid@9.0.1", "", { "bin": { "uuid": "dist/bin/uuid" } }, "sha512-b+1eJOlsR9K8HJpow9Ok3fiWOWSIcIzXodvv0rQjVoOVNpWMpxf1wZNpt4y9h10odCNrqnYp1OBzRktckBe3sA=="], + "@microsoft/api-extractor/semver/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/sign-client/@walletconnect/core": ["@walletconnect/core@2.19.2", "", { "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.19.2", "@walletconnect/utils": "2.19.2", "@walletconnect/window-getters": "1.0.1", "es-toolkit": "1.33.0", "events": "3.3.0", "uint8arrays": "3.1.0" } }, "sha512-iu0mgLj51AXcKpdNj8+4EdNNBd/mkNjLEhZn6UMc/r7BM9WbmpPMEydA39WeRLbdLO4kbpmq4wTbiskI1rg+HA=="], "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/@noble/ciphers": ["@noble/ciphers@1.2.1", "", {}, "sha512-rONPWMC7PeExE077uLE4oqWrZ1IvAfz3oH9LibVAcVCopJiA9R62uavnbEzdkVmJYI6M6Zgkbeb07+tWjlq2XA=="], @@ -5939,6 +5928,8 @@ "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem": ["viem@2.23.2", "", { "dependencies": { "@noble/curves": "1.8.1", "@noble/hashes": "1.7.1", "@scure/bip32": "1.6.2", "@scure/bip39": "1.5.4", "abitype": "1.0.8", "isows": "1.0.6", "ox": "0.6.7", "ws": "8.18.0" }, "peerDependencies": { "typescript": ">=5.0.4" }, "optionalPeers": ["typescript"] }, "sha512-NVmW/E0c5crMOtbEAqMF0e3NmvQykFXhLOc/CkLIXOlzHSA6KXVz3CYVmaKqBF8/xtjsjHAGjdJN3Ru1kFJLaA=="], + "@rushstack/node-core-library/semver/lru-cache/yallist": ["yallist@4.0.0", "", {}, "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A=="], + "@tkey/core/ethereum-cryptography/@scure/bip32/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], "@tkey/core/ethereum-cryptography/@scure/bip39/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], @@ -6017,16 +6008,10 @@ "@vanilla-extract/integration/find-up/locate-path/p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], - "@walletconnect/utils/viem/ox/@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.0", "", {}, "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg=="], - "@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], "@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], - "@walletconnect/utils/viem/ox/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], - - "@walletconnect/utils/viem/ox/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], - "eslint/find-up/locate-path/p-locate": ["p-locate@5.0.0", "", { "dependencies": { "p-limit": "^3.0.2" } }, "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw=="], "ghost-testrpc/chalk/ansi-styles/color-convert": ["color-convert@1.9.3", "", { "dependencies": { "color-name": "1.1.3" } }, "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg=="], @@ -6137,51 +6122,7 @@ "@ethereumjs/common/@ethereumjs/util/ethereum-cryptography/@scure/bip39/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="], - - "@happy.tech/txm/vitest/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild": ["esbuild@0.25.4", "", { "optionalDependencies": { "@esbuild/aix-ppc64": "0.25.4", "@esbuild/android-arm": "0.25.4", "@esbuild/android-arm64": "0.25.4", "@esbuild/android-x64": "0.25.4", "@esbuild/darwin-arm64": "0.25.4", "@esbuild/darwin-x64": "0.25.4", "@esbuild/freebsd-arm64": "0.25.4", "@esbuild/freebsd-x64": "0.25.4", "@esbuild/linux-arm": "0.25.4", "@esbuild/linux-arm64": "0.25.4", "@esbuild/linux-ia32": "0.25.4", "@esbuild/linux-loong64": "0.25.4", "@esbuild/linux-mips64el": "0.25.4", "@esbuild/linux-ppc64": "0.25.4", "@esbuild/linux-riscv64": "0.25.4", "@esbuild/linux-s390x": "0.25.4", "@esbuild/linux-x64": "0.25.4", "@esbuild/netbsd-arm64": "0.25.4", "@esbuild/netbsd-x64": "0.25.4", "@esbuild/openbsd-arm64": "0.25.4", "@esbuild/openbsd-x64": "0.25.4", "@esbuild/sunos-x64": "0.25.4", "@esbuild/win32-arm64": "0.25.4", "@esbuild/win32-ia32": "0.25.4", "@esbuild/win32-x64": "0.25.4" }, "bin": { "esbuild": "bin/esbuild" } }, "sha512-8pgjLUcUjcgDg+2Q4NYXnPbo/vncAY4UmyaCm0jZevERqCHZIaWwdJHkf8XQtu4AxSKCdvrUbT0XUr1IdZzI8Q=="], "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/bs58/base-x": ["base-x@5.0.1", "", {}, "sha512-M7uio8Zt++eg3jPj+rHMfCC+IuygQHHCOU+IYsVtik6FWjuYpVt/+MRKcgsAMHh8mMFAwnB+Bs+mTrFiXjMzKg=="], @@ -6227,35 +6168,63 @@ "solidity-coverage/web3-utils/ethereum-cryptography/@scure/bip39/@scure/base": ["@scure/base@1.1.9", "", {}, "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg=="], - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.0", "", {}, "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/aix-ppc64": ["@esbuild/aix-ppc64@0.25.4", "", { "os": "aix", "cpu": "ppc64" }, "sha512-1VCICWypeQKhVbE9oW/sJaAmjLxhVqacdkvPLEjwlttjfwENRSClS8EjBz0KzRyFSCPDIkuXW34Je/vk7zdB7Q=="], - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/android-arm": ["@esbuild/android-arm@0.25.4", "", { "os": "android", "cpu": "arm" }, "sha512-QNdQEps7DfFwE3hXiU4BZeOV68HHzYwGd0Nthhd3uCkkEKK7/R6MTgM0P7H7FAs5pU/DIWsviMmEGxEoxIZ+ZQ=="], - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/android-arm64": ["@esbuild/android-arm64@0.25.4", "", { "os": "android", "cpu": "arm64" }, "sha512-bBy69pgfhMGtCnwpC/x5QhfxAz/cBgQ9enbtwjf6V9lnPI/hMyT9iWpR1arm0l3kttTr4L0KSLpKmLp/ilKS9A=="], - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/android-x64": ["@esbuild/android-x64@0.25.4", "", { "os": "android", "cpu": "x64" }, "sha512-TVhdVtQIFuVpIIR282btcGC2oGQoSfZfmBdTip2anCaVYcqWlZXGcdcKIUklfX2wj0JklNYgz39OBqh2cqXvcQ=="], - "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/darwin-arm64": ["@esbuild/darwin-arm64@0.25.4", "", { "os": "darwin", "cpu": "arm64" }, "sha512-Y1giCfM4nlHDWEfSckMzeWNdQS31BQGs9/rouw6Ub91tkK79aIMTH3q9xHvzH8d0wDru5Ci0kWB8b3up/nl16g=="], - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.0", "", {}, "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/darwin-x64": ["@esbuild/darwin-x64@0.25.4", "", { "os": "darwin", "cpu": "x64" }, "sha512-CJsry8ZGM5VFVeyUYB3cdKpd/H69PYez4eJh1W/t38vzutdjEjtP7hB6eLKBoOdxcAlCtEYHzQ/PJ/oU9I4u0A=="], - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/freebsd-arm64": ["@esbuild/freebsd-arm64@0.25.4", "", { "os": "freebsd", "cpu": "arm64" }, "sha512-yYq+39NlTRzU2XmoPW4l5Ifpl9fqSk0nAJYM/V/WUGPEFfek1epLHJIkTQM6bBs1swApjO5nWgvr843g6TjxuQ=="], - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/freebsd-x64": ["@esbuild/freebsd-x64@0.25.4", "", { "os": "freebsd", "cpu": "x64" }, "sha512-0FgvOJ6UUMflsHSPLzdfDnnBBVoCDtBTVyn/MrWloUNvq/5SFmh13l3dvgRPkDihRxb77Y17MbqbCAa2strMQQ=="], - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/linux-arm": ["@esbuild/linux-arm@0.25.4", "", { "os": "linux", "cpu": "arm" }, "sha512-kro4c0P85GMfFYqW4TWOpvmF8rFShbWGnrLqlzp4X1TNWjRY3JMYUfDCtOxPKOIY8B0WC8HN51hGP4I4hz4AaQ=="], - "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/linux-arm64": ["@esbuild/linux-arm64@0.25.4", "", { "os": "linux", "cpu": "arm64" }, "sha512-+89UsQTfXdmjIvZS6nUnOOLoXnkUTB9hR5QAeLrQdzOSWZvNSAXAtcRDHWtqAUtAmv7ZM1WPOOeSxDzzzMogiQ=="], - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@adraffy/ens-normalize": ["@adraffy/ens-normalize@1.11.0", "", {}, "sha512-/3DDPKHqqIqxUULp8yP4zODUY1i+2xvVWsv8A79xGWdCAG+8sb0hRh0Rk2QyOJUnnbyPUAZYcpBuRe3nS2OIUg=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/linux-ia32": ["@esbuild/linux-ia32@0.25.4", "", { "os": "linux", "cpu": "ia32" }, "sha512-yTEjoapy8UP3rv8dB0ip3AfMpRbyhSN3+hY8mo/i4QXFeDxmiYbEKp3ZRjBKcOP862Ua4b1PDfwlvbuwY7hIGQ=="], - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/linux-loong64": ["@esbuild/linux-loong64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-NeqqYkrcGzFwi6CGRGNMOjWGGSYOpqwCjS9fvaUlX5s3zwOtn1qwg1s2iE2svBe4Q/YOG1q6875lcAoQK/F4VA=="], - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/linux-mips64el": ["@esbuild/linux-mips64el@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-IcvTlF9dtLrfL/M8WgNI/qJYBENP3ekgsHbYUIzEzq5XJzzVEV/fXY9WFPfEEXmu3ck2qJP8LG/p3Q8f7Zc2Xg=="], + + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/linux-ppc64": ["@esbuild/linux-ppc64@0.25.4", "", { "os": "linux", "cpu": "ppc64" }, "sha512-HOy0aLTJTVtoTeGZh4HSXaO6M95qu4k5lJcH4gxv56iaycfz1S8GO/5Jh6X4Y1YiI0h7cRyLi+HixMR+88swag=="], + + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/linux-riscv64": ["@esbuild/linux-riscv64@0.25.4", "", { "os": "linux", "cpu": "none" }, "sha512-i8JUDAufpz9jOzo4yIShCTcXzS07vEgWzyX3NH2G7LEFVgrLEhjwL3ajFE4fZI3I4ZgiM7JH3GQ7ReObROvSUA=="], + + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/linux-s390x": ["@esbuild/linux-s390x@0.25.4", "", { "os": "linux", "cpu": "s390x" }, "sha512-jFnu+6UbLlzIjPQpWCNh5QtrcNfMLjgIavnwPQAfoGx4q17ocOU9MsQ2QVvFxwQoWpZT8DvTLooTvmOQXkO51g=="], + + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/linux-x64": ["@esbuild/linux-x64@0.25.4", "", { "os": "linux", "cpu": "x64" }, "sha512-6e0cvXwzOnVWJHq+mskP8DNSrKBr1bULBvnFLpc1KY+d+irZSgZ02TGse5FsafKS5jg2e4pbvK6TPXaF/A6+CA=="], + + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/netbsd-x64": ["@esbuild/netbsd-x64@0.25.4", "", { "os": "none", "cpu": "x64" }, "sha512-XAg8pIQn5CzhOB8odIcAm42QsOfa98SBeKUdo4xa8OvX8LbMZqEtgeWE9P/Wxt7MlG2QqvjGths+nq48TrUiKw=="], + + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/openbsd-x64": ["@esbuild/openbsd-x64@0.25.4", "", { "os": "openbsd", "cpu": "x64" }, "sha512-xAGGhyOQ9Otm1Xu8NT1ifGLnA6M3sJxZ6ixylb+vIUVzvvd6GOALpwQrYrtlPouMqd/vSbgehz6HaVk4+7Afhw=="], - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@scure/bip32": ["@scure/bip32@1.7.0", "", { "dependencies": { "@noble/curves": "~1.9.0", "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-E4FFX/N3f4B80AKWp5dP6ow+flD1LQZo/w8UnLGYZO674jS6YnYeepycOOksv+vLPSpgN35wgKgy+ybfTb2SMw=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/sunos-x64": ["@esbuild/sunos-x64@0.25.4", "", { "os": "sunos", "cpu": "x64" }, "sha512-Mw+tzy4pp6wZEK0+Lwr76pWLjrtjmJyUB23tHKqEDP74R3q95luY/bXqXZeYl4NYlvwOqoRKlInQialgCKy67Q=="], - "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@scure/bip39": ["@scure/bip39@1.6.0", "", { "dependencies": { "@noble/hashes": "~1.8.0", "@scure/base": "~1.2.5" } }, "sha512-+lF0BbLiJNwVlev4eKelw1WWLaiKXw7sSl8T6FvBlWkdX+94aGJ4o8XjUdlyhTCjd8c+B3KT3JfS8P0bLRNU6A=="], + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/win32-arm64": ["@esbuild/win32-arm64@0.25.4", "", { "os": "win32", "cpu": "arm64" }, "sha512-AVUP428VQTSddguz9dO9ngb+E5aScyg7nOeJDrF1HPYu555gmza3bDGMPhmVXL8svDSoqPCsCPjb265yG/kLKQ=="], + + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/win32-ia32": ["@esbuild/win32-ia32@0.25.4", "", { "os": "win32", "cpu": "ia32" }, "sha512-i1sW+1i+oWvQzSgfRcxxG2k4I9n3O9NRqy8U+uugaT2Dy7kLO9Y7wI72haOahxceMX8hZAzgGou1FhndRldxRg=="], + + "@happy.tech/txm/vitest/vite-node/vite/esbuild/@esbuild/win32-x64": ["@esbuild/win32-x64@0.25.4", "", { "os": "win32", "cpu": "x64" }, "sha512-nOT2vZNw6hJ+z43oP1SPea/G/6AbN6X+bGNhNuq8NtRHy4wsMhw765IKLNmnjek7GvjWBYQ8Q5VBoYTFg9y1UQ=="], + + "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + + "@reown/appkit-controllers/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + + "@reown/appkit-utils/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], + + "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/curves": ["@noble/curves@1.9.1", "", { "dependencies": { "@noble/hashes": "1.8.0" } }, "sha512-k11yZxZg+t+gWvBbIswW0yoJlu8cHOC7dhunwOzoWH/mXGBiYyR4YY6hAEK/3EUs4UpB8la1RfdRpeGsFHkWsA=="], + + "@reown/appkit/@walletconnect/universal-provider/@walletconnect/utils/viem/ox/@noble/hashes": ["@noble/hashes@1.8.0", "", {}, "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A=="], "@vanilla-extract/integration/find-up/locate-path/p-locate/p-limit/yocto-queue": ["yocto-queue@0.1.0", "", {}, "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="],