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.
- package/Readme.MD +1515 -0
- package/cli.js +28 -0
- package/frontend/.vscode/settings.json +9 -0
- package/frontend/app/api/chat/route.ts +101 -0
- package/frontend/app/api/check-setup/route.ts +93 -0
- package/frontend/app/api/cleanup/route.ts +14 -0
- package/frontend/app/api/compile/route.ts +95 -0
- package/frontend/app/api/compile-stream/route.ts +98 -0
- package/frontend/app/api/complete/route.ts +86 -0
- package/frontend/app/api/deploy/route.ts +118 -0
- package/frontend/app/api/export-abi/route.ts +58 -0
- package/frontend/app/favicon.ico +0 -0
- package/frontend/app/globals.css +177 -0
- package/frontend/app/layout.tsx +29 -0
- package/frontend/app/ml/page.tsx +694 -0
- package/frontend/app/page.tsx +1132 -0
- package/frontend/app/providers.tsx +18 -0
- package/frontend/app/qlearning/page.tsx +188 -0
- package/frontend/app/raytracing/page.tsx +268 -0
- package/frontend/components/abi/ABIDialog.tsx +132 -0
- package/frontend/components/ai/AICompletionPopup.tsx +76 -0
- package/frontend/components/ai/ChatPanel.tsx +292 -0
- package/frontend/components/ai/QuickActions.tsx +128 -0
- package/frontend/components/blockchain/BlockchainContractBanner.tsx +64 -0
- package/frontend/components/blockchain/BlockchainLoadingDialog.tsx +188 -0
- package/frontend/components/deploy/DeployDialog.tsx +334 -0
- package/frontend/components/editor/FileTabs.tsx +181 -0
- package/frontend/components/editor/MonacoEditor.tsx +306 -0
- package/frontend/components/file-tree/ContextMenu.tsx +110 -0
- package/frontend/components/file-tree/DeleteConfirmDialog.tsx +61 -0
- package/frontend/components/file-tree/FileInputDialog.tsx +97 -0
- package/frontend/components/file-tree/FileNode.tsx +60 -0
- package/frontend/components/file-tree/FileTree.tsx +259 -0
- package/frontend/components/file-tree/FileTreeSkeleton.tsx +26 -0
- package/frontend/components/file-tree/FolderNode.tsx +105 -0
- package/frontend/components/github/GitHubLoadingDialog.tsx +201 -0
- package/frontend/components/github/GitHubMetadataBanner.tsx +61 -0
- package/frontend/components/github/LoadFromGitHubDialog.tsx +125 -0
- package/frontend/components/github/URLCopyButton.tsx +60 -0
- package/frontend/components/interact/ContractInteraction.tsx +323 -0
- package/frontend/components/interact/ContractPlaceholder.tsx +41 -0
- package/frontend/components/orbit/BenchmarkDialog.tsx +342 -0
- package/frontend/components/orbit/OrbitExplorer.tsx +273 -0
- package/frontend/components/project/ProjectActions.tsx +176 -0
- package/frontend/components/q-learning/ContractConfig.tsx +172 -0
- package/frontend/components/q-learning/MazeGrid.tsx +346 -0
- package/frontend/components/q-learning/PathAnimation.tsx +384 -0
- package/frontend/components/q-learning/QTableHeatmap.tsx +300 -0
- package/frontend/components/q-learning/TrainingForm.tsx +349 -0
- package/frontend/components/ray-tracing/ContractConfig.tsx +245 -0
- package/frontend/components/ray-tracing/MintingForm.tsx +280 -0
- package/frontend/components/ray-tracing/RenderCanvas.tsx +228 -0
- package/frontend/components/ray-tracing/RenderingPanel.tsx +259 -0
- package/frontend/components/ray-tracing/StyleControls.tsx +217 -0
- package/frontend/components/setup/SetupGuide.tsx +290 -0
- package/frontend/components/ui/KeyboardShortcutHint.tsx +74 -0
- package/frontend/components/ui/alert-dialog.tsx +157 -0
- package/frontend/components/ui/alert.tsx +66 -0
- package/frontend/components/ui/badge.tsx +46 -0
- package/frontend/components/ui/button.tsx +62 -0
- package/frontend/components/ui/card.tsx +92 -0
- package/frontend/components/ui/context-menu.tsx +252 -0
- package/frontend/components/ui/dialog.tsx +143 -0
- package/frontend/components/ui/dropdown-menu.tsx +257 -0
- package/frontend/components/ui/input.tsx +21 -0
- package/frontend/components/ui/label.tsx +24 -0
- package/frontend/components/ui/progress.tsx +31 -0
- package/frontend/components/ui/scroll-area.tsx +58 -0
- package/frontend/components/ui/select.tsx +190 -0
- package/frontend/components/ui/separator.tsx +28 -0
- package/frontend/components/ui/sheet.tsx +139 -0
- package/frontend/components/ui/skeleton.tsx +13 -0
- package/frontend/components/ui/slider.tsx +63 -0
- package/frontend/components/ui/sonner.tsx +40 -0
- package/frontend/components/ui/tabs.tsx +66 -0
- package/frontend/components/ui/textarea.tsx +18 -0
- package/frontend/components/wallet/ConnectButton.tsx +167 -0
- package/frontend/components/wallet/FaucetButton.tsx +256 -0
- package/frontend/components.json +22 -0
- package/frontend/eslint.config.mjs +18 -0
- package/frontend/hooks/useAICompletion.ts +75 -0
- package/frontend/hooks/useBlockchainLoader.ts +58 -0
- package/frontend/hooks/useChats.ts +137 -0
- package/frontend/hooks/useCompilation.ts +173 -0
- package/frontend/hooks/useFileTabs.ts +178 -0
- package/frontend/hooks/useGitHubLoader.ts +50 -0
- package/frontend/hooks/useKeyboardShortcuts.ts +47 -0
- package/frontend/hooks/usePanelState.ts +115 -0
- package/frontend/hooks/useProjectState.ts +276 -0
- package/frontend/hooks/useResponsive.ts +29 -0
- package/frontend/lib/abi-parser.ts +58 -0
- package/frontend/lib/blockchain-api.ts +374 -0
- package/frontend/lib/blockchain-explorers.ts +75 -0
- package/frontend/lib/blockchain-loader.ts +112 -0
- package/frontend/lib/cargo-template.ts +64 -0
- package/frontend/lib/compilation.ts +529 -0
- package/frontend/lib/constants.ts +31 -0
- package/frontend/lib/deployment.ts +176 -0
- package/frontend/lib/file-utils.ts +83 -0
- package/frontend/lib/github-api.ts +246 -0
- package/frontend/lib/github-loader.ts +369 -0
- package/frontend/lib/ml-contract-template.txt +900 -0
- package/frontend/lib/orbit-chains.ts +181 -0
- package/frontend/lib/output-formatter.ts +68 -0
- package/frontend/lib/project-manager.ts +632 -0
- package/frontend/lib/ray-tracing-abi.ts +206 -0
- package/frontend/lib/storage.ts +189 -0
- package/frontend/lib/templates.ts +1662 -0
- package/frontend/lib/url-parser.ts +188 -0
- package/frontend/lib/utils.ts +6 -0
- package/frontend/lib/wagmi-config.ts +24 -0
- package/frontend/next.config.ts +7 -0
- package/frontend/package-lock.json +16259 -0
- package/frontend/package.json +60 -0
- package/frontend/postcss.config.mjs +7 -0
- package/frontend/public/file.svg +1 -0
- package/frontend/public/globe.svg +1 -0
- package/frontend/public/ml-weights/.gitkeep +0 -0
- package/frontend/public/ml-weights/model.pkl +0 -0
- package/frontend/public/ml-weights/model_weights.json +27102 -0
- package/frontend/public/ml-weights/test_samples.json +7888 -0
- package/frontend/public/next.svg +1 -0
- package/frontend/public/vercel.svg +1 -0
- package/frontend/public/window.svg +1 -0
- package/frontend/scripts/check-env.js +52 -0
- package/frontend/scripts/setup.js +285 -0
- package/frontend/tailwind.config.ts +64 -0
- package/frontend/tsconfig.json +34 -0
- package/frontend/types/blockchain.ts +63 -0
- package/frontend/types/github.ts +54 -0
- package/frontend/types/project.ts +106 -0
- package/ml-training/README.md +56 -0
- package/ml-training/train_tiny_model.py +325 -0
- package/ml-training/update_template.py +59 -0
- 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
|
+
}
|