Skip to content

feat(streaming): implement superfluid sdk and react hooks#31

Open
HushLuxe wants to merge 25 commits intoGoodDollar:mainfrom
HushLuxe:feat/streaming-sdk
Open

feat(streaming): implement superfluid sdk and react hooks#31
HushLuxe wants to merge 25 commits intoGoodDollar:mainfrom
HushLuxe:feat/streaming-sdk

Conversation

@HushLuxe
Copy link
Contributor

@HushLuxe HushLuxe commented Feb 16, 2026

I'm adding the @goodsdks/streaming-sdk and @goodsdks/react-hooks (streaming module) to the monorepo. This provides a unified, type-safe way to handle Superfluid operations on Celo and Base, specifically optimized for G$ and GDA pools.

Changes

  • Implemented core StreamingSDK and GdaSDK in packages/streaming-sdk.
  • Added React hooks for stream management and GDA pool connectivity in packages/react-hooks.
  • Centralized protocol addresses and subgraph URLs in a single constants file.
  • Added comprehensive documentation and a testing guide.

Testing

I've verified the implementation with:

  • Unit Tests: All 37 tests for the core SDK and utilities are passing.
  • Build Verification: Both CommonJS and ESM builds are working as expected.
  • UI Testing: Manually tested stream creation/deletion and GDA pool connections in the demo app.
  • CI Prep: Ran turbo build and turbo lint across the workspace to ensure no regressions.

Checklist:

  • PR title follows convention: [(Feature) Superfluid Streaming SDK Implementation]
  • Code follows project style guidelines
  • Unit tests pass locally and cover the new functionality

Summary by Sourcery

Add a new Superfluid-based streaming SDK for Celo/Base and expose React hooks for managing streams and GDA pool interactions.

New Features:

  • Introduce @goodsdks/streaming-sdk providing StreamingSDK, GdaSDK, subgraph client, and utilities for Superfluid streams, GDA pools, and SUP reserve queries across supported chains and environments.
  • Add React streaming hooks in @goodsdks/react-hooks for listing streams, querying GDA pools and memberships, managing SUP reserves, and mutating streams and pool memberships via React Query.

Enhancements:

  • Centralize Superfluid protocol addresses, G$ SuperToken addresses, subgraph URLs, and chain metadata in shared constants for streaming operations.
  • Provide comprehensive README and TESTING documentation for the streaming SDK, including usage examples, configuration, and validation steps.

Tests:

  • Add a Vitest test suite for the streaming SDK covering utilities, chain validation, core StreamingSDK and GdaSDK behaviors, error handling, and edge cases.

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Hey - I've found 1 issue, and left some high level feedback:

  • In the React streaming hooks you eagerly instantiate StreamingSDK for all environments on every hook usage (e.g. useCreateStream, useUpdateStream, useDeleteStream); consider lazily creating only the environment actually used and/or sharing a memoized SDK factory to avoid repeated construction and to reduce unnecessary try/catch noise.
  • The userData parameter is accepted in the Streaming/GDA SDK methods and hook mutation params but is not consistently forwarded into the underlying contract calls (e.g. createStream uses setFlowrate without userData), which may be confusing to consumers; either wire it through where supported or remove it from the public API where it has no effect.
  • When resolving CFA_FORWARDER_ADDRESSES/GDA_FORWARDER_ADDRESSES by chainId, there is no guard for missing entries, so an unsupported or misconfigured chain could lead to an undefined address being used; consider validating that a forwarder address exists and throwing a clear error if not.
Prompt for AI Agents
Please address the comments from this code review:

## Overall Comments
- In the React streaming hooks you eagerly instantiate `StreamingSDK` for all environments on every hook usage (e.g. `useCreateStream`, `useUpdateStream`, `useDeleteStream`); consider lazily creating only the environment actually used and/or sharing a memoized SDK factory to avoid repeated construction and to reduce unnecessary try/catch noise.
- The `userData` parameter is accepted in the Streaming/GDA SDK methods and hook mutation params but is not consistently forwarded into the underlying contract calls (e.g. `createStream` uses `setFlowrate` without `userData`), which may be confusing to consumers; either wire it through where supported or remove it from the public API where it has no effect.
- When resolving `CFA_FORWARDER_ADDRESSES`/`GDA_FORWARDER_ADDRESSES` by `chainId`, there is no guard for missing entries, so an unsupported or misconfigured chain could lead to an `undefined` address being used; consider validating that a forwarder address exists and throwing a clear error if not.

## Individual Comments

### Comment 1
<location> `packages/react-hooks/src/streaming/index.ts:82` </location>
<code_context>
+    const { data: walletClient } = useWalletClient()
+    const queryClient = useQueryClient()
+
+    const sdks = useMemo(() => {
+        if (!publicClient) return new Map<string, StreamingSDK>()
+        const envs = ["production", "staging", "development"] as const
</code_context>

<issue_to_address>
**issue (complexity):** Consider extracting a shared helper hook to build the StreamingSDK instances by environment and reuse it across the stream hooks (and optionally the list hook) to centralize environment handling and SDK setup.

You can reduce duplication and make the hooks easier to reason about by extracting a shared SDK helper and a single env definition, then using that across the stream hooks (and optionally the list hook).

### 1. Extract a shared env list + SDK map helper

```ts
const STREAMING_ENVIRONMENTS = ["production", "staging", "development"] as const

function useStreamingSdksByEnvironment() {
  const publicClient = usePublicClient()
  const { data: walletClient } = useWalletClient()

  return useMemo(() => {
    if (!publicClient) return new Map<Environment, StreamingSDK>()
    const m = new Map<Environment, StreamingSDK>()
    for (const e of STREAMING_ENVIRONMENTS) {
      // keep current swallow behavior if you need backward compatibility
      try {
        m.set(
          e,
          new StreamingSDK(
            publicClient,
            walletClient ? (walletClient as any) : undefined,
            { environment: e },
          ),
        )
      } catch {
        // ignore
      }
    }
    return m
  }, [publicClient, walletClient])
}
```

### 2. Reuse in `useCreateStream`, `useUpdateStream`, `useDeleteStream`

```ts
export function useCreateStream() {
  const sdks = useStreamingSdksByEnvironment()
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: async ({
      receiver,
      token,
      flowRate,
      userData = "0x",
      environment = "production",
    }: UseCreateStreamParams): Promise<Hash> => {
      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 sdks = useStreamingSdksByEnvironment()
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: async ({
      receiver,
      token,
      newFlowRate,
      userData = "0x",
      environment = "production",
    }: UseUpdateStreamParams): Promise<Hash> => {
      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 sdks = useStreamingSdksByEnvironment()
  const queryClient = useQueryClient()

  return useMutation({
    mutationFn: async ({
      receiver,
      token,
      environment = "production",
    }: UseDeleteStreamParams): Promise<Hash> => {
      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"] })
    },
  })
}
```

This:

- Removes three nearly-identical `useMemo` blocks.
- Centralizes environment handling in one place.
- Removes repeated `publicClient`/`walletClient` checks inside each mutation (they’re implicitly handled by the `sdks` map being empty when clients are missing).

### 3. Optionally reuse helper in `useStreamList`

To keep environment handling consistent:

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

  return useQuery<StreamInfo[]>({
    queryKey: ["streams", account, direction, environment, publicClient?.chain?.id],
    queryFn: async () => {
      const sdk = sdks.get(environment)
      if (!sdk) throw new Error("SDK not available for selected environment")
      return sdk.getActiveStreams(account, direction)
    },
    enabled: enabled && !!account && !!publicClient,
  })
}
```

This keeps all existing behavior but consolidates the SDK construction and environment logic so future changes only need to be made in one helper.
</issue_to_address>

Sourcery is free for open source - if you like our reviews please consider sharing them ✨
Help me be more useful! Please click 👍 or 👎 on each comment and I'll use the feedback to improve your reviews.

- run: npm install --global turbo@latest
- run: npm install --global vite@latest
- run: yarn install --immutable
# Build dependencies first using turbo
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

what is this for?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for vercel deployment fixes, all reverted though

@@ -0,0 +1,10 @@
node_modules
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why did you add this file?

Copy link
Contributor Author

@HushLuxe HushLuxe Feb 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

was trying to fix frontend vercel deployment issues, but i have reverted all the identity app changes and kept it core sdk only

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

the vercel build reports that your lock file isnt in sync. verify it is

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

also another option is to upgrade vercel version

@@ -0,0 +1 @@
VITE_GRAPH_API_KEY=
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ALL changes to the demo-identity-app should be removed.
Create a new demo app for streaming

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have removed the frontend test files i pushed earlier as they were still causing vercel deployment failures

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

its important to have some kind of UI demo

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I’ve added a frontend for the demo

@sirpy sirpy linked an issue Feb 16, 2026 that may be closed by this pull request
13 tasks
@sirpy
Copy link
Contributor

sirpy commented Feb 16, 2026

@sourcery-ai guide does it fullfill issue #23

@sourcery-ai
Copy link
Contributor

sourcery-ai bot commented Feb 16, 2026

Reviewer's Guide

Adds a new Superfluid-based streaming SDK package for Celo/Base plus React hooks that wrap it, centralizing protocol constants, subgraph access, and flow-rate utilities, and wires everything into the monorepo with tests, build config, and docs.

File-Level Changes

Change Details Files
Introduce @goodsdks/streaming-sdk with StreamingSDK, GdaSDK, SubgraphClient, utilities, and chain/config constants for Superfluid operations.
  • Implement StreamingSDK for creating, updating, deleting streams, querying balances/history, and SUP reserves using CFA forwarder and subgraph client.
  • Implement GdaSDK for connecting/disconnecting to GDA pools and querying pools/memberships and SUP reserves via GDA forwarder and subgraph client.
  • Add SubgraphClient wrapping graphql-request with typed queries for streams, balances, balance history, GDA pools/memberships, and SUP reserve lockers.
  • Define shared types for streams, pools, memberships, SUP reserves, and SDK options, plus utilities for chain validation, G$ token lookup, and flow-rate calculations/formatting.
  • Centralize chain metadata, Superfluid forwarder addresses, G$ SuperToken addresses per environment, and subgraph URLs in constants.
  • Set up package build (tsup), TypeScript config, vitest config, and package exports for both ESM and CJS consumers.
  • Add README and TESTING docs describing installation, APIs, environments, subgraph usage, security, and validation steps.
packages/streaming-sdk/src/streaming-sdk.ts
packages/streaming-sdk/src/gda-sdk.ts
packages/streaming-sdk/src/subgraph/client.ts
packages/streaming-sdk/src/types.ts
packages/streaming-sdk/src/utils.ts
packages/streaming-sdk/src/constants.ts
packages/streaming-sdk/src/index.ts
packages/streaming-sdk/src/sdk.test.ts
packages/streaming-sdk/tsup.config.ts
packages/streaming-sdk/tsconfig.json
packages/streaming-sdk/vitest.config.ts
packages/streaming-sdk/README.md
packages/streaming-sdk/TESTING.md
packages/streaming-sdk/package.json
Add React hooks wrapping the streaming SDK for stream CRUD, GDA pools, memberships, and SUP reserves, and expose them from the react-hooks package.
  • Implement React Query based hooks for creating, updating, deleting streams and listing streams per account/environment using StreamingSDK instances cached per environment.
  • Implement hooks for listing GDA pools, querying pool memberships, and connecting/disconnecting pools using GdaSDK bound to the current wagmi public/wallet clients.
  • Implement hook for querying SUP reserve lockers on Base via SubgraphClient, optionally authenticated with an API key.
  • Export the new streaming hooks from the react-hooks entry point and document their usage in the package README.
  • Declare @goodsdks/streaming-sdk and @tanstack/react-query as dependencies of the react-hooks package.
packages/react-hooks/src/streaming/index.ts
packages/react-hooks/src/index.ts
packages/react-hooks/README.md
packages/react-hooks/package.json

Assessment against linked issues

Issue Objective Addressed Explanation
#23 Create a new @goodsdks/streaming-sdk package that uses @sfpro/sdk on Celo and Base with chain validation and env-aware G$ addresses, and that provides typed APIs for Superfluid stream lifecycle (create/update/delete), listing running streams, SuperToken balances and history, GDA pools and memberships, SUP reserve queries, and safe, limited subgraph/RPC scans.
#23 Expose a React-hooks compatible streaming layer in @goodsdks/react-hooks for managing streams and GDA pools (CRUD on streams, list streams, list pools, query memberships, connect/disconnect pools, query SUP reserves) wired to the new streaming SDK.
#23 Add proper package scaffolding, exports, and documentation for the streaming SDK and hooks, including tsup build config, public exports from src/index.ts, README/TESTING docs with usage examples and notes on address resolution and subgraph usage, and ensure the workspace builds.

Possibly linked issues

  • #: They match exactly: the PR delivers the requested Superfluid streaming SDK, subgraph features, GDA pools, SUP reserves, and React hooks.

Tips and commands

Interacting with Sourcery

  • Trigger a new review: Comment @sourcery-ai review on the pull request.
  • Continue discussions: Reply directly to Sourcery's review comments.
  • Generate a GitHub issue from a review comment: Ask Sourcery to create an
    issue from a review comment by replying to it. You can also reply to a
    review comment with @sourcery-ai issue to create an issue from it.
  • Generate a pull request title: Write @sourcery-ai anywhere in the pull
    request title to generate a title at any time. You can also comment
    @sourcery-ai title on the pull request to (re-)generate the title at any time.
  • Generate a pull request summary: Write @sourcery-ai summary anywhere in
    the pull request body to generate a PR summary at any time exactly where you
    want it. You can also comment @sourcery-ai summary on the pull request to
    (re-)generate the summary at any time.
  • Generate reviewer's guide: Comment @sourcery-ai guide on the pull
    request to (re-)generate the reviewer's guide at any time.
  • Resolve all Sourcery comments: Comment @sourcery-ai resolve on the
    pull request to resolve all Sourcery comments. Useful if you've already
    addressed all the comments and don't want to see them anymore.
  • Dismiss all Sourcery reviews: Comment @sourcery-ai dismiss on the pull
    request to dismiss all existing Sourcery reviews. Especially useful if you
    want to start fresh with a new review - don't forget to comment
    @sourcery-ai review to trigger a new review!

Customizing Your Experience

Access your dashboard to:

  • Enable or disable review features such as the Sourcery-generated pull request
    summary, the reviewer's guide, and others.
  • Change the review language.
  • Add, remove or edit custom review instructions.
  • Adjust other review settings.

Getting Help

@sirpy
Copy link
Contributor

sirpy commented Feb 16, 2026

@HushLuxe There's no specific integration of the G$/SUP tokens in the SDKs
The sdks should be initialized with a default token. the developer should not need to specify a token address.
@L03TJ3 how did you want this to be handled?

Copy link
Contributor

@sourcery-ai sourcery-ai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

New security issues found

@HushLuxe HushLuxe requested a review from sirpy February 18, 2026 00:05
@sirpy
Copy link
Contributor

sirpy commented Feb 18, 2026

@HushLuxe do not use force push. I can not review the changes.
please revert and add your last changes as a separate commit

@HushLuxe
Copy link
Contributor Author

@sirpy
Sorry about the forced push! I was trying to resolve a persistent security alert from the Sourcery scanner. It was still flagging my code,even when i have removed the hardcoded addresses it addressed.

I have now restored the original commit history but still looking for a way to fix the scanner issue

@sirpy
Copy link
Contributor

sirpy commented Feb 19, 2026

@HushLuxe we can just ignore sourcery security issue if it is invalid

@sirpy
Copy link
Contributor

sirpy commented Feb 19, 2026

@HushLuxe There's no specific integration of the G$/SUP tokens in the SDKs The sdks should be initialized with a default token. the developer should not need to specify a token address. @L03TJ3 how did you want this to be handled?

@HushLuxe The sdk should default to G$ token, the dev should not need to specify the token each time.
when creating the sdk the dev can specify the default token 'G$','SUP' or a specific token address. for G$/SUP the token address should be pulled automatically based on the env+chainid

@L03TJ3

@HushLuxe
Copy link
Contributor Author

Done.....
The SDK now defaults to G$ as requested. I have implemented the defaultToken option in the constructor so you can pass G$, SUP, or a specific address. If it's G$ or SUP, the contract address is automatically resolved based on the env and chainId. I have also updated the demo app with a toggle so you can test both tokens on Celo and Base

@sirpy

enabled: !!address,
}) as { data: StreamInfo[] | undefined, isLoading: boolean, refetch: () => void }

const { data: pools, isLoading: poolsLoading } = useGDAPools({
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are using here an environment param but its not defined as being a param for this hook?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have now added an environment as a declared param on the useGDAPools

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Update: Actually, I realized that adding the environment parameter wasn’t really necessary for the current production scope. I hv removed the environment param from useGDAPools (and the demo call site) to keep the API surface minimal and clean

)
}

return this.submitAndWait(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

setFlowRate only has three arguments and would fail through simulateContract because 'args expected 3, but received 4'.

How has this been tested?

Reference: https://sdk.superfluid.pro/docs/use-cases/manage-cfa-flow#using-setflowrate-recommended

Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, test script you can run with node from within sdks/streaming-sdk:
https://gist.github.com/L03TJ3/93debaa9db7aee5b74058fdea920fe55

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for the test script. it was very helpful. I ran it from within packages/streaming-sdk and all checks pass with the corrected args.

#### `getActiveStreams(account, direction?)`
Returns active streams for an account.
```typescript
const streams = await sdk.getActiveStreams('0x...', 'outgoing' | 'incoming' | 'all')
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Docs seem out of sync. the implementation expects an options object.
in this example, '0x...' is treated as options and the rest will be ignored

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have also updated this file

@HushLuxe HushLuxe requested a review from L03TJ3 February 24, 2026 17:07
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Superfluid Streaming SDK (Celo + Base)

3 participants