diff --git a/.gitignore b/.gitignore index a7e62ae..8f6a05f 100644 --- a/.gitignore +++ b/.gitignore @@ -39,4 +39,5 @@ yarn-error.log* .yarn/install-state.gz apps/engagement-app/tsconfig.tsbuildinfo .vscode/settings.json -e2e.log + +docs diff --git a/apps/demo-identity-app/README-streaming.md b/apps/demo-identity-app/README-streaming.md new file mode 100644 index 0000000..a87c220 --- /dev/null +++ b/apps/demo-identity-app/README-streaming.md @@ -0,0 +1,46 @@ +# Streaming SDK Test UI Guide + +This guide explains how to use the interactive test page for the Superfluid Streaming SDK within the `demo-identity-app`. + +## 📍 Location +The test page is implemented as a standalone component: +`src/components/StreamingTestPage.tsx` + +## 🚀 How to Run +From the repository root, run the **fast-path** command (recommended): +```bash +yarn turbo dev --filter=demo-identity-app... +``` +*Note: This builds the app and SDKs. When you see **"Found 0 errors. Watching for file changes"** in the terminal, the system is ready!* + +### ⚡ Direct URL +Navigate to: **[http://localhost:3000/streaming](http://localhost:3000/streaming)** +### 🕰️ Alternative (Standard) +If you prefer the standard way: +1. `cd apps/demo-identity-app` +2. `yarn dev` + +## 🛠️ Features to Test + +### 1. Money Streaming (CFA) +- **Create**: enter a receiver address, amount, and time unit (hour/day/month). The SDK calculates the flow rate automatically. +- **Update**: update the amount for a receiver you are already streaming to. +- **Delete**: stop an active stream. + +### 2. Distribution Pools (GDA) +- **Connect**: enter a pool address and click "Connect" (requires on-chain transaction). +- **Disconnect**: remove yourself from a pool to stop receiving distributions. +- **Visibility**: view available pools, see your connection status (badge), and current "Units". + +### 3. Network & Environment +- **Chain Switcher**: easily toggle between Celo, Base, and their testnets. +- **SDK Env**: switch between `production`, `staging`, and `development` to test against different G$ SuperToken deployments. + +### 4. Developer Tools +- **Code Examples**: view copy-pasteable snippets for hooks like `useCreateStream`, `usePoolMemberships`, and `useSupReserves`. +- **Tx History**: view transaction hashes with direct links to Block Explorers (CeloScan/BaseScan). + +## 💡 Developer Notes +- **SUP Reserve**: the SUP Reserve Holdings section only displays data when connected to **Base Mainnet**. +- **Transactions**: all write operations (Create, Connect, etc.) will trigger a wallet signature request. +- **Subgraph**: data updates may take a few seconds to appear after a transaction is confirmed due to subgraph indexing latency. diff --git a/apps/demo-identity-app/eslint.config.js b/apps/demo-identity-app/eslint.config.js new file mode 100644 index 0000000..fc7617f --- /dev/null +++ b/apps/demo-identity-app/eslint.config.js @@ -0,0 +1,28 @@ +import js from '@eslint/js' +import globals from 'globals' +import reactHooks from 'eslint-plugin-react-hooks' +import reactRefresh from 'eslint-plugin-react-refresh' +import tseslint from 'typescript-eslint' + +export default tseslint.config( + { ignores: ['dist'] }, + { + extends: [js.configs.recommended, ...tseslint.configs.recommended], + files: ['**/*.{ts,tsx}'], + languageOptions: { + ecmaVersion: 2020, + globals: globals.browser, + }, + plugins: { + 'react-hooks': reactHooks, + 'react-refresh': reactRefresh, + }, + rules: { + ...reactHooks.configs.recommended.rules, + 'react-refresh/only-export-components': [ + 'warn', + { allowConstantExport: true }, + ], + }, + }, +) \ No newline at end of file diff --git a/apps/demo-identity-app/package.json b/apps/demo-identity-app/package.json index 66cc7a7..f2b10fc 100644 --- a/apps/demo-identity-app/package.json +++ b/apps/demo-identity-app/package.json @@ -2,17 +2,19 @@ "name": "demo-identity-app", "version": "1.0.0", "private": true, + "type": "module", "scripts": { "dev": "vite --port 3000", "build": "vite build", "startS": "cross-env HTTPS=true SSL_CRT_FILE='./.certs/ssl.crt' SSL_KEY_FILE='./.certs/ssl.key' PORT=1122 vite --port 1122", "preview": "vite preview", - "lint": "eslint . --ext .ts,.tsx", + "lint": "eslint .", "format": "prettier --write ." }, "dependencies": { "@goodsdks/citizen-sdk": "*", "@goodsdks/react-hooks": "*", + "@goodsdks/streaming-sdk": "*", "@reown/appkit": "^1.7.2", "@reown/appkit-adapter-wagmi": "^1.7.2", "@reown/appkit-wallet": "^1.7.2", @@ -34,22 +36,22 @@ "zod": "^3.24.2" }, "devDependencies": { + "@eslint/js": "^9.17.0", "@tamagui/babel-plugin": "^1.125.22", "@types/react": "^18.3.18", "@types/react-dom": "^18.3.5", - "@typescript-eslint/eslint-plugin": "^5.62.0", - "@typescript-eslint/parser": "^5.62.0", "@vitejs/plugin-react": "^4.3.4", "autoprefixer": "^10.4.20", "cross-env": "^7.0.3", - "eslint": "^8.57.1", - "eslint-config-prettier": "^8.10.0", - "eslint-plugin-react": "^7.37.4", - "eslint-plugin-react-hooks": "^5.2.0", + "eslint": "^9.17.0", + "eslint-plugin-react-hooks": "^5.0.0", + "eslint-plugin-react-refresh": "^0.4.16", + "globals": "^15.14.0", "postcss": "^8.5.3", "prettier": "^3.5.3", "sass": "^1.85.1", "typescript": "^5.8.2", + "typescript-eslint": "^8.18.2", "vite": "6.3.5" } } diff --git a/apps/demo-identity-app/src/App.tsx b/apps/demo-identity-app/src/App.tsx index 8d30e72..4caf9b5 100644 --- a/apps/demo-identity-app/src/App.tsx +++ b/apps/demo-identity-app/src/App.tsx @@ -11,14 +11,16 @@ import { Stack, } from "tamagui" import { config } from "@tamagui/config/v3" -import { useLocation } from "react-router-dom" +import { useLocation, Routes, Route, Link } from "react-router-dom" import { useAccount } from "wagmi" import { useIdentitySDK } from "@goodsdks/react-hooks" +import { XStack } from "tamagui" import { VerifyButton } from "./components/VerifyButton" import { IdentityCard } from "./components/IdentityCard" import { SigningModal } from "./components/SigningModal" import { ClaimButton } from "./components/ClaimButton" +import { StreamingTestPage } from "./components/StreamingTestPage" const tamaguiConfig = createTamagui(config) @@ -99,176 +101,230 @@ const App: React.FC = () => { backgroundColor="#F7FAFC" alignItems="center" > - - - GoodDollar Identity Verification - - - {/* Disclaimer Section */} - - - Disclaimer: - This is a demo showing how to integrate a uniquely identified user - into your dApp using the GoodDollar protocol's Identity. This demo - uses the development contract.{" "} - + + + - Learn more about the sdk and how to integrate it here. - - - - - {/* User Interaction Section */} - - + + + + - {!isConnected ? ( - <> - - Please connect your wallet to proceed. + + 🌊 Streaming + + + + + + + } /> + + + + GoodDollar Identity Verification - - ) : null} - - - {isConnected && (loadingWhitelist || loading) ? ( - - ) : null} + {/* Disclaimer Section */} + + + Disclaimer: + This is a demo showing how to integrate a uniquely identified user + into your dApp using the GoodDollar protocol's Identity. This demo + uses the development contract.{" "} + + Learn more about the sdk and how to integrate it here. + + + - {isConnected && - !loadingWhitelist && - (isVerified || isWhitelisted) && ( - - - - You are successfully verified and/or whitelisted. - - - )} + {/* User Interaction Section */} + + + {!isConnected ? ( + <> + + Please connect your wallet to proceed. + + + ) : null} + + - {isConnected && - !loadingWhitelist && - !isVerified && - !isWhitelisted && - !error ? ( - - - - You need to verify your identity via GoodDollar to continue. - - - ) : null} + {isConnected && (loadingWhitelist || loading) ? ( + + ) : null} - {isConnected && error ? ( - - Error initializing Identity SDK: {error} - - ) : null} - + {isConnected && + !loadingWhitelist && + (isVerified || isWhitelisted) && ( + + + + You are successfully verified and/or whitelisted. + + + )} - {/* Help Section */} - - - Need help? Visit our docs:{" "} - - GoodDollar Docs - - . - - - Or join our Developer Communities at:{" "} - - GoodBuilders Discord - - . - - - + {isConnected && + !loadingWhitelist && + !isVerified && + !isWhitelisted && + !error ? ( + + + + You need to verify your identity via GoodDollar to continue. + + + ) : null} - {/* Claim UBI Section */} - - - Claim Your Daily UBI - - {isConnected ? ( - - ) : ( - <> - - Please connect your wallet to claim your UBI. - - - - )} - + {isConnected && error ? ( + + Error initializing Identity SDK: {error} + + ) : null} + + + {/* Help Section */} + + + Need help? Visit our docs:{" "} + + GoodDollar Docs + + . + + + Or join our Developer Communities at:{" "} + + GoodBuilders Discord + + . + + + - setIsSigningModalOpen(false)} - /> + {/* Claim UBI Section */} + + + Claim Your Daily UBI + + {isConnected ? ( + + ) : ( + <> + + Please connect your wallet to claim your UBI. + + + + )} + + + setIsSigningModalOpen(false)} + /> + + } + /> + - + ) } diff --git a/apps/demo-identity-app/src/components/StreamingTestPage.tsx b/apps/demo-identity-app/src/components/StreamingTestPage.tsx new file mode 100644 index 0000000..9ae8e95 --- /dev/null +++ b/apps/demo-identity-app/src/components/StreamingTestPage.tsx @@ -0,0 +1,824 @@ +import React, { useState } from "react" +import { + View, + Text, + YStack, + XStack, + Button, + Input, + ScrollView, + Spinner, + Separator, +} from "tamagui" +import { useAccount, usePublicClient, useWalletClient, useSwitchChain } from "wagmi" +import { + useCreateStream, + useUpdateStream, + useDeleteStream, + useStreamList, + useGDAPools, + usePoolMemberships, + useSupReserves, + useConnectToPool, + useDisconnectFromPool, +} from "@goodsdks/react-hooks" +import { calculateFlowRate, formatFlowRate } from "@goodsdks/streaming-sdk" +import { parseEther, formatEther, type Address } from "viem" + +export const StreamingTestPage: React.FC = () => { + const { address, isConnected } = useAccount() + const publicClient = usePublicClient() + const { data: walletClient } = useWalletClient() + const { switchChain } = useSwitchChain() + + + const [receiver, setReceiver] = useState("") + const [amount, setAmount] = useState("10") + const [timeUnit, setTimeUnit] = useState<"month" | "day" | "hour">("month") + + + const [updateReceiver, setUpdateReceiver] = useState("") + const [updateAmount, setUpdateAmount] = useState("20") + + + const [deleteReceiver, setDeleteReceiver] = useState("") + + + const [environment, setEnvironment] = useState<"production" | "staging" | "development">("production") + + + const [poolAddress, setPoolAddress] = useState("") + + + const [lastTxHash, setLastTxHash] = useState(null) + + + const { mutate: createStream, isLoading: isCreating } = useCreateStream() + const { mutate: updateStream, isLoading: isUpdating } = useUpdateStream() + const { mutate: deleteStream, isLoading: isDeleting } = useDeleteStream() + const { mutate: connectToPool, isLoading: isConnecting } = useConnectToPool() + const { mutate: disconnectFromPool, isLoading: isDisconnecting } = + useDisconnectFromPool() + + const { + data: streams, + isLoading: streamsLoading, + refetch: refetchStreams, + } = useStreamList({ + account: address as Address, + environment, + enabled: !!address, + }) + + const { data: pools, isLoading: poolsLoading } = useGDAPools({ + environment, + enabled: !!address, + }) + + const { data: memberships } = usePoolMemberships({ + account: address as Address, + environment, + enabled: !!address, + }) + + const { data: supReserves, isLoading: supLoading } = useSupReserves({ + enabled: isConnected && environment === "production", + }) + + const chainId = publicClient?.chain?.id + + const getG$Token = () => { + if (!chainId) return "0x" + try { + // Mapping for UI purposes + const addresses: any = { + production: { + 42220: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A", // Celo Mainnet + }, + staging: { + 42220: "0x61FA0fB802fd8345C06da558240E0651886fec69", // Celo Staging + }, + development: { + 44787: "0xFa51eFDc0910CCdA91732e6806912Fa12e2FD475", // Celo Alfajores + 42220: "0xFa51eFDc0910CCdA91732e6806912Fa12e2FD475", // Fallback for user case + } + } + return addresses[environment]?.[chainId] || addresses[environment]?.[42220] || "0x" + } catch (e) { + return "0x" + } + } + + const G$_TOKEN = getG$Token() + + const handleCreateStream = () => { + if (!receiver || !amount) { + alert("Please fill in all fields") + return + } + + try { + const flowRate = calculateFlowRate(parseEther(amount), timeUnit) + + createStream( + { + receiver: receiver as Address, + token: G$_TOKEN as Address, + flowRate, + environment, + }, + { + onSuccess: (hash) => { + console.log("Stream created:", hash) + setLastTxHash(hash) + refetchStreams() + setReceiver("") + setAmount("10") + }, + onError: (error) => { + console.error("Error creating stream:", error) + alert(`Error: ${error.message}`) + }, + }, + ) + } catch (error: any) { + alert(`Error: ${error.message}`) + } + } + + const handleUpdateStream = () => { + if (!updateReceiver || !updateAmount) { + alert("Please fill in all fields") + return + } + + try { + const newFlowRate = calculateFlowRate(parseEther(updateAmount), timeUnit) + + updateStream( + { + receiver: updateReceiver as Address, + token: G$_TOKEN as Address, + newFlowRate, + environment, + }, + { + onSuccess: (hash) => { + console.log("Stream updated:", hash) + setLastTxHash(hash) + refetchStreams() + }, + onError: (error) => { + console.error("Error updating stream:", error) + alert(`Error: ${error.message}`) + }, + }, + ) + } catch (error: any) { + alert(`Error: ${error.message}`) + } + } + + const handleDeleteStream = () => { + if (!deleteReceiver) { + alert("Please enter receiver address") + return + } + + deleteStream( + { + receiver: deleteReceiver as Address, + token: G$_TOKEN as Address, + environment, + }, + { + onSuccess: (hash) => { + console.log("Stream deleted:", hash) + setLastTxHash(hash) + refetchStreams() + setDeleteReceiver("") + }, + onError: (error) => { + console.error("Error deleting stream:", error) + alert(`Error: ${error.message}`) + }, + }, + ) + } + + const handleConnectToPool = () => { + if (!poolAddress) { + alert("Please enter pool address") + return + } + + connectToPool( + { + poolAddress: poolAddress as Address, + }, + { + onSuccess: (hash) => { + console.log("Connected to pool:", hash) + setLastTxHash(hash) + }, + onError: (error) => { + console.error("Error connecting to pool:", error) + alert(`Error: ${error.message}`) + }, + }, + ) + } + + if (!isConnected) { + return ( + + + Please connect your wallet to test the Streaming SDK + + + + ) + } + + return ( + + + {/* Status & Environment Selection */} + + + NETWORK + + {publicClient?.chain?.name || "Unknown Chain"} ({chainId}) + + + + SWITCH CHAIN + + {[ + { id: 42220, name: "Celo" }, + { id: 8453, name: "Base" }, + { id: 44787, name: "Alfajores" }, + { id: 84532, name: "Base Sep" } + ].map((chain) => ( + + ))} + + + + + + + SDK ENVIRONMENT + + {["production", "staging", "development"].map((env) => ( + + ))} + + + + CONNECTED AS + + {address?.slice(0, 6)}...{address?.slice(-4)} + + + + + + + 🌊 Streaming SDK Test Page + + + Test the GoodDollar Streaming SDK features + + + G$ Token: {G$_TOKEN} + + + + {/* Transaction Alert */} + {lastTxHash && ( + + + + ✅ Success! Transaction confirmed. + + + + + Hash: {lastTxHash} + + + + )} + + {/* Create Stream Section */} + + + 📤 Create Stream + + + Create a new money stream to a recipient + + + + + Receiver Address + + + + + + + + Amount (G$) + + + + + + + Time Unit + + + {(["hour", "day", "month"] as const).map((unit) => ( + + ))} + + + + + + + + {/* Update Stream Section */} + + + 🔄 Update Stream + + + Update the flow rate of an existing stream + + + + + Receiver Address + + + + + + + New Amount (G$ per {timeUnit}) + + + + + + + + {/* Delete Stream Section */} + + + 🗑️ Delete Stream + + + Stop and delete an active stream + + + + + Receiver Address + + + + + + + + + + {/* Active Streams Section */} + + + + 📊 Your Active Streams + + + + + {streamsLoading ? ( + + ) : streams && streams.length > 0 ? ( + + {streams.map((stream, index) => ( + + + + To: + + + {stream.receiver + ? `${stream.receiver.slice(0, 6)}...${stream.receiver.slice(-4)}` + : "Unknown"} + + + + + Flow Rate: + + + {formatFlowRate(stream.flowRate, "month")} + + + + + Streamed: + + + {stream.streamedSoFar + ? formatEther(stream.streamedSoFar) + : "0"}{" "} + G$ + + + + ))} + + ) : ( + + No active streams found + + )} + + + {/* GDA Pools Section */} + + + 🎯 Distribution Pools (GDA) + + + Connect to distribution pools for 1-to-many streaming + + + + + Pool Address + + + + + + + + + + {poolsLoading ? ( + + ) : pools && pools.length > 0 ? ( + + + Available Pools: + + {pools.slice(0, 5).map((pool, index) => { + const membership = memberships?.find( + (m) => m.pool.toLowerCase() === pool.id.toLowerCase(), + ) + const isMember = membership?.isConnected + + return ( + { + setPoolAddress(pool.id) + }} + > + + + + {pool.id.slice(0, 10)}...{pool.id.slice(-8)} + + {isMember && ( + + CONNECTED + + )} + + + USE THIS + + + + + + Flow Rate: {formatFlowRate(pool.flowRate, "month")} + + {isMember && ( + + Units: {membership.units?.toString()} + + )} + + + Admin: + + {pool.admin ? `${pool.admin.slice(0, 6)}...${pool.admin.slice(-4)}` : "Unknown"} + + + + + ) + })} + + ) : ( + + No pools found + + )} + + + {/* SUP Reserve Holdings Section */} + + + 🏦 SUP Reserve Holdings + + + Active SUP lockers from the dedicated reserve subgraph + + + (Only visible on Base mainnet) + + + {supLoading ? ( + + ) : supReserves && supReserves.length > 0 ? ( + + {supReserves.slice(0, 5).map((locker, index) => ( + + + + Locker: {locker.id.slice(0, 10)}...{locker.id.slice(-8)} + + + Owner: {locker.lockerOwner.slice(0, 12)}... + + + + + Block: {locker.blockNumber.toString()} + + + + ))} + + ) : ( + + No SUP lockers found or network not supported + + )} + + + {/* Documentation Section */} + + + 📚 SDK Usage Example + + + {`import { + useCreateStream, + usePoolMemberships, + useSupReserves +} from '@goodsdks/react-hooks' +import { calculateFlowRate } from '@goodsdks/streaming-sdk' +import { parseEther } from 'viem' + +// 1. One-to-One Streams +const { mutate: createStream } = useCreateStream() +const flowRate = calculateFlowRate(parseEther('100'), 'month') + +// 2. Data Visualization +const { data: memberships } = usePoolMemberships({ account: '0x...' }) +const { data: supLockers } = useSupReserves() + +createStream({ + receiver: '0x...', + token: '0x...', + flowRate, + environment: 'development' +})`} + + + + + ) +} diff --git a/package.json b/package.json index 9b193af..711b552 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "private": true, "scripts": { "build": "turbo build", - "dev": "turbo dev", + "dev": "turbo dev --concurrency 20", "lint": "turbo lint", "format": "prettier --write \"**/*.{ts,tsx,md}\"" }, diff --git a/packages/react-hooks/package.json b/packages/react-hooks/package.json index 4f8525e..e698b03 100644 --- a/packages/react-hooks/package.json +++ b/packages/react-hooks/package.json @@ -37,6 +37,8 @@ }, "dependencies": { "@goodsdks/citizen-sdk": "*", + "@goodsdks/streaming-sdk": "workspace:*", + "@tanstack/react-query": "^4.36.1", "lz-string": "^1.5.0", "react": "^19.1.1", "tsup": "^8.4.0" diff --git a/packages/react-hooks/src/index.ts b/packages/react-hooks/src/index.ts index 35b68e6..193ff02 100644 --- a/packages/react-hooks/src/index.ts +++ b/packages/react-hooks/src/index.ts @@ -1 +1,2 @@ export * from "./citizen-sdk" +export * from "./streaming" diff --git a/packages/react-hooks/src/streaming/index.ts b/packages/react-hooks/src/streaming/index.ts new file mode 100644 index 0000000..1287ffc --- /dev/null +++ b/packages/react-hooks/src/streaming/index.ts @@ -0,0 +1,13 @@ +// Streaming hooks +export { useCreateStream, type UseCreateStreamParams } from "./useCreateStream" +export { useUpdateStream, type UseUpdateStreamParams } from "./useUpdateStream" +export { useDeleteStream, type UseDeleteStreamParams } from "./useDeleteStream" +export { useStreamList, type UseStreamListParams } from "./useStreamList" +export { useGDAPools, type UseGDAPoolsParams } from "./useGDAPools" +export { usePoolMemberships } from "./usePoolMemberships" +export { useSupReserves } from "./useSupReserves" +export { useConnectToPool, type UseConnectToPoolParams } from "./useConnectToPool" +export { + useDisconnectFromPool, + type UseDisconnectFromPoolParams, +} from "./useDisconnectFromPool" diff --git a/packages/react-hooks/src/streaming/useConnectToPool.ts b/packages/react-hooks/src/streaming/useConnectToPool.ts new file mode 100644 index 0000000..0132c62 --- /dev/null +++ b/packages/react-hooks/src/streaming/useConnectToPool.ts @@ -0,0 +1,41 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { type Address, type Hash } from "viem" +import { usePublicClient, useWalletClient } from "wagmi" +import { GdaSDK } from "@goodsdks/streaming-sdk" + +export interface UseConnectToPoolParams { + poolAddress: Address + userData?: `0x${string}` +} + +/** + * Hook for connecting to a GDA distribution pool + */ +export function useConnectToPool() { + const publicClient = usePublicClient() + const { data: walletClient } = useWalletClient() + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + poolAddress, + userData = "0x", + }: UseConnectToPoolParams): Promise => { + if (!publicClient) { + throw new Error("Public client not available") + } + if (!walletClient) { + throw new Error("Wallet client not available") + } + + const sdk = new GdaSDK(publicClient, walletClient) + + return sdk.connectToPool({ poolAddress, userData }) + }, + onSuccess: () => { + // Invalidate pools and memberships queries + queryClient.invalidateQueries({ queryKey: ["gda-pools"] }) + queryClient.invalidateQueries({ queryKey: ["gda-memberships"] }) + }, + }) +} diff --git a/packages/react-hooks/src/streaming/useCreateStream.ts b/packages/react-hooks/src/streaming/useCreateStream.ts new file mode 100644 index 0000000..16bc2ce --- /dev/null +++ b/packages/react-hooks/src/streaming/useCreateStream.ts @@ -0,0 +1,48 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { type Address, type Hash } from "viem" +import { usePublicClient, useWalletClient } from "wagmi" +import { StreamingSDK } from "@goodsdks/streaming-sdk" + +export interface UseCreateStreamParams { + receiver: Address + token: Address + flowRate: bigint + userData?: `0x${string}` + environment?: "production" | "staging" | "development" +} + +/** + * Hook for creating a Superfluid stream + */ +export function useCreateStream() { + const publicClient = usePublicClient() + const { data: walletClient } = useWalletClient() + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + receiver, + token, + flowRate, + userData = "0x", + environment = "production", + }: UseCreateStreamParams): Promise => { + if (!publicClient) { + throw new Error("Public client not available") + } + if (!walletClient) { + throw new Error("Wallet client not available") + } + + const sdk = new StreamingSDK(publicClient, walletClient, { + environment, + }) + + return sdk.createStream({ receiver, token, flowRate, userData }) + }, + onSuccess: () => { + // Invalidate streams query to refetch + queryClient.invalidateQueries({ queryKey: ["streams"] }) + }, + }) +} diff --git a/packages/react-hooks/src/streaming/useDeleteStream.ts b/packages/react-hooks/src/streaming/useDeleteStream.ts new file mode 100644 index 0000000..14163ed --- /dev/null +++ b/packages/react-hooks/src/streaming/useDeleteStream.ts @@ -0,0 +1,44 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { type Address, type Hash } from "viem" +import { usePublicClient, useWalletClient } from "wagmi" +import { StreamingSDK } from "@goodsdks/streaming-sdk" + +export interface UseDeleteStreamParams { + receiver: Address + token: Address + environment?: "production" | "staging" | "development" +} + +/** + * Hook for deleting a Superfluid stream + */ +export function useDeleteStream() { + const publicClient = usePublicClient() + const { data: walletClient } = useWalletClient() + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + receiver, + token, + environment = "production", + }: UseDeleteStreamParams): Promise => { + if (!publicClient) { + throw new Error("Public client not available") + } + if (!walletClient) { + throw new Error("Wallet client not available") + } + + const sdk = new StreamingSDK(publicClient, walletClient, { + environment, + }) + + return sdk.deleteStream({ receiver, token }) + }, + onSuccess: () => { + // Invalidate streams query to refetch + queryClient.invalidateQueries({ queryKey: ["streams"] }) + }, + }) +} diff --git a/packages/react-hooks/src/streaming/useDisconnectFromPool.ts b/packages/react-hooks/src/streaming/useDisconnectFromPool.ts new file mode 100644 index 0000000..a21d4a0 --- /dev/null +++ b/packages/react-hooks/src/streaming/useDisconnectFromPool.ts @@ -0,0 +1,41 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { type Address, type Hash } from "viem" +import { usePublicClient, useWalletClient } from "wagmi" +import { GdaSDK } from "@goodsdks/streaming-sdk" + +export interface UseDisconnectFromPoolParams { + poolAddress: Address + userData?: `0x${string}` +} + +/** + * Hook for disconnecting from a GDA distribution pool + */ +export function useDisconnectFromPool() { + const publicClient = usePublicClient() + const { data: walletClient } = useWalletClient() + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + poolAddress, + userData = "0x", + }: UseDisconnectFromPoolParams): Promise => { + if (!publicClient) { + throw new Error("Public client not available") + } + if (!walletClient) { + throw new Error("Wallet client not available") + } + + const sdk = new GdaSDK(publicClient, walletClient) + + return sdk.disconnectFromPool({ poolAddress, userData }) + }, + onSuccess: () => { + // Invalidate pools and memberships queries + queryClient.invalidateQueries({ queryKey: ["gda-pools"] }) + queryClient.invalidateQueries({ queryKey: ["gda-memberships"] }) + }, + }) +} diff --git a/packages/react-hooks/src/streaming/useGDAPools.ts b/packages/react-hooks/src/streaming/useGDAPools.ts new file mode 100644 index 0000000..ffe1669 --- /dev/null +++ b/packages/react-hooks/src/streaming/useGDAPools.ts @@ -0,0 +1,32 @@ +import { useQuery } from "@tanstack/react-query" +import { usePublicClient } from "wagmi" +import { GdaSDK, type GDAPool } from "@goodsdks/streaming-sdk" + +export interface UseGDAPoolsParams { + environment?: "production" | "staging" | "development" + enabled?: boolean +} + +/** + * Hook for fetching GDA distribution pools + */ +export function useGDAPools({ + environment = "production", + enabled = true, +}: UseGDAPoolsParams = {}) { + const publicClient = usePublicClient() + + return useQuery({ + queryKey: ["gda-pools", environment], + queryFn: async () => { + if (!publicClient) { + throw new Error("Public client not available") + } + + const sdk = new GdaSDK(publicClient) + + return sdk.getDistributionPools() + }, + enabled: enabled && !!publicClient, + }) +} diff --git a/packages/react-hooks/src/streaming/usePoolMemberships.ts b/packages/react-hooks/src/streaming/usePoolMemberships.ts new file mode 100644 index 0000000..5b20edb --- /dev/null +++ b/packages/react-hooks/src/streaming/usePoolMemberships.ts @@ -0,0 +1,32 @@ +import { useQuery } from "@tanstack/react-query" +import { usePublicClient } from "wagmi" +import { GdaSDK, type PoolMembership } from "@goodsdks/streaming-sdk" +import { type Address } from "viem" + +export interface UsePoolMembershipsParams { + account: Address + environment?: "production" | "staging" | "development" + enabled?: boolean +} + +export function usePoolMemberships({ + account, + environment = "production", + enabled = true, +}: UsePoolMembershipsParams) { + const publicClient = usePublicClient() + + return useQuery({ + queryKey: ["gda-memberships", account, environment], + queryFn: async () => { + if (!publicClient) { + throw new Error("Public client not available") + } + + const sdk = new GdaSDK(publicClient) + + return sdk.getPoolMemberships(account) + }, + enabled: enabled && !!publicClient && !!account, + }) +} diff --git a/packages/react-hooks/src/streaming/useStreamList.ts b/packages/react-hooks/src/streaming/useStreamList.ts new file mode 100644 index 0000000..e948ef1 --- /dev/null +++ b/packages/react-hooks/src/streaming/useStreamList.ts @@ -0,0 +1,39 @@ +import { useQuery } from "@tanstack/react-query" +import { type Address } from "viem" +import { usePublicClient } from "wagmi" +import { StreamingSDK, type StreamInfo } from "@goodsdks/streaming-sdk" + +export interface UseStreamListParams { + account: Address + direction?: "incoming" | "outgoing" | "all" + environment?: "production" | "staging" | "development" + enabled?: boolean +} + +/** + * Hook for fetching active streams for an account + */ +export function useStreamList({ + account, + direction = "all", + environment = "production", + enabled = true, +}: UseStreamListParams) { + const publicClient = usePublicClient() + + return useQuery({ + queryKey: ["streams", account, direction, environment], + queryFn: async () => { + if (!publicClient) { + throw new Error("Public client not available") + } + + const sdk = new StreamingSDK(publicClient, undefined, { + environment, + }) + + return sdk.getActiveStreams(account, direction) + }, + enabled: enabled && !!account && !!publicClient, + }) +} diff --git a/packages/react-hooks/src/streaming/useSupReserves.ts b/packages/react-hooks/src/streaming/useSupReserves.ts new file mode 100644 index 0000000..d421fda --- /dev/null +++ b/packages/react-hooks/src/streaming/useSupReserves.ts @@ -0,0 +1,18 @@ +import { useQuery } from "@tanstack/react-query" +import { SubgraphClient, type SUPReserveLocker, SupportedChains } from "@goodsdks/streaming-sdk" + +export interface UseSupReservesParams { + enabled?: boolean +} + +export function useSupReserves({ enabled = true }: UseSupReservesParams = {}) { + return useQuery({ + queryKey: ["sup-reserves"], + queryFn: async () => { + // We use BASE for SUP reserves as per requirements + const client = new SubgraphClient(SupportedChains.BASE) + return client.querySUPReserves() + }, + enabled, + }) +} diff --git a/packages/react-hooks/src/streaming/useUpdateStream.ts b/packages/react-hooks/src/streaming/useUpdateStream.ts new file mode 100644 index 0000000..510403e --- /dev/null +++ b/packages/react-hooks/src/streaming/useUpdateStream.ts @@ -0,0 +1,48 @@ +import { useMutation, useQueryClient } from "@tanstack/react-query" +import { type Address, type Hash } from "viem" +import { usePublicClient, useWalletClient } from "wagmi" +import { StreamingSDK } from "@goodsdks/streaming-sdk" + +export interface UseUpdateStreamParams { + receiver: Address + token: Address + newFlowRate: bigint + userData?: `0x${string}` + environment?: "production" | "staging" | "development" +} + +/** + * Hook for updating a Superfluid stream's flow rate + */ +export function useUpdateStream() { + const publicClient = usePublicClient() + const { data: walletClient } = useWalletClient() + const queryClient = useQueryClient() + + return useMutation({ + mutationFn: async ({ + receiver, + token, + newFlowRate, + userData = "0x", + environment = "production", + }: UseUpdateStreamParams): Promise => { + if (!publicClient) { + throw new Error("Public client not available") + } + if (!walletClient) { + throw new Error("Wallet client not available") + } + + const sdk = new StreamingSDK(publicClient, walletClient, { + environment, + }) + + return sdk.updateStream({ receiver, token, newFlowRate, userData }) + }, + onSuccess: () => { + // Invalidate streams query to refetch + queryClient.invalidateQueries({ queryKey: ["streams"] }) + }, + }) +} diff --git a/packages/streaming-sdk/README.md b/packages/streaming-sdk/README.md new file mode 100644 index 0000000..2a35a5d --- /dev/null +++ b/packages/streaming-sdk/README.md @@ -0,0 +1,396 @@ +# @goodsdks/streaming-sdk + +TypeScript SDK for managing Superfluid streams on Celo and Base networks. Provides a typed, modular API for creating, updating, and querying G$ money streams with GDA (General Distribution Agreement) pool support. + +## Features + +- 🌊 **Stream Management** - Create, update, and delete Superfluid streams +- 📊 **Subgraph Integration** - Query historical data and balances efficiently +- 🎯 **GDA Pools** - Connect/disconnect from distribution pools for 1-to-many streaming +- ⛓️ **Multi-Chain** - Support for Celo and Base networks +- 🔧 **Environment-Aware** - Dev/staging/production configuration +- 📘 **TypeScript** - Full type safety with comprehensive interfaces + +## Installation + +```bash +yarn add @goodsdks/streaming-sdk viem +``` + +## Quick Start + +```typescript +import { StreamingSDK, GdaSDK, calculateFlowRate } from '@goodsdks/streaming-sdk' +import { createPublicClient, createWalletClient, http, parseEther } from 'viem' +import { celo } from 'viem/chains' + +// Initialize clients +const publicClient = createPublicClient({ + chain: celo, + transport: http() +}) + +const walletClient = createWalletClient({ + chain: celo, + transport: http() +}) + +// Create SDK instance +const streamingSDK = new StreamingSDK(publicClient, walletClient, { + chainId: 42220, // Celo + environment: 'production' +}) + +// Create a stream (100 G$ per month) +const flowRate = calculateFlowRate(parseEther('100'), 'month') +const hash = await streamingSDK.createStream({ + receiver: '0x...', + token: '0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A', // G$ on Celo + flowRate, + onHash: (hash) => console.log('Transaction:', hash) +}) +``` + +## API Reference + +### StreamingSDK + +Core SDK for stream operations. + +#### Constructor + +```typescript +new StreamingSDK( + publicClient: PublicClient, + walletClient?: WalletClient, + options?: { + chainId?: number + environment?: 'production' | 'staging' | 'development' + } +) +``` + +#### Methods + +**createStream(params)** + +Create a new stream. + +```typescript +const hash = await streamingSDK.createStream({ + receiver: '0x...', + token: '0x...', + flowRate: BigInt('1000000000000000'), // wei per second + onHash: (hash) => console.log(hash) +}) +``` + +**updateStream(params)** + +Update an existing stream's flow rate. + +```typescript +const hash = await streamingSDK.updateStream({ + receiver: '0x...', + token: '0x...', + newFlowRate: BigInt('2000000000000000') +}) +``` + +**deleteStream(params)** + +Delete a stream. + +```typescript +const hash = await streamingSDK.deleteStream({ + receiver: '0x...', + token: '0x...' +}) +``` + +**getActiveStreams(account, direction?)** + +Get active streams for an account. + +```typescript +// All streams +const streams = await streamingSDK.getActiveStreams('0x...') + +// Incoming only +const incoming = await streamingSDK.getActiveStreams('0x...', 'incoming') + +// Outgoing only +const outgoing = await streamingSDK.getActiveStreams('0x...', 'outgoing') +``` + +**getSuperTokenBalance(account)** + +Get SuperToken balance for an account. + +```typescript +const balance = await streamingSDK.getSuperTokenBalance('0x...') +console.log(`Balance: ${formatEther(balance)} G$`) +``` + +**getBalanceHistory(account, fromTimestamp?, toTimestamp?)** + +Get historical balance data. + +```typescript +const history = await streamingSDK.getBalanceHistory( + '0x...', + Date.now() - 30 * 24 * 60 * 60 * 1000, // 30 days ago + Date.now() +) +``` + +### GdaSDK + +SDK for GDA (General Distribution Agreement) pool operations. + +#### Constructor + +```typescript +new GdaSDK( + publicClient: PublicClient, + walletClient?: WalletClient, + chainId?: number +) +``` + +#### Methods + +**connectToPool(params)** + +Connect to a distribution pool. + +```typescript +const gdaSDK = new GdaSDK(publicClient, walletClient, 42220) + +const hash = await gdaSDK.connectToPool({ + poolAddress: '0x...', + onHash: (hash) => console.log(hash) +}) +``` + +**disconnectFromPool(params)** + +Disconnect from a distribution pool. + +```typescript +const hash = await gdaSDK.disconnectFromPool({ + poolAddress: '0x...' +}) +``` + +**getDistributionPools()** + +Get all distribution pools. + +```typescript +const pools = await gdaSDK.getDistributionPools() +``` + +**getPoolMemberships(account)** + +Get pool memberships for an account. + +```typescript +const memberships = await gdaSDK.getPoolMemberships('0x...') +``` + +**getPoolDetails(poolId)** + +Get details for a specific pool. + +```typescript +const pool = await gdaSDK.getPoolDetails('0x...') +``` + +### Utility Functions + +**calculateFlowRate(amountWei, timeUnit)** + +Calculate flow rate from amount and time unit. + +```typescript +import { calculateFlowRate } from '@goodsdks/streaming-sdk' +import { parseEther } from 'viem' + +const flowRate = calculateFlowRate(parseEther('100'), 'month') +``` + +**formatFlowRate(flowRate, timeUnit)** + +Format flow rate to human-readable string. + +```typescript +import { formatFlowRate } from '@goodsdks/streaming-sdk' + +const formatted = formatFlowRate(flowRate, 'month') +console.log(formatted) // "100 tokens/month" +``` + +**flowRateFromAmount(amount, timeUnit)** + +Convenience function to calculate flow rate from string amount. + +```typescript +import { flowRateFromAmount } from '@goodsdks/streaming-sdk' + +const flowRate = flowRateFromAmount('100', 'month') +``` + +## Environment Configuration + +The SDK supports three environments with different G$ token addresses: + +```typescript +const sdk = new StreamingSDK(publicClient, walletClient, { + environment: 'production' // or 'staging' or 'development' +}) +``` + +| Environment | Celo G$ Address | +|-------------|-----------------| +| production | `0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A` | +| staging | `0x61FA0fB802fd8345C06da558240E0651886fec69` | +| development | `0xFa51eFDc0910CCdA91732e6806912Fa12e2FD475` | + +## Supported Chains + +- **Celo** (Chain ID: 42220) - Primary network for G$ operations +- **Base** (Chain ID: 8453) - SUP token operations + +## Subgraph Queries + +The SDK uses Superfluid subgraphs for efficient historical data queries: + +```typescript +// Access the subgraph client directly +const subgraphClient = streamingSDK.getSubgraphClient() + +// Query streams +const streams = await subgraphClient.queryStreams({ + account: '0x...', + direction: 'all' +}) + +// Query balances +const balances = await subgraphClient.queryBalances('0x...') + +// Query pool memberships +const memberships = await subgraphClient.queryPoolMemberships('0x...') + +// Query SUP reserves +const reserves = await subgraphClient.querySUPReserves() +``` + +## Error Handling + +```typescript +try { + const hash = await streamingSDK.createStream({ + receiver: '0x...', + token: '0x...', + flowRate: BigInt('1000000000000000') + }) +} catch (error) { + if (error.message.includes('Unsupported chain')) { + console.error('Wrong network') + } else if (error.message.includes('Wallet client not initialized')) { + console.error('Connect wallet first') + } else { + console.error('Transaction failed:', error) + } +} +``` + +## Examples + +### Complete Stream Lifecycle + +```typescript +import { StreamingSDK, calculateFlowRate } from '@goodsdks/streaming-sdk' +import { parseEther } from 'viem' + +const sdk = new StreamingSDK(publicClient, walletClient) + +// Create stream +const flowRate = calculateFlowRate(parseEther('100'), 'month') +await sdk.createStream({ + receiver: '0xReceiverAddress', + token: '0xG$Address', + flowRate +}) + +// Update stream +await sdk.updateStream({ + receiver: '0xReceiverAddress', + token: '0xG$Address', + newFlowRate: calculateFlowRate(parseEther('200'), 'month') +}) + +// Delete stream +await sdk.deleteStream({ + receiver: '0xReceiverAddress', + token: '0xG$Address' +}) +``` + +### GDA Pool Workflow + +```typescript +import { GdaSDK } from '@goodsdks/streaming-sdk' + +const gdaSDK = new GdaSDK(publicClient, walletClient, 42220) + +// List all pools +const pools = await gdaSDK.getDistributionPools() + +// Connect to a pool +await gdaSDK.connectToPool({ + poolAddress: pools[0].id +}) + +// Check membership +const memberships = await gdaSDK.getPoolMemberships('0xYourAddress') + +// Disconnect from pool +await gdaSDK.disconnectFromPool({ + poolAddress: pools[0].id +}) +``` + +## TypeScript Types + +All types are exported for use in your application: + +```typescript +import type { + StreamInfo, + CreateStreamParams, + UpdateStreamParams, + DeleteStreamParams, + GDAPool, + PoolMembership, + SuperTokenBalance, + Environment +} from '@goodsdks/streaming-sdk' +``` + +## License + +MIT + +## Related Packages + +- [@goodsdks/react-hooks](../react-hooks) - React hooks for streaming operations +- [@goodsdks/citizen-sdk](../citizen-sdk) - GoodDollar identity and claim SDK +- [@goodsdks/savings-sdk](../savings-sdk) - G$ savings/staking SDK + +## References + +- [Superfluid Documentation](https://docs.superfluid.org) +- [Superfluid SDK](https://sdk.superfluid.pro) +- [GoodDollar Protocol](https://github.com/GoodDollar/GoodProtocol) diff --git a/packages/streaming-sdk/TESTING.md b/packages/streaming-sdk/TESTING.md new file mode 100644 index 0000000..395af7c --- /dev/null +++ b/packages/streaming-sdk/TESTING.md @@ -0,0 +1,352 @@ +# Testing the Superfluid Streaming SDK + +## Quick Testing Options + +### 1. **Create a Test Script (Recommended for Quick Validation)** + +Create a simple test file to verify the SDK works: + +```bash +# Create test directory +mkdir -p test-sdk +cd test-sdk +npm init -y +npm install viem @goodsdks/streaming-sdk +``` + +**test-sdk/index.js:** +```javascript +import { StreamingSDK, calculateFlowRate, SupportedChains } from '@goodsdks/streaming-sdk' +import { createPublicClient, http } from 'viem' +import { celo } from 'viem/chains' + +async function testSDK() { + console.log('🧪 Testing Superfluid Streaming SDK...\n') + + // 1. Test SDK initialization + console.log('✅ Step 1: Initialize SDK') + const publicClient = createPublicClient({ + chain: celo, + transport: http('https://forno.celo.org') + }) + + const sdk = new StreamingSDK(publicClient, undefined, { + chainId: SupportedChains.CELO, + environment: 'production' + }) + console.log(' SDK initialized successfully\n') + + // 2. Test flow rate calculation + console.log('✅ Step 2: Test flow rate utilities') + const flowRate = calculateFlowRate(BigInt('100000000000000000000'), 'month') // 100 tokens/month + console.log(` Flow rate: ${flowRate.toString()} wei/second\n`) + + // 3. Test subgraph queries + console.log('✅ Step 3: Query active streams') + try { + const streams = await sdk.getActiveStreams('0x1234567890123456789012345678901234567890') + console.log(` Found ${streams.length} active streams\n`) + } catch (error) { + console.log(` Query executed (account may not exist): ${error.message}\n`) + } + + // 4. Test balance query + console.log('✅ Step 4: Query SuperToken balance') + try { + const balance = await sdk.getSuperTokenBalance('0x1234567890123456789012345678901234567890') + console.log(` Balance: ${balance.toString()} wei\n`) + } catch (error) { + console.log(` Query executed: ${error.message}\n`) + } + + console.log('🎉 All tests completed!') +} + +testSDK().catch(console.error) +``` + +Run with: +```bash +node index.js +``` + +--- + +### 2. **Test in an Existing App (Integration Testing)** + +Add to your existing GoodDollar app: + +```bash +cd apps/demo-identity-app # or any existing app +yarn add @goodsdks/streaming-sdk +``` + +**Example usage in a React component:** +```typescript +import { usePublicClient, useWalletClient } from 'wagmi' +import { StreamingSDK, calculateFlowRate } from '@goodsdks/streaming-sdk' +import { parseEther } from 'viem' + +function StreamingDemo() { + const publicClient = usePublicClient() + const { data: walletClient } = useWalletClient() + + const createStream = async () => { + if (!publicClient || !walletClient) return + + const sdk = new StreamingSDK(publicClient, walletClient, { + environment: 'production' + }) + + const flowRate = calculateFlowRate(parseEther('100'), 'month') + + const hash = await sdk.createStream({ + receiver: '0x...', + token: '0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A', // G$ on Celo + flowRate, + onHash: (hash) => console.log('Transaction:', hash) + }) + + console.log('Stream created:', hash) + } + + return +} +``` + +--- + +### 3. **Test Subgraph Queries (No Wallet Needed)** + +You can test subgraph functionality without a wallet: + +```javascript +import { SubgraphClient, SupportedChains } from '@goodsdks/streaming-sdk' + +async function testSubgraph() { + const client = new SubgraphClient(SupportedChains.CELO) + + // Test querying streams for a known address + const streams = await client.queryStreams({ + account: '0x...', // Use a real address with streams + direction: 'all' + }) + + console.log('Streams found:', streams) + + // Test querying pools + const pools = await client.queryPools() + console.log('GDA Pools:', pools) +} + +testSubgraph() +``` + +--- + +### 4. **Test with Superfluid Subgraph Playground** + +Visit the Superfluid subgraph explorer to verify queries work: + +**Celo Subgraph:** +https://thegraph.com/explorer/subgraphs/6dRuPxMvaJAp32hvcTsYbAya69A4t1KUHh2EnV3YQeXU + +Test this query: +```graphql +{ + streams(where: { currentFlowRate_gt: "0" }, first: 5) { + id + sender + receiver + currentFlowRate + token { + id + symbol + } + } +} +``` + +--- + +### 5. **Manual Testing Checklist** + +#### ✅ **Build Verification** +```bash +# From repo root +yarn build +# Should complete without errors +``` + +#### ✅ **Type Checking** +```bash +yarn tsc --noEmit --project packages/streaming-sdk/tsconfig.json +# Should show no errors +``` + +#### ✅ **Import Test** +```bash +node -e "import('@goodsdks/streaming-sdk').then(m => console.log(Object.keys(m)))" +# Should show exported members +``` + +#### ✅ **Package Exports** +Check that all exports are accessible: +```javascript +import { + StreamingSDK, + GdaSDK, + SubgraphClient, + calculateFlowRate, + SupportedChains, + // ... etc +} from '@goodsdks/streaming-sdk' + +console.log('All exports loaded successfully') +``` + +--- + +### 6. **Test on Testnet (Safe Testing)** + +Before mainnet, test on Celo Alfajores testnet: + +1. Get testnet CELO from faucet +2. Wrap to SuperToken +3. Test stream creation with small amounts + +```typescript +const sdk = new StreamingSDK(publicClient, walletClient, { + chainId: 44787, // Alfajores testnet + environment: 'development' +}) +``` + +--- + +### 7. **React Hooks Testing** + +Test the React hooks in a component: + +```typescript +import { useCreateStream, useStreamList } from '@goodsdks/react-hooks' +import { useAccount } from 'wagmi' + +function StreamingComponent() { + const { address } = useAccount() + const { data: streams, isLoading } = useStreamList({ + account: address!, + enabled: !!address + }) + + const { mutate: createStream } = useCreateStream() + + return ( +
+

Streams: {streams?.length ?? 0}

+ +
+ ) +} +``` + +--- + +## Recommended Testing Flow + +### Phase 1: Local Verification (5 minutes) +1. ✅ Run `yarn build` - verify no errors +2. ✅ Run type check - verify no type errors +3. ✅ Test imports in Node REPL + +### Phase 2: Subgraph Testing (10 minutes) +1. ✅ Test SubgraphClient with real addresses +2. ✅ Verify queries return expected data +3. ✅ Test on Superfluid playground + +### Phase 3: Integration Testing (30 minutes) +1. ✅ Add to existing app +2. ✅ Test read operations (no wallet needed) +3. ✅ Test with wallet on testnet +4. ✅ Create a test stream with small amount + +### Phase 4: Production Testing (Careful!) +1. ✅ Test on mainnet with very small amounts (0.01 G$) +2. ✅ Verify transaction on block explorer +3. ✅ Confirm stream appears in Superfluid dashboard + +--- + +## Common Issues & Solutions + +### Issue: "Module not found" +**Solution:** Run `yarn install` from repo root + +### Issue: "Chain not supported" +**Solution:** Ensure you're using Celo (42220) or Base (8453) + +### Issue: "Wallet client not initialized" +**Solution:** Pass walletClient to SDK constructor for write operations + +### Issue: Subgraph query fails +**Solution:** Check network connectivity and subgraph endpoint + +--- + +## Quick Validation Script + +Save this as `validate-sdk.sh`: + +```bash +#!/bin/bash +echo "🧪 Validating Superfluid Streaming SDK..." + +# Build +echo "1️⃣ Building packages..." +yarn build > /dev/null 2>&1 +if [ $? -eq 0 ]; then + echo " ✅ Build successful" +else + echo " ❌ Build failed" + exit 1 +fi + +# Type check +echo "2️⃣ Type checking..." +yarn tsc --noEmit --project packages/streaming-sdk/tsconfig.json > /dev/null 2>&1 +if [ $? -eq 0 ]; then + echo " ✅ Type check passed" +else + echo " ❌ Type check failed" + exit 1 +fi + +# Check exports +echo "3️⃣ Checking exports..." +node -e "import('@goodsdks/streaming-sdk').then(() => console.log(' ✅ Exports valid'))" 2>/dev/null + +echo "" +echo "🎉 SDK validation complete!" +``` + +Run with: +```bash +chmod +x validate-sdk.sh +./validate-sdk.sh +``` + +--- + +## Next Steps + +1. **For PR Review:** Run Phase 1 & 2 tests and include results +2. **For Production Use:** Complete all 4 phases +3. **For Contributors:** Add automated tests using Vitest + +Would you like me to create any of these test files for you? diff --git a/packages/streaming-sdk/package.json b/packages/streaming-sdk/package.json new file mode 100644 index 0000000..afa3300 --- /dev/null +++ b/packages/streaming-sdk/package.json @@ -0,0 +1,46 @@ +{ + "name": "@goodsdks/streaming-sdk", + "version": "1.0.0", + "type": "module", + "scripts": { + "build": "tsup --clean", + "dev": "tsc --watch", + "test": "vitest", + "test:watch": "vitest --watch", + "test:coverage": "vitest --coverage" + }, + "main": "./dist/index.cjs", + "module": "./dist/index.js", + "types": "./dist/index.d.ts", + "exports": { + "import": { + "types": "./dist/index.d.ts", + "import": "./dist/index.js" + }, + "require": { + "types": "./dist/index.d.cts", + "require": "./dist/index.cjs" + } + }, + "files": [ + "dist", + "src" + ], + "devDependencies": { + "@repo/typescript-config": "workspace:*", + "@types/node": "latest", + "@vitest/coverage-v8": "^4.0.18", + "typescript": "latest", + "viem": "latest", + "vitest": "^4.0.18" + }, + "peerDependencies": { + "viem": "*" + }, + "dependencies": { + "@sfpro/sdk": "^0.1.0", + "graphql": "^16.9.0", + "graphql-request": "^7.1.2", + "tsup": "^8.3.5" + } +} diff --git a/packages/streaming-sdk/src/constants.ts b/packages/streaming-sdk/src/constants.ts new file mode 100644 index 0000000..d905aff --- /dev/null +++ b/packages/streaming-sdk/src/constants.ts @@ -0,0 +1,109 @@ +import { Address } from "viem" +import { Environment } from "./types" + +// Supported chains +export enum SupportedChains { + CELO = 42220, + CELO_ALFAJORES = 44787, + BASE = 8453, + BASE_SEPOLIA = 84532, +} + +export type SupportedChainId = SupportedChains + +// G$ SuperToken addresses per environment and chain +export const G$_SUPERTOKEN_ADDRESSES: Record< + Environment, + Partial> +> = { + production: { + [SupportedChains.CELO]: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A", + // Base address TBD + }, + staging: { + [SupportedChains.CELO]: "0x61FA0fB802fd8345C06da558240E0651886fec69", + // Base address TBD + }, + development: { + [SupportedChains.CELO_ALFAJORES]: + "0xFa51eFDc0910CCdA91732e6806912Fa12e2FD475", + // Base Sepolia address TBD + }, +} + +// Superfluid subgraph endpoints +export const SUBGRAPH_URLS: Record = { + [SupportedChains.CELO]: "https://celo-mainnet.subgraph.x.superfluid.dev/", + [SupportedChains.CELO_ALFAJORES]: + "https://celo-alfajores.subgraph.x.superfluid.dev/", + [SupportedChains.BASE]: "https://base-mainnet.subgraph.x.superfluid.dev/", + [SupportedChains.BASE_SEPOLIA]: + "https://base-sepolia.subgraph.x.superfluid.dev/", + supReserve: + "https://thegraph.com/explorer/subgraphs/6dRuPxMvaJAp32hvcTsYbAya69A4t1KUHh2EnV3YQeXU", +} + +// Chain configuration +export interface ChainConfig { + id: SupportedChains + name: string + rpcUrls: string[] + explorer: string +} + +export const CHAIN_CONFIGS: Record = { + [SupportedChains.CELO]: { + id: SupportedChains.CELO, + name: "Celo", + rpcUrls: ["https://forno.celo.org", "https://rpc.ankr.com/celo"], + explorer: "https://celoscan.io", + }, + [SupportedChains.CELO_ALFAJORES]: { + id: SupportedChains.CELO_ALFAJORES, + name: "Celo Alfajores", + rpcUrls: ["https://alfajores-forno.celo-testnet.org"], + explorer: "https://alfajores.celoscan.io", + }, + [SupportedChains.BASE]: { + id: SupportedChains.BASE, + name: "Base", + rpcUrls: ["https://mainnet.base.org", "https://base.llamarpc.com"], + explorer: "https://basescan.org", + }, + [SupportedChains.BASE_SEPOLIA]: { + id: SupportedChains.BASE_SEPOLIA, + name: "Base Sepolia", + rpcUrls: ["https://sepolia.base.org"], + explorer: "https://sepolia.basescan.org", + }, +} + +import { cfaForwarderAddress, gdaForwarderAddress } from "@sfpro/sdk/abi" + + +// Superfluid Forwarder addresses (pulled from @sfpro/sdk) +export const CFA_FORWARDER_ADDRESSES: Record = { + [SupportedChains.CELO]: cfaForwarderAddress[SupportedChains.CELO], + [SupportedChains.CELO_ALFAJORES]: + (cfaForwarderAddress as any)[SupportedChains.CELO_ALFAJORES] || + (cfaForwarderAddress as any)[44787] || + "0xcfA132E353cB4E398080B9700609bb008eceB125", + [SupportedChains.BASE]: cfaForwarderAddress[SupportedChains.BASE], + [SupportedChains.BASE_SEPOLIA]: + (cfaForwarderAddress as any)[SupportedChains.BASE_SEPOLIA] || + (cfaForwarderAddress as any)[84532] || + "0xcfA132E353cB4E398080B9700609bb008eceB125", +} + +export const GDA_FORWARDER_ADDRESSES: Record = { + [SupportedChains.CELO]: gdaForwarderAddress[SupportedChains.CELO], + [SupportedChains.CELO_ALFAJORES]: + (gdaForwarderAddress as any)[SupportedChains.CELO_ALFAJORES] || + (gdaForwarderAddress as any)[44787] || + "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08", + [SupportedChains.BASE]: gdaForwarderAddress[SupportedChains.BASE], + [SupportedChains.BASE_SEPOLIA]: + (gdaForwarderAddress as any)[SupportedChains.BASE_SEPOLIA] || + (gdaForwarderAddress as any)[84532] || + "0x6DA13Bde224A05a288748d857b9e7DDEffd1dE08", +} diff --git a/packages/streaming-sdk/src/gda-sdk.test.ts b/packages/streaming-sdk/src/gda-sdk.test.ts new file mode 100644 index 0000000..b11f32a --- /dev/null +++ b/packages/streaming-sdk/src/gda-sdk.test.ts @@ -0,0 +1,261 @@ +import { describe, it, expect, vi, beforeEach } from "vitest" +import { GdaSDK } from "./gda-sdk" +import { SupportedChains } from "./constants" +import type { Address, PublicClient, WalletClient } from "viem" + +// Mock clients +const createMockPublicClient = (chainId: number = 42220): PublicClient => { + return { + chain: { id: chainId }, + simulateContract: vi.fn().mockResolvedValue({ request: {} }), + waitForTransactionReceipt: vi + .fn() + .mockResolvedValue({ status: "success" }), + } as any +} + +const createMockWalletClient = (chainId: number = 42220): WalletClient => { + return { + chain: { id: chainId }, + writeContract: vi.fn().mockResolvedValue("0xtxhash" as Address), + getAddresses: vi + .fn() + .mockResolvedValue([ + "0x1234567890123456789012345678901234567890" as Address, + ]), + } as any +} + +describe("GdaSDK", () => { + let publicClient: PublicClient + let walletClient: WalletClient + + beforeEach(() => { + publicClient = createMockPublicClient() + walletClient = createMockWalletClient() + }) + + describe("Constructor", () => { + it("should initialize with valid clients", () => { + const sdk = new GdaSDK(publicClient, walletClient, SupportedChains.CELO) + + expect(sdk).toBeDefined() + }) + + it("should throw error without public client", () => { + expect(() => { + new GdaSDK(null as any) + }).toThrow("Public client is required") + }) + + it("should validate chain ID", () => { + const invalidPublicClient = createMockPublicClient(1) // Ethereum + + expect(() => { + new GdaSDK(invalidPublicClient, undefined, 1) + }).toThrow("Unsupported chain") + }) + + it("should work without wallet client", () => { + const sdk = new GdaSDK(publicClient) + expect(sdk).toBeDefined() + }) + }) + + describe("setWalletClient", () => { + it("should set wallet client with matching chain", () => { + const sdk = new GdaSDK(publicClient) + + expect(() => { + sdk.setWalletClient(walletClient) + }).not.toThrow() + }) + + it("should throw error for mismatched chains", () => { + const sdk = new GdaSDK(publicClient, undefined, SupportedChains.CELO) + + const baseWalletClient = createMockWalletClient(SupportedChains.BASE) + + expect(() => { + sdk.setWalletClient(baseWalletClient) + }).toThrow("does not match SDK chain") + }) + }) + + describe("connectToPool", () => { + it("should throw error without wallet client", async () => { + const sdk = new GdaSDK(publicClient) + + await expect( + sdk.connectToPool({ + poolAddress: "0xpool1" as Address, + }), + ).rejects.toThrow("Wallet client not initialized") + }) + + it("should call onHash callback when provided", async () => { + const sdk = new GdaSDK(publicClient, walletClient) + const onHashMock = vi.fn() + + await sdk.connectToPool({ + poolAddress: "0xpool1" as Address, + onHash: onHashMock, + }) + + expect(onHashMock).toHaveBeenCalledWith("0xtxhash") + }) + }) + + describe("disconnectFromPool", () => { + it("should throw error without wallet client", async () => { + const sdk = new GdaSDK(publicClient) + + await expect( + sdk.disconnectFromPool({ + poolAddress: "0xpool1" as Address, + }), + ).rejects.toThrow("Wallet client not initialized") + }) + }) + + describe("getDistributionPools", () => { + it("should return pools from subgraph", async () => { + const sdk = new GdaSDK(publicClient, undefined, SupportedChains.CELO) + + const mockPools = [ + { + id: "0xpool1" as Address, + token: "0xtoken1" as Address, + totalUnits: BigInt(1000), + totalAmountClaimed: BigInt(500), + flowRate: BigInt(10), + admin: "0xadmin" as Address, + }, + ] + + const mockQueryPools = vi.fn().mockResolvedValue(mockPools) + sdk["subgraphClient"].queryPools = mockQueryPools + + const pools = await sdk.getDistributionPools() + + expect(pools).toEqual(mockPools) + expect(mockQueryPools).toHaveBeenCalled() + }) + + it("should return empty array when no pools exist", async () => { + const sdk = new GdaSDK(publicClient) + + const mockQueryPools = vi.fn().mockResolvedValue([]) + sdk["subgraphClient"].queryPools = mockQueryPools + + const pools = await sdk.getDistributionPools() + + expect(pools).toEqual([]) + }) + }) + + describe("getPoolMemberships", () => { + it("should return memberships for account", async () => { + const sdk = new GdaSDK(publicClient) + + const mockMemberships = [ + { + pool: "0xpool1" as Address, + account: "0xaccount1" as Address, + units: BigInt(100), + isConnected: true, + totalAmountClaimed: BigInt(50), + }, + ] + + const mockQueryPoolMemberships = vi + .fn() + .mockResolvedValue(mockMemberships) + sdk["subgraphClient"].queryPoolMemberships = mockQueryPoolMemberships + + const memberships = await sdk.getPoolMemberships( + "0xaccount1" as Address, + ) + + expect(memberships).toEqual(mockMemberships) + expect(mockQueryPoolMemberships).toHaveBeenCalledWith( + "0xaccount1" as Address, + ) + }) + + it("should return empty array when no memberships exist", async () => { + const sdk = new GdaSDK(publicClient) + + const mockQueryPoolMemberships = vi.fn().mockResolvedValue([]) + sdk["subgraphClient"].queryPoolMemberships = mockQueryPoolMemberships + + const memberships = await sdk.getPoolMemberships( + "0xaccount1" as Address, + ) + + expect(memberships).toEqual([]) + }) + }) + + describe("getPoolDetails", () => { + it("should return pool by ID", async () => { + const sdk = new GdaSDK(publicClient) + + const mockPools = [ + { + id: "0xpool1" as Address, + token: "0xtoken1" as Address, + totalUnits: BigInt(1000), + totalAmountClaimed: BigInt(500), + flowRate: BigInt(10), + admin: "0xadmin" as Address, + }, + { + id: "0xpool2" as Address, + token: "0xtoken2" as Address, + totalUnits: BigInt(2000), + totalAmountClaimed: BigInt(1000), + flowRate: BigInt(20), + admin: "0xadmin2" as Address, + }, + ] + + vi.spyOn(sdk, "getDistributionPools").mockResolvedValue(mockPools) + + const pool = await sdk.getPoolDetails("0xpool1" as Address) + + expect(pool).toEqual(mockPools[0]) + }) + + it("should return null for non-existent pool", async () => { + const sdk = new GdaSDK(publicClient) + + vi.spyOn(sdk, "getDistributionPools").mockResolvedValue([]) + + const pool = await sdk.getPoolDetails("0xnonexistent" as Address) + + expect(pool).toBeNull() + }) + + it("should handle case-insensitive pool ID matching", async () => { + const sdk = new GdaSDK(publicClient) + + const mockPools = [ + { + id: "0xABCDEF" as Address, + token: "0xtoken1" as Address, + totalUnits: BigInt(1000), + totalAmountClaimed: BigInt(500), + flowRate: BigInt(10), + admin: "0xadmin" as Address, + }, + ] + + vi.spyOn(sdk, "getDistributionPools").mockResolvedValue(mockPools) + + const pool = await sdk.getPoolDetails("0xabcdef" as Address) + + expect(pool).toEqual(mockPools[0]) + }) + }) +}) diff --git a/packages/streaming-sdk/src/gda-sdk.ts b/packages/streaming-sdk/src/gda-sdk.ts new file mode 100644 index 0000000..e49fa88 --- /dev/null +++ b/packages/streaming-sdk/src/gda-sdk.ts @@ -0,0 +1,140 @@ +import { + Address, + Hash, + PublicClient, + WalletClient, + type SimulateContractParameters, +} from "viem" +import { gdaForwarderAbi } from "@sfpro/sdk/abi" +import { + GDAPool, + PoolMembership, + ConnectToPoolParams, + DisconnectFromPoolParams, +} from "./types" +import { validateChain } from "./utils/chains" +import { SupportedChains, GDA_FORWARDER_ADDRESSES } from "./constants" +import { SubgraphClient } from "./subgraph/client" + +export class GdaSDK { + private publicClient: PublicClient + private walletClient: WalletClient | null = null + private chainId: SupportedChains + private subgraphClient: SubgraphClient + + constructor( + publicClient: PublicClient, + walletClient?: WalletClient, + chainId?: number, + ) { + if (!publicClient) { + throw new Error("Public client is required") + } + + this.publicClient = publicClient + this.chainId = validateChain(chainId ?? publicClient.chain?.id) + + if (walletClient) { + this.setWalletClient(walletClient) + } + + this.subgraphClient = new SubgraphClient(this.chainId) + } + + setWalletClient(walletClient: WalletClient) { + const chainId = validateChain(walletClient.chain?.id) + if (chainId !== this.chainId) { + throw new Error( + `Wallet client chain (${chainId}) does not match SDK chain (${this.chainId})`, + ) + } + this.walletClient = walletClient + } + + async connectToPool(params: ConnectToPoolParams): Promise { + const { poolAddress, userData = "0x", onHash } = params + + return this.submitAndWait( + { + address: GDA_FORWARDER_ADDRESSES[this.chainId], + abi: gdaForwarderAbi, + functionName: "connectPool", + args: [poolAddress, userData], + }, + onHash, + ) + } + + async disconnectFromPool(params: DisconnectFromPoolParams): Promise { + const { poolAddress, userData = "0x", onHash } = params + + return this.submitAndWait( + { + address: GDA_FORWARDER_ADDRESSES[this.chainId], + abi: gdaForwarderAbi, + functionName: "disconnectPool", + args: [poolAddress, userData], + }, + onHash, + ) + } + + async getDistributionPools(): Promise { + return this.subgraphClient.queryPools() + } + + async getPoolMemberships(account: Address): Promise { + return this.subgraphClient.queryPoolMemberships(account) + } + + async getPoolDetails(poolId: Address): Promise { + const pools = await this.getDistributionPools() + return pools.find((p) => p.id.toLowerCase() === poolId.toLowerCase()) ?? null + } + + /** + * Submit transaction and wait for receipt + */ + private async submitAndWait( + simulateParams: SimulateContractParameters, + onHash?: (hash: Hash) => void, + ): Promise { + if (!this.walletClient) { + throw new Error("Wallet client not initialized") + } + + const account = await this.getAccount() + + const { request } = await this.publicClient.simulateContract({ + account, + ...simulateParams, + }) + + const hash = await this.walletClient.writeContract(request) + + if (onHash) { + onHash(hash) + } + + await this.publicClient.waitForTransactionReceipt({ hash }) + + return hash + } + + /** + * Get current account address from wallet client + */ + private async getAccount(): Promise
{ + if (!this.walletClient) { + throw new Error("Wallet client not initialized") + } + + const [account] = await this.walletClient.getAddresses() + + if (!account) { + throw new Error("No account found in wallet client") + } + + return account + } +} diff --git a/packages/streaming-sdk/src/index.ts b/packages/streaming-sdk/src/index.ts new file mode 100644 index 0000000..7aa8526 --- /dev/null +++ b/packages/streaming-sdk/src/index.ts @@ -0,0 +1,31 @@ +// Core SDK classes +export { StreamingSDK } from "./streaming-sdk" +export { GdaSDK } from "./gda-sdk" +export { SubgraphClient } from "./subgraph/client" + +// Types +export * from "./types" + +// Constants +export { + SupportedChains, + G$_SUPERTOKEN_ADDRESSES, + SUBGRAPH_URLS, + CHAIN_CONFIGS, +} from "./constants" + +// Utilities +export { + calculateFlowRate, + calculateStreamedAmount, + formatFlowRate, + flowRateFromAmount, + type TimeUnit, +} from "./utils/flowrate" + +export { + isSupportedChain, + validateChain, + getSuperTokenAddress, + getChainConfig, +} from "./utils/chains" diff --git a/packages/streaming-sdk/src/streaming-sdk.test.ts b/packages/streaming-sdk/src/streaming-sdk.test.ts new file mode 100644 index 0000000..78e9a0f --- /dev/null +++ b/packages/streaming-sdk/src/streaming-sdk.test.ts @@ -0,0 +1,368 @@ +import { describe, it, expect, vi, beforeEach } from "vitest" +import { StreamingSDK } from "./streaming-sdk" +import { SupportedChains } from "./constants" +import type { Address, PublicClient, WalletClient } from "viem" + +// Mock clients +const createMockPublicClient = (chainId: number = 42220): PublicClient => { + return { + chain: { id: chainId }, + simulateContract: vi.fn().mockResolvedValue({ request: {} }), + waitForTransactionReceipt: vi.fn().mockResolvedValue({ status: "success" }), + } as any +} + +const createMockWalletClient = (chainId: number = 42220): WalletClient => { + return { + chain: { id: chainId }, + writeContract: vi.fn().mockResolvedValue("0xtxhash" as Address), + getAddresses: vi + .fn() + .mockResolvedValue([ + "0x1234567890123456789012345678901234567890" as Address, + ]), + } as any +} + +describe("StreamingSDK", () => { + let publicClient: PublicClient + let walletClient: WalletClient + + beforeEach(() => { + publicClient = createMockPublicClient() + walletClient = createMockWalletClient() + }) + + describe("Constructor", () => { + it("should initialize with valid clients", () => { + const sdk = new StreamingSDK(publicClient, walletClient, { + chainId: SupportedChains.CELO, + environment: "production", + }) + + expect(sdk).toBeDefined() + expect(sdk.getSubgraphClient()).toBeDefined() + }) + + it("should throw error without public client", () => { + expect(() => { + new StreamingSDK(null as any) + }).toThrow("Public client is required") + }) + + it("should validate chain ID", () => { + const invalidPublicClient = createMockPublicClient(1) // Ethereum + + expect(() => { + new StreamingSDK(invalidPublicClient, undefined, { + chainId: 1, + }) + }).toThrow("Unsupported chain") + }) + + it("should default to production environment", () => { + const sdk = new StreamingSDK(publicClient) + expect(sdk).toBeDefined() + }) + + it("should work without wallet client", () => { + const sdk = new StreamingSDK(publicClient) + expect(sdk).toBeDefined() + }) + + it("should accept Base chain ID", () => { + const baseClient = createMockPublicClient(8453) + const sdk = new StreamingSDK(baseClient, undefined, { + chainId: SupportedChains.BASE, + }) + expect(sdk).toBeDefined() + }) + }) + + describe("setWalletClient", () => { + it("should set wallet client with matching chain", () => { + const sdk = new StreamingSDK(publicClient) + + expect(() => { + sdk.setWalletClient(walletClient) + }).not.toThrow() + }) + + it("should throw error for mismatched chains", () => { + const sdk = new StreamingSDK(publicClient, undefined, { + chainId: SupportedChains.CELO, + }) + + const baseWalletClient = createMockWalletClient(SupportedChains.BASE) + + expect(() => { + sdk.setWalletClient(baseWalletClient) + }).toThrow("does not match SDK chain") + }) + }) + + describe("createStream", () => { + it("should throw error for zero flow rate", async () => { + const sdk = new StreamingSDK(publicClient, walletClient) + + await expect( + sdk.createStream({ + receiver: + "0x1234567890123456789012345678901234567890" as Address, + token: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A" as Address, + flowRate: BigInt(0), + }), + ).rejects.toThrow("Flow rate must be greater than zero") + }) + + it("should throw error for negative flow rate", async () => { + const sdk = new StreamingSDK(publicClient, walletClient) + + await expect( + sdk.createStream({ + receiver: + "0x1234567890123456789012345678901234567890" as Address, + token: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A" as Address, + flowRate: BigInt(-1000), + }), + ).rejects.toThrow("Flow rate must be greater than zero") + }) + + it("should throw error without wallet client", async () => { + const sdk = new StreamingSDK(publicClient) + + await expect( + sdk.createStream({ + receiver: + "0x1234567890123456789012345678901234567890" as Address, + token: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A" as Address, + flowRate: BigInt(1000), + }), + ).rejects.toThrow("Wallet client not initialized") + }) + + it("should call onHash callback when provided", async () => { + const sdk = new StreamingSDK(publicClient, walletClient) + const onHashMock = vi.fn() + + await sdk.createStream({ + receiver: "0x1234567890123456789012345678901234567890" as Address, + token: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A" as Address, + flowRate: BigInt(1000), + onHash: onHashMock, + }) + + expect(onHashMock).toHaveBeenCalledWith("0xtxhash") + }) + }) + + describe("updateStream", () => { + it("should throw error without wallet client", async () => { + const sdk = new StreamingSDK(publicClient) + + await expect( + sdk.updateStream({ + receiver: + "0x1234567890123456789012345678901234567890" as Address, + token: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A" as Address, + newFlowRate: BigInt(2000), + }), + ).rejects.toThrow("Wallet client not initialized") + }) + }) + + describe("deleteStream", () => { + it("should throw error without wallet client", async () => { + const sdk = new StreamingSDK(publicClient) + + await expect( + sdk.deleteStream({ + receiver: + "0x1234567890123456789012345678901234567890" as Address, + token: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A" as Address, + }), + ).rejects.toThrow("Wallet client not initialized") + }) + }) + + describe("getActiveStreams", () => { + it("should return empty array when no streams exist", async () => { + const sdk = new StreamingSDK(publicClient) + + // Mock subgraph client + const mockQueryStreams = vi.fn().mockResolvedValue([]) + sdk.getSubgraphClient().queryStreams = mockQueryStreams + + const streams = await sdk.getActiveStreams( + "0x1234567890123456789012345678901234567890" as Address, + ) + + expect(mockQueryStreams).toHaveBeenCalledWith({ + account: "0x1234567890123456789012345678901234567890" as Address, + direction: "all", + }) + expect(streams).toEqual([]) + }) + + it("should filter by direction", async () => { + const sdk = new StreamingSDK(publicClient) + + const mockQueryStreams = vi.fn().mockResolvedValue([]) + sdk.getSubgraphClient().queryStreams = mockQueryStreams + + await sdk.getActiveStreams( + "0x1234567890123456789012345678901234567890" as Address, + "incoming", + ) + + expect(mockQueryStreams).toHaveBeenCalledWith({ + account: "0x1234567890123456789012345678901234567890" as Address, + direction: "incoming", + }) + }) + + it("should transform subgraph results to StreamInfo", async () => { + const sdk = new StreamingSDK(publicClient) + + const mockSubgraphResult = [ + { + id: "stream-1", + sender: "0xsender" as Address, + receiver: "0xreceiver" as Address, + token: "0xtoken" as Address, + currentFlowRate: BigInt(1000), + streamedUntilUpdatedAt: BigInt(5000), + updatedAtTimestamp: 1234567890, + createdAtTimestamp: 1234567800, + }, + ] + + const mockQueryStreams = vi + .fn() + .mockResolvedValue(mockSubgraphResult) + sdk.getSubgraphClient().queryStreams = mockQueryStreams + + const streams = await sdk.getActiveStreams( + "0x1234567890123456789012345678901234567890" as Address, + ) + + expect(streams).toHaveLength(1) + expect(streams[0]).toEqual({ + sender: "0xsender", + receiver: "0xreceiver", + token: "0xtoken", + flowRate: BigInt(1000), + timestamp: BigInt(1234567800), + streamedSoFar: BigInt(5000), + }) + }) + }) + + describe("getSuperTokenBalance", () => { + it("should return balance for account", async () => { + const sdk = new StreamingSDK(publicClient, undefined, { + environment: "production", + }) + + const mockQueryBalances = vi.fn().mockResolvedValue([ + { + token: "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A", + balance: BigInt("100000000000000000000"), // 100 tokens + }, + ]) + sdk.getSubgraphClient().queryBalances = mockQueryBalances + + const balance = await sdk.getSuperTokenBalance( + "0x1234567890123456789012345678901234567890" as Address, + ) + + expect(balance).toEqual(BigInt("100000000000000000000")) + }) + + it("should return zero for non-existent account", async () => { + const sdk = new StreamingSDK(publicClient) + + const mockQueryBalances = vi.fn().mockResolvedValue([]) + sdk.getSubgraphClient().queryBalances = mockQueryBalances + + const balance = await sdk.getSuperTokenBalance( + "0x1234567890123456789012345678901234567890" as Address, + ) + + expect(balance).toEqual(BigInt(0)) + }) + + it("should return zero when token not found in balances", async () => { + const sdk = new StreamingSDK(publicClient, undefined, { + environment: "production", + }) + + const mockQueryBalances = vi.fn().mockResolvedValue([ + { + token: "0xdifferenttoken", + balance: BigInt("100000000000000000000"), + }, + ]) + sdk.getSubgraphClient().queryBalances = mockQueryBalances + + const balance = await sdk.getSuperTokenBalance( + "0x1234567890123456789012345678901234567890" as Address, + ) + + expect(balance).toEqual(BigInt(0)) + }) + }) + + describe("getBalanceHistory", () => { + it("should query balance history with time range", async () => { + const sdk = new StreamingSDK(publicClient) + + const mockQueryBalanceHistory = vi.fn().mockResolvedValue([]) + sdk.getSubgraphClient().queryBalanceHistory = + mockQueryBalanceHistory + + const fromTimestamp = Date.now() - 30 * 24 * 60 * 60 * 1000 + const toTimestamp = Date.now() + + await sdk.getBalanceHistory( + "0x1234567890123456789012345678901234567890" as Address, + fromTimestamp, + toTimestamp, + ) + + expect(mockQueryBalanceHistory).toHaveBeenCalledWith({ + account: "0x1234567890123456789012345678901234567890" as Address, + fromTimestamp, + toTimestamp, + }) + }) + + it("should query balance history without time range", async () => { + const sdk = new StreamingSDK(publicClient) + + const mockQueryBalanceHistory = vi.fn().mockResolvedValue([]) + sdk.getSubgraphClient().queryBalanceHistory = + mockQueryBalanceHistory + + await sdk.getBalanceHistory( + "0x1234567890123456789012345678901234567890" as Address, + ) + + expect(mockQueryBalanceHistory).toHaveBeenCalledWith({ + account: "0x1234567890123456789012345678901234567890" as Address, + fromTimestamp: undefined, + toTimestamp: undefined, + }) + }) + }) + + describe("getSubgraphClient", () => { + it("should return subgraph client instance", () => { + const sdk = new StreamingSDK(publicClient) + + const client = sdk.getSubgraphClient() + + expect(client).toBeDefined() + }) + }) +}) diff --git a/packages/streaming-sdk/src/streaming-sdk.ts b/packages/streaming-sdk/src/streaming-sdk.ts new file mode 100644 index 0000000..ad49886 --- /dev/null +++ b/packages/streaming-sdk/src/streaming-sdk.ts @@ -0,0 +1,205 @@ +import { + Address, + Hash, + PublicClient, + WalletClient, + type SimulateContractParameters, +} from "viem" +import { cfaForwarderAbi } from "@sfpro/sdk/abi" +import { + StreamingSDKOptions, + CreateStreamParams, + UpdateStreamParams, + DeleteStreamParams, + StreamInfo, + GetStreamsOptions, + Environment, +} from "./types" +import { validateChain, getSuperTokenAddress } from "./utils/chains" +import { SupportedChains, CFA_FORWARDER_ADDRESSES } from "./constants" +import { SubgraphClient } from "./subgraph/client" + +export class StreamingSDK { + private publicClient: PublicClient + private walletClient: WalletClient | null = null + private chainId: SupportedChains + private environment: Environment + private subgraphClient: SubgraphClient + + constructor( + publicClient: PublicClient, + walletClient?: WalletClient, + options?: StreamingSDKOptions, + ) { + if (!publicClient) { + throw new Error("Public client is required") + } + + this.publicClient = publicClient + this.chainId = validateChain( + options?.chainId ?? publicClient.chain?.id, + ) + this.environment = options?.environment ?? "production" + + if (walletClient) { + this.setWalletClient(walletClient) + } + + this.subgraphClient = new SubgraphClient(this.chainId) + } + + setWalletClient(walletClient: WalletClient) { + const chainId = validateChain(walletClient.chain?.id) + if (chainId !== this.chainId) { + throw new Error( + `Wallet client chain (${chainId}) does not match SDK chain (${this.chainId})`, + ) + } + this.walletClient = walletClient + } + + async createStream(params: CreateStreamParams): Promise { + const { receiver, token, flowRate, userData = "0x", onHash } = params + + if (flowRate <= BigInt(0)) { + throw new Error("Flow rate must be greater than zero") + } + + return this.submitAndWait( + { + address: CFA_FORWARDER_ADDRESSES[this.chainId], + abi: cfaForwarderAbi, + functionName: "setFlowrate", + args: [token, receiver, flowRate], + }, + onHash, + ) + } + + async updateStream(params: UpdateStreamParams): Promise { + const { receiver, token, newFlowRate, userData = "0x", onHash } = params + + const account = await this.getAccount() + + return this.submitAndWait( + { + address: CFA_FORWARDER_ADDRESSES[this.chainId], + abi: cfaForwarderAbi, + functionName: "updateFlow", + args: [token, account, receiver, newFlowRate, userData], + }, + onHash, + ) + } + + async deleteStream(params: DeleteStreamParams): Promise { + const { receiver, token, onHash } = params + + const account = await this.getAccount() + + return this.submitAndWait( + { + address: CFA_FORWARDER_ADDRESSES[this.chainId], + abi: cfaForwarderAbi, + functionName: "deleteFlow", + args: [token, account, receiver, "0x"], + }, + onHash, + ) + } + + async getActiveStreams( + account: Address, + direction?: "incoming" | "outgoing" | "all", + ): Promise { + const streams = await this.subgraphClient.queryStreams({ + account, + direction: direction ?? "all", + }) + + return streams.map((stream) => ({ + sender: stream.sender, + receiver: stream.receiver, + token: stream.token, + flowRate: stream.currentFlowRate, + timestamp: BigInt(stream.createdAtTimestamp), + streamedSoFar: stream.streamedUntilUpdatedAt, + })) + } + + async getSuperTokenBalance(account: Address): Promise { + const token = getSuperTokenAddress(this.chainId, this.environment) + + const balances = await this.subgraphClient.queryBalances(account) + const tokenBalance = balances.find( + (b) => b.token.toLowerCase() === token.toLowerCase(), + ) + + return tokenBalance?.balance ?? BigInt(0) + } + + /** + * Get balance history for an account + */ + async getBalanceHistory( + account: Address, + fromTimestamp?: number, + toTimestamp?: number, + ) { + return this.subgraphClient.queryBalanceHistory({ + account, + fromTimestamp, + toTimestamp, + }) + } + + getSubgraphClient(): SubgraphClient { + return this.subgraphClient + } + + /** + * Submit transaction and wait for receipt + */ + private async submitAndWait( + simulateParams: SimulateContractParameters, + onHash?: (hash: Hash) => void, + ): Promise { + if (!this.walletClient) { + throw new Error("Wallet client not initialized") + } + + const account = await this.getAccount() + + const { request } = await this.publicClient.simulateContract({ + account, + ...simulateParams, + }) + + const hash = await this.walletClient.writeContract(request) + + if (onHash) { + onHash(hash) + } + + await this.publicClient.waitForTransactionReceipt({ hash }) + + return hash + } + + /** + * Get current account address from wallet client + */ + private async getAccount(): Promise
{ + if (!this.walletClient) { + throw new Error("Wallet client not initialized") + } + + const [account] = await this.walletClient.getAddresses() + + if (!account) { + throw new Error("No account found in wallet client") + } + + return account + } +} diff --git a/packages/streaming-sdk/src/subgraph/client.ts b/packages/streaming-sdk/src/subgraph/client.ts new file mode 100644 index 0000000..9a3634f --- /dev/null +++ b/packages/streaming-sdk/src/subgraph/client.ts @@ -0,0 +1,205 @@ +import { GraphQLClient } from "graphql-request" +import { Address } from "viem" +import { SUBGRAPH_URLS, SupportedChains } from "../constants" +import { + GET_STREAMS, + GET_TOKEN_BALANCE, + GET_BALANCE_HISTORY, + GET_POOL_MEMBERSHIPS, + GET_DISTRIBUTION_POOLS, + GET_POOL_DETAILS, + GET_SUP_RESERVES, +} from "./queries" +import { + StreamQueryResult, + SuperTokenBalance, + GDAPool, + PoolMembership, + SUPReserveLocker, + GetStreamsOptions, + GetBalanceHistoryOptions, +} from "../types" + +export class SubgraphClient { + private client: GraphQLClient + private chainId: SupportedChains + + constructor(chainId: SupportedChains) { + this.chainId = chainId + const endpoint = SUBGRAPH_URLS[chainId] + + if (!endpoint) { + throw new Error(`No subgraph endpoint configured for chain ${chainId}`) + } + + this.client = new GraphQLClient(endpoint) + } + + async queryStreams( + options: GetStreamsOptions, + ): Promise { + try { + const { account, direction = "all" } = options + + const data = await this.client.request<{ + outgoingStreams: any[] + incomingStreams: any[] + }>(GET_STREAMS, { + account: account.toLowerCase(), + first: 100, + skip: 0, + }) + + let streams: any[] = [] + + if (direction === "outgoing" || direction === "all") { + streams = [...streams, ...data.outgoingStreams] + } + + if (direction === "incoming" || direction === "all") { + streams = [...streams, ...data.incomingStreams] + } + + return streams.map((stream) => ({ + id: stream.id, + sender: stream.sender.id as Address, + receiver: stream.receiver.id as Address, + token: stream.token.id as Address, + currentFlowRate: BigInt(stream.currentFlowRate), + streamedUntilUpdatedAt: BigInt(stream.streamedUntilUpdatedAt), + updatedAtTimestamp: Number(stream.updatedAtTimestamp), + createdAtTimestamp: Number(stream.createdAtTimestamp), + })) + } catch (error) { + console.error("Error querying streams:", error) + throw new Error(`Failed to query streams: ${error}`) + } + } + + async queryBalances(account: Address): Promise { + try { + const data = await this.client.request<{ + account: { + accountTokenSnapshots: any[] + } | null + }>(GET_TOKEN_BALANCE, { + account: account.toLowerCase(), + }) + + if (!data.account) { + return [] + } + + return data.account.accountTokenSnapshots.map((snapshot) => ({ + account, + token: snapshot.token.id as Address, + balance: BigInt(snapshot.balanceUntilUpdatedAt), + balanceUntilUpdatedAt: BigInt(snapshot.balanceUntilUpdatedAt), + updatedAtTimestamp: Number(snapshot.updatedAtTimestamp), + })) + } catch (error) { + console.error("Error querying balances:", error) + throw new Error(`Failed to query balances: ${error}`) + } + } + + async queryBalanceHistory( + options: GetBalanceHistoryOptions, + ): Promise { + try { + const { account, fromTimestamp, toTimestamp } = options + + const data = await this.client.request<{ + accountTokenSnapshotLogs: any[] + }>(GET_BALANCE_HISTORY, { + account: account.toLowerCase(), + fromTimestamp: fromTimestamp + ? Math.floor(fromTimestamp / 1000) + : undefined, + toTimestamp: toTimestamp ? Math.floor(toTimestamp / 1000) : undefined, + }) + + return data.accountTokenSnapshotLogs.map((log) => ({ + account, + token: log.token.id as Address, + balance: BigInt(log.balance), + balanceUntilUpdatedAt: BigInt(log.balance), + updatedAtTimestamp: Number(log.timestamp), + })) + } catch (error) { + console.error("Error querying balance history:", error) + throw new Error(`Failed to query balance history: ${error}`) + } + } + + async queryPoolMemberships(account: Address): Promise { + try { + const data = await this.client.request<{ + account: { + poolMemberships: any[] + } | null + }>(GET_POOL_MEMBERSHIPS, { + account: account.toLowerCase(), + }) + + if (!data.account) { + return [] + } + + return data.account.poolMemberships.map((membership) => ({ + pool: membership.pool.id as Address, + account, + units: BigInt(membership.units), + isConnected: membership.isConnected, + totalAmountClaimed: BigInt(membership.totalAmountClaimed), + })) + } catch (error) { + console.error("Error querying pool memberships:", error) + throw new Error(`Failed to query pool memberships: ${error}`) + } + } + + async queryPools(): Promise { + try { + const data = await this.client.request<{ + pools: any[] + }>(GET_DISTRIBUTION_POOLS, { + first: 100, + skip: 0, + }) + + return data.pools.map((pool) => ({ + id: pool.id as Address, + token: pool.token.id as Address, + totalUnits: BigInt(pool.totalUnits), + totalAmountClaimed: BigInt(pool.totalAmountDistributedUntilUpdatedAt), + flowRate: BigInt(pool.flowRate), + admin: pool.admin.id as Address, + })) + } catch (error) { + console.error("Error querying pools:", error) + throw new Error(`Failed to query pools: ${error}`) + } + } + + async querySUPReserves(): Promise { + try { + // Use SUP reserve subgraph + const supClient = new GraphQLClient(SUBGRAPH_URLS.supReserve) + + const data = await supClient.request<{ + lockers: any[] + }>(GET_SUP_RESERVES) + + return data.lockers.map((locker) => ({ + id: locker.id, + lockerOwner: locker.lockerOwner.id as Address, + blockNumber: BigInt(locker.blockNumber), + blockTimestamp: BigInt(locker.blockTimestamp), + })) + } catch (error) { + console.error("Error querying SUP reserves:", error) + throw new Error(`Failed to query SUP reserves: ${error}`) + } + } +} diff --git a/packages/streaming-sdk/src/subgraph/queries.ts b/packages/streaming-sdk/src/subgraph/queries.ts new file mode 100644 index 0000000..3d1c411 --- /dev/null +++ b/packages/streaming-sdk/src/subgraph/queries.ts @@ -0,0 +1,189 @@ +import { gql } from "graphql-request" + +export const GET_STREAMS = gql` + query GetStreams($account: String!, $skip: Int = 0, $first: Int = 100) { + # Outgoing streams + outgoingStreams: streams( + where: { sender: $account, currentFlowRate_gt: "0" } + first: $first + skip: $skip + orderBy: createdAtTimestamp + orderDirection: desc + ) { + id + sender { + id + } + receiver { + id + } + token { + id + symbol + } + currentFlowRate + streamedUntilUpdatedAt + updatedAtTimestamp + createdAtTimestamp + } + + # Incoming streams + incomingStreams: streams( + where: { receiver: $account, currentFlowRate_gt: "0" } + first: $first + skip: $skip + orderBy: createdAtTimestamp + orderDirection: desc + ) { + id + sender { + id + } + receiver { + id + } + token { + id + symbol + } + currentFlowRate + streamedUntilUpdatedAt + updatedAtTimestamp + createdAtTimestamp + } + } +` + +export const GET_TOKEN_BALANCE = gql` + query GetTokenBalance($account: String!) { + account(id: $account) { + id + accountTokenSnapshots { + token { + id + name + symbol + } + balanceUntilUpdatedAt + updatedAtTimestamp + totalNetFlowRate + } + } + } +` + +export const GET_BALANCE_HISTORY = gql` + query GetBalanceHistory( + $account: String! + $fromTimestamp: Int + $toTimestamp: Int + ) { + accountTokenSnapshotLogs( + where: { + account: $account + timestamp_gte: $fromTimestamp + timestamp_lte: $toTimestamp + } + orderBy: timestamp + orderDirection: asc + ) { + id + timestamp + token { + id + symbol + } + balance + totalNetFlowRate + } + } +` + +export const GET_POOL_MEMBERSHIPS = gql` + query GetPoolMemberships($account: String!) { + account(id: $account) { + poolMemberships { + pool { + id + token { + id + symbol + } + totalUnits + totalAmountDistributedUntilUpdatedAt + flowRate + admin { + id + } + } + units + isConnected + totalAmountClaimed + } + } + } +` + +export const GET_DISTRIBUTION_POOLS = gql` + query GetDistributionPools($first: Int = 100, $skip: Int = 0) { + pools( + first: $first + skip: $skip + orderBy: createdAtTimestamp + orderDirection: desc + ) { + id + token { + id + symbol + } + totalUnits + totalAmountDistributedUntilUpdatedAt + flowRate + admin { + id + } + createdAtTimestamp + } + } +` + +export const GET_POOL_DETAILS = gql` + query GetPoolDetails($poolId: String!) { + pool(id: $poolId) { + id + token { + id + symbol + } + totalUnits + totalAmountDistributedUntilUpdatedAt + flowRate + admin { + id + } + createdAtTimestamp + poolMembers { + account { + id + } + units + isConnected + totalAmountClaimed + } + } + } +` + +export const GET_SUP_RESERVES = gql` + query GetSUPReserves { + lockers(orderBy: blockTimestamp, orderDirection: desc) { + id + lockerOwner { + id + } + blockNumber + blockTimestamp + } + } +` diff --git a/packages/streaming-sdk/src/types.ts b/packages/streaming-sdk/src/types.ts new file mode 100644 index 0000000..b405805 --- /dev/null +++ b/packages/streaming-sdk/src/types.ts @@ -0,0 +1,110 @@ +import { Address, Hash } from "viem" + +export type Environment = "production" | "staging" | "development" + +export interface StreamingSDKOptions { + chainId?: number + environment?: Environment +} + +// Stream Types +export interface StreamInfo { + sender: Address + receiver: Address + token: Address + flowRate: bigint + timestamp: bigint + streamedSoFar?: bigint +} + +export interface CreateStreamParams { + receiver: Address + token: Address + flowRate: bigint + userData?: `0x${string}` + onHash?: (hash: Hash) => void +} + +export interface UpdateStreamParams { + receiver: Address + token: Address + newFlowRate: bigint + userData?: `0x${string}` + onHash?: (hash: Hash) => void +} + +export interface DeleteStreamParams { + receiver: Address + token: Address + onHash?: (hash: Hash) => void +} + +// Subgraph Types +export interface SuperTokenBalance { + account: Address + token: Address + balance: bigint + balanceUntilUpdatedAt: bigint + updatedAtTimestamp: number +} + +export interface StreamQueryResult { + id: string + sender: Address + receiver: Address + token: Address + currentFlowRate: bigint + streamedUntilUpdatedAt: bigint + updatedAtTimestamp: number + createdAtTimestamp: number +} + +// GDA Pool Types +export interface GDAPool { + id: Address + token: Address + totalUnits: bigint + totalAmountClaimed: bigint + flowRate: bigint + admin: Address +} + +export interface PoolMembership { + pool: Address + account: Address + units: bigint + isConnected: boolean + totalAmountClaimed: bigint +} + +export interface ConnectToPoolParams { + poolAddress: Address + userData?: `0x${string}` + onHash?: (hash: Hash) => void +} + +export interface DisconnectFromPoolParams { + poolAddress: Address + userData?: `0x${string}` + onHash?: (hash: Hash) => void +} + +// SUP Reserve Types +export interface SUPReserveLocker { + id: string + lockerOwner: Address + blockNumber: bigint + blockTimestamp: bigint +} + +// Query Options +export interface GetStreamsOptions { + account: Address + direction?: "incoming" | "outgoing" | "all" +} + +export interface GetBalanceHistoryOptions { + account: Address + fromTimestamp?: number + toTimestamp?: number +} diff --git a/packages/streaming-sdk/src/utils/chains.test.ts b/packages/streaming-sdk/src/utils/chains.test.ts new file mode 100644 index 0000000..0b4f20d --- /dev/null +++ b/packages/streaming-sdk/src/utils/chains.test.ts @@ -0,0 +1,109 @@ +import { describe, it, expect } from "vitest" +import { + isSupportedChain, + validateChain, + getSuperTokenAddress, + getChainConfig, +} from "./chains" +import { SupportedChains } from "../constants" + +describe("Chain Utilities", () => { + describe("isSupportedChain", () => { + it("should return true for Celo", () => { + expect(isSupportedChain(42220)).toBe(true) + }) + + it("should return true for Base", () => { + expect(isSupportedChain(8453)).toBe(true) + }) + + it("should return false for unsupported chains", () => { + expect(isSupportedChain(1)).toBe(false) // Ethereum + expect(isSupportedChain(137)).toBe(false) // Polygon + expect(isSupportedChain(undefined)).toBe(false) + expect(isSupportedChain(999999)).toBe(false) + }) + }) + + describe("validateChain", () => { + it("should return chain ID for supported chains", () => { + expect(validateChain(42220)).toEqual(SupportedChains.CELO) + expect(validateChain(8453)).toEqual(SupportedChains.BASE) + }) + + it("should throw error for unsupported chains", () => { + expect(() => validateChain(1)).toThrow("Unsupported chain") + expect(() => validateChain(undefined)).toThrow("Unsupported chain") + expect(() => validateChain(999999)).toThrow( + "Supported chains: Celo (42220), Alfajores (44787), Base (8453), Base Sepolia (84532)", + ) + }) + }) + + describe("getSuperTokenAddress", () => { + it("should return production address for Celo", () => { + const address = getSuperTokenAddress( + SupportedChains.CELO, + "production", + ) + + expect(address).toEqual( + "0x62B8B11039FcfE5aB0C56E502b1C372A3d2a9c7A", + ) + }) + + it("should return staging address for Celo", () => { + const address = getSuperTokenAddress( + SupportedChains.CELO, + "staging", + ) + + expect(address).toEqual( + "0x61FA0fB802fd8345C06da558240E0651886fec69", + ) + }) + + it("should return development address for Celo Alfajores", () => { + const address = getSuperTokenAddress( + SupportedChains.CELO_ALFAJORES, + "development", + ) + + expect(address).toEqual( + "0xFa51eFDc0910CCdA91732e6806912Fa12e2FD475", + ) + }) + + it("should throw error for unconfigured chain/env combination", () => { + expect(() => { + getSuperTokenAddress(SupportedChains.BASE, "production") + }).toThrow("G$ SuperToken address not configured") + + expect(() => { + getSuperTokenAddress(SupportedChains.BASE, "staging") + }).toThrow("G$ SuperToken address not configured") + }) + }) + + describe("getChainConfig", () => { + it("should return Celo config", () => { + const config = getChainConfig(SupportedChains.CELO) + + expect(config.id).toEqual(SupportedChains.CELO) + expect(config.name).toEqual("Celo") + expect(config.rpcUrls).toContain("https://forno.celo.org") + expect(config.rpcUrls).toContain("https://rpc.ankr.com/celo") + expect(config.explorer).toEqual("https://celoscan.io") + }) + + it("should return Base config", () => { + const config = getChainConfig(SupportedChains.BASE) + + expect(config.id).toEqual(SupportedChains.BASE) + expect(config.name).toEqual("Base") + expect(config.rpcUrls).toContain("https://mainnet.base.org") + expect(config.rpcUrls).toContain("https://base.llamarpc.com") + expect(config.explorer).toEqual("https://basescan.org") + }) + }) +}) diff --git a/packages/streaming-sdk/src/utils/chains.ts b/packages/streaming-sdk/src/utils/chains.ts new file mode 100644 index 0000000..bc755bd --- /dev/null +++ b/packages/streaming-sdk/src/utils/chains.ts @@ -0,0 +1,46 @@ +import { Address } from "viem" +import { + SupportedChains, + G$_SUPERTOKEN_ADDRESSES, + CHAIN_CONFIGS, +} from "../constants" +import { Environment } from "../types" + +export function isSupportedChain( + chainId: number | undefined, +): chainId is SupportedChains { + return ( + chainId === SupportedChains.CELO || + chainId === SupportedChains.CELO_ALFAJORES || + chainId === SupportedChains.BASE || + chainId === SupportedChains.BASE_SEPOLIA + ) +} + +export function validateChain(chainId: number | undefined): SupportedChains { + if (!isSupportedChain(chainId)) { + throw new Error( + `Unsupported chain ID: ${chainId}. Supported chains: Celo (42220), Alfajores (44787), Base (8453), Base Sepolia (84532)`, + ) + } + return chainId +} + +export function getSuperTokenAddress( + chainId: SupportedChains, + environment: Environment, +): Address { + const address = G$_SUPERTOKEN_ADDRESSES[environment][chainId] + + if (!address) { + throw new Error( + `G$ SuperToken address not configured for chain ${CHAIN_CONFIGS[chainId].name} in ${environment} environment`, + ) + } + + return address +} + +export function getChainConfig(chainId: SupportedChains) { + return CHAIN_CONFIGS[chainId] +} diff --git a/packages/streaming-sdk/src/utils/flowrate.test.ts b/packages/streaming-sdk/src/utils/flowrate.test.ts new file mode 100644 index 0000000..c1f28d9 --- /dev/null +++ b/packages/streaming-sdk/src/utils/flowrate.test.ts @@ -0,0 +1,134 @@ +import { describe, it, expect } from "vitest" +import { + calculateFlowRate, + calculateStreamedAmount, + formatFlowRate, + flowRateFromAmount, +} from "./flowrate" +import { parseEther } from "viem" + +describe("Flow Rate Utilities", () => { + describe("calculateFlowRate", () => { + it("should calculate flow rate per second", () => { + const amountWei = parseEther("100") // 100 tokens + const flowRate = calculateFlowRate(amountWei, "second") + + expect(flowRate).toEqual(parseEther("100")) + }) + + it("should calculate flow rate per month", () => { + const amountWei = parseEther("100") // 100 tokens/month + const flowRate = calculateFlowRate(amountWei, "month") + + // 100 tokens / 2592000 seconds + const expected = parseEther("100") / BigInt(2592000) + expect(flowRate).toEqual(expected) + }) + + it("should calculate flow rate per year", () => { + const amountWei = parseEther("1000") // 1000 tokens/year + const flowRate = calculateFlowRate(amountWei, "year") + + const expected = parseEther("1000") / BigInt(31536000) + expect(flowRate).toEqual(expected) + }) + + it("should calculate flow rate per minute", () => { + const amountWei = parseEther("60") // 60 tokens/minute + const flowRate = calculateFlowRate(amountWei, "minute") + + const expected = parseEther("60") / BigInt(60) + expect(flowRate).toEqual(expected) + }) + + it("should calculate flow rate per hour", () => { + const amountWei = parseEther("3600") // 3600 tokens/hour + const flowRate = calculateFlowRate(amountWei, "hour") + + const expected = parseEther("3600") / BigInt(3600) + expect(flowRate).toEqual(expected) + }) + + it("should calculate flow rate per day", () => { + const amountWei = parseEther("86400") // 86400 tokens/day + const flowRate = calculateFlowRate(amountWei, "day") + + const expected = parseEther("86400") / BigInt(86400) + expect(flowRate).toEqual(expected) + }) + + it("should calculate flow rate per week", () => { + const amountWei = parseEther("604800") // 604800 tokens/week + const flowRate = calculateFlowRate(amountWei, "week") + + const expected = parseEther("604800") / BigInt(604800) + expect(flowRate).toEqual(expected) + }) + }) + + describe("calculateStreamedAmount", () => { + it("should calculate streamed amount over duration", () => { + const flowRate = BigInt("1000000000000000") // wei per second + const duration = BigInt(3600) // 1 hour + + const amount = calculateStreamedAmount(flowRate, duration) + + expect(amount).toEqual(flowRate * duration) + }) + + it("should calculate zero for zero duration", () => { + const flowRate = BigInt("1000000000000000") + const duration = BigInt(0) + + const amount = calculateStreamedAmount(flowRate, duration) + + expect(amount).toEqual(BigInt(0)) + }) + + it("should calculate zero for zero flow rate", () => { + const flowRate = BigInt(0) + const duration = BigInt(3600) + + const amount = calculateStreamedAmount(flowRate, duration) + + expect(amount).toEqual(BigInt(0)) + }) + }) + + describe("formatFlowRate", () => { + it("should format flow rate to readable string", () => { + const flowRate = parseEther("100") / BigInt(2592000) // 100/month + + const formatted = formatFlowRate(flowRate, "month") + + expect(formatted).toContain("tokens/month") + // Allow for floating point precision issues + expect(formatted).toMatch(/99\.9+|100/) + }) + + it("should format flow rate per second", () => { + const flowRate = parseEther("1") + + const formatted = formatFlowRate(flowRate, "second") + + expect(formatted).toContain("tokens/second") + expect(formatted).toContain("1") + }) + }) + + describe("flowRateFromAmount", () => { + it("should calculate flow rate from string amount", () => { + const flowRate = flowRateFromAmount("100", "month") + + const expected = parseEther("100") / BigInt(2592000) + expect(flowRate).toEqual(expected) + }) + + it("should calculate flow rate from decimal string", () => { + const flowRate = flowRateFromAmount("10.5", "day") + + const expected = parseEther("10.5") / BigInt(86400) + expect(flowRate).toEqual(expected) + }) + }) +}) diff --git a/packages/streaming-sdk/src/utils/flowrate.ts b/packages/streaming-sdk/src/utils/flowrate.ts new file mode 100644 index 0000000..f2611b0 --- /dev/null +++ b/packages/streaming-sdk/src/utils/flowrate.ts @@ -0,0 +1,49 @@ +import { parseEther, formatEther } from "viem" + +export type TimeUnit = "second" | "minute" | "hour" | "day" | "week" | "month" | "year" + +const TIME_UNITS_IN_SECONDS: Record = { + second: 1, + minute: 60, + hour: 3600, + day: 86400, + week: 604800, + month: 2592000, // 30 days + year: 31536000, // 365 days +} + +export function calculateFlowRate( + amountWei: bigint, + timeUnit: TimeUnit, +): bigint { + const secondsInUnit = BigInt(TIME_UNITS_IN_SECONDS[timeUnit]) + return amountWei / secondsInUnit +} + +export function calculateStreamedAmount( + flowRate: bigint, + durationSeconds: bigint, +): bigint { + return flowRate * durationSeconds +} + +export function formatFlowRate(flowRate: bigint, timeUnit: TimeUnit): string { + const secondsInUnit = BigInt(TIME_UNITS_IN_SECONDS[timeUnit]) + const amountPerUnit = flowRate * secondsInUnit + const formatted = formatEther(amountPerUnit) + + // Round to 4 decimal places for cleaner UI + const [integer, fraction] = formatted.split(".") + if (fraction && fraction.length > 4) { + return `${integer}.${fraction.slice(0, 4)} tokens/${timeUnit}` + } + return `${formatted} tokens/${timeUnit}` +} + +export function flowRateFromAmount( + amount: string, + timeUnit: TimeUnit, +): bigint { + const amountWei = parseEther(amount) + return calculateFlowRate(amountWei, timeUnit) +} diff --git a/packages/streaming-sdk/tsconfig.json b/packages/streaming-sdk/tsconfig.json new file mode 100644 index 0000000..0896596 --- /dev/null +++ b/packages/streaming-sdk/tsconfig.json @@ -0,0 +1,14 @@ +{ + "extends": "@repo/typescript-config/base.json", + "compilerOptions": { + "outDir": "dist", + "rootDir": "src" + }, + "include": [ + "src" + ], + "exclude": [ + "node_modules", + "dist" + ] +} \ No newline at end of file diff --git a/packages/streaming-sdk/tsup.config.ts b/packages/streaming-sdk/tsup.config.ts new file mode 100644 index 0000000..158c45f --- /dev/null +++ b/packages/streaming-sdk/tsup.config.ts @@ -0,0 +1,8 @@ +import { defineConfig } from "tsup"; + +export default defineConfig({ + entry: ["src/index.ts"], + dts: true, + format: ["esm", "cjs"], + treeshake: true, +}); diff --git a/packages/streaming-sdk/vitest.config.ts b/packages/streaming-sdk/vitest.config.ts new file mode 100644 index 0000000..7ae15f1 --- /dev/null +++ b/packages/streaming-sdk/vitest.config.ts @@ -0,0 +1,14 @@ +import { defineConfig } from "vitest/config" + +export default defineConfig({ + test: { + globals: true, + environment: "node", + coverage: { + provider: "v8", + reporter: ["text", "json", "html"], + include: ["src/**/*.ts"], + exclude: ["src/**/*.test.ts", "src/**/__tests__/**"], + }, + }, +}) diff --git a/yarn.lock b/yarn.lock index e83fe8c..d1c2d88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -747,6 +747,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-string-parser@npm:^7.27.1": + version: 7.27.1 + resolution: "@babel/helper-string-parser@npm:7.27.1" + checksum: 10c0/8bda3448e07b5583727c103560bcf9c4c24b3c1051a4c516d4050ef69df37bb9a4734a585fe12725b8c2763de0a265aa1e909b485a4e3270b7cfd3e4dbe4b602 + languageName: node + linkType: hard + "@babel/helper-validator-identifier@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-validator-identifier@npm:7.25.9" @@ -754,6 +761,13 @@ __metadata: languageName: node linkType: hard +"@babel/helper-validator-identifier@npm:^7.28.5": + version: 7.28.5 + resolution: "@babel/helper-validator-identifier@npm:7.28.5" + checksum: 10c0/42aaebed91f739a41f3d80b72752d1f95fd7c72394e8e4bd7cdd88817e0774d80a432451bcba17c2c642c257c483bf1d409dd4548883429ea9493a3bc4ab0847 + languageName: node + linkType: hard + "@babel/helper-validator-option@npm:^7.25.9": version: 7.25.9 resolution: "@babel/helper-validator-option@npm:7.25.9" @@ -803,6 +817,17 @@ __metadata: languageName: node linkType: hard +"@babel/parser@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/parser@npm:7.29.0" + dependencies: + "@babel/types": "npm:^7.29.0" + bin: + parser: ./bin/babel-parser.js + checksum: 10c0/333b2aa761264b91577a74bee86141ef733f9f9f6d4fc52548e4847dc35dfbf821f58c46832c637bfa761a6d9909d6a68f7d1ed59e17e4ffbb958dc510c17b62 + languageName: node + linkType: hard + "@babel/plugin-syntax-jsx@npm:^7.25.9": version: 7.25.9 resolution: "@babel/plugin-syntax-jsx@npm:7.25.9" @@ -941,6 +966,23 @@ __metadata: languageName: node linkType: hard +"@babel/types@npm:^7.29.0": + version: 7.29.0 + resolution: "@babel/types@npm:7.29.0" + dependencies: + "@babel/helper-string-parser": "npm:^7.27.1" + "@babel/helper-validator-identifier": "npm:^7.28.5" + checksum: 10c0/23cc3466e83bcbfab8b9bd0edaafdb5d4efdb88b82b3be6728bbade5ba2f0996f84f63b1c5f7a8c0d67efded28300898a5f930b171bb40b311bca2029c4e9b4f + languageName: node + linkType: hard + +"@bcoe/v8-coverage@npm:^1.0.2": + version: 1.0.2 + resolution: "@bcoe/v8-coverage@npm:1.0.2" + checksum: 10c0/1eb1dc93cc17fb7abdcef21a6e7b867d6aa99a7ec88ec8207402b23d9083ab22a8011213f04b2cf26d535f1d22dc26139b7929e6c2134c254bd1e14ba5e678c3 + languageName: node + linkType: hard + "@bytecodealliance/preview2-shim@npm:0.17.0": version: 0.17.0 resolution: "@bytecodealliance/preview2-shim@npm:0.17.0" @@ -1026,6 +1068,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/aix-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/aix-ppc64@npm:0.27.3" + conditions: os=aix & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/android-arm64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/android-arm64@npm:0.24.2" @@ -1040,6 +1089,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm64@npm:0.27.3" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/android-arm@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/android-arm@npm:0.24.2" @@ -1054,6 +1110,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-arm@npm:0.27.3" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@esbuild/android-x64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/android-x64@npm:0.24.2" @@ -1068,6 +1131,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/android-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/android-x64@npm:0.27.3" + conditions: os=android & cpu=x64 + languageName: node + linkType: hard + "@esbuild/darwin-arm64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/darwin-arm64@npm:0.24.2" @@ -1082,6 +1152,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-arm64@npm:0.27.3" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/darwin-x64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/darwin-x64@npm:0.24.2" @@ -1096,6 +1173,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/darwin-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/darwin-x64@npm:0.27.3" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@esbuild/freebsd-arm64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/freebsd-arm64@npm:0.24.2" @@ -1110,6 +1194,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-arm64@npm:0.27.3" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/freebsd-x64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/freebsd-x64@npm:0.24.2" @@ -1124,6 +1215,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/freebsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/freebsd-x64@npm:0.27.3" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/linux-arm64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/linux-arm64@npm:0.24.2" @@ -1138,6 +1236,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm64@npm:0.27.3" + conditions: os=linux & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/linux-arm@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/linux-arm@npm:0.24.2" @@ -1152,6 +1257,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-arm@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-arm@npm:0.27.3" + conditions: os=linux & cpu=arm + languageName: node + linkType: hard + "@esbuild/linux-ia32@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/linux-ia32@npm:0.24.2" @@ -1166,6 +1278,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ia32@npm:0.27.3" + conditions: os=linux & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/linux-loong64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/linux-loong64@npm:0.24.2" @@ -1180,6 +1299,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-loong64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-loong64@npm:0.27.3" + conditions: os=linux & cpu=loong64 + languageName: node + linkType: hard + "@esbuild/linux-mips64el@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/linux-mips64el@npm:0.24.2" @@ -1194,6 +1320,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-mips64el@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-mips64el@npm:0.27.3" + conditions: os=linux & cpu=mips64el + languageName: node + linkType: hard + "@esbuild/linux-ppc64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/linux-ppc64@npm:0.24.2" @@ -1208,6 +1341,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-ppc64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-ppc64@npm:0.27.3" + conditions: os=linux & cpu=ppc64 + languageName: node + linkType: hard + "@esbuild/linux-riscv64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/linux-riscv64@npm:0.24.2" @@ -1222,6 +1362,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-riscv64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-riscv64@npm:0.27.3" + conditions: os=linux & cpu=riscv64 + languageName: node + linkType: hard + "@esbuild/linux-s390x@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/linux-s390x@npm:0.24.2" @@ -1236,6 +1383,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-s390x@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-s390x@npm:0.27.3" + conditions: os=linux & cpu=s390x + languageName: node + linkType: hard + "@esbuild/linux-x64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/linux-x64@npm:0.24.2" @@ -1250,6 +1404,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/linux-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/linux-x64@npm:0.27.3" + conditions: os=linux & cpu=x64 + languageName: node + linkType: hard + "@esbuild/netbsd-arm64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/netbsd-arm64@npm:0.24.2" @@ -1264,6 +1425,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-arm64@npm:0.27.3" + conditions: os=netbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/netbsd-x64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/netbsd-x64@npm:0.24.2" @@ -1278,6 +1446,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/netbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/netbsd-x64@npm:0.27.3" + conditions: os=netbsd & cpu=x64 + languageName: node + linkType: hard + "@esbuild/openbsd-arm64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/openbsd-arm64@npm:0.24.2" @@ -1292,6 +1467,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-arm64@npm:0.27.3" + conditions: os=openbsd & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/openbsd-x64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/openbsd-x64@npm:0.24.2" @@ -1306,6 +1488,20 @@ __metadata: languageName: node linkType: hard +"@esbuild/openbsd-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openbsd-x64@npm:0.27.3" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@esbuild/openharmony-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/openharmony-arm64@npm:0.27.3" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/sunos-x64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/sunos-x64@npm:0.24.2" @@ -1320,6 +1516,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/sunos-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/sunos-x64@npm:0.27.3" + conditions: os=sunos & cpu=x64 + languageName: node + linkType: hard + "@esbuild/win32-arm64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/win32-arm64@npm:0.24.2" @@ -1334,6 +1537,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-arm64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-arm64@npm:0.27.3" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@esbuild/win32-ia32@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/win32-ia32@npm:0.24.2" @@ -1348,6 +1558,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-ia32@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-ia32@npm:0.27.3" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + "@esbuild/win32-x64@npm:0.24.2": version: 0.24.2 resolution: "@esbuild/win32-x64@npm:0.24.2" @@ -1362,6 +1579,13 @@ __metadata: languageName: node linkType: hard +"@esbuild/win32-x64@npm:0.27.3": + version: 0.27.3 + resolution: "@esbuild/win32-x64@npm:0.27.3" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@eslint-community/eslint-utils@npm:^4.2.0, @eslint-community/eslint-utils@npm:^4.4.0": version: 4.4.1 resolution: "@eslint-community/eslint-utils@npm:4.4.1" @@ -1373,7 +1597,7 @@ __metadata: languageName: node linkType: hard -"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1, @eslint-community/regexpp@npm:^4.4.0, @eslint-community/regexpp@npm:^4.6.1": +"@eslint-community/regexpp@npm:^4.10.0, @eslint-community/regexpp@npm:^4.12.1": version: 4.12.1 resolution: "@eslint-community/regexpp@npm:4.12.1" checksum: 10c0/a03d98c246bcb9109aec2c08e4d10c8d010256538dcb3f56610191607214523d4fb1b00aa81df830b6dffb74c5fa0be03642513a289c567949d3e550ca11cdf6 @@ -1400,23 +1624,6 @@ __metadata: languageName: node linkType: hard -"@eslint/eslintrc@npm:^2.1.4": - version: 2.1.4 - resolution: "@eslint/eslintrc@npm:2.1.4" - dependencies: - ajv: "npm:^6.12.4" - debug: "npm:^4.3.2" - espree: "npm:^9.6.0" - globals: "npm:^13.19.0" - ignore: "npm:^5.2.0" - import-fresh: "npm:^3.2.1" - js-yaml: "npm:^4.1.0" - minimatch: "npm:^3.1.2" - strip-json-comments: "npm:^3.1.1" - checksum: 10c0/32f67052b81768ae876c84569ffd562491ec5a5091b0c1e1ca1e0f3c24fb42f804952fdd0a137873bc64303ba368a71ba079a6f691cee25beee9722d94cc8573 - languageName: node - linkType: hard - "@eslint/eslintrc@npm:^3.3.0": version: 3.3.0 resolution: "@eslint/eslintrc@npm:3.3.0" @@ -1434,13 +1641,6 @@ __metadata: languageName: node linkType: hard -"@eslint/js@npm:8.57.1": - version: 8.57.1 - resolution: "@eslint/js@npm:8.57.1" - checksum: 10c0/b489c474a3b5b54381c62e82b3f7f65f4b8a5eaaed126546520bf2fede5532a8ed53212919fed1e9048dcf7f37167c8561d58d0ba4492a4244004e7793805223 - languageName: node - linkType: hard - "@eslint/js@npm:9.21.0, @eslint/js@npm:^9.17.0": version: 9.21.0 resolution: "@eslint/js@npm:9.21.0" @@ -1999,7 +2199,9 @@ __metadata: resolution: "@goodsdks/react-hooks@workspace:packages/react-hooks" dependencies: "@goodsdks/citizen-sdk": "npm:*" + "@goodsdks/streaming-sdk": "workspace:*" "@repo/typescript-config": "workspace:*" + "@tanstack/react-query": "npm:^4.36.1" "@types/react": "npm:^19" lz-string: "npm:^1.5.0" react: "npm:^19.1.1" @@ -2041,6 +2243,25 @@ __metadata: languageName: unknown linkType: soft +"@goodsdks/streaming-sdk@npm:*, @goodsdks/streaming-sdk@workspace:*, @goodsdks/streaming-sdk@workspace:packages/streaming-sdk": + version: 0.0.0-use.local + resolution: "@goodsdks/streaming-sdk@workspace:packages/streaming-sdk" + dependencies: + "@repo/typescript-config": "workspace:*" + "@sfpro/sdk": "npm:^0.1.0" + "@types/node": "npm:latest" + "@vitest/coverage-v8": "npm:^4.0.18" + graphql: "npm:^16.9.0" + graphql-request: "npm:^7.1.2" + tsup: "npm:^8.3.5" + typescript: "npm:latest" + viem: "npm:latest" + vitest: "npm:^4.0.18" + peerDependencies: + viem: "*" + languageName: unknown + linkType: soft + "@goodsdks/ui-components@npm:*, @goodsdks/ui-components@workspace:packages/ui-components": version: 0.0.0-use.local resolution: "@goodsdks/ui-components@workspace:packages/ui-components" @@ -2057,6 +2278,15 @@ __metadata: languageName: unknown linkType: soft +"@graphql-typed-document-node/core@npm:^3.2.0": + version: 3.2.0 + resolution: "@graphql-typed-document-node/core@npm:3.2.0" + peerDependencies: + graphql: ^0.8.0 || ^0.9.0 || ^0.10.0 || ^0.11.0 || ^0.12.0 || ^0.13.0 || ^14.0.0 || ^15.0.0 || ^16.0.0 || ^17.0.0 + checksum: 10c0/94e9d75c1f178bbae8d874f5a9361708a3350c8def7eaeb6920f2c820e82403b7d4f55b3735856d68e145e86c85cbfe2adc444fdc25519cd51f108697e99346c + languageName: node + linkType: hard + "@hookform/resolvers@npm:^3.10.0": version: 3.10.0 resolution: "@hookform/resolvers@npm:3.10.0" @@ -2083,17 +2313,6 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/config-array@npm:^0.13.0": - version: 0.13.0 - resolution: "@humanwhocodes/config-array@npm:0.13.0" - dependencies: - "@humanwhocodes/object-schema": "npm:^2.0.3" - debug: "npm:^4.3.1" - minimatch: "npm:^3.0.5" - checksum: 10c0/205c99e756b759f92e1f44a3dc6292b37db199beacba8f26c2165d4051fe73a4ae52fdcfd08ffa93e7e5cb63da7c88648f0e84e197d154bbbbe137b2e0dd332e - languageName: node - linkType: hard - "@humanwhocodes/module-importer@npm:^1.0.1": version: 1.0.1 resolution: "@humanwhocodes/module-importer@npm:1.0.1" @@ -2101,13 +2320,6 @@ __metadata: languageName: node linkType: hard -"@humanwhocodes/object-schema@npm:^2.0.3": - version: 2.0.3 - resolution: "@humanwhocodes/object-schema@npm:2.0.3" - checksum: 10c0/80520eabbfc2d32fe195a93557cef50dfe8c8905de447f022675aaf66abc33ae54098f5ea78548d925aa671cd4ab7c7daa5ad704fe42358c9b5e7db60f80696c - languageName: node - linkType: hard - "@humanwhocodes/retry@npm:^0.3.0": version: 0.3.1 resolution: "@humanwhocodes/retry@npm:0.3.1" @@ -2177,6 +2389,13 @@ __metadata: languageName: node linkType: hard +"@jridgewell/sourcemap-codec@npm:^1.5.5": + version: 1.5.5 + resolution: "@jridgewell/sourcemap-codec@npm:1.5.5" + checksum: 10c0/f9e538f302b63c0ebc06eecb1dd9918dd4289ed36147a0ddce35d6ea4d7ebbda243cda7b2213b6a5e1d8087a298d5cf630fb2bd39329cdecb82017023f6081a0 + languageName: node + linkType: hard + "@jridgewell/trace-mapping@npm:0.3.9": version: 0.3.9 resolution: "@jridgewell/trace-mapping@npm:0.3.9" @@ -2197,6 +2416,16 @@ __metadata: languageName: node linkType: hard +"@jridgewell/trace-mapping@npm:^0.3.31": + version: 0.3.31 + resolution: "@jridgewell/trace-mapping@npm:0.3.31" + dependencies: + "@jridgewell/resolve-uri": "npm:^3.1.0" + "@jridgewell/sourcemap-codec": "npm:^1.4.14" + checksum: 10c0/4b30ec8cd56c5fd9a661f088230af01e0c1a3888d11ffb6b47639700f71225be21d1f7e168048d6d4f9449207b978a235c07c8f15c07705685d16dc06280e9d9 + languageName: node + linkType: hard + "@lit-labs/ssr-dom-shim@npm:^1.0.0, @lit-labs/ssr-dom-shim@npm:^1.1.0, @lit-labs/ssr-dom-shim@npm:^1.2.0": version: 1.3.0 resolution: "@lit-labs/ssr-dom-shim@npm:1.3.0" @@ -2694,7 +2923,7 @@ __metadata: languageName: node linkType: hard -"@nodelib/fs.walk@npm:^1.2.3, @nodelib/fs.walk@npm:^1.2.8": +"@nodelib/fs.walk@npm:^1.2.3": version: 1.2.8 resolution: "@nodelib/fs.walk@npm:1.2.8" dependencies: @@ -4178,6 +4407,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm-eabi@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-android-arm-eabi@npm:4.57.1" + conditions: os=android & cpu=arm + languageName: node + linkType: hard + "@rollup/rollup-android-arm64@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-android-arm64@npm:4.34.9" @@ -4192,6 +4428,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-android-arm64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-android-arm64@npm:4.57.1" + conditions: os=android & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-arm64@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-darwin-arm64@npm:4.34.9" @@ -4206,6 +4449,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-arm64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-darwin-arm64@npm:4.57.1" + conditions: os=darwin & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-darwin-x64@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-darwin-x64@npm:4.34.9" @@ -4220,6 +4470,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-darwin-x64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-darwin-x64@npm:4.57.1" + conditions: os=darwin & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-freebsd-arm64@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-freebsd-arm64@npm:4.34.9" @@ -4234,6 +4491,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-arm64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-freebsd-arm64@npm:4.57.1" + conditions: os=freebsd & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-freebsd-x64@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-freebsd-x64@npm:4.34.9" @@ -4248,6 +4512,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-freebsd-x64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-freebsd-x64@npm:4.57.1" + conditions: os=freebsd & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-gnueabihf@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.34.9" @@ -4262,6 +4533,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-gnueabihf@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-arm-gnueabihf@npm:4.57.1" + conditions: os=linux & cpu=arm & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm-musleabihf@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.34.9" @@ -4276,6 +4554,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm-musleabihf@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-arm-musleabihf@npm:4.57.1" + conditions: os=linux & cpu=arm & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-gnu@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.34.9" @@ -4290,6 +4575,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-arm64-gnu@npm:4.57.1" + conditions: os=linux & cpu=arm64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-arm64-musl@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-linux-arm64-musl@npm:4.34.9" @@ -4304,6 +4596,27 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-arm64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-arm64-musl@npm:4.57.1" + conditions: os=linux & cpu=arm64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-loong64-gnu@npm:4.57.1" + conditions: os=linux & cpu=loong64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-loong64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-loong64-musl@npm:4.57.1" + conditions: os=linux & cpu=loong64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-loongarch64-gnu@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-linux-loongarch64-gnu@npm:4.34.9" @@ -4332,6 +4645,20 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-ppc64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-ppc64-gnu@npm:4.57.1" + conditions: os=linux & cpu=ppc64 & libc=glibc + languageName: node + linkType: hard + +"@rollup/rollup-linux-ppc64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-ppc64-musl@npm:4.57.1" + conditions: os=linux & cpu=ppc64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-gnu@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.34.9" @@ -4346,6 +4673,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-riscv64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-riscv64-gnu@npm:4.57.1" + conditions: os=linux & cpu=riscv64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-riscv64-musl@npm:4.44.2": version: 4.44.2 resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.44.2" @@ -4353,6 +4687,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-riscv64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-riscv64-musl@npm:4.57.1" + conditions: os=linux & cpu=riscv64 & libc=musl + languageName: node + linkType: hard + "@rollup/rollup-linux-s390x-gnu@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.34.9" @@ -4367,6 +4708,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-s390x-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-s390x-gnu@npm:4.57.1" + conditions: os=linux & cpu=s390x & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-gnu@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-linux-x64-gnu@npm:4.34.9" @@ -4381,6 +4729,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-x64-gnu@npm:4.57.1" + conditions: os=linux & cpu=x64 & libc=glibc + languageName: node + linkType: hard + "@rollup/rollup-linux-x64-musl@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-linux-x64-musl@npm:4.34.9" @@ -4395,6 +4750,27 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-linux-x64-musl@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-linux-x64-musl@npm:4.57.1" + conditions: os=linux & cpu=x64 & libc=musl + languageName: node + linkType: hard + +"@rollup/rollup-openbsd-x64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-openbsd-x64@npm:4.57.1" + conditions: os=openbsd & cpu=x64 + languageName: node + linkType: hard + +"@rollup/rollup-openharmony-arm64@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-openharmony-arm64@npm:4.57.1" + conditions: os=openharmony & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-win32-arm64-msvc@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.34.9" @@ -4409,6 +4785,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-arm64-msvc@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-win32-arm64-msvc@npm:4.57.1" + conditions: os=win32 & cpu=arm64 + languageName: node + linkType: hard + "@rollup/rollup-win32-ia32-msvc@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.34.9" @@ -4423,6 +4806,20 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-ia32-msvc@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-win32-ia32-msvc@npm:4.57.1" + conditions: os=win32 & cpu=ia32 + languageName: node + linkType: hard + +"@rollup/rollup-win32-x64-gnu@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-win32-x64-gnu@npm:4.57.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@rollup/rollup-win32-x64-msvc@npm:4.34.9": version: 4.34.9 resolution: "@rollup/rollup-win32-x64-msvc@npm:4.34.9" @@ -4437,6 +4834,13 @@ __metadata: languageName: node linkType: hard +"@rollup/rollup-win32-x64-msvc@npm:4.57.1": + version: 4.57.1 + resolution: "@rollup/rollup-win32-x64-msvc@npm:4.57.1" + conditions: os=win32 & cpu=x64 + languageName: node + linkType: hard + "@safe-global/safe-apps-provider@npm:0.18.5": version: 0.18.5 resolution: "@safe-global/safe-apps-provider@npm:0.18.5" @@ -4623,6 +5027,27 @@ __metadata: languageName: node linkType: hard +"@sfpro/sdk@npm:^0.1.0": + version: 0.1.9 + resolution: "@sfpro/sdk@npm:0.1.9" + peerDependencies: + "@wagmi/core": ^2 + react: ">=18" + viem: ^2 + wagmi: ^2 + peerDependenciesMeta: + "@wagmi/core": + optional: true + react: + optional: true + viem: + optional: true + wagmi: + optional: true + checksum: 10c0/b800a428948d18674fb9ca2a1cec013cd56cdfd844f9b859249d7c174d1f4f99a2eafe74f542f4c11122c7c049389f68d5bbdc72278bcbbbb89586379034abe7 + languageName: node + linkType: hard + "@smithy/abort-controller@npm:^4.0.1": version: 4.0.1 resolution: "@smithy/abort-controller@npm:4.0.1" @@ -7170,6 +7595,16 @@ __metadata: languageName: node linkType: hard +"@types/chai@npm:^5.2.2": + version: 5.2.3 + resolution: "@types/chai@npm:5.2.3" + dependencies: + "@types/deep-eql": "npm:*" + assertion-error: "npm:^2.0.1" + checksum: 10c0/e0ef1de3b6f8045a5e473e867c8565788c444271409d155588504840ad1a53611011f85072188c2833941189400228c1745d78323dac13fcede9c2b28bacfb2f + languageName: node + linkType: hard + "@types/cookie@npm:^0.6.0": version: 0.6.0 resolution: "@types/cookie@npm:0.6.0" @@ -7269,7 +7704,7 @@ __metadata: languageName: node linkType: hard -"@types/estree@npm:1.0.8": +"@types/estree@npm:1.0.8, @types/estree@npm:^1.0.0": version: 1.0.8 resolution: "@types/estree@npm:1.0.8" checksum: 10c0/39d34d1afaa338ab9763f37ad6066e3f349444f9052b9676a7cc0252ef9485a41c6d81c9c4e0d26e9077993354edf25efc853f3224dd4b447175ef62bdcc86a5 @@ -7302,7 +7737,7 @@ __metadata: languageName: node linkType: hard -"@types/json-schema@npm:^7.0.15, @types/json-schema@npm:^7.0.9": +"@types/json-schema@npm:^7.0.15": version: 7.0.15 resolution: "@types/json-schema@npm:7.0.15" checksum: 10c0/a996a745e6c5d60292f36731dd41341339d4eeed8180bb09226e5c8d23759067692b1d88e5d91d72ee83dfc00d3aca8e7bd43ea120516c17922cbcb7c3e252db @@ -7362,6 +7797,15 @@ __metadata: languageName: node linkType: hard +"@types/node@npm:latest": + version: 25.2.3 + resolution: "@types/node@npm:25.2.3" + dependencies: + undici-types: "npm:~7.16.0" + checksum: 10c0/925833029ce0bb4a72c36f90b93287184d3511aeb0fa60a994ae94b5430c22f9be6693d67d210df79267cb54c6f6978caaefb149d99ab5f83af5827ba7cb9822 + languageName: node + linkType: hard + "@types/pbkdf2@npm:^3.0.0": version: 3.1.2 resolution: "@types/pbkdf2@npm:3.1.2" @@ -7439,13 +7883,6 @@ __metadata: languageName: node linkType: hard -"@types/semver@npm:^7.3.12": - version: 7.5.8 - resolution: "@types/semver@npm:7.5.8" - checksum: 10c0/8663ff927234d1c5fcc04b33062cb2b9fcfbe0f5f351ed26c4d1e1581657deebd506b41ff7fdf89e787e3d33ce05854bc01686379b89e9c49b564c4cfa988efa - languageName: node - linkType: hard - "@types/trusted-types@npm:^2.0.2": version: 2.0.7 resolution: "@types/trusted-types@npm:2.0.7" @@ -7476,32 +7913,8 @@ __metadata: peerDependencies: "@typescript-eslint/parser": ^8.0.0 || ^8.0.0-alpha.0 eslint: ^8.57.0 || ^9.0.0 - typescript: ">=4.8.4 <5.9.0" - checksum: 10c0/b270467672c5cb7fb9085ae063364252af2910a424899f2a9f54cfbe84aba6ce80dbbf5027f1f33f17cc587da9883de212a4b3dc969f22ded30076889b499dd8 - languageName: node - linkType: hard - -"@typescript-eslint/eslint-plugin@npm:^5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/eslint-plugin@npm:5.62.0" - dependencies: - "@eslint-community/regexpp": "npm:^4.4.0" - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/type-utils": "npm:5.62.0" - "@typescript-eslint/utils": "npm:5.62.0" - debug: "npm:^4.3.4" - graphemer: "npm:^1.4.0" - ignore: "npm:^5.2.0" - natural-compare-lite: "npm:^1.4.0" - semver: "npm:^7.3.7" - tsutils: "npm:^3.21.0" - peerDependencies: - "@typescript-eslint/parser": ^5.0.0 - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/3f40cb6bab5a2833c3544e4621b9fdacd8ea53420cadc1c63fac3b89cdf5c62be1e6b7bcf56976dede5db4c43830de298ced3db60b5494a3b961ca1b4bff9f2a + typescript: ">=4.8.4 <5.9.0" + checksum: 10c0/b270467672c5cb7fb9085ae063364252af2910a424899f2a9f54cfbe84aba6ce80dbbf5027f1f33f17cc587da9883de212a4b3dc969f22ded30076889b499dd8 languageName: node linkType: hard @@ -7521,33 +7934,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/parser@npm:^5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/parser@npm:5.62.0" - dependencies: - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/typescript-estree": "npm:5.62.0" - debug: "npm:^4.3.4" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/315194b3bf39beb9bd16c190956c46beec64b8371e18d6bb72002108b250983eb1e186a01d34b77eb4045f4941acbb243b16155fbb46881105f65e37dc9e24d4 - languageName: node - linkType: hard - -"@typescript-eslint/scope-manager@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/scope-manager@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - checksum: 10c0/861253235576c1c5c1772d23cdce1418c2da2618a479a7de4f6114a12a7ca853011a1e530525d0931c355a8fd237b9cd828fac560f85f9623e24054fd024726f - languageName: node - linkType: hard - "@typescript-eslint/scope-manager@npm:8.26.0": version: 8.26.0 resolution: "@typescript-eslint/scope-manager@npm:8.26.0" @@ -7558,23 +7944,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/type-utils@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/type-utils@npm:5.62.0" - dependencies: - "@typescript-eslint/typescript-estree": "npm:5.62.0" - "@typescript-eslint/utils": "npm:5.62.0" - debug: "npm:^4.3.4" - tsutils: "npm:^3.21.0" - peerDependencies: - eslint: "*" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/93112e34026069a48f0484b98caca1c89d9707842afe14e08e7390af51cdde87378df29d213d3bbd10a7cfe6f91b228031b56218515ce077bdb62ddea9d9f474 - languageName: node - linkType: hard - "@typescript-eslint/type-utils@npm:8.26.0": version: 8.26.0 resolution: "@typescript-eslint/type-utils@npm:8.26.0" @@ -7590,13 +7959,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/types@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/types@npm:5.62.0" - checksum: 10c0/7febd3a7f0701c0b927e094f02e82d8ee2cada2b186fcb938bc2b94ff6fbad88237afc304cbaf33e82797078bbbb1baf91475f6400912f8b64c89be79bfa4ddf - languageName: node - linkType: hard - "@typescript-eslint/types@npm:8.26.0": version: 8.26.0 resolution: "@typescript-eslint/types@npm:8.26.0" @@ -7604,24 +7966,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/typescript-estree@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/typescript-estree@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/visitor-keys": "npm:5.62.0" - debug: "npm:^4.3.4" - globby: "npm:^11.1.0" - is-glob: "npm:^4.0.3" - semver: "npm:^7.3.7" - tsutils: "npm:^3.21.0" - peerDependenciesMeta: - typescript: - optional: true - checksum: 10c0/d7984a3e9d56897b2481940ec803cb8e7ead03df8d9cfd9797350be82ff765dfcf3cfec04e7355e1779e948da8f02bc5e11719d07a596eb1cb995c48a95e38cf - languageName: node - linkType: hard - "@typescript-eslint/typescript-estree@npm:8.26.0": version: 8.26.0 resolution: "@typescript-eslint/typescript-estree@npm:8.26.0" @@ -7640,24 +7984,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/utils@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/utils@npm:5.62.0" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@types/json-schema": "npm:^7.0.9" - "@types/semver": "npm:^7.3.12" - "@typescript-eslint/scope-manager": "npm:5.62.0" - "@typescript-eslint/types": "npm:5.62.0" - "@typescript-eslint/typescript-estree": "npm:5.62.0" - eslint-scope: "npm:^5.1.1" - semver: "npm:^7.3.7" - peerDependencies: - eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 - checksum: 10c0/f09b7d9952e4a205eb1ced31d7684dd55cee40bf8c2d78e923aa8a255318d97279825733902742c09d8690f37a50243f4c4d383ab16bd7aefaf9c4b438f785e1 - languageName: node - linkType: hard - "@typescript-eslint/utils@npm:8.26.0": version: 8.26.0 resolution: "@typescript-eslint/utils@npm:8.26.0" @@ -7673,16 +7999,6 @@ __metadata: languageName: node linkType: hard -"@typescript-eslint/visitor-keys@npm:5.62.0": - version: 5.62.0 - resolution: "@typescript-eslint/visitor-keys@npm:5.62.0" - dependencies: - "@typescript-eslint/types": "npm:5.62.0" - eslint-visitor-keys: "npm:^3.3.0" - checksum: 10c0/7c3b8e4148e9b94d9b7162a596a1260d7a3efc4e65199693b8025c71c4652b8042501c0bc9f57654c1e2943c26da98c0f77884a746c6ae81389fcb0b513d995d - languageName: node - linkType: hard - "@typescript-eslint/visitor-keys@npm:8.26.0": version: 8.26.0 resolution: "@typescript-eslint/visitor-keys@npm:8.26.0" @@ -7693,13 +8009,6 @@ __metadata: languageName: node linkType: hard -"@ungap/structured-clone@npm:^1.2.0": - version: 1.3.0 - resolution: "@ungap/structured-clone@npm:1.3.0" - checksum: 10c0/0fc3097c2540ada1fc340ee56d58d96b5b536a2a0dab6e3ec17d4bfc8c4c86db345f61a375a8185f9da96f01c69678f836a2b57eeaa9e4b8eeafd26428e57b0a - languageName: node - linkType: hard - "@vitejs/plugin-react@npm:^4.3.4": version: 4.3.4 resolution: "@vitejs/plugin-react@npm:4.3.4" @@ -7715,6 +8024,110 @@ __metadata: languageName: node linkType: hard +"@vitest/coverage-v8@npm:^4.0.18": + version: 4.0.18 + resolution: "@vitest/coverage-v8@npm:4.0.18" + dependencies: + "@bcoe/v8-coverage": "npm:^1.0.2" + "@vitest/utils": "npm:4.0.18" + ast-v8-to-istanbul: "npm:^0.3.10" + istanbul-lib-coverage: "npm:^3.2.2" + istanbul-lib-report: "npm:^3.0.1" + istanbul-reports: "npm:^3.2.0" + magicast: "npm:^0.5.1" + obug: "npm:^2.1.1" + std-env: "npm:^3.10.0" + tinyrainbow: "npm:^3.0.3" + peerDependencies: + "@vitest/browser": 4.0.18 + vitest: 4.0.18 + peerDependenciesMeta: + "@vitest/browser": + optional: true + checksum: 10c0/e23e0da86f0b2a020c51562bc40ebdc7fc7553c24f8071dfb39a6df0161badbd5eaf2eebbf8ceaef18933a18c1934ff52d1c0c4bde77bb87e0c1feb0c8cbee4d + languageName: node + linkType: hard + +"@vitest/expect@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/expect@npm:4.0.18" + dependencies: + "@standard-schema/spec": "npm:^1.0.0" + "@types/chai": "npm:^5.2.2" + "@vitest/spy": "npm:4.0.18" + "@vitest/utils": "npm:4.0.18" + chai: "npm:^6.2.1" + tinyrainbow: "npm:^3.0.3" + checksum: 10c0/123b0aa111682e82ec5289186df18037b1a1768700e468ee0f9879709aaa320cf790463c15c0d8ee10df92b402f4394baf5d27797e604d78e674766d87bcaadc + languageName: node + linkType: hard + +"@vitest/mocker@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/mocker@npm:4.0.18" + dependencies: + "@vitest/spy": "npm:4.0.18" + estree-walker: "npm:^3.0.3" + magic-string: "npm:^0.30.21" + peerDependencies: + msw: ^2.4.9 + vite: ^6.0.0 || ^7.0.0-0 + peerDependenciesMeta: + msw: + optional: true + vite: + optional: true + checksum: 10c0/fb0a257e7e167759d4ad228d53fa7bad2267586459c4a62188f2043dd7163b4b02e1e496dc3c227837f776e7d73d6c4343613e89e7da379d9d30de8260f1ee4b + languageName: node + linkType: hard + +"@vitest/pretty-format@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/pretty-format@npm:4.0.18" + dependencies: + tinyrainbow: "npm:^3.0.3" + checksum: 10c0/0086b8c88eeca896d8e4b98fcdef452c8041a1b63eb9e85d3e0bcc96c8aa76d8e9e0b6990ebb0bb0a697c4ebab347e7735888b24f507dbff2742ddce7723fd94 + languageName: node + linkType: hard + +"@vitest/runner@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/runner@npm:4.0.18" + dependencies: + "@vitest/utils": "npm:4.0.18" + pathe: "npm:^2.0.3" + checksum: 10c0/fdb4afa411475133c05ba266c8092eaf1e56cbd5fb601f92ec6ccb9bab7ca52e06733ee8626599355cba4ee71cb3a8f28c84d3b69dc972e41047edc50229bc01 + languageName: node + linkType: hard + +"@vitest/snapshot@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/snapshot@npm:4.0.18" + dependencies: + "@vitest/pretty-format": "npm:4.0.18" + magic-string: "npm:^0.30.21" + pathe: "npm:^2.0.3" + checksum: 10c0/d3bfefa558db9a69a66886ace6575eb96903a5ba59f4d9a5d0fecb4acc2bb8dbb443ef409f5ac1475f2e1add30bd1d71280f98912da35e89c75829df9e84ea43 + languageName: node + linkType: hard + +"@vitest/spy@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/spy@npm:4.0.18" + checksum: 10c0/6de537890b3994fcadb8e8d8ac05942320ae184f071ec395d978a5fba7fa928cbb0c5de85af86a1c165706c466e840de8779eaff8c93450c511c7abaeb9b8a4e + languageName: node + linkType: hard + +"@vitest/utils@npm:4.0.18": + version: 4.0.18 + resolution: "@vitest/utils@npm:4.0.18" + dependencies: + "@vitest/pretty-format": "npm:4.0.18" + tinyrainbow: "npm:^3.0.3" + checksum: 10c0/4a3c43c1421eb90f38576926496f6c80056167ba111e63f77cf118983902673737a1a38880b890d7c06ec0a12475024587344ee502b3c43093781533022f2aeb + languageName: node + linkType: hard + "@wagmi/connectors@npm:5.7.7": version: 5.7.7 resolution: "@wagmi/connectors@npm:5.7.7" @@ -8415,7 +8828,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.4.1, acorn@npm:^8.9.0": +"acorn@npm:^8.11.0, acorn@npm:^8.14.0, acorn@npm:^8.4.1": version: 8.14.0 resolution: "acorn@npm:8.14.0" bin: @@ -8753,6 +9166,24 @@ __metadata: languageName: node linkType: hard +"assertion-error@npm:^2.0.1": + version: 2.0.1 + resolution: "assertion-error@npm:2.0.1" + checksum: 10c0/bbbcb117ac6480138f8c93cf7f535614282dea9dc828f540cdece85e3c665e8f78958b96afac52f29ff883c72638e6a87d469ecc9fe5bc902df03ed24a55dba8 + languageName: node + linkType: hard + +"ast-v8-to-istanbul@npm:^0.3.10": + version: 0.3.11 + resolution: "ast-v8-to-istanbul@npm:0.3.11" + dependencies: + "@jridgewell/trace-mapping": "npm:^0.3.31" + estree-walker: "npm:^3.0.3" + js-tokens: "npm:^10.0.0" + checksum: 10c0/0667dcb5f42bd16f5d50b8687f3471f9b9d000ea7f8808c3cd0ddabc1ef7d5b1a61e19f498d5ca7b1285e6c185e11d0ae724c4f9291491b50b6340110ce63108 + languageName: node + linkType: hard + "astral-regex@npm:^2.0.0": version: 2.0.0 resolution: "astral-regex@npm:2.0.0" @@ -9276,6 +9707,13 @@ __metadata: languageName: node linkType: hard +"chai@npm:^6.2.1": + version: 6.2.2 + resolution: "chai@npm:6.2.2" + checksum: 10c0/e6c69e5f0c11dffe6ea13d0290936ebb68fcc1ad688b8e952e131df6a6d5797d5e860bc55cef1aca2e950c3e1f96daf79e9d5a70fb7dbaab4e46355e2635ed53 + languageName: node + linkType: hard + "chalk@npm:4.1.2, chalk@npm:^4.0.0, chalk@npm:^4.1.0": version: 4.1.2 resolution: "chalk@npm:4.1.2" @@ -9710,7 +10148,7 @@ __metadata: languageName: node linkType: hard -"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.2, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": +"cross-spawn@npm:^7.0.0, cross-spawn@npm:^7.0.1, cross-spawn@npm:^7.0.3, cross-spawn@npm:^7.0.6": version: 7.0.6 resolution: "cross-spawn@npm:7.0.6" dependencies: @@ -10033,8 +10471,10 @@ __metadata: version: 0.0.0-use.local resolution: "demo-identity-app@workspace:apps/demo-identity-app" dependencies: + "@eslint/js": "npm:^9.17.0" "@goodsdks/citizen-sdk": "npm:*" "@goodsdks/react-hooks": "npm:*" + "@goodsdks/streaming-sdk": "npm:*" "@reown/appkit": "npm:^1.7.2" "@reown/appkit-adapter-wagmi": "npm:^1.7.2" "@reown/appkit-wallet": "npm:^1.7.2" @@ -10047,16 +10487,14 @@ __metadata: "@tanstack/react-query": "npm:^4.36.1" "@types/react": "npm:^18.3.18" "@types/react-dom": "npm:^18.3.5" - "@typescript-eslint/eslint-plugin": "npm:^5.62.0" - "@typescript-eslint/parser": "npm:^5.62.0" "@vitejs/plugin-react": "npm:^4.3.4" autoprefixer: "npm:^10.4.20" clsx: "npm:^1.2.1" cross-env: "npm:^7.0.3" - eslint: "npm:^8.57.1" - eslint-config-prettier: "npm:^8.10.0" - eslint-plugin-react: "npm:^7.37.4" - eslint-plugin-react-hooks: "npm:^5.2.0" + eslint: "npm:^9.17.0" + eslint-plugin-react-hooks: "npm:^5.0.0" + eslint-plugin-react-refresh: "npm:^0.4.16" + globals: "npm:^15.14.0" lz-string: "npm:^1.5.0" moment: "npm:^2.30.1" postcss: "npm:^8.5.3" @@ -10067,6 +10505,7 @@ __metadata: sass: "npm:^1.85.1" tamagui: "npm:^1.125.22" typescript: "npm:^5.8.2" + typescript-eslint: "npm:^8.18.2" viem: "npm:^1.21.4" vite: "npm:6.3.5" wagmi: "npm:^1.4.13" @@ -10182,15 +10621,6 @@ __metadata: languageName: node linkType: hard -"doctrine@npm:^3.0.0": - version: 3.0.0 - resolution: "doctrine@npm:3.0.0" - dependencies: - esutils: "npm:^2.0.2" - checksum: 10c0/c96bdccabe9d62ab6fea9399fdff04a66e6563c1d6fb3a3a063e8d53c3bb136ba63e84250bbf63d00086a769ad53aef92d2bd483f03f837fc97b71cbee6b2520 - languageName: node - linkType: hard - "dotenv@npm:16.0.3": version: 16.0.3 resolution: "dotenv@npm:16.0.3" @@ -10477,6 +10907,13 @@ __metadata: languageName: node linkType: hard +"es-module-lexer@npm:^1.7.0": + version: 1.7.0 + resolution: "es-module-lexer@npm:1.7.0" + checksum: 10c0/4c935affcbfeba7fb4533e1da10fa8568043df1e3574b869385980de9e2d475ddc36769891936dbb07036edb3c3786a8b78ccf44964cd130dedc1f2c984b6c7b + languageName: node + linkType: hard + "es-object-atoms@npm:^1.0.0": version: 1.0.1 resolution: "es-object-atoms@npm:1.0.1" @@ -10738,6 +11175,95 @@ __metadata: languageName: node linkType: hard +"esbuild@npm:^0.27.0": + version: 0.27.3 + resolution: "esbuild@npm:0.27.3" + dependencies: + "@esbuild/aix-ppc64": "npm:0.27.3" + "@esbuild/android-arm": "npm:0.27.3" + "@esbuild/android-arm64": "npm:0.27.3" + "@esbuild/android-x64": "npm:0.27.3" + "@esbuild/darwin-arm64": "npm:0.27.3" + "@esbuild/darwin-x64": "npm:0.27.3" + "@esbuild/freebsd-arm64": "npm:0.27.3" + "@esbuild/freebsd-x64": "npm:0.27.3" + "@esbuild/linux-arm": "npm:0.27.3" + "@esbuild/linux-arm64": "npm:0.27.3" + "@esbuild/linux-ia32": "npm:0.27.3" + "@esbuild/linux-loong64": "npm:0.27.3" + "@esbuild/linux-mips64el": "npm:0.27.3" + "@esbuild/linux-ppc64": "npm:0.27.3" + "@esbuild/linux-riscv64": "npm:0.27.3" + "@esbuild/linux-s390x": "npm:0.27.3" + "@esbuild/linux-x64": "npm:0.27.3" + "@esbuild/netbsd-arm64": "npm:0.27.3" + "@esbuild/netbsd-x64": "npm:0.27.3" + "@esbuild/openbsd-arm64": "npm:0.27.3" + "@esbuild/openbsd-x64": "npm:0.27.3" + "@esbuild/openharmony-arm64": "npm:0.27.3" + "@esbuild/sunos-x64": "npm:0.27.3" + "@esbuild/win32-arm64": "npm:0.27.3" + "@esbuild/win32-ia32": "npm:0.27.3" + "@esbuild/win32-x64": "npm:0.27.3" + dependenciesMeta: + "@esbuild/aix-ppc64": + optional: true + "@esbuild/android-arm": + optional: true + "@esbuild/android-arm64": + optional: true + "@esbuild/android-x64": + optional: true + "@esbuild/darwin-arm64": + optional: true + "@esbuild/darwin-x64": + optional: true + "@esbuild/freebsd-arm64": + optional: true + "@esbuild/freebsd-x64": + optional: true + "@esbuild/linux-arm": + optional: true + "@esbuild/linux-arm64": + optional: true + "@esbuild/linux-ia32": + optional: true + "@esbuild/linux-loong64": + optional: true + "@esbuild/linux-mips64el": + optional: true + "@esbuild/linux-ppc64": + optional: true + "@esbuild/linux-riscv64": + optional: true + "@esbuild/linux-s390x": + optional: true + "@esbuild/linux-x64": + optional: true + "@esbuild/netbsd-arm64": + optional: true + "@esbuild/netbsd-x64": + optional: true + "@esbuild/openbsd-arm64": + optional: true + "@esbuild/openbsd-x64": + optional: true + "@esbuild/openharmony-arm64": + optional: true + "@esbuild/sunos-x64": + optional: true + "@esbuild/win32-arm64": + optional: true + "@esbuild/win32-ia32": + optional: true + "@esbuild/win32-x64": + optional: true + bin: + esbuild: bin/esbuild + checksum: 10c0/fdc3f87a3f08b3ef98362f37377136c389a0d180fda4b8d073b26ba930cf245521db0a368f119cc7624bc619248fff1439f5811f062d853576f8ffa3df8ee5f1 + languageName: node + linkType: hard + "escalade@npm:^3.1.1, escalade@npm:^3.2.0": version: 3.2.0 resolution: "escalade@npm:3.2.0" @@ -10778,17 +11304,6 @@ __metadata: languageName: node linkType: hard -"eslint-config-prettier@npm:^8.10.0": - version: 8.10.0 - resolution: "eslint-config-prettier@npm:8.10.0" - peerDependencies: - eslint: ">=7.0.0" - bin: - eslint-config-prettier: bin/cli.js - checksum: 10c0/19f8c497d9bdc111a17a61b25ded97217be3755bbc4714477dfe535ed539dddcaf42ef5cf8bb97908b058260cf89a3d7c565cb0be31096cbcd39f4c2fa5fe43c - languageName: node - linkType: hard - "eslint-config-prettier@npm:^9.1.0": version: 9.1.0 resolution: "eslint-config-prettier@npm:9.1.0" @@ -10807,7 +11322,7 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-react-hooks@npm:^5.0.0, eslint-plugin-react-hooks@npm:^5.2.0": +"eslint-plugin-react-hooks@npm:^5.0.0": version: 5.2.0 resolution: "eslint-plugin-react-hooks@npm:5.2.0" peerDependencies: @@ -10825,7 +11340,7 @@ __metadata: languageName: node linkType: hard -"eslint-plugin-react@npm:^7.37.2, eslint-plugin-react@npm:^7.37.4": +"eslint-plugin-react@npm:^7.37.2": version: 7.37.4 resolution: "eslint-plugin-react@npm:7.37.4" dependencies: @@ -10865,26 +11380,6 @@ __metadata: languageName: node linkType: hard -"eslint-scope@npm:^5.1.1": - version: 5.1.1 - resolution: "eslint-scope@npm:5.1.1" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^4.1.1" - checksum: 10c0/d30ef9dc1c1cbdece34db1539a4933fe3f9b14e1ffb27ecc85987902ee663ad7c9473bbd49a9a03195a373741e62e2f807c4938992e019b511993d163450e70a - languageName: node - linkType: hard - -"eslint-scope@npm:^7.2.2": - version: 7.2.2 - resolution: "eslint-scope@npm:7.2.2" - dependencies: - esrecurse: "npm:^4.3.0" - estraverse: "npm:^5.2.0" - checksum: 10c0/613c267aea34b5a6d6c00514e8545ef1f1433108097e857225fed40d397dd6b1809dffd11c2fde23b37ca53d7bf935fe04d2a18e6fc932b31837b6ad67e1c116 - languageName: node - linkType: hard - "eslint-scope@npm:^8.2.0": version: 8.2.0 resolution: "eslint-scope@npm:8.2.0" @@ -10895,7 +11390,7 @@ __metadata: languageName: node linkType: hard -"eslint-visitor-keys@npm:^3.3.0, eslint-visitor-keys@npm:^3.4.1, eslint-visitor-keys@npm:^3.4.3": +"eslint-visitor-keys@npm:^3.4.3": version: 3.4.3 resolution: "eslint-visitor-keys@npm:3.4.3" checksum: 10c0/92708e882c0a5ffd88c23c0b404ac1628cf20104a108c745f240a13c332a11aac54f49a22d5762efbffc18ecbc9a580d1b7ad034bf5f3cc3307e5cbff2ec9820 @@ -10909,54 +11404,6 @@ __metadata: languageName: node linkType: hard -"eslint@npm:^8.57.1": - version: 8.57.1 - resolution: "eslint@npm:8.57.1" - dependencies: - "@eslint-community/eslint-utils": "npm:^4.2.0" - "@eslint-community/regexpp": "npm:^4.6.1" - "@eslint/eslintrc": "npm:^2.1.4" - "@eslint/js": "npm:8.57.1" - "@humanwhocodes/config-array": "npm:^0.13.0" - "@humanwhocodes/module-importer": "npm:^1.0.1" - "@nodelib/fs.walk": "npm:^1.2.8" - "@ungap/structured-clone": "npm:^1.2.0" - ajv: "npm:^6.12.4" - chalk: "npm:^4.0.0" - cross-spawn: "npm:^7.0.2" - debug: "npm:^4.3.2" - doctrine: "npm:^3.0.0" - escape-string-regexp: "npm:^4.0.0" - eslint-scope: "npm:^7.2.2" - eslint-visitor-keys: "npm:^3.4.3" - espree: "npm:^9.6.1" - esquery: "npm:^1.4.2" - esutils: "npm:^2.0.2" - fast-deep-equal: "npm:^3.1.3" - file-entry-cache: "npm:^6.0.1" - find-up: "npm:^5.0.0" - glob-parent: "npm:^6.0.2" - globals: "npm:^13.19.0" - graphemer: "npm:^1.4.0" - ignore: "npm:^5.2.0" - imurmurhash: "npm:^0.1.4" - is-glob: "npm:^4.0.0" - is-path-inside: "npm:^3.0.3" - js-yaml: "npm:^4.1.0" - json-stable-stringify-without-jsonify: "npm:^1.0.1" - levn: "npm:^0.4.1" - lodash.merge: "npm:^4.6.2" - minimatch: "npm:^3.1.2" - natural-compare: "npm:^1.4.0" - optionator: "npm:^0.9.3" - strip-ansi: "npm:^6.0.1" - text-table: "npm:^0.2.0" - bin: - eslint: bin/eslint.js - checksum: 10c0/1fd31533086c1b72f86770a4d9d7058ee8b4643fd1cfd10c7aac1ecb8725698e88352a87805cf4b2ce890aa35947df4b4da9655fb7fdfa60dbb448a43f6ebcf1 - languageName: node - linkType: hard - "eslint@npm:^9.15.0, eslint@npm:^9.17.0": version: 9.21.0 resolution: "eslint@npm:9.21.0" @@ -11024,17 +11471,6 @@ __metadata: languageName: node linkType: hard -"espree@npm:^9.6.0, espree@npm:^9.6.1": - version: 9.6.1 - resolution: "espree@npm:9.6.1" - dependencies: - acorn: "npm:^8.9.0" - acorn-jsx: "npm:^5.3.2" - eslint-visitor-keys: "npm:^3.4.1" - checksum: 10c0/1a2e9b4699b715347f62330bcc76aee224390c28bb02b31a3752e9d07549c473f5f986720483c6469cf3cfb3c9d05df612ffc69eb1ee94b54b739e67de9bb460 - languageName: node - linkType: hard - "esprima@npm:2.7.x, esprima@npm:^2.7.1": version: 2.7.3 resolution: "esprima@npm:2.7.3" @@ -11055,7 +11491,7 @@ __metadata: languageName: node linkType: hard -"esquery@npm:^1.4.2, esquery@npm:^1.5.0": +"esquery@npm:^1.5.0": version: 1.6.0 resolution: "esquery@npm:1.6.0" dependencies: @@ -11080,13 +11516,6 @@ __metadata: languageName: node linkType: hard -"estraverse@npm:^4.1.1": - version: 4.3.0 - resolution: "estraverse@npm:4.3.0" - checksum: 10c0/9cb46463ef8a8a4905d3708a652d60122a0c20bb58dec7e0e12ab0e7235123d74214fc0141d743c381813e1b992767e2708194f6f6e0f9fd00c1b4e0887b8b6d - languageName: node - linkType: hard - "estraverse@npm:^5.1.0, estraverse@npm:^5.2.0, estraverse@npm:^5.3.0": version: 5.3.0 resolution: "estraverse@npm:5.3.0" @@ -11094,6 +11523,15 @@ __metadata: languageName: node linkType: hard +"estree-walker@npm:^3.0.3": + version: 3.0.3 + resolution: "estree-walker@npm:3.0.3" + dependencies: + "@types/estree": "npm:^1.0.0" + checksum: 10c0/c12e3c2b2642d2bcae7d5aa495c60fa2f299160946535763969a1c83fc74518ffa9c2cd3a8b69ac56aea547df6a8aac25f729a342992ef0bbac5f1c73e78995d + languageName: node + linkType: hard + "esutils@npm:^2.0.2": version: 2.0.3 resolution: "esutils@npm:2.0.3" @@ -11331,6 +11769,13 @@ __metadata: languageName: node linkType: hard +"expect-type@npm:^1.2.2": + version: 1.3.0 + resolution: "expect-type@npm:1.3.0" + checksum: 10c0/8412b3fe4f392c420ab41dae220b09700e4e47c639a29ba7ba2e83cc6cffd2b4926f7ac9e47d7e277e8f4f02acda76fd6931cb81fd2b382fa9477ef9ada953fd + languageName: node + linkType: hard + "exponential-backoff@npm:^3.1.1": version: 3.1.1 resolution: "exponential-backoff@npm:3.1.1" @@ -11375,7 +11820,7 @@ __metadata: languageName: node linkType: hard -"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.2.9, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2": +"fast-glob@npm:^3.0.3, fast-glob@npm:^3.2.11, fast-glob@npm:^3.3.0, fast-glob@npm:^3.3.2": version: 3.3.3 resolution: "fast-glob@npm:3.3.3" dependencies: @@ -11508,12 +11953,15 @@ __metadata: languageName: node linkType: hard -"file-entry-cache@npm:^6.0.1": - version: 6.0.1 - resolution: "file-entry-cache@npm:6.0.1" - dependencies: - flat-cache: "npm:^3.0.4" - checksum: 10c0/58473e8a82794d01b38e5e435f6feaf648e3f36fdb3a56e98f417f4efae71ad1c0d4ebd8a9a7c50c3ad085820a93fc7494ad721e0e4ebc1da3573f4e1c3c7cdd +"fdir@npm:^6.5.0": + version: 6.5.0 + resolution: "fdir@npm:6.5.0" + peerDependencies: + picomatch: ^3 || ^4 + peerDependenciesMeta: + picomatch: + optional: true + checksum: 10c0/e345083c4306b3aed6cb8ec551e26c36bab5c511e99ea4576a16750ddc8d3240e63826cc624f5ae17ad4dc82e68a253213b60d556c11bfad064b7607847ed07f languageName: node linkType: hard @@ -11590,20 +12038,9 @@ __metadata: linkType: hard "find-value@npm:^1.0.12": - version: 1.0.13 - resolution: "find-value@npm:1.0.13" - checksum: 10c0/54846d5ed6925d1aaba8f95b82537e0aad23e48ca779a0bd4e11a1de7fd55c09bf9c5efcaa9037a7d7d865e969409143bf6ad248369b7149c7849ea2d9d62d15 - languageName: node - linkType: hard - -"flat-cache@npm:^3.0.4": - version: 3.2.0 - resolution: "flat-cache@npm:3.2.0" - dependencies: - flatted: "npm:^3.2.9" - keyv: "npm:^4.5.3" - rimraf: "npm:^3.0.2" - checksum: 10c0/b76f611bd5f5d68f7ae632e3ae503e678d205cf97a17c6ab5b12f6ca61188b5f1f7464503efae6dc18683ed8f0b41460beb48ac4b9ac63fe6201296a91ba2f75 + version: 1.0.13 + resolution: "find-value@npm:1.0.13" + checksum: 10c0/54846d5ed6925d1aaba8f95b82537e0aad23e48ca779a0bd4e11a1de7fd55c09bf9c5efcaa9037a7d7d865e969409143bf6ad248369b7149c7849ea2d9d62d15 languageName: node linkType: hard @@ -12044,15 +12481,6 @@ __metadata: languageName: node linkType: hard -"globals@npm:^13.19.0": - version: 13.24.0 - resolution: "globals@npm:13.24.0" - dependencies: - type-fest: "npm:^0.20.2" - checksum: 10c0/d3c11aeea898eb83d5ec7a99508600fbe8f83d2cf00cbb77f873dbf2bcb39428eff1b538e4915c993d8a3b3473fa71eeebfe22c9bb3a3003d1e26b1f2c8a42cd - languageName: node - linkType: hard - "globals@npm:^14.0.0": version: 14.0.0 resolution: "globals@npm:14.0.0" @@ -12093,20 +12521,6 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.1.0": - version: 11.1.0 - resolution: "globby@npm:11.1.0" - dependencies: - array-union: "npm:^2.1.0" - dir-glob: "npm:^3.0.1" - fast-glob: "npm:^3.2.9" - ignore: "npm:^5.2.0" - merge2: "npm:^1.4.1" - slash: "npm:^3.0.0" - checksum: 10c0/b39511b4afe4bd8a7aead3a27c4ade2b9968649abab0a6c28b1a90141b96ca68ca5db1302f7c7bd29eab66bf51e13916b8e0a3d0ac08f75e1e84a39b35691189 - languageName: node - linkType: hard - "globby@npm:^13.1.4": version: 13.2.2 resolution: "globby@npm:13.2.2" @@ -12141,6 +12555,24 @@ __metadata: languageName: node linkType: hard +"graphql-request@npm:^7.1.2": + version: 7.4.0 + resolution: "graphql-request@npm:7.4.0" + dependencies: + "@graphql-typed-document-node/core": "npm:^3.2.0" + peerDependencies: + graphql: 14 - 16 + checksum: 10c0/066531d70b9c4656251e51c950ce9bee3df05291e12e2b983608d75ac3a70d700efd6488d273b043235dc125658973db38b21eba59e808863831a5a19a6a7b41 + languageName: node + linkType: hard + +"graphql@npm:^16.9.0": + version: 16.12.0 + resolution: "graphql@npm:16.12.0" + checksum: 10c0/b6fffa4e8a4e4a9933ebe85e7470b346dbf49050c1a482fac5e03e4a1a7bed2ecd3a4c97e29f04457af929464bc5e4f2aac991090c2f320111eef26e902a5c75 + languageName: node + linkType: hard + "h3@npm:^1.13.0": version: 1.13.1 resolution: "h3@npm:1.13.1" @@ -12390,6 +12822,13 @@ __metadata: languageName: node linkType: hard +"html-escaper@npm:^2.0.0": + version: 2.0.2 + resolution: "html-escaper@npm:2.0.2" + checksum: 10c0/208e8a12de1a6569edbb14544f4567e6ce8ecc30b9394fcaa4e7bb1e60c12a7c9a1ed27e31290817157e8626f3a4f29e76c8747030822eb84a6abb15c255f0a0 + languageName: node + linkType: hard + "http-cache-semantics@npm:^4.1.1": version: 4.1.1 resolution: "http-cache-semantics@npm:4.1.1" @@ -12819,13 +13258,6 @@ __metadata: languageName: node linkType: hard -"is-path-inside@npm:^3.0.3": - version: 3.0.3 - resolution: "is-path-inside@npm:3.0.3" - checksum: 10c0/cf7d4ac35fb96bab6a1d2c3598fe5ebb29aafb52c0aaa482b5a3ed9d8ba3edc11631e3ec2637660c44b3ce0e61a08d54946e8af30dec0b60a7c27296c68ffd05 - languageName: node - linkType: hard - "is-plain-obj@npm:^2.1.0": version: 2.1.0 resolution: "is-plain-obj@npm:2.1.0" @@ -13001,6 +13433,34 @@ __metadata: languageName: node linkType: hard +"istanbul-lib-coverage@npm:^3.0.0, istanbul-lib-coverage@npm:^3.2.2": + version: 3.2.2 + resolution: "istanbul-lib-coverage@npm:3.2.2" + checksum: 10c0/6c7ff2106769e5f592ded1fb418f9f73b4411fd5a084387a5410538332b6567cd1763ff6b6cadca9b9eb2c443cce2f7ea7d7f1b8d315f9ce58539793b1e0922b + languageName: node + linkType: hard + +"istanbul-lib-report@npm:^3.0.0, istanbul-lib-report@npm:^3.0.1": + version: 3.0.1 + resolution: "istanbul-lib-report@npm:3.0.1" + dependencies: + istanbul-lib-coverage: "npm:^3.0.0" + make-dir: "npm:^4.0.0" + supports-color: "npm:^7.1.0" + checksum: 10c0/84323afb14392de8b6a5714bd7e9af845cfbd56cfe71ed276cda2f5f1201aea673c7111901227ee33e68e4364e288d73861eb2ed48f6679d1e69a43b6d9b3ba7 + languageName: node + linkType: hard + +"istanbul-reports@npm:^3.2.0": + version: 3.2.0 + resolution: "istanbul-reports@npm:3.2.0" + dependencies: + html-escaper: "npm:^2.0.0" + istanbul-lib-report: "npm:^3.0.0" + checksum: 10c0/d596317cfd9c22e1394f22a8d8ba0303d2074fe2e971887b32d870e4b33f8464b10f8ccbe6847808f7db485f084eba09e6c2ed706b3a978e4b52f07085b8f9bc + languageName: node + linkType: hard + "iterate-object@npm:^1.3.4": version: 1.3.5 resolution: "iterate-object@npm:1.3.5" @@ -13065,6 +13525,13 @@ __metadata: languageName: node linkType: hard +"js-tokens@npm:^10.0.0": + version: 10.0.0 + resolution: "js-tokens@npm:10.0.0" + checksum: 10c0/a93498747812ba3e0c8626f95f75ab29319f2a13613a0de9e610700405760931624433a0de59eb7c27ff8836e526768fb20783861b86ef89be96676f2c996b64 + languageName: node + linkType: hard + "js-tokens@npm:^3.0.0 || ^4.0.0, js-tokens@npm:^4.0.0": version: 4.0.0 resolution: "js-tokens@npm:4.0.0" @@ -13235,7 +13702,7 @@ __metadata: languageName: node linkType: hard -"keyv@npm:^4.5.3, keyv@npm:^4.5.4": +"keyv@npm:^4.5.4": version: 4.5.4 resolution: "keyv@npm:4.5.4" dependencies: @@ -13555,6 +14022,26 @@ __metadata: languageName: node linkType: hard +"magic-string@npm:^0.30.21": + version: 0.30.21 + resolution: "magic-string@npm:0.30.21" + dependencies: + "@jridgewell/sourcemap-codec": "npm:^1.5.5" + checksum: 10c0/299378e38f9a270069fc62358522ddfb44e94244baa0d6a8980ab2a9b2490a1d03b236b447eee309e17eb3bddfa482c61259d47960eb018a904f0ded52780c4a + languageName: node + linkType: hard + +"magicast@npm:^0.5.1": + version: 0.5.2 + resolution: "magicast@npm:0.5.2" + dependencies: + "@babel/parser": "npm:^7.29.0" + "@babel/types": "npm:^7.29.0" + source-map-js: "npm:^1.2.1" + checksum: 10c0/924af677643c5a0a7d6cdb3247c0eb96fa7611b2ba6a5e720d35d81c503d3d9f5948eb5227f80f90f82ea3e7d38cffd10bb988f3fc09020db428e14f26e960d7 + languageName: node + linkType: hard + "make-dir@npm:^3.0.2": version: 3.1.0 resolution: "make-dir@npm:3.1.0" @@ -13564,6 +14051,15 @@ __metadata: languageName: node linkType: hard +"make-dir@npm:^4.0.0": + version: 4.0.0 + resolution: "make-dir@npm:4.0.0" + dependencies: + semver: "npm:^7.5.3" + checksum: 10c0/69b98a6c0b8e5c4fe9acb61608a9fbcfca1756d910f51e5dbe7a9e5cfb74fca9b8a0c8a0ffdf1294a740826c1ab4871d5bf3f62f72a3049e5eac6541ddffed68 + languageName: node + linkType: hard + "make-error@npm:^1.1.1": version: 1.3.6 resolution: "make-error@npm:1.3.6" @@ -13957,6 +14453,15 @@ __metadata: languageName: node linkType: hard +"nanoid@npm:^3.3.11": + version: 3.3.11 + resolution: "nanoid@npm:3.3.11" + bin: + nanoid: bin/nanoid.cjs + checksum: 10c0/40e7f70b3d15f725ca072dfc4f74e81fcf1fbb02e491cf58ac0c79093adc9b0a73b152bcde57df4b79cd097e13023d7504acb38404a4da7bc1cd8e887b82fe0b + languageName: node + linkType: hard + "nanoid@npm:^3.3.8": version: 3.3.8 resolution: "nanoid@npm:3.3.8" @@ -13966,13 +14471,6 @@ __metadata: languageName: node linkType: hard -"natural-compare-lite@npm:^1.4.0": - version: 1.4.0 - resolution: "natural-compare-lite@npm:1.4.0" - checksum: 10c0/f6cef26f5044515754802c0fc475d81426f3b90fe88c20fabe08771ce1f736ce46e0397c10acb569a4dd0acb84c7f1ee70676122f95d5bfdd747af3a6c6bbaa8 - languageName: node - linkType: hard - "natural-compare@npm:^1.4.0": version: 1.4.0 resolution: "natural-compare@npm:1.4.0" @@ -14268,6 +14766,13 @@ __metadata: languageName: node linkType: hard +"obug@npm:^2.1.1": + version: 2.1.1 + resolution: "obug@npm:2.1.1" + checksum: 10c0/59dccd7de72a047e08f8649e94c1015ec72f94eefb6ddb57fb4812c4b425a813bc7e7cd30c9aca20db3c59abc3c85cc7a62bb656a968741d770f4e8e02bc2e78 + languageName: node + linkType: hard + "ofetch@npm:^1.4.1": version: 1.4.1 resolution: "ofetch@npm:1.4.1" @@ -14551,6 +15056,13 @@ __metadata: languageName: node linkType: hard +"pathe@npm:^2.0.3": + version: 2.0.3 + resolution: "pathe@npm:2.0.3" + checksum: 10c0/c118dc5a8b5c4166011b2b70608762e260085180bb9e33e80a50dcdb1e78c010b1624f4280c492c92b05fc276715a4c357d1f9edc570f8f1b3d90b6839ebaca1 + languageName: node + linkType: hard + "pathval@npm:^1.1.1": version: 1.1.1 resolution: "pathval@npm:1.1.1" @@ -14592,6 +15104,13 @@ __metadata: languageName: node linkType: hard +"picomatch@npm:^4.0.3": + version: 4.0.3 + resolution: "picomatch@npm:4.0.3" + checksum: 10c0/9582c951e95eebee5434f59e426cddd228a7b97a0161a375aed4be244bd3fe8e3a31b846808ea14ef2c8a2527a6eeab7b3946a67d5979e81694654f939473ae2 + languageName: node + linkType: hard + "pify@npm:^2.3.0": version: 2.3.0 resolution: "pify@npm:2.3.0" @@ -14822,6 +15341,17 @@ __metadata: languageName: node linkType: hard +"postcss@npm:^8.5.6": + version: 8.5.6 + resolution: "postcss@npm:8.5.6" + dependencies: + nanoid: "npm:^3.3.11" + picocolors: "npm:^1.1.1" + source-map-js: "npm:^1.2.1" + checksum: 10c0/5127cc7c91ed7a133a1b7318012d8bfa112da9ef092dddf369ae699a1f10ebbd89b1b9f25f3228795b84585c72aabd5ced5fc11f2ba467eedf7b081a66fad024 + languageName: node + linkType: hard + "preact@npm:^10.16.0, preact@npm:^10.24.2": version: 10.25.4 resolution: "preact@npm:10.25.4" @@ -15576,17 +16106,6 @@ __metadata: languageName: node linkType: hard -"rimraf@npm:^3.0.2": - version: 3.0.2 - resolution: "rimraf@npm:3.0.2" - dependencies: - glob: "npm:^7.1.3" - bin: - rimraf: bin.js - checksum: 10c0/9cb7757acb489bd83757ba1a274ab545eafd75598a9d817e0c3f8b164238dd90eba50d6b848bd4dcc5f3040912e882dc7ba71653e35af660d77b25c381d402e8 - languageName: node - linkType: hard - "rimraf@npm:^5.0.5": version: 5.0.10 resolution: "rimraf@npm:5.0.10" @@ -15766,6 +16285,96 @@ __metadata: languageName: node linkType: hard +"rollup@npm:^4.43.0": + version: 4.57.1 + resolution: "rollup@npm:4.57.1" + dependencies: + "@rollup/rollup-android-arm-eabi": "npm:4.57.1" + "@rollup/rollup-android-arm64": "npm:4.57.1" + "@rollup/rollup-darwin-arm64": "npm:4.57.1" + "@rollup/rollup-darwin-x64": "npm:4.57.1" + "@rollup/rollup-freebsd-arm64": "npm:4.57.1" + "@rollup/rollup-freebsd-x64": "npm:4.57.1" + "@rollup/rollup-linux-arm-gnueabihf": "npm:4.57.1" + "@rollup/rollup-linux-arm-musleabihf": "npm:4.57.1" + "@rollup/rollup-linux-arm64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-arm64-musl": "npm:4.57.1" + "@rollup/rollup-linux-loong64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-loong64-musl": "npm:4.57.1" + "@rollup/rollup-linux-ppc64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-ppc64-musl": "npm:4.57.1" + "@rollup/rollup-linux-riscv64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-riscv64-musl": "npm:4.57.1" + "@rollup/rollup-linux-s390x-gnu": "npm:4.57.1" + "@rollup/rollup-linux-x64-gnu": "npm:4.57.1" + "@rollup/rollup-linux-x64-musl": "npm:4.57.1" + "@rollup/rollup-openbsd-x64": "npm:4.57.1" + "@rollup/rollup-openharmony-arm64": "npm:4.57.1" + "@rollup/rollup-win32-arm64-msvc": "npm:4.57.1" + "@rollup/rollup-win32-ia32-msvc": "npm:4.57.1" + "@rollup/rollup-win32-x64-gnu": "npm:4.57.1" + "@rollup/rollup-win32-x64-msvc": "npm:4.57.1" + "@types/estree": "npm:1.0.8" + fsevents: "npm:~2.3.2" + dependenciesMeta: + "@rollup/rollup-android-arm-eabi": + optional: true + "@rollup/rollup-android-arm64": + optional: true + "@rollup/rollup-darwin-arm64": + optional: true + "@rollup/rollup-darwin-x64": + optional: true + "@rollup/rollup-freebsd-arm64": + optional: true + "@rollup/rollup-freebsd-x64": + optional: true + "@rollup/rollup-linux-arm-gnueabihf": + optional: true + "@rollup/rollup-linux-arm-musleabihf": + optional: true + "@rollup/rollup-linux-arm64-gnu": + optional: true + "@rollup/rollup-linux-arm64-musl": + optional: true + "@rollup/rollup-linux-loong64-gnu": + optional: true + "@rollup/rollup-linux-loong64-musl": + optional: true + "@rollup/rollup-linux-ppc64-gnu": + optional: true + "@rollup/rollup-linux-ppc64-musl": + optional: true + "@rollup/rollup-linux-riscv64-gnu": + optional: true + "@rollup/rollup-linux-riscv64-musl": + optional: true + "@rollup/rollup-linux-s390x-gnu": + optional: true + "@rollup/rollup-linux-x64-gnu": + optional: true + "@rollup/rollup-linux-x64-musl": + optional: true + "@rollup/rollup-openbsd-x64": + optional: true + "@rollup/rollup-openharmony-arm64": + optional: true + "@rollup/rollup-win32-arm64-msvc": + optional: true + "@rollup/rollup-win32-ia32-msvc": + optional: true + "@rollup/rollup-win32-x64-gnu": + optional: true + "@rollup/rollup-win32-x64-msvc": + optional: true + fsevents: + optional: true + bin: + rollup: dist/bin/rollup + checksum: 10c0/a90aaf1166fc495920e44e52dced0b12283aaceb0924abd6f863102128dd428bbcbf85970f792c06bc63d2a2168e7f073b73e05f6f8d76fdae17b7ac6cacba06 + languageName: node + linkType: hard + "run-parallel@npm:^1.1.9": version: 1.2.0 resolution: "run-parallel@npm:1.2.0" @@ -15933,7 +16542,7 @@ __metadata: languageName: node linkType: hard -"semver@npm:^7.3.7, semver@npm:^7.5.1": +"semver@npm:^7.5.1": version: 7.7.1 resolution: "semver@npm:7.7.1" bin: @@ -15942,6 +16551,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.5.3": + version: 7.7.4 + resolution: "semver@npm:7.7.4" + bin: + semver: bin/semver.js + checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 + languageName: node + linkType: hard + "serialize-javascript@npm:^6.0.2": version: 6.0.2 resolution: "serialize-javascript@npm:6.0.2" @@ -16125,6 +16743,13 @@ __metadata: languageName: node linkType: hard +"siginfo@npm:^2.0.0": + version: 2.0.0 + resolution: "siginfo@npm:2.0.0" + checksum: 10c0/3def8f8e516fbb34cb6ae415b07ccc5d9c018d85b4b8611e3dc6f8be6d1899f693a4382913c9ed51a06babb5201639d76453ab297d1c54a456544acf5c892e34 + languageName: node + linkType: hard + "signal-exit@npm:^3.0.2, signal-exit@npm:^3.0.3": version: 3.0.7 resolution: "signal-exit@npm:3.0.7" @@ -16382,6 +17007,13 @@ __metadata: languageName: node linkType: hard +"stackback@npm:0.0.2": + version: 0.0.2 + resolution: "stackback@npm:0.0.2" + checksum: 10c0/89a1416668f950236dd5ac9f9a6b2588e1b9b62b1b6ad8dff1bfc5d1a15dbf0aafc9b52d2226d00c28dffff212da464eaeebfc6b7578b9d180cef3e3782c5983 + languageName: node + linkType: hard + "stacktrace-parser@npm:^0.1.10": version: 0.1.10 resolution: "stacktrace-parser@npm:0.1.10" @@ -16398,6 +17030,13 @@ __metadata: languageName: node linkType: hard +"std-env@npm:^3.10.0": + version: 3.10.0 + resolution: "std-env@npm:3.10.0" + checksum: 10c0/1814927a45004d36dde6707eaf17552a546769bc79a6421be2c16ce77d238158dfe5de30910b78ec30d95135cc1c59ea73ee22d2ca170f8b9753f84da34c427f + languageName: node + linkType: hard + "stream-shift@npm:^1.0.2": version: 1.0.3 resolution: "stream-shift@npm:1.0.3" @@ -16819,13 +17458,6 @@ __metadata: languageName: node linkType: hard -"text-table@npm:^0.2.0": - version: 0.2.0 - resolution: "text-table@npm:0.2.0" - checksum: 10c0/02805740c12851ea5982686810702e2f14369a5f4c5c40a836821e3eefc65ffeec3131ba324692a37608294b0fd8c1e55a2dd571ffed4909822787668ddbee5c - languageName: node - linkType: hard - "thenify-all@npm:^1.0.0": version: 1.6.0 resolution: "thenify-all@npm:1.6.0" @@ -16869,6 +17501,13 @@ __metadata: languageName: node linkType: hard +"tinybench@npm:^2.9.0": + version: 2.9.0 + resolution: "tinybench@npm:2.9.0" + checksum: 10c0/c3500b0f60d2eb8db65250afe750b66d51623057ee88720b7f064894a6cb7eb93360ca824a60a31ab16dab30c7b1f06efe0795b352e37914a9d4bad86386a20c + languageName: node + linkType: hard + "tinyexec@npm:^0.3.2": version: 0.3.2 resolution: "tinyexec@npm:0.3.2" @@ -16876,6 +17515,13 @@ __metadata: languageName: node linkType: hard +"tinyexec@npm:^1.0.2": + version: 1.0.2 + resolution: "tinyexec@npm:1.0.2" + checksum: 10c0/1261a8e34c9b539a9aae3b7f0bb5372045ff28ee1eba035a2a059e532198fe1a182ec61ac60fa0b4a4129f0c4c4b1d2d57355b5cb9aa2d17ac9454ecace502ee + languageName: node + linkType: hard + "tinyglobby@npm:^0.2.11": version: 0.2.12 resolution: "tinyglobby@npm:0.2.12" @@ -16896,6 +17542,16 @@ __metadata: languageName: node linkType: hard +"tinyglobby@npm:^0.2.15": + version: 0.2.15 + resolution: "tinyglobby@npm:0.2.15" + dependencies: + fdir: "npm:^6.5.0" + picomatch: "npm:^4.0.3" + checksum: 10c0/869c31490d0d88eedb8305d178d4c75e7463e820df5a9b9d388291daf93e8b1eb5de1dad1c1e139767e4269fe75f3b10d5009b2cc14db96ff98986920a186844 + languageName: node + linkType: hard + "tinyglobby@npm:^0.2.6": version: 0.2.10 resolution: "tinyglobby@npm:0.2.10" @@ -16906,6 +17562,13 @@ __metadata: languageName: node linkType: hard +"tinyrainbow@npm:^3.0.3": + version: 3.0.3 + resolution: "tinyrainbow@npm:3.0.3" + checksum: 10c0/1e799d35cd23cabe02e22550985a3051dc88814a979be02dc632a159c393a998628eacfc558e4c746b3006606d54b00bcdea0c39301133956d10a27aa27e988c + languageName: node + linkType: hard + "tmp@npm:0.0.33": version: 0.0.33 resolution: "tmp@npm:0.0.33" @@ -17033,7 +17696,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:1.14.1, tslib@npm:^1.11.1, tslib@npm:^1.8.1, tslib@npm:^1.9.3": +"tslib@npm:1.14.1, tslib@npm:^1.11.1, tslib@npm:^1.9.3": version: 1.14.1 resolution: "tslib@npm:1.14.1" checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 @@ -17102,17 +17765,6 @@ __metadata: languageName: node linkType: hard -"tsutils@npm:^3.21.0": - version: 3.21.0 - resolution: "tsutils@npm:3.21.0" - dependencies: - tslib: "npm:^1.8.1" - peerDependencies: - typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - checksum: 10c0/02f19e458ec78ead8fffbf711f834ad8ecd2cc6ade4ec0320790713dccc0a412b99e7fd907c4cda2a1dc602c75db6f12e0108e87a5afad4b2f9e90a24cabd5a2 - languageName: node - linkType: hard - "turbo-darwin-64@npm:2.4.4": version: 2.4.4 resolution: "turbo-darwin-64@npm:2.4.4" @@ -17517,6 +18169,13 @@ __metadata: languageName: node linkType: hard +"undici-types@npm:~7.16.0": + version: 7.16.0 + resolution: "undici-types@npm:7.16.0" + checksum: 10c0/3033e2f2b5c9f1504bdc5934646cb54e37ecaca0f9249c983f7b1fc2e87c6d18399ebb05dc7fd5419e02b2e915f734d872a65da2e3eeed1813951c427d33cc9a + languageName: node + linkType: hard + "undici@npm:^5.14.0": version: 5.28.4 resolution: "undici@npm:5.28.4" @@ -17942,6 +18601,120 @@ __metadata: languageName: node linkType: hard +"vite@npm:^6.0.0 || ^7.0.0": + version: 7.3.1 + resolution: "vite@npm:7.3.1" + dependencies: + esbuild: "npm:^0.27.0" + fdir: "npm:^6.5.0" + fsevents: "npm:~2.3.3" + picomatch: "npm:^4.0.3" + postcss: "npm:^8.5.6" + rollup: "npm:^4.43.0" + tinyglobby: "npm:^0.2.15" + peerDependencies: + "@types/node": ^20.19.0 || >=22.12.0 + jiti: ">=1.21.0" + less: ^4.0.0 + lightningcss: ^1.21.0 + sass: ^1.70.0 + sass-embedded: ^1.70.0 + stylus: ">=0.54.8" + sugarss: ^5.0.0 + terser: ^5.16.0 + tsx: ^4.8.1 + yaml: ^2.4.2 + dependenciesMeta: + fsevents: + optional: true + peerDependenciesMeta: + "@types/node": + optional: true + jiti: + optional: true + less: + optional: true + lightningcss: + optional: true + sass: + optional: true + sass-embedded: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + tsx: + optional: true + yaml: + optional: true + bin: + vite: bin/vite.js + checksum: 10c0/5c7548f5f43a23533e53324304db4ad85f1896b1bfd3ee32ae9b866bac2933782c77b350eb2b52a02c625c8ad1ddd4c000df077419410650c982cd97fde8d014 + languageName: node + linkType: hard + +"vitest@npm:^4.0.18": + version: 4.0.18 + resolution: "vitest@npm:4.0.18" + dependencies: + "@vitest/expect": "npm:4.0.18" + "@vitest/mocker": "npm:4.0.18" + "@vitest/pretty-format": "npm:4.0.18" + "@vitest/runner": "npm:4.0.18" + "@vitest/snapshot": "npm:4.0.18" + "@vitest/spy": "npm:4.0.18" + "@vitest/utils": "npm:4.0.18" + es-module-lexer: "npm:^1.7.0" + expect-type: "npm:^1.2.2" + magic-string: "npm:^0.30.21" + obug: "npm:^2.1.1" + pathe: "npm:^2.0.3" + picomatch: "npm:^4.0.3" + std-env: "npm:^3.10.0" + tinybench: "npm:^2.9.0" + tinyexec: "npm:^1.0.2" + tinyglobby: "npm:^0.2.15" + tinyrainbow: "npm:^3.0.3" + vite: "npm:^6.0.0 || ^7.0.0" + why-is-node-running: "npm:^2.3.0" + peerDependencies: + "@edge-runtime/vm": "*" + "@opentelemetry/api": ^1.9.0 + "@types/node": ^20.0.0 || ^22.0.0 || >=24.0.0 + "@vitest/browser-playwright": 4.0.18 + "@vitest/browser-preview": 4.0.18 + "@vitest/browser-webdriverio": 4.0.18 + "@vitest/ui": 4.0.18 + happy-dom: "*" + jsdom: "*" + peerDependenciesMeta: + "@edge-runtime/vm": + optional: true + "@opentelemetry/api": + optional: true + "@types/node": + optional: true + "@vitest/browser-playwright": + optional: true + "@vitest/browser-preview": + optional: true + "@vitest/browser-webdriverio": + optional: true + "@vitest/ui": + optional: true + happy-dom: + optional: true + jsdom: + optional: true + bin: + vitest: vitest.mjs + checksum: 10c0/b913cd32032c95f29ff08c931f4b4c6fd6d2da498908d6770952c561a1b8d75c62499a1f04cadf82fb89cc0f9a33f29fb5dfdb899f6dbb27686a9d91571be5fa + languageName: node + linkType: hard + "w-json@npm:1.3.10": version: 1.3.10 resolution: "w-json@npm:1.3.10" @@ -18140,6 +18913,18 @@ __metadata: languageName: node linkType: hard +"why-is-node-running@npm:^2.3.0": + version: 2.3.0 + resolution: "why-is-node-running@npm:2.3.0" + dependencies: + siginfo: "npm:^2.0.0" + stackback: "npm:0.0.2" + bin: + why-is-node-running: cli.js + checksum: 10c0/1cde0b01b827d2cf4cb11db962f3958b9175d5d9e7ac7361d1a7b0e2dc6069a263e69118bd974c4f6d0a890ef4eedfe34cf3d5167ec14203dbc9a18620537054 + languageName: node + linkType: hard + "widest-line@npm:^3.1.0": version: 3.1.0 resolution: "widest-line@npm:3.1.0"