create-stylus-ide 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/Readme.MD +1515 -0
  2. package/cli.js +28 -0
  3. package/frontend/.vscode/settings.json +9 -0
  4. package/frontend/app/api/chat/route.ts +101 -0
  5. package/frontend/app/api/check-setup/route.ts +93 -0
  6. package/frontend/app/api/cleanup/route.ts +14 -0
  7. package/frontend/app/api/compile/route.ts +95 -0
  8. package/frontend/app/api/compile-stream/route.ts +98 -0
  9. package/frontend/app/api/complete/route.ts +86 -0
  10. package/frontend/app/api/deploy/route.ts +118 -0
  11. package/frontend/app/api/export-abi/route.ts +58 -0
  12. package/frontend/app/favicon.ico +0 -0
  13. package/frontend/app/globals.css +177 -0
  14. package/frontend/app/layout.tsx +29 -0
  15. package/frontend/app/ml/page.tsx +694 -0
  16. package/frontend/app/page.tsx +1132 -0
  17. package/frontend/app/providers.tsx +18 -0
  18. package/frontend/app/qlearning/page.tsx +188 -0
  19. package/frontend/app/raytracing/page.tsx +268 -0
  20. package/frontend/components/abi/ABIDialog.tsx +132 -0
  21. package/frontend/components/ai/AICompletionPopup.tsx +76 -0
  22. package/frontend/components/ai/ChatPanel.tsx +292 -0
  23. package/frontend/components/ai/QuickActions.tsx +128 -0
  24. package/frontend/components/blockchain/BlockchainContractBanner.tsx +64 -0
  25. package/frontend/components/blockchain/BlockchainLoadingDialog.tsx +188 -0
  26. package/frontend/components/deploy/DeployDialog.tsx +334 -0
  27. package/frontend/components/editor/FileTabs.tsx +181 -0
  28. package/frontend/components/editor/MonacoEditor.tsx +306 -0
  29. package/frontend/components/file-tree/ContextMenu.tsx +110 -0
  30. package/frontend/components/file-tree/DeleteConfirmDialog.tsx +61 -0
  31. package/frontend/components/file-tree/FileInputDialog.tsx +97 -0
  32. package/frontend/components/file-tree/FileNode.tsx +60 -0
  33. package/frontend/components/file-tree/FileTree.tsx +259 -0
  34. package/frontend/components/file-tree/FileTreeSkeleton.tsx +26 -0
  35. package/frontend/components/file-tree/FolderNode.tsx +105 -0
  36. package/frontend/components/github/GitHubLoadingDialog.tsx +201 -0
  37. package/frontend/components/github/GitHubMetadataBanner.tsx +61 -0
  38. package/frontend/components/github/LoadFromGitHubDialog.tsx +125 -0
  39. package/frontend/components/github/URLCopyButton.tsx +60 -0
  40. package/frontend/components/interact/ContractInteraction.tsx +323 -0
  41. package/frontend/components/interact/ContractPlaceholder.tsx +41 -0
  42. package/frontend/components/orbit/BenchmarkDialog.tsx +342 -0
  43. package/frontend/components/orbit/OrbitExplorer.tsx +273 -0
  44. package/frontend/components/project/ProjectActions.tsx +176 -0
  45. package/frontend/components/q-learning/ContractConfig.tsx +172 -0
  46. package/frontend/components/q-learning/MazeGrid.tsx +346 -0
  47. package/frontend/components/q-learning/PathAnimation.tsx +384 -0
  48. package/frontend/components/q-learning/QTableHeatmap.tsx +300 -0
  49. package/frontend/components/q-learning/TrainingForm.tsx +349 -0
  50. package/frontend/components/ray-tracing/ContractConfig.tsx +245 -0
  51. package/frontend/components/ray-tracing/MintingForm.tsx +280 -0
  52. package/frontend/components/ray-tracing/RenderCanvas.tsx +228 -0
  53. package/frontend/components/ray-tracing/RenderingPanel.tsx +259 -0
  54. package/frontend/components/ray-tracing/StyleControls.tsx +217 -0
  55. package/frontend/components/setup/SetupGuide.tsx +290 -0
  56. package/frontend/components/ui/KeyboardShortcutHint.tsx +74 -0
  57. package/frontend/components/ui/alert-dialog.tsx +157 -0
  58. package/frontend/components/ui/alert.tsx +66 -0
  59. package/frontend/components/ui/badge.tsx +46 -0
  60. package/frontend/components/ui/button.tsx +62 -0
  61. package/frontend/components/ui/card.tsx +92 -0
  62. package/frontend/components/ui/context-menu.tsx +252 -0
  63. package/frontend/components/ui/dialog.tsx +143 -0
  64. package/frontend/components/ui/dropdown-menu.tsx +257 -0
  65. package/frontend/components/ui/input.tsx +21 -0
  66. package/frontend/components/ui/label.tsx +24 -0
  67. package/frontend/components/ui/progress.tsx +31 -0
  68. package/frontend/components/ui/scroll-area.tsx +58 -0
  69. package/frontend/components/ui/select.tsx +190 -0
  70. package/frontend/components/ui/separator.tsx +28 -0
  71. package/frontend/components/ui/sheet.tsx +139 -0
  72. package/frontend/components/ui/skeleton.tsx +13 -0
  73. package/frontend/components/ui/slider.tsx +63 -0
  74. package/frontend/components/ui/sonner.tsx +40 -0
  75. package/frontend/components/ui/tabs.tsx +66 -0
  76. package/frontend/components/ui/textarea.tsx +18 -0
  77. package/frontend/components/wallet/ConnectButton.tsx +167 -0
  78. package/frontend/components/wallet/FaucetButton.tsx +256 -0
  79. package/frontend/components.json +22 -0
  80. package/frontend/eslint.config.mjs +18 -0
  81. package/frontend/hooks/useAICompletion.ts +75 -0
  82. package/frontend/hooks/useBlockchainLoader.ts +58 -0
  83. package/frontend/hooks/useChats.ts +137 -0
  84. package/frontend/hooks/useCompilation.ts +173 -0
  85. package/frontend/hooks/useFileTabs.ts +178 -0
  86. package/frontend/hooks/useGitHubLoader.ts +50 -0
  87. package/frontend/hooks/useKeyboardShortcuts.ts +47 -0
  88. package/frontend/hooks/usePanelState.ts +115 -0
  89. package/frontend/hooks/useProjectState.ts +276 -0
  90. package/frontend/hooks/useResponsive.ts +29 -0
  91. package/frontend/lib/abi-parser.ts +58 -0
  92. package/frontend/lib/blockchain-api.ts +374 -0
  93. package/frontend/lib/blockchain-explorers.ts +75 -0
  94. package/frontend/lib/blockchain-loader.ts +112 -0
  95. package/frontend/lib/cargo-template.ts +64 -0
  96. package/frontend/lib/compilation.ts +529 -0
  97. package/frontend/lib/constants.ts +31 -0
  98. package/frontend/lib/deployment.ts +176 -0
  99. package/frontend/lib/file-utils.ts +83 -0
  100. package/frontend/lib/github-api.ts +246 -0
  101. package/frontend/lib/github-loader.ts +369 -0
  102. package/frontend/lib/ml-contract-template.txt +900 -0
  103. package/frontend/lib/orbit-chains.ts +181 -0
  104. package/frontend/lib/output-formatter.ts +68 -0
  105. package/frontend/lib/project-manager.ts +632 -0
  106. package/frontend/lib/ray-tracing-abi.ts +206 -0
  107. package/frontend/lib/storage.ts +189 -0
  108. package/frontend/lib/templates.ts +1662 -0
  109. package/frontend/lib/url-parser.ts +188 -0
  110. package/frontend/lib/utils.ts +6 -0
  111. package/frontend/lib/wagmi-config.ts +24 -0
  112. package/frontend/next.config.ts +7 -0
  113. package/frontend/package-lock.json +16259 -0
  114. package/frontend/package.json +60 -0
  115. package/frontend/postcss.config.mjs +7 -0
  116. package/frontend/public/file.svg +1 -0
  117. package/frontend/public/globe.svg +1 -0
  118. package/frontend/public/ml-weights/.gitkeep +0 -0
  119. package/frontend/public/ml-weights/model.pkl +0 -0
  120. package/frontend/public/ml-weights/model_weights.json +27102 -0
  121. package/frontend/public/ml-weights/test_samples.json +7888 -0
  122. package/frontend/public/next.svg +1 -0
  123. package/frontend/public/vercel.svg +1 -0
  124. package/frontend/public/window.svg +1 -0
  125. package/frontend/scripts/check-env.js +52 -0
  126. package/frontend/scripts/setup.js +285 -0
  127. package/frontend/tailwind.config.ts +64 -0
  128. package/frontend/tsconfig.json +34 -0
  129. package/frontend/types/blockchain.ts +63 -0
  130. package/frontend/types/github.ts +54 -0
  131. package/frontend/types/project.ts +106 -0
  132. package/ml-training/README.md +56 -0
  133. package/ml-training/train_tiny_model.py +325 -0
  134. package/ml-training/update_template.py +59 -0
  135. package/package.json +30 -0
@@ -0,0 +1,342 @@
1
+ 'use client';
2
+
3
+ import { useState } from 'react';
4
+ import { Dialog, DialogContent, DialogDescription, DialogHeader, DialogTitle } from '@/components/ui/dialog';
5
+ import { Button } from '@/components/ui/button';
6
+ import { Input } from '@/components/ui/input';
7
+ import { Label } from '@/components/ui/label';
8
+ import { Loader2, Zap, TrendingDown, ExternalLink } from 'lucide-react';
9
+ import { arbitrumSepolia } from 'wagmi/chains';
10
+ import { orbitChains, getOrbitChain } from '@/lib/orbit-chains';
11
+ import { createPublicClient, http, type Address, type Abi } from 'viem';
12
+
13
+ interface BenchmarkResult {
14
+ chainId: number;
15
+ chainName: string;
16
+ gasToken: string;
17
+ gasUsed: bigint;
18
+ estimatedCost: string; // in USD
19
+ success: boolean;
20
+ error?: string;
21
+ explorerUrl?: string;
22
+ }
23
+
24
+ interface BenchmarkDialogProps {
25
+ open: boolean;
26
+ onOpenChange: (open: boolean) => void;
27
+ abi: Abi;
28
+ functionName: string;
29
+ args?: any[];
30
+ title?: string;
31
+ description?: string;
32
+ }
33
+
34
+ export function BenchmarkDialog({
35
+ open,
36
+ onOpenChange,
37
+ abi,
38
+ functionName,
39
+ args = [],
40
+ title = 'Multi-Chain Gas Benchmark',
41
+ description = 'Compare gas costs across Arbitrum and Orbit chains',
42
+ }: BenchmarkDialogProps) {
43
+ const [addresses, setAddresses] = useState<Record<number, string>>({
44
+ [arbitrumSepolia.id]: '',
45
+ ...Object.fromEntries(orbitChains.map((chain) => [chain.id, ''])),
46
+ });
47
+ const [results, setResults] = useState<BenchmarkResult[]>([]);
48
+ const [isRunning, setIsRunning] = useState(false);
49
+
50
+ const allChains = [
51
+ { id: arbitrumSepolia.id, name: arbitrumSepolia.name, rpc: arbitrumSepolia.rpcUrls.default.http[0], gasToken: 'ETH', explorer: 'https://sepolia.arbiscan.io' },
52
+ ...orbitChains.map((chain) => ({
53
+ id: chain.id,
54
+ name: chain.name,
55
+ rpc: chain.chain.rpcUrls.default.http[0],
56
+ gasToken: chain.gasToken,
57
+ explorer: chain.chain.blockExplorers?.default.url,
58
+ })),
59
+ ];
60
+
61
+ const runBenchmark = async () => {
62
+ setIsRunning(true);
63
+ setResults([]);
64
+
65
+ const benchmarkResults: BenchmarkResult[] = [];
66
+
67
+ // Filter chains that have addresses
68
+ const chainsToTest = allChains.filter((chain) => addresses[chain.id]?.trim());
69
+
70
+ if (chainsToTest.length === 0) {
71
+ alert('Please enter at least one contract address');
72
+ setIsRunning(false);
73
+ return;
74
+ }
75
+
76
+ // Run benchmarks in parallel
77
+ await Promise.all(
78
+ chainsToTest.map(async (chain) => {
79
+ try {
80
+ const client = createPublicClient({
81
+ chain: chain.id === arbitrumSepolia.id
82
+ ? arbitrumSepolia
83
+ : orbitChains.find(c => c.id === chain.id)!.chain,
84
+ transport: http(chain.rpc),
85
+ });
86
+
87
+ const contractAddress = addresses[chain.id] as Address;
88
+
89
+ // Estimate gas
90
+ const gas = await client.estimateContractGas({
91
+ address: contractAddress,
92
+ abi,
93
+ functionName,
94
+ args,
95
+ });
96
+
97
+ // Estimate cost (rough calculation)
98
+ // Different chains have different gas prices
99
+ const gasPriceGwei = chain.gasToken === 'ETH' ? 0.02 : 0.01; // Orbit chains typically cheaper
100
+ const ethPrice = 2500; // $2500 per ETH (rough estimate)
101
+ const costUSD = ((Number(gas) * gasPriceGwei) / 1e9) * ethPrice;
102
+
103
+ benchmarkResults.push({
104
+ chainId: chain.id,
105
+ chainName: chain.name,
106
+ gasToken: chain.gasToken,
107
+ gasUsed: gas,
108
+ estimatedCost: costUSD.toFixed(6),
109
+ success: true,
110
+ explorerUrl: chain.explorer ? `${chain.explorer}/address/${contractAddress}` : undefined,
111
+ });
112
+ } catch (error) {
113
+ benchmarkResults.push({
114
+ chainId: chain.id,
115
+ chainName: chain.name,
116
+ gasToken: chain.gasToken,
117
+ gasUsed: BigInt(0),
118
+ estimatedCost: '0',
119
+ success: false,
120
+ error: error instanceof Error ? error.message : 'Unknown error',
121
+ });
122
+ }
123
+ })
124
+ );
125
+
126
+ // Sort by gas used (successful ones first, then by gas)
127
+ benchmarkResults.sort((a, b) => {
128
+ if (a.success && !b.success) return -1;
129
+ if (!a.success && b.success) return 1;
130
+ return Number(a.gasUsed) - Number(b.gasUsed);
131
+ });
132
+
133
+ setResults(benchmarkResults);
134
+ setIsRunning(false);
135
+ };
136
+
137
+ const cheapestChain = results.find((r) => r.success);
138
+ const avgGas = results.filter(r => r.success).length > 0
139
+ ? results.filter(r => r.success).reduce((sum, r) => sum + Number(r.gasUsed), 0) / results.filter(r => r.success).length
140
+ : 0;
141
+
142
+ return (
143
+ <Dialog open={open} onOpenChange={onOpenChange}>
144
+ <DialogContent className="max-w-4xl max-h-[90vh] overflow-y-auto">
145
+ <DialogHeader>
146
+ <DialogTitle className="flex items-center gap-2">
147
+ <Zap className="h-5 w-5 text-primary" />
148
+ {title}
149
+ </DialogTitle>
150
+ <DialogDescription>{description}</DialogDescription>
151
+ </DialogHeader>
152
+
153
+ <div className="space-y-6">
154
+ {/* Address Input Section */}
155
+ <div className="space-y-4">
156
+ <div className="flex items-center justify-between">
157
+ <h3 className="text-sm font-medium">Contract Addresses</h3>
158
+ <p className="text-xs text-muted-foreground">
159
+ Enter addresses for chains where you deployed
160
+ </p>
161
+ </div>
162
+
163
+ <div className="grid grid-cols-1 md:grid-cols-2 gap-4">
164
+ {/* Arbitrum Sepolia */}
165
+ <div className="space-y-2">
166
+ <Label htmlFor="arb-sepolia" className="text-sm">
167
+ Arbitrum Sepolia
168
+ <span className="ml-1 text-xs text-muted-foreground">(ETH)</span>
169
+ </Label>
170
+ <Input
171
+ id="arb-sepolia"
172
+ placeholder="0x..."
173
+ value={addresses[arbitrumSepolia.id]}
174
+ onChange={(e) =>
175
+ setAddresses({ ...addresses, [arbitrumSepolia.id]: e.target.value })
176
+ }
177
+ disabled={isRunning}
178
+ />
179
+ </div>
180
+
181
+ {/* Orbit Chains */}
182
+ {orbitChains.map((chain) => (
183
+ <div key={chain.id} className="space-y-2">
184
+ <Label htmlFor={`chain-${chain.id}`} className="text-sm flex items-center gap-1">
185
+ {chain.name}
186
+ <span className="text-xs text-muted-foreground">({chain.gasToken})</span>
187
+ {chain.recommended && (
188
+ <span className="text-xs bg-primary/20 text-primary px-1.5 py-0.5 rounded">
189
+ Orbit
190
+ </span>
191
+ )}
192
+ </Label>
193
+ <Input
194
+ id={`chain-${chain.id}`}
195
+ placeholder="0x..."
196
+ value={addresses[chain.id]}
197
+ onChange={(e) =>
198
+ setAddresses({ ...addresses, [chain.id]: e.target.value })
199
+ }
200
+ disabled={isRunning}
201
+ />
202
+ </div>
203
+ ))}
204
+ </div>
205
+ </div>
206
+
207
+ {/* Run Benchmark Button */}
208
+ <Button onClick={runBenchmark} disabled={isRunning} className="w-full">
209
+ {isRunning ? (
210
+ <>
211
+ <Loader2 className="h-4 w-4 mr-2 animate-spin" />
212
+ Running Benchmark...
213
+ </>
214
+ ) : (
215
+ <>
216
+ <Zap className="h-4 w-4 mr-2" />
217
+ Run Benchmark
218
+ </>
219
+ )}
220
+ </Button>
221
+
222
+ {/* Results Section */}
223
+ {results.length > 0 && (
224
+ <div className="space-y-4">
225
+ <h3 className="text-sm font-medium">Benchmark Results</h3>
226
+
227
+ {/* Summary Stats */}
228
+ {cheapestChain && (
229
+ <div className="grid grid-cols-1 md:grid-cols-3 gap-3">
230
+ <div className="bg-green-500/10 border border-green-500/20 rounded-lg p-3">
231
+ <div className="flex items-center gap-2 mb-1">
232
+ <TrendingDown className="h-4 w-4 text-green-500" />
233
+ <span className="text-xs font-medium text-green-400">Cheapest Chain</span>
234
+ </div>
235
+ <p className="text-sm font-semibold">{cheapestChain.chainName}</p>
236
+ <p className="text-xs text-muted-foreground">{Number(cheapestChain.gasUsed).toLocaleString()} gas</p>
237
+ </div>
238
+
239
+ <div className="bg-muted rounded-lg p-3">
240
+ <span className="text-xs font-medium text-muted-foreground block mb-1">Average Gas</span>
241
+ <p className="text-sm font-semibold">{Math.round(avgGas).toLocaleString()}</p>
242
+ <p className="text-xs text-muted-foreground">Across {results.filter(r => r.success).length} chains</p>
243
+ </div>
244
+
245
+ <div className="bg-muted rounded-lg p-3">
246
+ <span className="text-xs font-medium text-muted-foreground block mb-1">Function</span>
247
+ <p className="text-sm font-semibold font-mono">{functionName}()</p>
248
+ <p className="text-xs text-muted-foreground">{args.length} args</p>
249
+ </div>
250
+ </div>
251
+ )}
252
+
253
+ {/* Results Table */}
254
+ <div className="border rounded-lg overflow-hidden">
255
+ <table className="w-full text-sm">
256
+ <thead className="bg-muted">
257
+ <tr>
258
+ <th className="text-left p-3 font-medium">Chain</th>
259
+ <th className="text-left p-3 font-medium">Gas Token</th>
260
+ <th className="text-right p-3 font-medium">Gas Used</th>
261
+ <th className="text-right p-3 font-medium">Est. Cost (USD)</th>
262
+ <th className="text-center p-3 font-medium">Status</th>
263
+ </tr>
264
+ </thead>
265
+ <tbody>
266
+ {results.map((result, index) => (
267
+ <tr
268
+ key={result.chainId}
269
+ className={`border-t ${
270
+ result.success && result.chainId === cheapestChain?.chainId
271
+ ? 'bg-green-500/5'
272
+ : ''
273
+ }`}
274
+ >
275
+ <td className="p-3">
276
+ <div className="flex items-center gap-2">
277
+ <span className="font-medium">{result.chainName}</span>
278
+ {result.success && result.chainId === cheapestChain?.chainId && (
279
+ <TrendingDown className="h-3 w-3 text-green-500" />
280
+ )}
281
+ {result.explorerUrl && (
282
+ <a
283
+ href={result.explorerUrl}
284
+ target="_blank"
285
+ rel="noopener noreferrer"
286
+ className="text-muted-foreground hover:text-primary"
287
+ >
288
+ <ExternalLink className="h-3 w-3" />
289
+ </a>
290
+ )}
291
+ </div>
292
+ </td>
293
+ <td className="p-3">
294
+ <span className="text-xs font-mono">{result.gasToken}</span>
295
+ </td>
296
+ <td className="p-3 text-right font-mono">
297
+ {result.success ? Number(result.gasUsed).toLocaleString() : '-'}
298
+ </td>
299
+ <td className="p-3 text-right font-mono">
300
+ {result.success ? `$${result.estimatedCost}` : '-'}
301
+ </td>
302
+ <td className="p-3 text-center">
303
+ {result.success ? (
304
+ <span className="text-xs text-green-500">✓ Success</span>
305
+ ) : (
306
+ <span className="text-xs text-destructive" title={result.error}>
307
+ ✗ Failed
308
+ </span>
309
+ )}
310
+ </td>
311
+ </tr>
312
+ ))}
313
+ </tbody>
314
+ </table>
315
+ </div>
316
+
317
+ {/* Insights */}
318
+ {cheapestChain && results.filter(r => r.success).length > 1 && (
319
+ <div className="bg-blue-500/10 border border-blue-500/20 rounded-lg p-4 text-sm">
320
+ <p className="font-medium text-blue-400 mb-2">💡 Insights:</p>
321
+ <ul className="space-y-1 text-blue-300 text-xs">
322
+ {orbitChains.some(c => results.find(r => r.chainId === c.id && r.success)) && (
323
+ <li>
324
+ • Orbit chains show similar gas usage but may have lower gas prices
325
+ </li>
326
+ )}
327
+ <li>
328
+ • Gas costs vary by network load and gas token economics
329
+ </li>
330
+ <li>
331
+ • For high-frequency operations, choose chains with lowest costs
332
+ </li>
333
+ </ul>
334
+ </div>
335
+ )}
336
+ </div>
337
+ )}
338
+ </div>
339
+ </DialogContent>
340
+ </Dialog>
341
+ );
342
+ }
@@ -0,0 +1,273 @@
1
+ 'use client';
2
+
3
+ import { useMemo, useState } from 'react';
4
+ import {
5
+ Card,
6
+ CardContent,
7
+ CardDescription,
8
+ CardHeader,
9
+ CardTitle,
10
+ } from '@/components/ui/card';
11
+ import { Button } from '@/components/ui/button';
12
+ import { Badge } from '@/components/ui/badge';
13
+ import { ExternalLink, Rocket, Zap, Copy, Check } from 'lucide-react';
14
+ import { orbitChains, type OrbitChainInfo } from '@/lib/orbit-chains';
15
+ import { useSwitchChain } from 'wagmi';
16
+
17
+ export function OrbitExplorer() {
18
+ const { switchChain } = useSwitchChain();
19
+ const [copiedRpc, setCopiedRpc] = useState<number | null>(null);
20
+
21
+ const copyRpc = async (chainId: number, rpc: string) => {
22
+ try {
23
+ await navigator.clipboard.writeText(rpc);
24
+ setCopiedRpc(chainId);
25
+ setTimeout(() => setCopiedRpc(null), 2000);
26
+ } catch (err) {
27
+ console.error('Failed to copy RPC:', err);
28
+ }
29
+ };
30
+
31
+ return (
32
+ <div className="space-y-5 sm:space-y-6">
33
+ {/* Header */}
34
+ <div className="text-center space-y-2 px-1">
35
+ <div className="flex items-center justify-center gap-2">
36
+ <Rocket className="h-7 w-7 sm:h-8 sm:w-8 text-primary" />
37
+ <h2 className="text-2xl sm:text-3xl font-bold tracking-tight">
38
+ Explore Orbit Chains
39
+ </h2>
40
+ </div>
41
+
42
+ <p className="text-muted-foreground max-w-2xl mx-auto text-xs sm:text-sm leading-relaxed px-2 sm:px-0">
43
+ Arbitrum Orbit chains are app-specific L3s with customizable features,
44
+ lower costs, and tailored infrastructure. Deploy your Stylus contracts
45
+ to these chains for optimized performance.
46
+ </p>
47
+ </div>
48
+
49
+ {/* Chain Cards */}
50
+ <div className="grid grid-cols-1 sm:grid-cols-2 lg:grid-cols-3 gap-3 sm:gap-4">
51
+ {orbitChains.map((chain) => (
52
+ <OrbitChainCard
53
+ key={chain.id}
54
+ chain={chain}
55
+ onSwitchChain={() => switchChain({ chainId: chain.id })}
56
+ onCopyRpc={() => copyRpc(chain.id, chain.chain.rpcUrls.default.http[0])}
57
+ isCopied={copiedRpc === chain.id}
58
+ />
59
+ ))}
60
+ </div>
61
+
62
+ {/* Why Orbit Section */}
63
+ <Card className="border-primary/20">
64
+ <CardHeader className="p-4 sm:p-6">
65
+ <CardTitle className="flex items-center gap-2 text-base sm:text-lg">
66
+ <Zap className="h-5 w-5 text-primary" />
67
+ Why Deploy to Orbit Chains?
68
+ </CardTitle>
69
+ </CardHeader>
70
+
71
+ <CardContent className="p-4 pt-0 sm:p-6 sm:pt-0 space-y-3 text-sm">
72
+ <div className="grid grid-cols-1 sm:grid-cols-2 gap-3 sm:gap-4">
73
+ <div className="space-y-2">
74
+ <h4 className="font-medium text-primary text-sm">Cost Optimization</h4>
75
+ <ul className="space-y-1 text-muted-foreground text-xs leading-relaxed">
76
+ <li>• Lower gas prices than L2</li>
77
+ <li>• Custom gas token options</li>
78
+ <li>• Reduced transaction costs by up to 60%</li>
79
+ </ul>
80
+ </div>
81
+
82
+ <div className="space-y-2">
83
+ <h4 className="font-medium text-primary text-sm">Customization</h4>
84
+ <ul className="space-y-1 text-muted-foreground text-xs leading-relaxed">
85
+ <li>• App-specific optimizations</li>
86
+ <li>• Custom sequencing logic</li>
87
+ <li>• Tailored governance models</li>
88
+ </ul>
89
+ </div>
90
+
91
+ <div className="space-y-2">
92
+ <h4 className="font-medium text-primary text-sm">Performance</h4>
93
+ <ul className="space-y-1 text-muted-foreground text-xs leading-relaxed">
94
+ <li>• Faster block times</li>
95
+ <li>• Dedicated infrastructure</li>
96
+ <li>• Predictable throughput</li>
97
+ </ul>
98
+ </div>
99
+
100
+ <div className="space-y-2">
101
+ <h4 className="font-medium text-primary text-sm">Stylus Benefits</h4>
102
+ <ul className="space-y-1 text-muted-foreground text-xs leading-relaxed">
103
+ <li>• ML inference at lower cost</li>
104
+ <li>• Complex compute operations</li>
105
+ <li>• High-frequency contract calls</li>
106
+ </ul>
107
+ </div>
108
+ </div>
109
+ </CardContent>
110
+ </Card>
111
+
112
+ {/* Getting Started */}
113
+ <Card>
114
+ <CardHeader className="p-4 sm:p-6">
115
+ <CardTitle className="text-base sm:text-lg">Quick Start Guide</CardTitle>
116
+ <CardDescription className="text-xs sm:text-sm">
117
+ Deploy your first contract to an Orbit chain
118
+ </CardDescription>
119
+ </CardHeader>
120
+
121
+ <CardContent className="p-4 pt-0 sm:p-6 sm:pt-0 space-y-3 text-sm">
122
+ <div className="space-y-3">
123
+ {[
124
+ {
125
+ title: 'Get testnet tokens',
126
+ desc: 'Click the faucet button in the header to get free testnet tokens for any Orbit chain',
127
+ },
128
+ {
129
+ title: 'Switch network',
130
+ desc: 'Use the chain selector in your wallet or click "Switch Network" on any Orbit card below',
131
+ },
132
+ {
133
+ title: 'Compile and deploy',
134
+ desc: 'Use the same Stylus contracts - they work on all Orbit chains without modification',
135
+ },
136
+ {
137
+ title: 'Compare gas costs',
138
+ desc: 'Use the "Benchmark Orbit" feature to compare gas usage across chains',
139
+ },
140
+ ].map((step, idx) => (
141
+ <div key={idx} className="flex items-start gap-3">
142
+ <div className="flex items-center justify-center w-6 h-6 rounded-full bg-primary/20 text-primary text-xs font-bold shrink-0">
143
+ {idx + 1}
144
+ </div>
145
+ <div className="min-w-0">
146
+ <p className="font-medium text-sm">{step.title}</p>
147
+ <p className="text-xs text-muted-foreground leading-relaxed">
148
+ {step.desc}
149
+ </p>
150
+ </div>
151
+ </div>
152
+ ))}
153
+ </div>
154
+ </CardContent>
155
+ </Card>
156
+ </div>
157
+ );
158
+ }
159
+
160
+ interface OrbitChainCardProps {
161
+ chain: OrbitChainInfo;
162
+ onSwitchChain: () => void;
163
+ onCopyRpc: () => void;
164
+ isCopied: boolean;
165
+ }
166
+
167
+ function OrbitChainCard({ chain, onSwitchChain, onCopyRpc, isCopied }: OrbitChainCardProps) {
168
+ const hasExplorer = useMemo(
169
+ () => Boolean(chain.chain.blockExplorers?.default?.url),
170
+ [chain.chain.blockExplorers]
171
+ );
172
+
173
+ return (
174
+ <Card
175
+ className={[
176
+ 'h-full',
177
+ chain.recommended ? 'border-primary/50' : '',
178
+ ].join(' ')}
179
+ >
180
+ <CardHeader className="p-4 sm:p-5">
181
+ <div className="flex items-start justify-between gap-3">
182
+ <div className="space-y-1 flex-1 min-w-0">
183
+ <CardTitle className="text-base leading-tight wrap-break-words">
184
+ {chain.name}
185
+ </CardTitle>
186
+ <CardDescription className="text-xs leading-snug">
187
+ {chain.focus}
188
+ </CardDescription>
189
+ </div>
190
+
191
+ {chain.recommended && (
192
+ <Badge variant="default" className="text-[11px] px-2 py-0.5 shrink-0">
193
+ Recommended
194
+ </Badge>
195
+ )}
196
+ </div>
197
+ </CardHeader>
198
+
199
+ <CardContent className="p-4 pt-0 sm:p-5 sm:pt-0 space-y-4">
200
+ <p className="text-xs text-muted-foreground leading-relaxed">
201
+ {chain.description}
202
+ </p>
203
+
204
+ {/* Gas Token */}
205
+ <div className="flex items-center justify-between gap-3 text-xs">
206
+ <span className="text-muted-foreground">Gas Token:</span>
207
+ <Badge variant="outline" className="font-mono text-[11px] max-w-[60%] truncate">
208
+ {chain.gasToken}
209
+ </Badge>
210
+ </div>
211
+
212
+ {/* Benefits */}
213
+ <div className="space-y-1">
214
+ <p className="text-xs font-medium">Key Benefits:</p>
215
+ <ul className="space-y-0.5">
216
+ {chain.benefits.slice(0, 2).map((benefit, i) => (
217
+ <li key={i} className="text-xs text-muted-foreground leading-relaxed">
218
+ • {benefit}
219
+ </li>
220
+ ))}
221
+ </ul>
222
+ </div>
223
+
224
+ {/* Actions */}
225
+ <div className="space-y-2 pt-3 border-t border-border">
226
+ <Button onClick={onSwitchChain} size="sm" className="w-full">
227
+ <Rocket className="h-3.5 w-3.5 mr-2" />
228
+ Switch Network
229
+ </Button>
230
+
231
+ <div className={hasExplorer ? 'grid grid-cols-2 gap-2' : 'grid grid-cols-1'}>
232
+ <Button
233
+ variant="outline"
234
+ size="sm"
235
+ onClick={onCopyRpc}
236
+ className="text-xs w-full"
237
+ >
238
+ {isCopied ? (
239
+ <>
240
+ <Check className="h-3.5 w-3.5 mr-1" />
241
+ Copied
242
+ </>
243
+ ) : (
244
+ <>
245
+ <Copy className="h-3.5 w-3.5 mr-1" />
246
+ Copy RPC
247
+ </>
248
+ )}
249
+ </Button>
250
+
251
+ {hasExplorer && (
252
+ <Button variant="outline" size="sm" asChild className="text-xs w-full">
253
+ <a
254
+ href={chain.chain.blockExplorers!.default.url}
255
+ target="_blank"
256
+ rel="noopener noreferrer"
257
+ >
258
+ <ExternalLink className="h-3.5 w-3.5 mr-1" />
259
+ Explorer
260
+ </a>
261
+ </Button>
262
+ )}
263
+ </div>
264
+ </div>
265
+
266
+ {/* Chain ID for reference */}
267
+ <p className="text-xs text-muted-foreground text-center pt-3 border-t border-border">
268
+ Chain ID: <span className="font-mono">{chain.id}</span>
269
+ </p>
270
+ </CardContent>
271
+ </Card>
272
+ );
273
+ }