How Streaming Works
Technical explanation of Superfluid streaming on LinQup
How Real-Time Streaming Works
Deep dive into the technical architecture powering LinQup's payment streaming.
Overview
LinQup uses Superfluid Protocol to enable continuous, real-time payment streams on Polygon blockchain.
Key Concept
Instead of discrete transactions, streams are continuous agreements where funds flow every second without requiring individual transactions.
Superfluid Protocol
What is Superfluid?
Superfluid is a smart contract framework for programmable cashflows on Ethereum-compatible blockchains.
Core innovation:
- Money streams continuously
- No per-second transactions needed
- Gas-efficient (one tx to start, one to stop)
- Composable with DeFi
Constant Flow Agreements (CFA)
CFA = The streaming primitive
A CFA is a smart contract agreement between two addresses:
{
sender: 0xABC... (supporter)
receiver: 0xXYZ... (creator)
flowRate: 385802469135 (wei per second)
token: USDCx
startTime: 1234567890
}flowRate explained:
- Measured in wei per second (smallest unit, 18 decimals for USDCx)
- Example: 3,858,024,691,358,024 wei/sec ≈ $10/month
- Immutable once created (must delete and recreate to change)
Super Tokens
What are Super Tokens?
ERC-20 tokens extended with streaming capability.
USDCx = USDC Super Token
- Wraps regular USDC (1:1)
- Adds streaming functions
- Fully compatible with USDC otherwise
- Can unwrap back anytime
Technical implementation:
// Wrapping USDC → USDCx
function upgrade(uint256 amount) external;
// Unwrapping USDCx → USDC
function downgrade(uint256 amount) external;How Streams Work
Starting a Stream
High-level flow:
- User action: Click "Start Stream" in widget
- Approval (if first time): Approve USDCx spending
- Create Flow: Call Superfluid CFAv1.createFlow()
- Smart contract: Records agreement on-chain
- Streaming begins: Balance updates every second
Smart contract call:
// Simplified version
await cfaV1.createFlow({
sender: supporterAddress,
receiver: creatorAddress,
superToken: usdcxAddress,
flowRate: "3858024691358024", // wei per second for $10/month
});On-chain state change:
mapping(address => mapping(address => int96)) flowRates;
// flowRates[supporter][creator] = 3858024691358024Calculating Balance
Real-time balance = No database queries needed
Superfluid calculates balances mathematically:
function realtimeBalanceOf(address account) returns (uint256) {
int256 balance = staticBalance[account];
int96 netFlowRate = totalInflow[account] - totalOutflow[account];
uint256 timeSinceUpdate = block.timestamp - lastUpdateTime[account];
return balance + (netFlowRate * timeSinceUpdate);
}Example:
- Static balance: 100 USDCx (last recorded)
- Net flow: -0.0000038 USDCx/sec (outflow of $10/month)
- Time elapsed: 86400 seconds (1 day)
- Real-time balance: 100 - (0.0000038 × 86400) = 99.67 USDCx
Updating a Stream
Cannot update flow rate directly
Must delete old flow and create new flow:
// Change from $10/month to $20/month
await cfaV1.deleteFlow({
sender: supporterAddress,
receiver: creatorAddress,
superToken: usdcxAddress,
});
await cfaV1.createFlow({
sender: supporterAddress,
receiver: creatorAddress,
superToken: usdcxAddress,
flowRate: "7716049382716049", // $20/month
});LinQup widget handles this automatically
Stopping a Stream
Simple delete operation:
await cfaV1.deleteFlow({
sender: supporterAddress,
receiver: creatorAddress,
superToken: usdcxAddress,
});What happens:
- Flow rate set to 0
- Final balances calculated
- Stream agreement removed
- Both parties can access their updated balances
Gas Efficiency
Why Streaming is Gas-Efficient
Traditional approach (sending $10/month in daily payments):
- 30 transactions per month
- 30 × $0.01 = $0.30 gas cost
Streaming approach:
- 1 transaction to start
- 1 transaction to stop
- 2 × $0.01 = $0.02 gas cost
Savings: 93% reduction for 1-month stream
Gas Costs Breakdown
One-time setup (per wallet):
- Wrap USDC → USDCx: ~50,000 gas
- Approve USDCx spending: ~45,000 gas
- Total: ~95,000 gas ≈ $0.02
Per stream:
- Create flow: ~250,000 gas ≈ $0.01
- Update flow: ~300,000 gas ≈ $0.015
- Delete flow: ~100,000 gas ≈ $0.005
While streaming:
- Gas cost per second: 0 gas
- No transactions needed
- Balance updates are pure calculations
Smart Contract Architecture
LinQup Contract Interactions
┌─────────────┐
│ Wallet │
│ (MetaMask) │
└──────┬──────┘
│
├─────────────────┐
│ │
v v
┌──────────────┐ ┌─────────────────┐
│ USDCx Token │ │ Superfluid CFAv1│
│ (ERC-20+) │ │ (Streaming) │
└──────────────┘ └─────────────────┘Key contracts on Polygon:
- USDC:
0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359 - USDCx:
0x07b24BBD834c1c546EcE89fF95f71D9F13a2eBD1 - CFAv1:
0x6EeE6060f715257b970700bc2656De21dEdF074C
CFAv1 Interface
Core functions LinQup uses:
interface IConstantFlowAgreementV1 {
// Create new stream
function createFlow(
ISuperToken token,
address receiver,
int96 flowRate,
bytes calldata ctx
) external returns (bytes memory newCtx);
// Update existing stream
function updateFlow(
ISuperToken token,
address receiver,
int96 flowRate,
bytes calldata ctx
) external returns (bytes memory newCtx);
// Delete stream
function deleteFlow(
ISuperToken token,
address sender,
address receiver,
bytes calldata ctx
) external returns (bytes memory newCtx);
// Query flow data
function getFlow(
ISuperToken token,
address sender,
address receiver
) external view returns (
uint256 timestamp,
int96 flowRate,
uint256 deposit,
uint256 owedDeposit
);
}Flow Rate Calculations
Converting $/month to wei/second
Formula:
function monthlyToFlowRate(dollarPerMonth: number): string {
const SECONDS_PER_MONTH = 30 * 24 * 60 * 60; // 2,592,000
const USDCX_DECIMALS = 18; // USDCx uses 18 decimals (standard ERC-20)
// Convert dollars to smallest unit (wei with 18 decimals)
const weiPerMonth = BigInt(dollarPerMonth) * (10n ** BigInt(USDCX_DECIMALS));
// Divide by seconds in month (integer division)
const flowRate = weiPerMonth / BigInt(SECONDS_PER_MONTH);
return flowRate.toString();
}
// Example: $10/month
// = 10 * 10^18 wei
// = 10,000,000,000,000,000,000 / 2,592,000
// = 3,858,024,691,358,024 wei per secondPrecision:
Superfluid uses int96 for flow rates:
- Max value: 2^95 - 1 (positive flows)
- Min value: -2^95 (negative flows)
- Precision: 1 wei per second
Converting wei/second to $/month
Reverse formula:
function flowRateToMonthly(flowRate: string): number {
const SECONDS_PER_MONTH = 30 * 24 * 60 * 60;
const USDCX_DECIMALS = 18;
const weiPerSecond = BigInt(flowRate);
const weiPerMonth = weiPerSecond * BigInt(SECONDS_PER_MONTH);
const dollarsPerMonth = Number(weiPerMonth) / (10 ** USDCX_DECIMALS);
return dollarsPerMonth;
}
// Example: 3,858,024,691,358,024 wei/sec
// = 3,858,024,691,358,024 * 2,592,000
// = 9,999,999,999,999,998,208,000 wei per month
// = 9.999999999... USDCx
// ≈ $10/monthDeposits & Solvency
Security Deposit
Why deposits exist:
Superfluid requires a small deposit when creating flows to ensure solvency.
Deposit calculation:
deposit = flowRate × 3600 secondsExample: $10/month stream
- Flow rate: 3,858,024,691,358,024 wei/sec
- Deposit: 3,858,024,691,358,024 × 3600 = 13,888,888,888,888,886,400 wei
- = 0.0139 USDCx deposit (approximately 1 hour worth)
What happens to deposit:
- Locked while stream active
- Returned when stream stops
- Acts as buffer for protocol
Solvency Enforcement
Critical balance = Point where account becomes insolvent
criticalBalance = -depositWhat happens at critical balance:
- Stream automatically stops
- Deposit used to pay final amounts
- Account marked as critical (liquidatable)
- Must add funds to resume
LinQup prevents this:
- Widget shows time until funds run out
- Warns before reaching critical balance
- Stops stream before insolvency
Advanced: Aggregate Flows
Net Flow Rate
For accounts with multiple streams:
netFlowRate = totalInflow - totalOutflowExample supporter streaming to 3 creators:
- Creator A: 3,858,024,691,358,024 wei/sec ($10/month)
- Creator B: 1,929,012,345,679,012 wei/sec ($5/month)
- Creator C: 7,716,049,382,716,049 wei/sec ($20/month)
- Total outflow: 13,503,086,419,753,085 wei/sec ($35/month)
- Net flow: -13,503,086,419,753,085 wei/sec (negative = losing money)
Example creator receiving from 5 supporters:
- Supporter 1: 3,858,024,691,358,024 wei/sec
- Supporter 2: 7,716,049,382,716,049 wei/sec
- Supporter 3: 1,929,012,345,679,012 wei/sec
- Supporter 4: 3,858,024,691,358,024 wei/sec
- Supporter 5: 11,574,074,074,074,074 wei/sec
- Total inflow: 28,935,185,185,185,183 wei/sec ($75/month)
- Net flow: +28,935,185,185,185,183 wei/sec (positive = earning money)
Balance Projections
Time until balance runs out:
function timeUntilEmpty(balance: bigint, netFlowRate: bigint): number {
if (netFlowRate >= 0) return Infinity; // Earning or neutral
const secondsUntilEmpty = balance / -netFlowRate;
return Number(secondsUntilEmpty);
}
// Example:
// Balance: 100 USDCx = 100 * 10^18 wei = 100,000,000,000,000,000,000 wei
// Net flow: -3,858,024,691,358,024 wei/sec ($10/month outflow)
// Time: 100,000,000,000,000,000,000 / 3,858,024,691,358,024 ≈ 25,920,000 seconds
// = 432,000 minutes = 7,200 hours = 300 days (about 10 months)Ethereum Events
Monitoring Streams
FlowUpdated event:
event FlowUpdated(
ISuperToken indexed token,
address indexed sender,
address indexed receiver,
int96 flowRate,
int256 totalSenderFlowRate,
int256 totalReceiverFlowRate,
bytes userData
);LinQup uses this to:
- Update dashboard in real-time
- Track stream history
- Calculate cumulative totals
- Trigger notifications
Querying Historical Streams
Using The Graph (Superfluid Subgraph):
query GetUserStreams($address: String!) {
streams(where: { sender: $address }) {
receiver {
id
}
currentFlowRate
streamedUntilUpdatedAt
updatedAtTimestamp
}
}Polygon Superfluid Subgraph:
https://api.thegraph.com/subgraphs/name/superfluid-finance/protocol-v1-matic
Security Considerations
Immutability
What you CAN'T do:
- Increase someone else's outflow
- Steal streaming funds
- Reverse/cancel past streams
- Deny stream deletion by sender
What you CAN do:
- Delete your own outgoing streams anytime
- Stop receiving (delete incoming stream)
- Withdraw your balance anytime
Smart Contract Risks
Audited by:
- OpenZeppelin
- Consensys Diligence
- Runtime Verification
Deployed contracts:
- Immutable (cannot be upgraded maliciously)
- Open source (verified on Polygonscan)
- Battle-tested (millions in TVL)
Common Attacks & Mitigations
Attack: Drain wallet via stream
- Mitigation: Only sender can create flows from their address
- Signing: Requires wallet signature approval
Attack: Prevent stream deletion
- Mitigation: deleteFlow always succeeds for sender
- Protocol guarantee: Sender has absolute control
Attack: Front-run stream updates
- Mitigation: Updates are atomic on-chain
- MEV protection: Polygon PoS less vulnerable than mainnet
Implementation in LinQup
Widget Flow
Starting a stream from LinQup widget:
// 1. Check USDCx balance
const balance = await usdcxContract.balanceOf(userAddress);
// 2. Calculate flow rate from monthly input
const monthlyAmount = 10; // $10/month from user input
const flowRate = calculateFlowRate(monthlyAmount);
// 3. Check if flow already exists
const existingFlow = await cfaV1.getFlow({
superToken: usdcxAddress,
sender: userAddress,
receiver: creatorAddress,
});
// 4. Create or update flow
if (existingFlow.flowRate === "0") {
// Create new flow
await cfaV1.createFlow({
receiver: creatorAddress,
flowRate: flowRate,
userData: "0x", // Optional metadata
});
} else {
// Update existing flow (delete + create)
await cfaV1.deleteFlow({
sender: userAddress,
receiver: creatorAddress,
});
await cfaV1.createFlow({
receiver: creatorAddress,
flowRate: flowRate,
});
}
// 5. Record in LinQup database for analytics
await recordStreamStart({
supporterId: userAddress,
creatorId: creatorAddress,
flowRate: flowRate,
});Database Integration
LinQup stores:
- Stream metadata (names, messages)
- Historical totals
- Supporter preferences
- NFT associations
LinQup does NOT store:
- Current balances (query blockchain)
- Real-time flow rates (query Superfluid)
- Active stream state (query CFAv1)
Why? Blockchain is source of truth for money.
Testing Streaming
Local Development
Using Hardhat + Superfluid SDK:
# Install dependencies
npm install @superfluid-finance/ethereum-contracts
# Deploy Superfluid framework locally
npx hardhat run scripts/deploy-superfluid-framework.js
# Test streaming
npx hardhat test test/streaming.test.tsMumbai Testnet (Polygon Testnet)
Test USDCx on Mumbai:
- Address:
0x42bb40bF79730451B11f6De1CbA222F17b87Afd7 - Get from faucet:
https://faucet.polygon.technology/
CFAv1 on Mumbai:
- Address:
0x49e565Ed1bdc17F3d220f72DF0857C26FA83F873
Performance Characteristics
Gas Costs (Polygon)
Typical gas prices:
- Low: 30 gwei
- Average: 100 gwei
- High: 500 gwei
At 100 gwei:
- Create flow: 250,000 gas × 100 gwei = 0.025 MATIC ≈ $0.01
- Delete flow: 100,000 gas × 100 gwei = 0.01 MATIC ≈ $0.005
Scalability
Per-account limits:
- Max simultaneous outflows: Unlimited (gas-limited)
- Max simultaneous inflows: Unlimited
- Max flow rate: 2^95 wei/sec (practically unlimited)
Network limits:
- Polygon: ~30 TPS (conservative)
- Superfluid operations: ~300-500 gas per second (streaming)
- Sustainable streams per block: Thousands
Resources
Official Documentation
Developer Tools
LinQup Implementation
- Widget Package - IIFE widget code
- SDK Package - Superfluid wrapper
- Database Schema - Stream data models
Next Steps
- Start Streaming - Use LinQup
- Dashboard Guide - Manage streams
- Troubleshooting - Common issues