Skip to content

Commit 911eb14

Browse files
cursoragentOba-One
andcommitted
feat: Integrate Superfluid streaming and improve jar config
This commit introduces Superfluid streaming capabilities, allowing for continuous token flows into the jar. It also refactors the jar configuration hook to use more accurate contract function names and adds new hooks for Superfluid account and token information. The pending tokens hook is updated to use the Superfluid SDK for fetching stream data. Co-authored-by: contact <[email protected]>
1 parent c2916cb commit 911eb14

File tree

12 files changed

+5172
-438
lines changed

12 files changed

+5172
-438
lines changed

client/components/jar/StreamingPanel.tsx

Lines changed: 176 additions & 86 deletions
Original file line numberDiff line numberDiff line change
@@ -20,10 +20,12 @@ import {
2020
Settings,
2121
ExternalLink
2222
} from "lucide-react";
23-
import { formatUnits, parseUnits, isAddress } from "viem";
23+
import { formatUnits, isAddress } from "viem";
2424
import { formatAddress } from "@/lib/app/utils";
2525
import { useStreamingData } from "@/hooks/jar/useStreamingData";
2626
import { useStreamingActions } from "@/hooks/jar/useStreamingActions";
27+
import { useSuperfluidAccountInfo } from "@/hooks/jar/useSuperfluidAccountInfo";
28+
import { useSuperfluidTokenInfo } from "@/hooks/blockchain/useSuperfluidTokenInfo";
2729
import { StreamProcessingCard } from "./StreamProcessingCard";
2830

2931

@@ -45,7 +47,7 @@ export const StreamingPanel: React.FC<StreamingPanelProps> = ({
4547
const [newStreamToken, setNewStreamToken] = useState("");
4648
const [newStreamRate, setNewStreamRate] = useState("");
4749

48-
// Use real contract hooks
50+
// Use real Superfluid SDK hooks
4951
const {
5052
streams,
5153
// streamingConfig: config,
@@ -57,18 +59,23 @@ export const StreamingPanel: React.FC<StreamingPanelProps> = ({
5759

5860
const {
5961
createSuperStream,
60-
isRegistering,
61-
isApproving,
62-
isProcessing,
62+
isCreating,
63+
updateSuperStream,
64+
deleteSuperStream,
6365
} = useStreamingActions(jarAddress);
6466

67+
const { data: accountInfo } = useSuperfluidAccountInfo(jarAddress);
68+
6569
const handleCreateSuperStream = async () => {
66-
await createSuperStream(newStreamToken, newStreamRate);
70+
try {
71+
await createSuperStream(newStreamToken, newStreamRate);
6772

68-
// Reset form on success
69-
setNewStreamSender("");
70-
setNewStreamToken("");
71-
setNewStreamRate("");
73+
// Reset form on success
74+
setNewStreamToken("");
75+
setNewStreamRate("");
76+
} catch (error) {
77+
console.error("Failed to create stream:", error);
78+
}
7279
};
7380

7481
if (isLoadingStreams && !streams.length) {
@@ -93,13 +100,65 @@ export const StreamingPanel: React.FC<StreamingPanelProps> = ({
93100
</CardHeader>
94101

95102
<CardContent>
96-
<Tabs defaultValue="active" className="space-y-4">
103+
<Tabs defaultValue="overview" className="space-y-4">
97104
<TabsList className="grid w-full grid-cols-3">
105+
<TabsTrigger value="overview">Overview</TabsTrigger>
98106
<TabsTrigger value="active">Active Streams</TabsTrigger>
99107
{isAdmin && <TabsTrigger value="manage">Manage</TabsTrigger>}
100-
<TabsTrigger value="config">Configuration</TabsTrigger>
101108
</TabsList>
102109

110+
<TabsContent value="overview" className="space-y-4">
111+
{accountInfo && (
112+
<Card>
113+
<CardHeader>
114+
<CardTitle className="text-lg">Jar Flow Overview</CardTitle>
115+
</CardHeader>
116+
<CardContent>
117+
<div className="grid grid-cols-2 gap-4 text-sm">
118+
<div>
119+
<span className="text-gray-600">Net Flow Rate:</span>
120+
<div className="font-mono text-green-600">
121+
{accountInfo.formattedNetFlowRate} ETH/s
122+
</div>
123+
</div>
124+
<div>
125+
<span className="text-gray-600">Total Deposit:</span>
126+
<div className="font-mono text-blue-600">
127+
{accountInfo.formattedTotalDeposit} ETH
128+
</div>
129+
</div>
130+
</div>
131+
</CardContent>
132+
</Card>
133+
)}
134+
135+
<Card>
136+
<CardHeader>
137+
<CardTitle className="text-lg">Stream Statistics</CardTitle>
138+
</CardHeader>
139+
<CardContent>
140+
<div className="grid grid-cols-3 gap-4 text-center">
141+
<div>
142+
<div className="text-2xl font-bold text-blue-600">{streams.length}</div>
143+
<div className="text-sm text-gray-600">Active Streams</div>
144+
</div>
145+
<div>
146+
<div className="text-2xl font-bold text-green-600">
147+
{streams.reduce((sum, stream) => sum + Number(formatUnits(stream.ratePerSecond, 18)), 0).toFixed(3)}
148+
</div>
149+
<div className="text-sm text-gray-600">Total Rate (ETH/s)</div>
150+
</div>
151+
<div>
152+
<div className="text-2xl font-bold text-purple-600">
153+
{streams.reduce((sum, stream) => sum + Number(formatUnits(stream.totalStreamed, 18)), 0).toFixed(3)}
154+
</div>
155+
<div className="text-sm text-gray-600">Total Streamed (ETH)</div>
156+
</div>
157+
</div>
158+
</CardContent>
159+
</Card>
160+
</TabsContent>
161+
103162
<TabsContent value="active" className="space-y-4">
104163
{streams.length === 0 ? (
105164
<div className="text-center py-8 text-gray-500">
@@ -108,78 +167,14 @@ export const StreamingPanel: React.FC<StreamingPanelProps> = ({
108167
</div>
109168
) : (
110169
streams.map((stream) => (
111-
<Card key={stream.id} className="border-l-4 border-l-blue-500">
112-
<CardContent className="p-4">
113-
<div className="flex items-start justify-between">
114-
<div className="space-y-2">
115-
<div className="flex items-center gap-2">
116-
<Badge variant={stream.isApproved ? "default" : "secondary"}>
117-
{stream.isApproved ? "Active" : "Pending Approval"}
118-
</Badge>
119-
<span className="text-sm font-mono">
120-
Stream #{stream.id}
121-
</span>
122-
</div>
123-
124-
<div className="text-sm space-y-1">
125-
<p>
126-
<span className="font-medium">From:</span>{" "}
127-
<span className="font-mono">{formatAddress(stream.sender)}</span>
128-
</p>
129-
<p>
130-
<span className="font-medium">Rate:</span>{" "}
131-
{formatStreamRate(stream.ratePerSecond, 18)}
132-
</p>
133-
<p>
134-
<span className="font-medium">Total Streamed:</span>{" "}
135-
{formatUnits(stream.totalStreamed, 18)}
136-
</p>
137-
</div>
138-
139-
{stream.isApproved && (
140-
<div className="bg-green-50 p-2 rounded text-sm">
141-
<p className="text-green-700 font-medium">
142-
Claimable: {formatUnits(calculateClaimable(stream), 18)} tokens
143-
</p>
144-
</div>
145-
)}
146-
</div>
147-
148-
<div className="flex gap-2">
149-
{/* {!stream.isApproved && isAdmin && (
150-
<Button
151-
size="sm"
152-
onClick={() => handleApproveStream(stream.id)}
153-
disabled={isApproving}
154-
>
155-
{isApproving ? (
156-
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-1"></div>
157-
) : (
158-
<CheckCircle className="h-4 w-4 mr-1" />
159-
)}
160-
Approve
161-
</Button>
162-
)} */}
163-
{/*
164-
{stream.isApproved && (
165-
<Button
166-
size="sm"
167-
variant="outline"
168-
onClick={() => handleProcessStream(stream.id)}
169-
disabled={isProcessing}
170-
>
171-
{isProcessing ? (
172-
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-400 mr-1"></div>
173-
) : (
174-
<Play className="h-4 w-4 mr-1" />
175-
)}
176-
Process
177-
</Button>
178-
)} */}
179-
</div>
180-
</div>
181-
</CardContent>
182-
</Card>
170+
<StreamCard
171+
key={stream.id}
172+
stream={stream}
173+
onUpdate={(rate) => updateSuperStream(stream.token, rate)}
174+
onDelete={() => deleteSuperStream(stream.token)}
175+
isUpdating={isUpdating}
176+
isDeleting={isDeleting}
177+
/>
183178
))
184179
)}
185180
</TabsContent>
@@ -215,10 +210,10 @@ export const StreamingPanel: React.FC<StreamingPanelProps> = ({
215210

216211
<Button
217212
onClick={handleCreateSuperStream}
218-
disabled={isRegistering}
213+
disabled={isCreating}
219214
className="w-full"
220215
>
221-
{isRegistering ? (
216+
{isCreating ? (
222217
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-white mr-2"></div>
223218
) : (
224219
<Settings className="h-4 w-4 mr-2" />
@@ -234,3 +229,98 @@ export const StreamingPanel: React.FC<StreamingPanelProps> = ({
234229
</Card>
235230
);
236231
};
232+
233+
interface StreamCardProps {
234+
stream: any; // Using any for now, should be properly typed
235+
onUpdate: (rate: string) => void;
236+
onDelete: () => void;
237+
isUpdating: boolean;
238+
isDeleting: boolean;
239+
}
240+
241+
const StreamCard: React.FC<StreamCardProps> = ({
242+
stream,
243+
onUpdate,
244+
onDelete,
245+
isUpdating,
246+
isDeleting
247+
}) => {
248+
const { data: tokenInfo } = useSuperfluidTokenInfo(stream.token);
249+
250+
return (
251+
<Card className="border-l-4 border-l-blue-500">
252+
<CardContent className="p-4">
253+
<div className="flex items-start justify-between">
254+
<div className="space-y-2 flex-1">
255+
<div className="flex items-center gap-2">
256+
<Badge variant={stream.isActive ? "default" : "secondary"}>
257+
{stream.isActive ? "Active" : "Inactive"}
258+
</Badge>
259+
<span className="text-sm font-mono text-gray-600">
260+
{stream.tokenSymbol || "TOKEN"}
261+
</span>
262+
</div>
263+
264+
<div className="text-sm space-y-1">
265+
<p>
266+
<span className="font-medium">From:</span>{" "}
267+
<span className="font-mono">{formatAddress(stream.sender)}</span>
268+
</p>
269+
<p>
270+
<span className="font-medium">Rate:</span>{" "}
271+
{formatStreamRate(stream.ratePerSecond, 18)}
272+
</p>
273+
<p>
274+
<span className="font-medium">Total Streamed:</span>{" "}
275+
{formatUnits(stream.totalStreamed, 18)}
276+
</p>
277+
</div>
278+
279+
{stream.isActive && (
280+
<div className="bg-green-50 p-2 rounded text-sm">
281+
<p className="text-green-700 font-medium">
282+
Claimable: {formatUnits(calculateClaimable(stream), 18)} tokens
283+
</p>
284+
</div>
285+
)}
286+
</div>
287+
288+
<div className="flex gap-2 ml-4">
289+
{isAdmin && (
290+
<>
291+
<Button
292+
size="sm"
293+
variant="outline"
294+
onClick={() => {
295+
const newRate = prompt("Enter new flow rate (wei per second):", stream.ratePerSecond.toString());
296+
if (newRate) onUpdate(newRate);
297+
}}
298+
disabled={isUpdating}
299+
>
300+
{isUpdating ? (
301+
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-gray-400"></div>
302+
) : (
303+
<Settings className="h-4 w-4" />
304+
)}
305+
</Button>
306+
<Button
307+
size="sm"
308+
variant="outline"
309+
onClick={onDelete}
310+
disabled={isDeleting}
311+
className="text-red-600 hover:text-red-700"
312+
>
313+
{isDeleting ? (
314+
<div className="animate-spin rounded-full h-4 w-4 border-b-2 border-red-400"></div>
315+
) : (
316+
<Pause className="h-4 w-4" />
317+
)}
318+
</Button>
319+
</>
320+
)}
321+
</div>
322+
</div>
323+
</CardContent>
324+
</Card>
325+
);
326+
};
Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,34 @@
1+
"use client";
2+
3+
import { useQuery } from "@tanstack/react-query";
4+
import { useChainId } from "wagmi";
5+
import { createSuperfluidFramework, isSuperfluidSupported } from "@/lib/blockchain/superfluid-config";
6+
7+
/**
8+
* Hook for initializing and caching Superfluid Framework
9+
* This creates the framework once per chain and caches it
10+
*/
11+
export const useSuperfluidFramework = () => {
12+
const chainId = useChainId();
13+
14+
return useQuery({
15+
queryKey: ["superfluidFramework", chainId],
16+
queryFn: async () => {
17+
if (!isSuperfluidSupported(chainId)) {
18+
throw new Error(`Superfluid not supported on chain ${chainId}`);
19+
}
20+
21+
// Create and cache the framework instance
22+
return await createSuperfluidFramework(chainId);
23+
},
24+
staleTime: Infinity, // Framework doesn't change during session
25+
gcTime: 30 * 60 * 1000, // Keep in cache for 30 minutes
26+
enabled: !!chainId && isSuperfluidSupported(chainId),
27+
retry: (failureCount, error) => {
28+
// Don't retry if chain is not supported
29+
if (error.message.includes("not supported")) return false;
30+
// Retry up to 3 times for network errors
31+
return failureCount < 3;
32+
},
33+
});
34+
};

0 commit comments

Comments
 (0)