Superfluid Integration (Streaming Payments)
Traditional periodic payment approach in Web3 — approve + transferFrom on schedule, or prepay for multiple periods. Both require active user participation or trusting contract to hold large amounts. Superfluid solves differently: money flows per-second, balance updates in real-time without separate transactions. For subscription dApps, salary streaming or grant distribution — this is significant UX difference.
How Superfluid works under the hood
Super Tokens and real-time
Superfluid doesn't work with regular ERC-20. Need Super Token — overlay over ERC-20 via upgrade() function. User deposits 100 USDC → gets 100 USDCx (Super Token). USDCx is ERC-20 that understands streams.
balanceOf(address account) on Super Token returns real value accounting for all active streams: staticBalance + netFlowRate * (block.timestamp - lastUpdated). This view function looks forward and backward. No on-chain records each second — just update on opening/closing/changing stream.
Practical consequence: balance changes every second without transactions. This means transfer(recipient, amount) with "entire balance" is dangerous: between your balance calculation and transaction execution, time passes, real balance decreased.
CFAv1 (Constant Flow Agreement)
Main tool — IConstantFlowAgreementV1. Open stream:
ISuperfluid(host).callAgreement(
cfa,
abi.encodeWithSelector(
cfa.createFlow.selector,
token, // USDCx
receiver, // recipient address
flowRate, // wei per second (int96)
new bytes(0) // userData
),
"0x"
);
flowRate is int96, not uint256. Negative value means incoming stream. To calculate flowRate: monthlyAmount * 1e18 / (30 * 24 * 3600) — amount of wei USDCx per second.
Important nuance: int96 limited to ~39.6 * 10^27 wei/sec. For most use cases sufficient, but with non-standard decimals tokens need overflow check.
Liquidation and solvency buffer
Superfluid protects recipients if sender runs out of funds via liquidation mechanism. On stream opening sender deposits solvency buffer — usually 4-hour stream. If balance hits zero but stream unclosed — anyone can call liquidation via deleteFlow, getting buffer part as reward.
This changes UX requirements: integrating in dApp must warn user about minimum balance. If user has USDCx for 3-hour stream they can't open new stream (buffer = 4 hours). Common cause of confusing INSUFFICIENT_BALANCE errors.
Practical integration
Superfluid SDK
import { Framework } from "@superfluid-finance/sdk-core";
import { ethers } from "ethers";
const sf = await Framework.create({
chainId: 137, // Polygon
provider,
});
const usdcx = await sf.loadSuperToken("USDCx");
const createFlowOperation = usdcx.createFlow({
sender: userAddress,
receiver: recipientAddress,
flowRate: "385802469135802", // ~1000 USDC/month
});
const tx = await createFlowOperation.exec(signer);
SDK abstracts callAgreement calls. For production code use batchCall to combine multiple operations in one transaction: upgrade + createFlow for single gas.
Event handling
Key events to monitor:
-
FlowCreated(token, sender, receiver, flowRate, totalSenderFlowRate, totalReceiverFlowRate)— new stream -
FlowUpdated(...)— rate change -
FlowDeleted(...)— stream close (including liquidations)
For frontend real-time updates: WebSocket subscription via wagmi watchContractEvent or The Graph subscription (if Superfluid subgraph deployed for your network). Superfluid has official subgraph on mainnet, Polygon, Optimism, Arbitrum, BNB Chain.
Real case: subscription dApp without FlowDeleted monitoring from liquidations — users got content after subscription ended because frontend didn't know stream was liquidated. Solution: event listener + status check via cfa.getFlow().
Working with userData
createFlow takes bytes userData — arbitrary data emitted in event. Use for:
- Binding stream to subscription ID
- Passing referral code
- Identifying plan ID
bytes memory userData = abi.encode(subscriptionId, planId);
On event handler side — decode:
const [subscriptionId, planId] = ethers.utils.defaultAbiCoder.decode(
["uint256", "uint256"],
event.userData
);
ACL (Access Control List) for automation
If you need smart contract to manage streams on user's behalf (e.g., auto-update flowRate on plan change), use Superfluid ACL:
// User gives permission to contract
cfa.authorizeFlowOperatorWithFullControl(token, operatorContract, "0x");
Like ERC-20 approve but for stream management. Operator can create, modify, close streams on user's behalf within granted rights.
Supported networks and tokens
| Network | USDCx | ETHx | Native liquidity |
|---|---|---|---|
| Ethereum mainnet | Yes | Yes | Low (gas expensive) |
| Polygon | Yes | Yes | High |
| Optimism | Yes | Yes | Medium |
| Arbitrum One | Yes | Yes | Medium |
| BNB Chain | Yes | — | Medium |
For custom tokens: deploy Pure Super Token (no wrapper, native super token) via SuperTokenFactory. Used for in-app currency where underlying ERC-20 unneeded.
Working process
Analytics (0.5-1 day). Determine use case: subscriptions, salary, grants, rewards streaming. Choose network. Need ACL for auto stream management.
Development (2-4 days). Smart contract (if custom logic needed) + SDK integration on frontend + event handlers + liquidation monitoring.
Testing. Superfluid has testnet deploys on Polygon Mumbai (deprecated) and Sepolia. Fork tests with mainnet state via Foundry for complex business logic.
Timeline estimates
Basic integration (create/manage streams in frontend) without custom contract — 2-3 days. Full subscription system with custom contract, ACL and liquidation monitoring — 4-7 days.
Cost calculated individually after business logic analysis.







