diff --git a/apps/solana/file-upload/.env.example b/apps/solana/file-upload/.env.example new file mode 100644 index 0000000..48f7efa --- /dev/null +++ b/apps/solana/file-upload/.env.example @@ -0,0 +1,6 @@ +# Solana RPC endpoint (defaults to testnet) +NEXT_PUBLIC_SOLANA_RPC_URL=https://api.testnet.solana.com + +# Shelby API key - get one from https://docs.shelby.xyz/sdks/typescript/acquire-api-keys +NEXT_PUBLIC_SHELBY_API_KEY=AG-xxx + diff --git a/apps/solana/file-upload/README.md b/apps/solana/file-upload/README.md new file mode 100644 index 0000000..15c69c6 --- /dev/null +++ b/apps/solana/file-upload/README.md @@ -0,0 +1,95 @@ +# Solana File Upload Example + +A Next.js example application demonstrating how Solana developers can build a file upload dApp on [Shelby Protocol](https://shelby.xyz). This app allows users to connect their Solana wallet and upload files to decentralized storage. + +## Features + +- Connect Solana wallets via [@solana/react-hooks](https://www.npmjs.com/package/@solana/react-hooks) +- Upload files to Shelby's decentralized storage +- Automatic storage account derivation from Solana address +- Clean UI with drag-and-drop file uploads + +## Prerequisites + +- [Node.js](https://nodejs.org/) v18 or higher +- [pnpm](https://pnpm.io/) package manager +- A Solana wallet (e.g., Phantom, Solflare) +- A Shelby API key + +## Getting Started + +### 1. Clone the Repository + +```bash +git clone https://github.com/shelby-protocol/shelby-examples.git +cd shelby-examples/apps/solana/file-upload +``` + +### 2. Install Dependencies + +From the monorepo root: + +```bash +pnpm install +``` + +Or from this directory: + +```bash +pnpm install +``` + +### 3. Set Up Environment Variables + +Copy the example environment file: + +```bash +cp .env.example .env +``` + +### 4. Get Your Shelby API Key + +1. Visit the [Shelby API Keys documentation](https://docs.shelby.xyz/sdks/typescript/acquire-api-keys) +2. Follow the instructions to acquire your API key +3. Add your API key to the `.env` file: + +```env +NEXT_PUBLIC_SHELBY_API_KEY=your-api-key-here +``` + +### 5. Run the Development Server + +```bash +pnpm dev +``` + +Open [http://localhost:3000](http://localhost:3000) in your browser. + +## How It Works + +This example uses the following Shelby packages: + +- [`@shelby-protocol/sdk`](https://docs.shelby.xyz/sdks/typescript) - Core TypeScript SDK for interacting with Shelby Protocol +- [`@shelby-protocol/solana-kit`](https://docs.shelby.xyz/sdks/solana-kit/react) - Solana-specific utilities for wallet integration +- [`@shelby-protocol/react`](https://docs.shelby.xyz/sdks/typescript) - React hooks for blob uploads + +### Key Components + +- **FileUpload** - Main component handling file selection and upload +- **Header** - Navigation with wallet connection button +- **WalletButton** - Wallet connection using @solana/react-hooks +- **providers.tsx** - SolanaProvider and QueryClientProvider configuration + +### Storage Account + +When you connect your Solana wallet, a Shelby storage account is automatically derived from your Solana address. This allows you to upload and manage files using your existing wallet. + +## Learn More + +- [Shelby Documentation](https://docs.shelby.xyz) +- [Solana Kit React Reference](https://docs.shelby.xyz/sdks/solana-kit/react) +- [Shelby Explorer](https://explorer.shelby.xyz) - View your uploaded files + +## License + +MIT diff --git a/apps/solana/file-upload/app/components/file-upload.tsx b/apps/solana/file-upload/app/components/file-upload.tsx new file mode 100644 index 0000000..2677645 --- /dev/null +++ b/apps/solana/file-upload/app/components/file-upload.tsx @@ -0,0 +1,199 @@ +"use client"; + +import { useUploadBlobs } from "@shelby-protocol/react"; +import { useStorageAccount } from "@shelby-protocol/solana-kit/react"; +import { useWalletConnection } from "@solana/react-hooks"; +import { useCallback, useRef, useState } from "react"; +import type { ReactNode } from "react"; +import { shelbyClient } from "../lib/shelbyClient"; + +type UploadStep = "idle" | "uploading" | "done" | "error"; + +export function FileUpload() { + const { status, wallet } = useWalletConnection(); + const fileInputRef = useRef(null); + + // Use the new simplified API - pass wallet directly + const { storageAccountAddress, signAndSubmitTransaction } = useStorageAccount( + { + client: shelbyClient, + wallet, + }, + ); + + const { mutateAsync: uploadBlobs, isPending } = useUploadBlobs({ + client: shelbyClient, + }); + + const [selectedFile, setSelectedFile] = useState(null); + const [step, setStep] = useState("idle"); + const [statusMessage, setStatusMessage] = useState(null); + + const clearFile = useCallback(() => { + setSelectedFile(null); + if (fileInputRef.current) { + fileInputRef.current.value = ""; + } + }, []); + + const handleFileChange = useCallback( + (e: React.ChangeEvent) => { + const file = e.target.files?.[0] ?? null; + setSelectedFile(file); + setStep("idle"); + setStatusMessage(null); + }, + [], + ); + + const handleUpload = useCallback(async () => { + if (!selectedFile || !storageAccountAddress) return; + + try { + setStep("uploading"); + setStatusMessage("Uploading file to Shelby..."); + + const fileBytes = new Uint8Array(await selectedFile.arrayBuffer()); + + await uploadBlobs({ + signer: { account: storageAccountAddress, signAndSubmitTransaction }, + blobs: [ + { + blobName: selectedFile.name, + blobData: fileBytes, + }, + ], + // 30 days from now in microseconds + expirationMicros: (1000 * 60 * 60 * 24 * 30 + Date.now()) * 1000, + }); + + setStep("done"); + const explorerUrl = `https://explorer.shelby.xyz/shelbynet/account/${storageAccountAddress.toString()}/blobs`; + setStatusMessage( + <> + Successfully uploaded: {selectedFile.name}.{" "} + + View in Explorer + + , + ); + clearFile(); + } catch (err) { + setStep("error"); + const message = err instanceof Error ? err.message : "Unknown error"; + const cause = + err instanceof Error && err.cause instanceof Error + ? err.cause.message + : undefined; + setStatusMessage( + cause ? `Error: ${message} — ${cause}` : `Error: ${message}`, + ); + } + }, [ + selectedFile, + storageAccountAddress, + signAndSubmitTransaction, + uploadBlobs, + clearFile, + ]); + + const handleSelectFile = useCallback(() => { + fileInputRef.current?.click(); + }, []); + + const isProcessing = step === "uploading" || isPending; + + if (status !== "connected") { + return ( +
+
+

Upload File to Shelby

+

+ Connect your wallet to upload files to decentralized storage. +

+
+
+ Wallet not connected +
+
+ ); + } + + return ( +
+
+

Upload File to Shelby

+

+ Upload any file to Shelby's decentralized storage using your + Solana wallet. +

+
+ + {/* File Input */} +
+ + +
+ {selectedFile ? ( +
+

{selectedFile.name}

+

+ {(selectedFile.size / 1024).toFixed(1)} KB +

+
+ ) : ( +
+

Click to select a file

+
+ )} +
+ + {/* Upload Button */} + +
+ + {/* Status Message */} + {statusMessage && ( +
+ {statusMessage} +
+ )} + + {/* Storage Account Info */} +
+

+ Shelby Storage Account:{" "} + {storageAccountAddress?.toString()} +

+
+
+ ); +} diff --git a/apps/solana/file-upload/app/components/header.tsx b/apps/solana/file-upload/app/components/header.tsx new file mode 100644 index 0000000..4641ae8 --- /dev/null +++ b/apps/solana/file-upload/app/components/header.tsx @@ -0,0 +1,20 @@ +"use client"; + +import Link from "next/link"; +import { WalletButton } from "./wallet-button"; + +export function Header() { + return ( +
+
+ + + FU + + File Upload + + +
+
+ ); +} diff --git a/apps/solana/file-upload/app/components/providers.tsx b/apps/solana/file-upload/app/components/providers.tsx new file mode 100644 index 0000000..57c68a2 --- /dev/null +++ b/apps/solana/file-upload/app/components/providers.tsx @@ -0,0 +1,32 @@ +"use client"; + +import { autoDiscover, createClient } from "@solana/client"; +import { SolanaProvider } from "@solana/react-hooks"; +import { QueryClient, QueryClientProvider } from "@tanstack/react-query"; +import { config } from "../lib/config"; + +// Filter to only show wallets that support Solana features +const isSolanaWallet = (wallet: { features: Record }) => { + return Object.keys(wallet.features).some((feature) => + feature.startsWith("solana:"), + ); +}; + +const client = createClient({ + endpoint: config.solanaRpcUrl, + walletConnectors: autoDiscover({ filter: isSolanaWallet }), +}); + +const queryClient = new QueryClient(); + +export function Providers({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + {children} + + ); +} diff --git a/apps/solana/file-upload/app/components/wallet-button.tsx b/apps/solana/file-upload/app/components/wallet-button.tsx new file mode 100644 index 0000000..479de76 --- /dev/null +++ b/apps/solana/file-upload/app/components/wallet-button.tsx @@ -0,0 +1,191 @@ +"use client"; + +import { useStorageAccount } from "@shelby-protocol/solana-kit/react"; +import { useWalletConnection } from "@solana/react-hooks"; +import { useEffect, useMemo, useRef, useState } from "react"; +import { createPortal } from "react-dom"; +import { shelbyClient } from "../lib/shelbyClient"; + +function formatAddress(address?: string) { + if (!address) return "Not connected"; + return `${address.slice(0, 4)}…${address.slice(-4)}`; +} + +export function WalletButton() { + const { connectors, connect, disconnect, wallet, status } = + useWalletConnection(); + const [isModalOpen, setIsModalOpen] = useState(false); + const [isMenuOpen, setIsMenuOpen] = useState(false); + const menuRef = useRef(null); + + const walletAddress = wallet?.account.address.toString(); + + // Storage account derived from the connected wallet (using new API) + const { storageAccountAddress } = useStorageAccount({ + client: shelbyClient, + wallet, + }); + + // Close modal when connection succeeds + useEffect(() => { + if (status === "connected") { + // eslint-disable-next-line react-hooks/set-state-in-effect + setIsModalOpen(false); + } + }, [status]); + + // Close dropdown when clicking outside + useEffect(() => { + if (!isMenuOpen) return; + + function handleClickOutside(e: MouseEvent) { + if (menuRef.current && !menuRef.current.contains(e.target as Node)) { + setIsMenuOpen(false); + } + } + + document.addEventListener("mousedown", handleClickOutside); + return () => document.removeEventListener("mousedown", handleClickOutside); + }, [isMenuOpen]); + + const buttonLabel = useMemo( + () => + status === "connected" + ? formatAddress(walletAddress) + : status === "connecting" + ? "Connecting..." + : "Connect wallet", + [status, walletAddress], + ); + + return ( +
+ + + {isMenuOpen && status === "connected" && ( +
+ {/* Storage Account */} +
+
Storage account
+
+ + {storageAccountAddress + ? `${storageAccountAddress.toString().slice(0, 6)}...${storageAccountAddress.toString().slice(-5)}` + : "Not available"} + + +
+
+ + {/* Disconnect */} +
+ +
+
+ )} + + {isModalOpen && + status !== "connected" && + createPortal( +
setIsModalOpen(false)} + > +
e.stopPropagation()} + > +
+
+

Select a wallet

+

+ Choose any discovered connector to continue. +

+
+ +
+ +
+ {connectors.map((connector) => ( + + ))} + + {connectors.length === 0 && ( +
+ No wallets discovered. Ensure a Solana wallet extension is + installed. +
+ )} +
+
+
, + document.body, + )} +
+ ); +} diff --git a/apps/solana/file-upload/app/globals.css b/apps/solana/file-upload/app/globals.css new file mode 100644 index 0000000..18a9823 --- /dev/null +++ b/apps/solana/file-upload/app/globals.css @@ -0,0 +1,61 @@ +@import "tailwindcss"; + +:root { + --background: #ffffff; + --foreground: oklch(0.145 0 0); /* deep neutral */ + --border-strong: oklch(0.839 0.0187 78.23); + --border-low: oklch(0.927 0.0122 96.43); + --accent: oklch(0 0 0); + --muted: #3a3a3a; /* darker for better light-mode contrast */ + --cream: oklch(0.9553 0.0029 84.56); + --card: #ffffff; + --card-foreground: oklch(0.145 0 0); + --radius: 0.625rem; + --font-sans: var(--font-inter), system-ui, sans-serif; + --font-mono: var(--font-geist-mono), ui-monospace, monospace; +} + +@media (prefers-color-scheme: dark) { + :root { + --background: oklch(0.145 0 0); + --foreground: oklch(0.985 0 0); + --border-strong: oklch(1 0 0 / 0.12); + --border-low: oklch(1 0 0 / 0.08); + --accent: oklch(0.922 0 0); + --muted: oklch(0.75 0.01 260); /* brighten muted text in dark mode */ + --cream: oklch(0.22 0.009 70.82); + --card: oklch(0.205 0 0); + --card-foreground: oklch(0.985 0 0); + } +} + +@theme inline { + --color-background: var(--background); + --color-bg1: var(--background); + --color-foreground: var(--foreground); + --color-cream: var(--cream); + --color-border: var(--border-strong); + --color-border-strong: var(--border-strong); + --color-border-low: var(--border-low); + --color-primary: var(--accent); + --color-muted: var(--muted); + --color-card: var(--card); + --color-card-foreground: var(--card-foreground); + --font-sans: var(--font-sans); + --font-mono: var(--font-mono); + --radius-md: calc(var(--radius) - 2px); + --radius-lg: var(--radius); + --radius-xl: calc(var(--radius) + 4px); +} + +@layer base { + * { + @apply border-border outline-transparent; + } + + body { + @apply bg-bg1 text-foreground antialiased; + font-family: var(--font-sans); + color-scheme: light dark; + } +} diff --git a/apps/solana/file-upload/app/icon.svg b/apps/solana/file-upload/app/icon.svg new file mode 100644 index 0000000..bab08c4 --- /dev/null +++ b/apps/solana/file-upload/app/icon.svg @@ -0,0 +1,10 @@ + + + + + + + diff --git a/apps/solana/file-upload/app/layout.tsx b/apps/solana/file-upload/app/layout.tsx new file mode 100644 index 0000000..5a961df --- /dev/null +++ b/apps/solana/file-upload/app/layout.tsx @@ -0,0 +1,42 @@ +import type { Metadata } from "next"; +import { Geist_Mono, Inter } from "next/font/google"; +import "./globals.css"; +import { Providers } from "./components/providers"; + +const inter = Inter({ + variable: "--font-inter", + subsets: ["latin"], + display: "swap", +}); + +const geistMono = Geist_Mono({ + variable: "--font-geist-mono", + subsets: ["latin"], +}); + +export const metadata: Metadata = { + title: "Solana File Upload", + description: "Upload files to Shelby using your Solana wallet", + icons: { + icon: "/icon.svg", + shortcut: "/icon.svg", + apple: "/icon.svg", + }, +}; + +export default function RootLayout({ + children, +}: Readonly<{ + children: React.ReactNode; +}>) { + return ( + + + {children} + + + ); +} diff --git a/apps/solana/file-upload/app/lib/config.ts b/apps/solana/file-upload/app/lib/config.ts new file mode 100644 index 0000000..8a65ffb --- /dev/null +++ b/apps/solana/file-upload/app/lib/config.ts @@ -0,0 +1,8 @@ +/** + * Configuration for the Solana file upload app. + */ +export const config = { + // Solana RPC endpoint + solanaRpcUrl: + process.env.NEXT_PUBLIC_SOLANA_RPC_URL || "https://api.testnet.solana.com", +} as const; diff --git a/apps/solana/file-upload/app/lib/shelbyClient.ts b/apps/solana/file-upload/app/lib/shelbyClient.ts new file mode 100644 index 0000000..1ac569d --- /dev/null +++ b/apps/solana/file-upload/app/lib/shelbyClient.ts @@ -0,0 +1,12 @@ +"use client"; + +import { ShelbyClient } from "@shelby-protocol/sdk/browser"; +import { Network } from "@shelby-protocol/solana-kit/react"; + +/** + * Shared Shelby client for all storage interactions. + */ +export const shelbyClient = new ShelbyClient({ + network: Network.SHELBYNET, + apiKey: process.env.NEXT_PUBLIC_SHELBY_API_KEY || "", +}); diff --git a/apps/solana/file-upload/app/page.tsx b/apps/solana/file-upload/app/page.tsx new file mode 100644 index 0000000..bb6e76b --- /dev/null +++ b/apps/solana/file-upload/app/page.tsx @@ -0,0 +1,29 @@ +"use client"; + +import { FileUpload } from "./components/file-upload"; +import { Header } from "./components/header"; + +export default function Home() { + return ( +
+
+ +
+
+

+ Solana File Upload +

+

+ Upload files to Shelby +

+

+ Connect your Solana wallet and upload files to decentralized + storage. Files are stored on Shelby Protocol. +

+
+ + +
+
+ ); +} diff --git a/apps/solana/file-upload/eslint.config.mjs b/apps/solana/file-upload/eslint.config.mjs new file mode 100644 index 0000000..2a710a1 --- /dev/null +++ b/apps/solana/file-upload/eslint.config.mjs @@ -0,0 +1,12 @@ +import { defineConfig, globalIgnores } from "eslint/config"; +import nextVitals from "eslint-config-next/core-web-vitals"; +import nextTs from "eslint-config-next/typescript"; + +const eslintConfig = defineConfig([ + ...nextVitals, + ...nextTs, + globalIgnores([".next/**", "out/**", "build/**", "next-env.d.ts"]), +]); + +export default eslintConfig; + diff --git a/apps/solana/file-upload/next-env.d.ts b/apps/solana/file-upload/next-env.d.ts new file mode 100644 index 0000000..c4b7818 --- /dev/null +++ b/apps/solana/file-upload/next-env.d.ts @@ -0,0 +1,6 @@ +/// +/// +import "./.next/dev/types/routes.d.ts"; + +// NOTE: This file should not be edited +// see https://nextjs.org/docs/app/api-reference/config/typescript for more information. diff --git a/apps/solana/file-upload/next.config.ts b/apps/solana/file-upload/next.config.ts new file mode 100644 index 0000000..3ff910e --- /dev/null +++ b/apps/solana/file-upload/next.config.ts @@ -0,0 +1,7 @@ +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + serverExternalPackages: ["ws"], +}; + +export default nextConfig; diff --git a/apps/solana/file-upload/package.json b/apps/solana/file-upload/package.json new file mode 100644 index 0000000..d2f05c4 --- /dev/null +++ b/apps/solana/file-upload/package.json @@ -0,0 +1,46 @@ +{ + "name": "solana-file-upload", + "scripts": { + "build": "next build", + "dev": "next dev", + "format": "prettier --write .", + "format:check": "prettier --check .", + "lint": "eslint", + "start": "next start" + }, + "description": "Simple Solana file upload to Shelby using @solana/react-hooks", + "keywords": [ + "nextjs", + "react", + "solana-kit", + "shelby", + "tailwind", + "typescript" + ], + "version": "0.0.0", + "private": true, + "dependencies": { + "@shelby-protocol/react": "latest", + "@shelby-protocol/sdk": "latest", + "@shelby-protocol/solana-kit": "latest", + "@solana/client": "^1.2.0", + "@solana/kit": "^5.1.0", + "@solana/react-hooks": "^1.1.5", + "@tanstack/react-query": "^5.90.16", + "next": "16.0.10", + "react": "^19.1.0", + "react-dom": "^19.1.0", + "ws": "^8.18.3" + }, + "devDependencies": { + "@tailwindcss/postcss": "^4", + "@types/node": "^20", + "@types/react": "^19", + "@types/react-dom": "^19", + "eslint": "^9", + "eslint-config-next": "16.0.10", + "prettier": "^3.6.2", + "tailwindcss": "^4", + "typescript": "^5" + } +} diff --git a/apps/solana/file-upload/postcss.config.mjs b/apps/solana/file-upload/postcss.config.mjs new file mode 100644 index 0000000..61e3684 --- /dev/null +++ b/apps/solana/file-upload/postcss.config.mjs @@ -0,0 +1,7 @@ +const config = { + plugins: { + "@tailwindcss/postcss": {}, + }, +}; + +export default config; diff --git a/apps/solana/file-upload/tsconfig.json b/apps/solana/file-upload/tsconfig.json new file mode 100644 index 0000000..15c7b97 --- /dev/null +++ b/apps/solana/file-upload/tsconfig.json @@ -0,0 +1,34 @@ +{ + "compilerOptions": { + "target": "ES2020", + "lib": ["dom", "dom.iterable", "esnext"], + "allowJs": true, + "skipLibCheck": true, + "strict": true, + "noEmit": true, + "esModuleInterop": true, + "module": "esnext", + "moduleResolution": "bundler", + "resolveJsonModule": true, + "isolatedModules": true, + "jsx": "react-jsx", + "incremental": true, + "plugins": [ + { + "name": "next" + } + ], + "paths": { + "@/*": ["./*"] + } + }, + "include": [ + "next-env.d.ts", + "**/*.ts", + "**/*.tsx", + ".next/types/**/*.ts", + ".next/dev/types/**/*.ts", + "**/*.mts" + ], + "exclude": ["node_modules"] +} diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 8f2887e..d0989b1 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -245,6 +245,70 @@ importers: specifier: ^3.2.4 version: 3.2.4(@types/debug@4.1.12)(@types/node@25.1.0)(jiti@2.6.1)(lightningcss@1.30.2)(tsx@4.21.0) + apps/solana/file-upload: + dependencies: + '@shelby-protocol/react': + specifier: latest + version: 0.0.5(@aptos-labs/ts-sdk@5.2.1(got@11.8.6))(@shelby-protocol/sdk@0.0.9(@aptos-labs/ts-sdk@5.2.1(got@11.8.6)))(@tanstack/react-query@5.90.20(react@19.2.4))(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3) + '@shelby-protocol/sdk': + specifier: latest + version: 0.0.9(@aptos-labs/ts-sdk@5.2.1(got@11.8.6)) + '@shelby-protocol/solana-kit': + specifier: latest + version: 0.2.0(@wallet-standard/core@1.1.1)(bs58@6.0.0)(bufferutil@4.1.0)(got@11.8.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@solana/client': + specifier: ^1.2.0 + version: 1.7.0(@solana/sysvars@5.5.1(typescript@5.9.3))(@types/react@19.2.10)(bufferutil@4.1.0)(react@19.2.4)(typescript@5.9.3)(use-sync-external-store@1.6.0(react@19.2.4))(utf-8-validate@5.0.10) + '@solana/kit': + specifier: ^5.1.0 + version: 5.5.1(bufferutil@4.1.0)(typescript@5.9.3)(utf-8-validate@5.0.10) + '@solana/react-hooks': + specifier: ^1.1.5 + version: 1.4.1(@solana/sysvars@5.5.1(typescript@5.9.3))(@types/react@19.2.10)(bufferutil@4.1.0)(react@19.2.4)(typescript@5.9.3)(use-sync-external-store@1.6.0(react@19.2.4))(utf-8-validate@5.0.10) + '@tanstack/react-query': + specifier: ^5.90.16 + version: 5.90.20(react@19.2.4) + next: + specifier: 16.0.10 + version: 16.0.10(@babel/core@7.28.6)(react-dom@19.2.4(react@19.2.4))(react@19.2.4) + react: + specifier: ^19.1.0 + version: 19.2.4 + react-dom: + specifier: ^19.1.0 + version: 19.2.4(react@19.2.4) + ws: + specifier: ^8.18.3 + version: 8.19.0(bufferutil@4.1.0)(utf-8-validate@5.0.10) + devDependencies: + '@tailwindcss/postcss': + specifier: ^4 + version: 4.1.18 + '@types/node': + specifier: ^20 + version: 20.19.30 + '@types/react': + specifier: ^19 + version: 19.2.10 + '@types/react-dom': + specifier: ^19 + version: 19.2.3(@types/react@19.2.10) + eslint: + specifier: ^9 + version: 9.39.2(jiti@2.6.1) + eslint-config-next: + specifier: 16.0.10 + version: 16.0.10(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + prettier: + specifier: ^3.6.2 + version: 3.8.1 + tailwindcss: + specifier: ^4 + version: 4.1.18 + typescript: + specifier: ^5 + version: 5.9.3 + apps/solana/token-gated: dependencies: '@aptos-labs/ace-sdk': @@ -316,7 +380,7 @@ importers: version: 9.39.2(jiti@2.6.1) eslint-config-next: specifier: 16.0.10 - version: 16.0.10(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + version: 16.0.10(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) prettier: specifier: ^3.6.2 version: 3.8.1 @@ -8254,7 +8318,7 @@ snapshots: dependencies: '@types/http-cache-semantics': 4.2.0 '@types/keyv': 3.1.4 - '@types/node': 25.1.0 + '@types/node': 22.19.7 '@types/responselike': 1.0.3 '@types/chai@5.2.3': @@ -8264,7 +8328,7 @@ snapshots: '@types/connect@3.4.38': dependencies: - '@types/node': 20.19.30 + '@types/node': 22.19.7 '@types/debug@4.1.12': dependencies: @@ -8282,7 +8346,7 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 25.1.0 + '@types/node': 22.19.7 '@types/ms@2.1.0': {} @@ -8307,6 +8371,7 @@ snapshots: '@types/node@25.1.0': dependencies: undici-types: 7.16.0 + optional: true '@types/prop-types@15.7.15': {} @@ -8329,17 +8394,17 @@ snapshots: '@types/responselike@1.0.3': dependencies: - '@types/node': 25.1.0 + '@types/node': 22.19.7 '@types/uuid@8.3.4': {} '@types/ws@7.4.7': dependencies: - '@types/node': 20.19.30 + '@types/node': 22.19.7 '@types/ws@8.18.1': dependencies: - '@types/node': 20.19.30 + '@types/node': 22.19.7 '@typescript-eslint/eslint-plugin@8.53.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3)': dependencies: @@ -9212,12 +9277,32 @@ snapshots: escape-string-regexp@4.0.0: {} eslint-config-next@16.0.10(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): + dependencies: + '@next/eslint-plugin-next': 16.0.10 + eslint: 9.39.2(jiti@2.6.1) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@2.6.1)) + globals: 16.4.0 + typescript-eslint: 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + optionalDependencies: + typescript: 5.9.3 + transitivePeerDependencies: + - '@typescript-eslint/parser' + - eslint-import-resolver-webpack + - eslint-plugin-import-x + - supports-color + + eslint-config-next@16.0.10(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3): dependencies: '@next/eslint-plugin-next': 16.0.10 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-jsx-a11y: 6.10.2(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react: 7.37.5(eslint@9.39.2(jiti@2.6.1)) eslint-plugin-react-hooks: 7.0.1(eslint@9.39.2(jiti@2.6.1)) @@ -9239,6 +9324,21 @@ snapshots: transitivePeerDependencies: - supports-color + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): + dependencies: + '@nolyfill/is-core-module': 1.0.39 + debug: 4.4.3 + eslint: 9.39.2(jiti@2.6.1) + get-tsconfig: 4.13.0 + is-bun-module: 2.0.0 + stable-hash: 0.0.5 + tinyglobby: 0.2.15 + unrs-resolver: 1.11.1 + optionalDependencies: + eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + transitivePeerDependencies: + - supports-color + eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)): dependencies: '@nolyfill/is-core-module': 1.0.39 @@ -9250,22 +9350,32 @@ snapshots: tinyglobby: 0.2.15 unrs-resolver: 1.11.1 optionalDependencies: - eslint-plugin-import: 2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) + eslint-plugin-import: 2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): + eslint-module-utils@2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): dependencies: debug: 3.2.7 optionalDependencies: '@typescript-eslint/parser': 8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3) + eslint: 9.39.2(jiti@2.6.1) + eslint-import-resolver-node: 0.3.9 + eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) + transitivePeerDependencies: + - supports-color + + eslint-module-utils@2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): + dependencies: + debug: 3.2.7 + optionalDependencies: eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 eslint-import-resolver-typescript: 3.10.1(eslint-plugin-import@2.32.0)(eslint@9.39.2(jiti@2.6.1)) transitivePeerDependencies: - supports-color - eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): + eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.9 @@ -9276,7 +9386,7 @@ snapshots: doctrine: 2.1.0 eslint: 9.39.2(jiti@2.6.1) eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) + eslint-module-utils: 2.12.1(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1(eslint-plugin-import@2.32.0(@typescript-eslint/parser@8.53.0(eslint@9.39.2(jiti@2.6.1))(typescript@5.9.3))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)))(eslint@9.39.2(jiti@2.6.1)) hasown: 2.0.2 is-core-module: 2.16.1 is-glob: 4.0.3 @@ -9294,6 +9404,33 @@ snapshots: - eslint-import-resolver-webpack - supports-color + eslint-plugin-import@2.32.0(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)): + dependencies: + '@rtsao/scc': 1.1.0 + array-includes: 3.1.9 + array.prototype.findlastindex: 1.2.6 + array.prototype.flat: 1.3.3 + array.prototype.flatmap: 1.3.3 + debug: 3.2.7 + doctrine: 2.1.0 + eslint: 9.39.2(jiti@2.6.1) + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.1(eslint-import-resolver-node@0.3.9)(eslint-import-resolver-typescript@3.10.1)(eslint@9.39.2(jiti@2.6.1)) + hasown: 2.0.2 + is-core-module: 2.16.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.1 + semver: 6.3.1 + string.prototype.trimend: 1.0.9 + tsconfig-paths: 3.15.0 + transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack + - supports-color + eslint-plugin-jsx-a11y@6.10.2(eslint@9.39.2(jiti@2.6.1)): dependencies: aria-query: 5.3.2