Skip to content

Conversation

@gtspencer
Copy link
Contributor

@gtspencer gtspencer commented Aug 12, 2025

Link to Issue

Description

Superfluid action providers, broken up by functionality (create stream, create pool, update stream, update pool). We experienced need for Superfluid functionality in our agent and decided to include the implementation in the core repo.

Qualified Impact

Very little qualified impact due to the self contained nature of the action. Only dependency is the graphql-request package, which can be omitted in the event of an error.

Pool Actions:

  • Create pool
  • Update member units

Stream Actions:

  • Create stream
  • Update stream
  • Close stream

Query Actions (easily extensible for other queries):

  • Query open streams

Create Super Token

  • Creates a SuperToken (wrapper) implementation for an underlying ERC20

Wrap Token

  • Wraps an ERC20 into its Super Token implementation

Tests

Chatbot: langchain-cdp-chatbot
Network: Base Mainnet
Setup: Add relevant actions (SuperfluidStreamActionProvider, etc.) to the chatbot
Create Super Token
Prompt: Can you create a super token for this erc20: 0xe8E55A847Bb446d967ef92F4580162fb8f2d3F38

-------------------
0x2D92C37A341f9C7AE0438179ee2835B9Bb692e40
Created super token for 0xe8E55A847Bb446d967ef92F4580162fb8f2d3F38.  Super token address at 0x2D92C37A341f9C7AE0438179ee2835B9Bb692e40  Transaction hash: 0xa9ea729bcbe30e3bb8610f9adf91fc4f4162e4cda5c33273c1f0d830388492da
-------------------
The super token has been successfully created for the ERC20 address `0xe8E55A847Bb446d967ef92F4580162fb8f2d3F38`. 

- **Super token address**: `0x2D92C37A341f9C7AE0438179ee2835B9Bb692e40`
- **Transaction hash**: `0xa9ea729bcbe30e3bb8610f9adf91fc4f4162e4cda5c33273c1f0d830388492da`

Let me know if you need further assistance!
-------------------
Wrap Super Token
Prompt: Can you wrap 1000 of this erc20 0xe8E55A847Bb446d967ef92F4580162fb8f2d3F38 into this super token 0x2D92C37A341f9C7AE0438179ee2835B9Bb692e40 on base mainnet

-------------------
Wallet Details:
- Provider: cdp_evm_wallet_provider
- Address: 0x77841ee4399EFfc9b515c09cf239D784392F897F
- Network:
  * Protocol Family: evm
  * Network ID: base-mainnet
  * Chain ID: 8453
- Native Balance: 37507762138705 WEI
-------------------

-------------------
Wrapped 1000 of token 0xe8E55A847Bb446d967ef92F4580162fb8f2d3F38 as a SuperToken.  Transaction hash: 0xd073bb4a85cbc79dc55f9996c500d141a257ccc2e173cce8f09017ec53b7cce6
-------------------
Successfully wrapped 1000 of the ERC20 token (0xe8E55A847Bb446d967ef92F4580162fb8f2d3F38) into the Super token. 

Transaction hash: **0xd073bb4a85cbc79dc55f9996c500d141a257ccc2e173cce8f09017ec53b7cce6**. 

If you have any more requests or questions, feel free to ask!
-------------------
Create Stream
Prompt: Can you create a superfluid stream of the super token 0x2D92C37A341f9C7AE0438179ee2835B9Bb692e40 at a rate of 100 to address 0x8F4359D1C2166452b5e7a02742D6fe9ca5448FDe

-------------------
Created stream of token 0x2D92C37A341f9C7AE0438179ee2835B9Bb692e40 to 0x8F4359D1C2166452b5e7a02742D6fe9ca5448FDe at a rate of 100. The link to the stream is https://app.superfluid.finance/stream/base-mainnet/0x77841ee4399EFfc9b515c09cf239D784392F897F-0x8F4359D1C2166452b5e7a02742D6fe9ca5448FDe-0x2D92C37A341f9C7AE0438179ee2835B9Bb692e40.  Transaction hash: 0xe108156834b91a52ab431e92095e171c93800d02f590271c5364eac26561dda3
-------------------
The Superfluid stream has been successfully created! You are streaming the SuperToken (0x2D92C37A341f9C7AE0438179ee2835B9Bb692e40) at a rate of 100 to the address **0x8F4359D1C2166452b5e7a02742D6fe9ca5448FDe**.

Here is the transaction hash for your reference: **0xe108156834b91a52ab431e92095e171c93800d02f590271c5364eac26561dda3**.

You can view the stream details using the following link: [View Stream](https://app.superfluid.finance/stream/base-mainnet/0x77841ee4399EFfc9b515c09cf239D784392F897F-0x8F4359D1C2166452b5e7a02742D6fe9ca5448FDe-0x2D92C37A341f9C7AE0438179ee2835B9Bb692e40).
-------------------
Pools
Prompt: Can you create a pool of the token 0x2D92C37A341f9C7AE0438179ee2835B9Bb692e40

-------------------
Created pool of token 0x2D92C37A341f9C7AE0438179ee2835B9Bb692e40 at 0x8651059749637291C4175d539d4eA62340aFBcAD.  Transaction hash: 0x0b717b24504452ebf18a3f707b882f247a25cff1d7302d21f71916f21b9379a5
-------------------
The pool for the token **0x2D92C37A341f9C7AE0438179ee2835B9Bb692e40** has been created successfully. 

- **Pool Address:** 0x8651059749637291C4175d539d4eA62340aFBcAD
- **Transaction Hash:** [0x0b717b24504452ebf18a3f707b882f247a25cff1d7302d21f71916f21b9379a5](https://etherscan.io/tx/0x0b717b24504452ebf18a3f707b882f247a25cff1d7302d21f71916f21b9379a5)

If you need any further assistance, feel free to ask!
-------------------

Checklist

A couple of things to include in your PR for completeness:

  • Added documentation to all relevant README.md files
  • Added a changelog entry

@gtspencer gtspencer requested a review from murrlincoln as a code owner August 12, 2025 20:03
@cb-heimdall
Copy link

cb-heimdall commented Aug 12, 2025

✅ Heimdall Review Status

Requirement Status More Info
Reviews 1/1
Denominator calculation
Show calculation
1 if user is bot 0
1 if user is external 0
2 if repo is sensitive 0
From .codeflow.yml 1
Additional review requirements
Show calculation
Max 0
0
From CODEOWNERS 0
Global minimum 0
Max 1
1
1 if commit is unverified 0
Sum 1

@github-actions github-actions bot added documentation Improvements or additions to documentation action provider New action provider typescript labels Aug 12, 2025
@phdargen
Copy link
Contributor

Hi @gtspencer, thanks for the contribution! Looks pretty good, a few initial comments below before I will do some manual testing

@phdargen
Copy link
Contributor

We require all commits to be verified, unfortunately your last one is not signed. Please rebase against main and resign the commit

@phdargen
Copy link
Contributor

Could you please add more comprehensive test output to the PR description, ideally prompts for all new actions?

@@ -0,0 +1,5 @@
---
"@coinbase/agentkit": minor
Copy link
Contributor

Choose a reason for hiding this comment

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

minor -> patch

export * from "./schemas";
export * from "./superfluidStreamActionProvider";
export * from "./superfluidPoolActionProvider";
export * from "./superfluidQueryActionProvider";
Copy link
Contributor

Choose a reason for hiding this comment

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

might be good to add a 'SuperfluidActionProvider' wrapper combining the 3 action providers for convenience

export const SuperfluidCreateStreamSchema = z
.object({
erc20TokenAddress: z.string().describe("The ERC20 token to start or update streaming"),
chainId: z.string().describe("The EVM chain ID on which the ERC20 is deployed"),
Copy link
Contributor

Choose a reason for hiding this comment

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

it appears chainId is unused

).args;

if (success) {
return `Created pool of token ${args.erc20TokenAddress} at ${poolAddress}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

please add txHashs in all returns

* @param network - The network to check.
* @returns True if the Superfluid action provider supports the network, false otherwise.
*/
supportsNetwork = (network: Network) => network.protocolFamily === "evm";
Copy link
Contributor

Choose a reason for hiding this comment

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

should be restricted to base, base-sepolia here

*/
export const SuperfluidDeleteStreamSchema = z
.object({
erc20TokenAddress: z.string().describe("The ERC20 token to start streaming"),
Copy link
Contributor

Choose a reason for hiding this comment

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

suggest to add .regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format") to all addresses


return `Updated member units of pool ${args.poolAddress} for member ${args.recipientAddress}, with new member units ${args.units}`;
} catch (error) {
return `Error creating Superfluid pool: ${error}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

creating -> updating


return `Current outflows are ${JSON.stringify(activeOutflows)}`;
} catch (error) {
return `Error creating Superfluid pool: ${error}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

creating -> querying


return `Updated stream of token ${args.erc20TokenAddress} to ${args.recipientAddress} at a rate of ${args.flowRate}`;
} catch (error) {
return `Error creating Superfluid stream: ${error}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

creating -> updating


return `Stopped stream of token ${args.erc20TokenAddress} to ${args.recipientAddress}`;
} catch (error) {
return `Error creating Superfluid stream: ${error}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

creating -> stopping

@phdargen
Copy link
Contributor

Main concern is Superfluid wrapped tokens vs erc20.

It is my understanding that you cant stream a erc20 token directly but need to supply a superfluid wrapped token.
At the very least, this should clearly be stated in the README and action descriptions.

Ideally, the createStream action would first wrap a given erc20 token before calling createFlow or alternatively a new action added that does the wrapping.

@gtspencer gtspencer force-pushed the feat/superfluid-typescript branch from 1d03ef4 to e3f5f84 Compare August 14, 2025 16:06
@gtspencer
Copy link
Contributor Author

hey @phdargen thanks for the review! just addressed all your concerns:

  • fixed minor issues involving hash response, networks, changeset type, and verbiage
  • added a superfluid wrapper and Super Token creator action. This way, if a stream action fails, the agent knows it may need to create the super token first and will prompt the user with that action
  • Signed single commit

Ready for a new review at your leisure!

@gtspencer gtspencer changed the title Feat/superfluid typescript feat: superfluid typescript action provider Aug 14, 2025
@gtspencer gtspencer force-pushed the feat/superfluid-typescript branch from 7487dfd to 716b63d Compare August 15, 2025 15:29
@gtspencer gtspencer requested a review from phdargen August 15, 2025 20:19
},
] as const;

export const SuperTokenFactoryAddress = "0xe20B9a38E0c96F61d1bA6b42a61512D56Fea1Eb3" as const;
Copy link
Contributor

Choose a reason for hiding this comment

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

superTokenFactory address is different on base-sepolia (0x7447E94Dfe3d804a9f46Bf12838d467c912C8F6C), see https://docs.superfluid.org/docs/protocol/contract-addresses. Would be good if users could try the workflow on testnet

Comment on lines 1 to 6
export * from "./schemas";
export * from "./superfluidStreamActionProvider";
export * from "./superfluidPoolActionProvider";
export * from "./superfluidQueryActionProvider";
export * from "./superfluidWrapperActionProvider";
export * from "./superfluidSuperTokenCreatorActionProvider";
Copy link
Contributor

Choose a reason for hiding this comment

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

Please move exports back to index.ts.

Instead add the following here that aggregates all superfluid actions a for a single import in eg chatbot files:

Suggested change
export * from "./schemas";
export * from "./superfluidStreamActionProvider";
export * from "./superfluidPoolActionProvider";
export * from "./superfluidQueryActionProvider";
export * from "./superfluidWrapperActionProvider";
export * from "./superfluidSuperTokenCreatorActionProvider";
import { ActionProvider } from "../actionProvider";
import { EvmWalletProvider } from "../../wallet-providers";
import { Network } from "../../network";
import { SuperfluidStreamActionProvider } from "./superfluidStreamActionProvider";
import { SuperfluidPoolActionProvider } from "./superfluidPoolActionProvider";
import { SuperfluidQueryActionProvider } from "./superfluidQueryActionProvider";
import { SuperfluidWrapperActionProvider } from "./superfluidWrapperActionProvider";
import { SuperfluidSuperTokenCreatorActionProvider } from "./superfluidSuperTokenCreatorActionProvider";
/**
* SuperfluidActionProvider aggregates all Superfluid-related actions into a single provider.
*/
export class SuperfluidActionProvider extends ActionProvider<EvmWalletProvider> {
constructor() {
super("superfluid", [
new SuperfluidStreamActionProvider(),
new SuperfluidPoolActionProvider(),
new SuperfluidQueryActionProvider(),
new SuperfluidWrapperActionProvider(),
new SuperfluidSuperTokenCreatorActionProvider(),
]);
}
/**
* Supports Base mainnet and Base sepolia like the underlying Superfluid providers.
*/
supportsNetwork = (network: Network) =>
network.networkId === "base-mainnet" || network.networkId === "base-sepolia";
}
export const superfluidActionProvider = () => new SuperfluidActionProvider();


return `Created super token for ${args.erc20TokenAddress}. Super token address at ${superTokenAddress} Transaction hash: ${createSuperTokenHash}`;
} catch (error) {
return `Error creating Superfluid stream: ${error}`;
Copy link
Contributor

Choose a reason for hiding this comment

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

stream -> token

Comment on lines 68 to 71
const publicClient = createPublicClient({
chain: NETWORK_ID_TO_VIEM_CHAIN[walletProvider.getNetwork().networkId || "base-mainnet"],
transport: http(),
});

// We don't get contract call results, so we need to simulate to get the pool address
const { result } = await publicClient.simulateContract({
Copy link
Contributor

Choose a reason for hiding this comment

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

Following #824, please remove createPublicClient and use walletProvider.getPublicClient().simulateContract instead

@@ -0,0 +1,187 @@
# Superfluid Action Provider

This directory contains the implementation for Superfluid, broken up by functionality:
Copy link
Contributor

Choose a reason for hiding this comment

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

Please add link to docs: https://docs.superfluid.org/

@phdargen
Copy link
Contributor

Please also add an entry in agentkit/README (https://github.com/coinbase/agentkit/blob/main/typescript/agentkit/README.md), should follow alphabetic order

name: "wrap_token",
description: `
This tool will directly wrap an amount of ERC20 tokens into its corresponding Super token.
The user must provide the erc20 address, and the super token address. This will be done on Base Mainnet
Copy link
Contributor

Choose a reason for hiding this comment

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

remove "This will be done on Base Mainnet"

* @returns A JSON string containing the account details or error message
*/
@CreateAction({
name: "wrap_token",
Copy link
Contributor

Choose a reason for hiding this comment

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

wrap_token -> wrap_superfluid_token

args: [],
});

const amount = parseUnits(String(args.wrapAmount), Number(decimals));
Copy link
Contributor

Choose a reason for hiding this comment

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

This didnt work well in my tests, the LLM always confused the units.

Try rephrasing the action description: "The user must provide the erc20 address, and the super token address. " -> " It takes the following inputs:

  • erc20TokenAddress: The address of the ERC20 token to wrap
  • superTokenAddress: The address of the Super token
  • wrapAmount: The amount of tokens to wrap in whole units (e.g. 1.5 WETH, 10 USDC)
    Use wrapAmount units exactly as provided, do not convert to wei or any other units.
    "

and in shema: "The amount of token to wrap. This should be in units of token (and will not take decimals into account)" -> "The amount of tokens to wrap in whole units (e.g. 1.5 WETH, 10 USDC)"

Comment on lines 8 to 19
erc20TokenAddress: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
.describe("The ERC20 Super token to start or update streaming"),
recipientAddress: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
.describe("The EVM address to stream the token to."),
flowRate: z.string().describe("The rate at which the ERC20 is streamed to the recipient"),
})
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: erc20TokenAddress -> superTokenAddress, same in SuperfluidDeleteStreamSchema and SuperfluidCreatePoolSchema

erc20TokenAddress: z
.string()
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
.describe("The ERC20 token to start streaming"),
Copy link
Contributor

Choose a reason for hiding this comment

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

token -> Super token

.string()
.regex(/^0x[a-fA-F0-9]{40}$/, "Invalid Ethereum address format")
.describe("The EVM address to stream the token to."),
flowRate: z.string().describe("The rate at which the ERC20 is streamed to the recipient"),
Copy link
Contributor

Choose a reason for hiding this comment

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

The flow rate needs additional explanation. Is this tokens per sec/min/h/day/...? Does it consider token decimals?
Please also add this info to action description

const receipt = await walletProvider.waitForTransactionReceipt(createSuperTokenHash);

const superTokenAddress = extractCreatedSuperTokenAddressAbi(receipt);
console.log(superTokenAddress);
Copy link
Contributor

Choose a reason for hiding this comment

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

please remove all console.log


const receipt = await walletProvider.waitForTransactionReceipt(createSuperTokenHash);

const superTokenAddress = extractCreatedSuperTokenAddressAbi(receipt);
Copy link
Contributor

Choose a reason for hiding this comment

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

Note: this fails for CdpSmartWallets as receipts doesnt include logs. You can ignore this, will look into it

@phdargen
Copy link
Contributor

hey @phdargen thanks for the review! just addressed all your concerns:

  • fixed minor issues involving hash response, networks, changeset type, and verbiage
  • added a superfluid wrapper and Super Token creator action. This way, if a stream action fails, the agent knows it may need to create the super token first and will prompt the user with that action
  • Signed single commit

Ready for a new review at your leisure!

Hi @gtspencer, thanks for the update! I tested it and it works well except for some confusion with the units. Please have a look at my follow-up comments, then we should be able to get this in

@gtspencer gtspencer force-pushed the feat/superfluid-typescript branch from f5dfe88 to 7a267b3 Compare August 20, 2025 18:02
@gtspencer
Copy link
Contributor Author

Hey @phdargen , thanks for the review! Updated everything, made some changes to the prompts, and added more information on flow rate. Let me know if there's anything else outstanding. Thank you!

});

const createSuperTokenHash = await walletProvider.sendTransaction({
to: SuperTokenFactoryAddress as `0x${string}`,
Copy link
Contributor

Choose a reason for hiding this comment

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

should be: to: walletProvider.getNetwork().networkId==="base-sepolia" ? SuperTokenFactoryAddress_Base_Sepolia as 0x${string}: SuperTokenFactoryAddress as0x${string},

@phdargen
Copy link
Contributor

Hey @phdargen , thanks for the review! Updated everything, made some changes to the prompts, and added more information on flow rate. Let me know if there's anything else outstanding. Thank you!

Hi @gtspencer, looks good! Just one more fix to use the correct contract depending on network

@gtspencer gtspencer force-pushed the feat/superfluid-typescript branch from 4513f32 to 86b6ba7 Compare August 20, 2025 23:04
@gtspencer
Copy link
Contributor Author

oops! Just updated! @phdargen

@phdargen
Copy link
Contributor

This is tested and good to go imo @CarsonRoscoe

@CarsonRoscoe CarsonRoscoe merged commit 4d0e5d1 into coinbase:main Aug 21, 2025
26 checks passed
@phdargen phdargen mentioned this pull request Sep 4, 2025
7 tasks
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

action provider New action provider documentation Improvements or additions to documentation typescript

Development

Successfully merging this pull request may close these issues.

4 participants