Skip to content
Open
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
8b8ca73
feat(streaming): implement consolidated superfluid sdk and react hooks
HushLuxe Feb 16, 2026
deeda20
refactor(streaming): optimize sdk initialization and hook logic
HushLuxe Feb 16, 2026
23ae225
refactor(streaming): optimize hook logic and fix type safety
HushLuxe Feb 16, 2026
42091d2
chore: revert frontend and config changes to match main scope
HushLuxe Feb 16, 2026
ead6715
chore: remove accidental files and clean up package.json
HushLuxe Feb 16, 2026
4ef249b
chore: remove all demo-identity-app changes from PR
HushLuxe Feb 16, 2026
aa82b15
chore: include updated yarn.lock for new packages
HushLuxe Feb 16, 2026
5df9d13
chore: use * for internal dependency as requested
HushLuxe Feb 16, 2026
4a09487
refactor(streaming): make token parameter optional with auto-resolution
HushLuxe Feb 17, 2026
0791279
feat(demo): add streaming SDK demo app
HushLuxe Feb 17, 2026
a441adc
edit readme file
HushLuxe Feb 17, 2026
168af8c
chore: update demo readme
HushLuxe Feb 17, 2026
389bc15
chore: remove hardcoded addresses from readme
HushLuxe Feb 17, 2026
bc12016
fix: update security config
HushLuxe Feb 17, 2026
e14148d
docs: simplify readme and cleanup address placeholders
HushLuxe Feb 18, 2026
72427d3
docs: update demo readme
HushLuxe Feb 18, 2026
8ff61e7
docs: use bracketed placeholders for addresses
HushLuxe Feb 18, 2026
f1f3bd4
added gitiLgnore
HushLuxe Feb 18, 2026
76e4bc6
feat: implement multi-token support and update demo app
HushLuxe Feb 19, 2026
9670b4a
feat: implement safe batching and pagination for subgraph queries
HushLuxe Feb 19, 2026
f24af64
feat: sdk polish and defensive guards
HushLuxe Feb 19, 2026
fe10910
Merge remote-tracking branch 'upstream/main' into feat/streaming-sdk
HushLuxe Feb 24, 2026
75d6e37
fix: correct setFlowrate args, align hook params, sync docs
HushLuxe Feb 24, 2026
2165ec9
fix(streaming): align docs/hooks, remove unsupported chain refs
HushLuxe Feb 24, 2026
0f508b5
fix(streaming): align CFA calls, hooks, and demo
HushLuxe Feb 24, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
13 changes: 13 additions & 0 deletions packages/react-hooks/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,19 @@ export const App = () => (
- Builds on `useIdentitySDK` and returns a ready `ClaimSDK` once identity checks resolve.
- Surfaces entitlement errors via the returned `error` string.

### Streaming Hooks

- `useStreamList({ account, environment, enabled })`
- Fetches all active streams for an account.
- `useGDAPools({ environment, enabled })`
- Lists all available distribution pools.
- `useSupReserves({ apiKey, environment, enabled })`
- Fetches SUP reserve holdings. **Requires `apiKey`** for Base mainnet (decentralized subgraph).
- `useCreateStream()`, `useUpdateStream()`, `useDeleteStream()`
- Mutators for managing 1-to-1 streams.
- `useConnectToPool()`, `useDisconnectFromPool()`
- Mutators for GDA pool memberships.

Both hooks re-run whenever the connected wallet, public client, or environment changes.

## Demo & Further Reading
Expand Down
2 changes: 2 additions & 0 deletions packages/react-hooks/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -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"
Expand Down
1 change: 1 addition & 0 deletions packages/react-hooks/src/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from "./citizen-sdk"
export * from "./streaming"
306 changes: 306 additions & 0 deletions packages/react-hooks/src/streaming/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,306 @@
import { useMemo } from "react"
import { useQuery, useMutation, useQueryClient } from "@tanstack/react-query"
import { type Address, type Hash } from "viem"
import { usePublicClient, useWalletClient } from "wagmi"
import {
StreamingSDK,
GdaSDK,
SubgraphClient,
SupportedChains,
type StreamInfo,
type GDAPool,
type PoolMembership,
type SUPReserveLocker,
type Environment,
} from "@goodsdks/streaming-sdk"

/**
* Hook parameter interfaces
*/
export interface UseCreateStreamParams {
receiver: Address
token: Address
flowRate: bigint
userData?: `0x${string}`
environment?: Environment
}

export interface UseUpdateStreamParams {
receiver: Address
token: Address
newFlowRate: bigint
userData?: `0x${string}`
environment?: Environment
}

export interface UseDeleteStreamParams {
receiver: Address
token: Address
environment?: Environment
}

export interface UseStreamListParams {
account: Address
direction?: "incoming" | "outgoing" | "all"
environment?: Environment
enabled?: boolean
}

export interface UseGDAPoolsParams {
enabled?: boolean
}

export interface UsePoolMembershipsParams {
account: Address
enabled?: boolean
}

export interface UseConnectToPoolParams {
poolAddress: Address
userData?: `0x${string}`
}

export interface UseDisconnectFromPoolParams {
poolAddress: Address
userData?: `0x${string}`
}

export interface UseSupReservesParams {
environment?: Environment
apiKey?: string
enabled?: boolean
}

/**
* React Hooks for Superfluid operations
*/
export function useCreateStream() {
const publicClient = usePublicClient()
const { data: walletClient } = useWalletClient()
const queryClient = useQueryClient()

const sdks = useMemo(() => {
if (!publicClient) return new Map<string, StreamingSDK>()
const envs = ["production", "staging", "development"] as const
const m = new Map<string, StreamingSDK>()
for (const e of envs) {
try {
m.set(e, new StreamingSDK(publicClient, walletClient ? walletClient : undefined, { environment: e }))
} catch (err) {
// ignore
}
}
return m
}, [publicClient, walletClient])

return useMutation({
mutationFn: async ({
receiver,
token,
flowRate,
userData = "0x",
environment = "production",
}: UseCreateStreamParams): Promise<Hash> => {
if (!publicClient) throw new Error("Public client not available")
if (!walletClient) throw new Error("Wallet client not available")
const sdk = sdks.get(environment)
if (!sdk) throw new Error("SDK not available for selected environment")
return sdk.createStream({ receiver, token, flowRate, userData })
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["streams"] })
},
})
}

export function useUpdateStream() {
const publicClient = usePublicClient()
const { data: walletClient } = useWalletClient()
const queryClient = useQueryClient()

const sdks = useMemo(() => {
if (!publicClient) return new Map<string, StreamingSDK>()
const envs = ["production", "staging", "development"] as const
const m = new Map<string, StreamingSDK>()
for (const e of envs) {
try {
m.set(e, new StreamingSDK(publicClient, walletClient as any, { environment: e }))
} catch (err) {
// ignore
}
}
return m
}, [publicClient, walletClient])

return useMutation({
mutationFn: async ({
receiver,
token,
newFlowRate,
userData = "0x",
environment = "production",
}: UseUpdateStreamParams): Promise<Hash> => {
if (!publicClient) throw new Error("Public client not available")
if (!walletClient) throw new Error("Wallet client not available")
const sdk = sdks.get(environment)
if (!sdk) throw new Error("SDK not available for selected environment")
return sdk.updateStream({ receiver, token, newFlowRate, userData })
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["streams"] })
},
})
}

export function useDeleteStream() {
const publicClient = usePublicClient()
const { data: walletClient } = useWalletClient()
const queryClient = useQueryClient()

const sdks = useMemo(() => {
if (!publicClient) return new Map<string, StreamingSDK>()
const envs = ["production", "staging", "development"] as const
const m = new Map<string, StreamingSDK>()
for (const e of envs) {
try {
m.set(e, new StreamingSDK(publicClient, walletClient as any, { environment: e }))
} catch (err) {
// ignore
}
}
return m
}, [publicClient, walletClient])

return useMutation({
mutationFn: async ({
receiver,
token,
environment = "production",
}: UseDeleteStreamParams): Promise<Hash> => {
if (!publicClient) throw new Error("Public client not available")
if (!walletClient) throw new Error("Wallet client not available")
const sdk = sdks.get(environment)
if (!sdk) throw new Error("SDK not available for selected environment")
return sdk.deleteStream({ receiver, token })
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["streams"] })
},
})
}

export function useStreamList({
account,
direction = "all",
environment = "production",
enabled = true,
}: UseStreamListParams) {
const publicClient = usePublicClient()

return useQuery<StreamInfo[]>({
queryKey: ["streams", account, direction, environment, publicClient?.chain?.id],
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,
})
}

export function useGDAPools({
enabled = true
}: UseGDAPoolsParams = {}) {
const publicClient = usePublicClient()
const sdk = useMemo(() => {
if (!publicClient) return null
return new GdaSDK(publicClient, undefined, { chainId: publicClient.chain?.id })
}, [publicClient])

return useQuery<GDAPool[]>({
queryKey: ["gda-pools", publicClient?.chain?.id],
queryFn: async () => {
if (!sdk) throw new Error("Public client not available")
return sdk.getDistributionPools()
},
enabled: enabled && !!publicClient,
})
}

export function usePoolMemberships({
account,
enabled = true,
}: UsePoolMembershipsParams) {
const publicClient = usePublicClient()
const sdk = useMemo(() => {
if (!publicClient) return null
return new GdaSDK(publicClient)
}, [publicClient])

return useQuery<PoolMembership[]>({
queryKey: ["gda-memberships", account, publicClient?.chain?.id],
queryFn: async () => {
if (!sdk) throw new Error("Public client not available")
return sdk.getPoolMemberships(account)
},
enabled: enabled && !!publicClient && !!account,
})
}

export function useConnectToPool() {
const publicClient = usePublicClient()
const { data: walletClient } = useWalletClient()
const queryClient = useQueryClient()

return useMutation({
mutationFn: async ({
poolAddress,
userData = "0x",
}: UseConnectToPoolParams): Promise<Hash> => {
if (!publicClient) throw new Error("Public client not available")
if (!walletClient) throw new Error("Wallet client not available")
const sdk = new GdaSDK(publicClient, walletClient as any)
return sdk.connectToPool({ poolAddress, userData })
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["gda-pools"] })
queryClient.invalidateQueries({ queryKey: ["gda-memberships"] })
},
})
}

export function useDisconnectFromPool() {
const publicClient = usePublicClient()
const { data: walletClient } = useWalletClient()
const queryClient = useQueryClient()

return useMutation({
mutationFn: async ({
poolAddress,
userData = "0x",
}: UseDisconnectFromPoolParams): Promise<Hash> => {
if (!publicClient) throw new Error("Public client not available")
if (!walletClient) throw new Error("Wallet client not available")
const sdk = new GdaSDK(publicClient, walletClient as any)
return sdk.disconnectFromPool({ poolAddress, userData })
},
onSuccess: () => {
queryClient.invalidateQueries({ queryKey: ["gda-pools"] })
queryClient.invalidateQueries({ queryKey: ["gda-memberships"] })
},
})
}

export function useSupReserves({
apiKey,
enabled = true
}: UseSupReservesParams = {}) {
return useQuery<SUPReserveLocker[]>({
queryKey: ["sup-reserves", SupportedChains.BASE, apiKey],
queryFn: async () => {
const client = new SubgraphClient(SupportedChains.BASE, { apiKey })
return client.querySUPReserves()
},
enabled,
})
}
Loading