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,125 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import {
|
|
5
|
+
Dialog,
|
|
6
|
+
DialogContent,
|
|
7
|
+
DialogDescription,
|
|
8
|
+
DialogHeader,
|
|
9
|
+
DialogTitle,
|
|
10
|
+
DialogTrigger,
|
|
11
|
+
} from '@/components/ui/dialog';
|
|
12
|
+
import { Button } from '@/components/ui/button';
|
|
13
|
+
import { Input } from '@/components/ui/input';
|
|
14
|
+
import { Label } from '@/components/ui/label';
|
|
15
|
+
import { Github, Loader2 } from 'lucide-react';
|
|
16
|
+
import { parseURL } from '@/lib/url-parser';
|
|
17
|
+
|
|
18
|
+
interface LoadFromGitHubDialogProps {
|
|
19
|
+
onLoadURL: (url: string) => void;
|
|
20
|
+
isLoading?: boolean;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export function LoadFromGitHubDialog({ onLoadURL, isLoading }: LoadFromGitHubDialogProps) {
|
|
24
|
+
const [open, setOpen] = useState(false);
|
|
25
|
+
const [url, setUrl] = useState('');
|
|
26
|
+
const [error, setError] = useState('');
|
|
27
|
+
|
|
28
|
+
const handleSubmit = (e: React.FormEvent) => {
|
|
29
|
+
e.preventDefault();
|
|
30
|
+
setError('');
|
|
31
|
+
|
|
32
|
+
if (!url.trim()) {
|
|
33
|
+
setError('Please enter a GitHub URL');
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const parsed = parseURL(url.trim());
|
|
38
|
+
|
|
39
|
+
if (parsed.type !== 'github') {
|
|
40
|
+
setError('Please enter a valid GitHub repository URL');
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
onLoadURL(url.trim());
|
|
45
|
+
setOpen(false);
|
|
46
|
+
setUrl('');
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
const exampleRepos = [
|
|
50
|
+
{
|
|
51
|
+
name: 'Stylus Hello World',
|
|
52
|
+
url: 'https://github.com/OffchainLabs/stylus-hello-world',
|
|
53
|
+
},
|
|
54
|
+
{
|
|
55
|
+
name: 'Stylus Workshop',
|
|
56
|
+
url: 'https://github.com/OffchainLabs/stylus-workshop-rust-solidity',
|
|
57
|
+
},
|
|
58
|
+
];
|
|
59
|
+
|
|
60
|
+
return (
|
|
61
|
+
<Dialog open={open} onOpenChange={setOpen}>
|
|
62
|
+
<DialogTrigger asChild>
|
|
63
|
+
<Button variant="outline" size="sm" className="gap-2">
|
|
64
|
+
<Github className="h-4 w-4" />
|
|
65
|
+
<span className="hidden md:inline">Load from GitHub</span>
|
|
66
|
+
</Button>
|
|
67
|
+
</DialogTrigger>
|
|
68
|
+
|
|
69
|
+
<DialogContent className="sm:max-w-md">
|
|
70
|
+
<DialogHeader>
|
|
71
|
+
<DialogTitle>Load from GitHub</DialogTitle>
|
|
72
|
+
<DialogDescription>
|
|
73
|
+
Enter a GitHub repository URL to load it into the IDE
|
|
74
|
+
</DialogDescription>
|
|
75
|
+
</DialogHeader>
|
|
76
|
+
|
|
77
|
+
<form onSubmit={handleSubmit} className="space-y-4">
|
|
78
|
+
<div className="space-y-2">
|
|
79
|
+
<Label htmlFor="github-url">Repository URL</Label>
|
|
80
|
+
<Input
|
|
81
|
+
id="github-url"
|
|
82
|
+
placeholder="https://github.com/owner/repo"
|
|
83
|
+
value={url}
|
|
84
|
+
onChange={(e) => {
|
|
85
|
+
setUrl(e.target.value);
|
|
86
|
+
setError('');
|
|
87
|
+
}}
|
|
88
|
+
disabled={isLoading}
|
|
89
|
+
/>
|
|
90
|
+
{error && <p className="text-sm text-red-500">{error}</p>}
|
|
91
|
+
</div>
|
|
92
|
+
|
|
93
|
+
{/* Example Repos */}
|
|
94
|
+
<div className="space-y-2">
|
|
95
|
+
<Label className="text-xs text-muted-foreground">Quick Examples:</Label>
|
|
96
|
+
<div className="space-y-1">
|
|
97
|
+
{exampleRepos.map((repo) => (
|
|
98
|
+
<button
|
|
99
|
+
key={repo.url}
|
|
100
|
+
type="button"
|
|
101
|
+
onClick={() => setUrl(repo.url)}
|
|
102
|
+
className="block w-full text-left text-xs text-blue-500 hover:text-blue-600 hover:underline"
|
|
103
|
+
disabled={isLoading}
|
|
104
|
+
>
|
|
105
|
+
{repo.name}
|
|
106
|
+
</button>
|
|
107
|
+
))}
|
|
108
|
+
</div>
|
|
109
|
+
</div>
|
|
110
|
+
|
|
111
|
+
<Button type="submit" className="w-full" disabled={isLoading}>
|
|
112
|
+
{isLoading ? (
|
|
113
|
+
<>
|
|
114
|
+
<Loader2 className="h-4 w-4 mr-2 animate-spin" />
|
|
115
|
+
Loading...
|
|
116
|
+
</>
|
|
117
|
+
) : (
|
|
118
|
+
'Load Repository'
|
|
119
|
+
)}
|
|
120
|
+
</Button>
|
|
121
|
+
</form>
|
|
122
|
+
</DialogContent>
|
|
123
|
+
</Dialog>
|
|
124
|
+
);
|
|
125
|
+
}
|
|
@@ -0,0 +1,60 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Button } from '@/components/ui/button';
|
|
5
|
+
import { Check, Link2 } from 'lucide-react';
|
|
6
|
+
import { toast } from 'sonner';
|
|
7
|
+
|
|
8
|
+
interface URLCopyButtonProps {
|
|
9
|
+
githubUrl: string;
|
|
10
|
+
branch?: string;
|
|
11
|
+
file?: string;
|
|
12
|
+
folderPath?: string;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export function URLCopyButton({ githubUrl, branch, file, folderPath }: URLCopyButtonProps) {
|
|
16
|
+
const [copied, setCopied] = useState(false);
|
|
17
|
+
|
|
18
|
+
const handleCopy = () => {
|
|
19
|
+
// Build shareable URL
|
|
20
|
+
const baseUrl = window.location.origin;
|
|
21
|
+
const params = new URLSearchParams();
|
|
22
|
+
|
|
23
|
+
params.set('url', githubUrl);
|
|
24
|
+
if (branch) params.set('branch', branch);
|
|
25
|
+
if (file) params.set('file', file);
|
|
26
|
+
if (folderPath) params.set('path', folderPath);
|
|
27
|
+
|
|
28
|
+
const shareUrl = `${baseUrl}/?${params.toString()}`;
|
|
29
|
+
|
|
30
|
+
navigator.clipboard.writeText(shareUrl);
|
|
31
|
+
setCopied(true);
|
|
32
|
+
|
|
33
|
+
toast.success('Link copied!', {
|
|
34
|
+
description: 'Share this URL to open this exact project state',
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
setTimeout(() => setCopied(false), 2000);
|
|
38
|
+
};
|
|
39
|
+
|
|
40
|
+
return (
|
|
41
|
+
<Button
|
|
42
|
+
variant="ghost"
|
|
43
|
+
size="sm"
|
|
44
|
+
className="h-6 text-xs gap-1"
|
|
45
|
+
onClick={handleCopy}
|
|
46
|
+
>
|
|
47
|
+
{copied ? (
|
|
48
|
+
<>
|
|
49
|
+
<Check className="h-3 w-3" />
|
|
50
|
+
Copied
|
|
51
|
+
</>
|
|
52
|
+
) : (
|
|
53
|
+
<>
|
|
54
|
+
<Link2 className="h-3 w-3" />
|
|
55
|
+
Share
|
|
56
|
+
</>
|
|
57
|
+
)}
|
|
58
|
+
</Button>
|
|
59
|
+
);
|
|
60
|
+
}
|
|
@@ -0,0 +1,323 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { useState } from 'react';
|
|
4
|
+
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card';
|
|
5
|
+
import { Tabs, TabsContent, TabsList, TabsTrigger } from '@/components/ui/tabs';
|
|
6
|
+
import { Button } from '@/components/ui/button';
|
|
7
|
+
import { Input } from '@/components/ui/input';
|
|
8
|
+
import { Label } from '@/components/ui/label';
|
|
9
|
+
import { BookOpen, Edit, Loader2, ExternalLink } from 'lucide-react';
|
|
10
|
+
import { useAccount, useChainId, usePublicClient, useWalletClient } from 'wagmi';
|
|
11
|
+
import { parseABI, groupFunctionsByType, formatFunctionSignature, ParsedFunction } from '@/lib/abi-parser';
|
|
12
|
+
import { encodeFunctionData, decodeFunctionResult, type Address } from 'viem';
|
|
13
|
+
import { arbitrum, arbitrumSepolia } from 'wagmi/chains';
|
|
14
|
+
|
|
15
|
+
interface ContractInteractionProps {
|
|
16
|
+
contractAddress: string;
|
|
17
|
+
abi: string;
|
|
18
|
+
}
|
|
19
|
+
|
|
20
|
+
export function ContractInteraction({ contractAddress, abi }: ContractInteractionProps) {
|
|
21
|
+
const [inputValues, setInputValues] = useState<Record<string, string[]>>({});
|
|
22
|
+
const [results, setResults] = useState<Record<string, any>>({});
|
|
23
|
+
const [loading, setLoading] = useState<Record<string, boolean>>({});
|
|
24
|
+
|
|
25
|
+
const { address: userAddress, isConnected } = useAccount();
|
|
26
|
+
const chainId = useChainId();
|
|
27
|
+
const publicClient = usePublicClient();
|
|
28
|
+
const { data: walletClient } = useWalletClient();
|
|
29
|
+
|
|
30
|
+
const functions = parseABI(abi);
|
|
31
|
+
const { read: readFunctions, write: writeFunctions } = groupFunctionsByType(functions);
|
|
32
|
+
|
|
33
|
+
const getExplorerUrl = () => {
|
|
34
|
+
if (chainId === arbitrumSepolia.id) {
|
|
35
|
+
return `https://sepolia.arbiscan.io/address/${contractAddress}`;
|
|
36
|
+
}
|
|
37
|
+
if (chainId === arbitrum.id) {
|
|
38
|
+
return `https://arbiscan.io/address/${contractAddress}`;
|
|
39
|
+
}
|
|
40
|
+
return `https://sepolia.arbiscan.io/address/${contractAddress}`;
|
|
41
|
+
};
|
|
42
|
+
|
|
43
|
+
const handleInputChange = (funcName: string, index: number, value: string) => {
|
|
44
|
+
setInputValues((prev) => ({
|
|
45
|
+
...prev,
|
|
46
|
+
[funcName]: {
|
|
47
|
+
...prev[funcName],
|
|
48
|
+
[index]: value,
|
|
49
|
+
},
|
|
50
|
+
}));
|
|
51
|
+
};
|
|
52
|
+
|
|
53
|
+
const getInputValues = (funcName: string, func: ParsedFunction): any[] => {
|
|
54
|
+
const values = inputValues[funcName] || [];
|
|
55
|
+
return func.inputs.map((input, index) => {
|
|
56
|
+
const value = values[index] || '';
|
|
57
|
+
|
|
58
|
+
// Convert based on type
|
|
59
|
+
if (input.type.includes('uint') || input.type.includes('int')) {
|
|
60
|
+
return value ? BigInt(value) : BigInt(0);
|
|
61
|
+
}
|
|
62
|
+
if (input.type === 'bool') {
|
|
63
|
+
return value.toLowerCase() === 'true';
|
|
64
|
+
}
|
|
65
|
+
if (input.type === 'address') {
|
|
66
|
+
return value as Address;
|
|
67
|
+
}
|
|
68
|
+
if (input.type.includes('bytes')) {
|
|
69
|
+
return value as `0x${string}`;
|
|
70
|
+
}
|
|
71
|
+
return value;
|
|
72
|
+
});
|
|
73
|
+
};
|
|
74
|
+
|
|
75
|
+
const handleRead = async (func: ParsedFunction) => {
|
|
76
|
+
if (!publicClient) return;
|
|
77
|
+
|
|
78
|
+
const funcKey = func.name;
|
|
79
|
+
setLoading((prev) => ({ ...prev, [funcKey]: true }));
|
|
80
|
+
setResults((prev) => ({ ...prev, [funcKey]: undefined }));
|
|
81
|
+
|
|
82
|
+
try {
|
|
83
|
+
const args = getInputValues(funcKey, func);
|
|
84
|
+
|
|
85
|
+
const data = await publicClient.readContract({
|
|
86
|
+
address: contractAddress as Address,
|
|
87
|
+
abi: [func],
|
|
88
|
+
functionName: func.name,
|
|
89
|
+
args: args.length > 0 ? args : undefined,
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
setResults((prev) => ({
|
|
93
|
+
...prev,
|
|
94
|
+
[funcKey]: { success: true, data },
|
|
95
|
+
}));
|
|
96
|
+
} catch (error) {
|
|
97
|
+
console.error('Read error:', error);
|
|
98
|
+
setResults((prev) => ({
|
|
99
|
+
...prev,
|
|
100
|
+
[funcKey]: {
|
|
101
|
+
success: false,
|
|
102
|
+
error: error instanceof Error ? error.message : 'Read failed',
|
|
103
|
+
},
|
|
104
|
+
}));
|
|
105
|
+
} finally {
|
|
106
|
+
setLoading((prev) => ({ ...prev, [funcKey]: false }));
|
|
107
|
+
}
|
|
108
|
+
};
|
|
109
|
+
|
|
110
|
+
const handleWrite = async (func: ParsedFunction) => {
|
|
111
|
+
if (!walletClient || !userAddress) {
|
|
112
|
+
alert('Please connect your wallet');
|
|
113
|
+
return;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
const funcKey = func.name;
|
|
117
|
+
setLoading((prev) => ({ ...prev, [funcKey]: true }));
|
|
118
|
+
setResults((prev) => ({ ...prev, [funcKey]: undefined }));
|
|
119
|
+
|
|
120
|
+
try {
|
|
121
|
+
const args = getInputValues(funcKey, func);
|
|
122
|
+
|
|
123
|
+
const hash = await walletClient.writeContract({
|
|
124
|
+
address: contractAddress as Address,
|
|
125
|
+
abi: [func],
|
|
126
|
+
functionName: func.name,
|
|
127
|
+
args: args.length > 0 ? args : undefined,
|
|
128
|
+
account: userAddress,
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Wait for transaction
|
|
132
|
+
if (publicClient) {
|
|
133
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash });
|
|
134
|
+
|
|
135
|
+
setResults((prev) => ({
|
|
136
|
+
...prev,
|
|
137
|
+
[funcKey]: {
|
|
138
|
+
success: true,
|
|
139
|
+
txHash: hash,
|
|
140
|
+
status: receipt.status,
|
|
141
|
+
},
|
|
142
|
+
}));
|
|
143
|
+
} else {
|
|
144
|
+
setResults((prev) => ({
|
|
145
|
+
...prev,
|
|
146
|
+
[funcKey]: { success: true, txHash: hash },
|
|
147
|
+
}));
|
|
148
|
+
}
|
|
149
|
+
} catch (error) {
|
|
150
|
+
console.error('Write error:', error);
|
|
151
|
+
setResults((prev) => ({
|
|
152
|
+
...prev,
|
|
153
|
+
[funcKey]: {
|
|
154
|
+
success: false,
|
|
155
|
+
error: error instanceof Error ? error.message : 'Transaction failed',
|
|
156
|
+
},
|
|
157
|
+
}));
|
|
158
|
+
} finally {
|
|
159
|
+
setLoading((prev) => ({ ...prev, [funcKey]: false }));
|
|
160
|
+
}
|
|
161
|
+
};
|
|
162
|
+
|
|
163
|
+
const renderFunctionCard = (func: ParsedFunction, isWrite: boolean) => {
|
|
164
|
+
const funcKey = func.name;
|
|
165
|
+
const isLoading = loading[funcKey];
|
|
166
|
+
const result = results[funcKey];
|
|
167
|
+
|
|
168
|
+
return (
|
|
169
|
+
<Card key={func.name}>
|
|
170
|
+
<CardHeader className="pb-3">
|
|
171
|
+
<CardTitle className="text-base font-mono">{func.name}</CardTitle>
|
|
172
|
+
<CardDescription className="text-xs">
|
|
173
|
+
{formatFunctionSignature(func)}
|
|
174
|
+
</CardDescription>
|
|
175
|
+
</CardHeader>
|
|
176
|
+
<CardContent className="space-y-3">
|
|
177
|
+
{func.inputs.length > 0 && (
|
|
178
|
+
<div className="space-y-2">
|
|
179
|
+
{func.inputs.map((input, index) => (
|
|
180
|
+
<div key={index} className="space-y-1">
|
|
181
|
+
<Label className="text-xs">
|
|
182
|
+
{input.name || `param${index}`} ({input.type})
|
|
183
|
+
</Label>
|
|
184
|
+
<Input
|
|
185
|
+
placeholder={`Enter ${input.type}`}
|
|
186
|
+
value={inputValues[funcKey]?.[index] || ''}
|
|
187
|
+
onChange={(e) =>
|
|
188
|
+
handleInputChange(funcKey, index, e.target.value)
|
|
189
|
+
}
|
|
190
|
+
disabled={isLoading}
|
|
191
|
+
className="h-8 text-sm"
|
|
192
|
+
/>
|
|
193
|
+
</div>
|
|
194
|
+
))}
|
|
195
|
+
</div>
|
|
196
|
+
)}
|
|
197
|
+
|
|
198
|
+
<Button
|
|
199
|
+
onClick={() => (isWrite ? handleWrite(func) : handleRead(func))}
|
|
200
|
+
disabled={isLoading || (isWrite && !isConnected)}
|
|
201
|
+
size="sm"
|
|
202
|
+
variant={isWrite ? 'default' : 'outline'}
|
|
203
|
+
className="w-full"
|
|
204
|
+
>
|
|
205
|
+
{isLoading ? (
|
|
206
|
+
<>
|
|
207
|
+
<Loader2 className="h-3 w-3 mr-2 animate-spin" />
|
|
208
|
+
{isWrite ? 'Sending...' : 'Reading...'}
|
|
209
|
+
</>
|
|
210
|
+
) : (
|
|
211
|
+
<>{isWrite ? 'Write' : 'Read'}</>
|
|
212
|
+
)}
|
|
213
|
+
</Button>
|
|
214
|
+
|
|
215
|
+
{result && (
|
|
216
|
+
<div
|
|
217
|
+
className={`p-3 rounded-md text-xs ${result.success
|
|
218
|
+
? 'bg-green-500/10 border border-green-500/20'
|
|
219
|
+
: 'bg-red-500/10 border border-red-500/20'
|
|
220
|
+
}`}
|
|
221
|
+
>
|
|
222
|
+
{result.success ? (
|
|
223
|
+
<div className="space-y-1">
|
|
224
|
+
{result.data !== undefined && (
|
|
225
|
+
<div>
|
|
226
|
+
<span className="font-medium">Result: </span>
|
|
227
|
+
<code className="break-all">
|
|
228
|
+
{typeof result.data === 'bigint'
|
|
229
|
+
? result.data.toString()
|
|
230
|
+
: JSON.stringify(result.data)}
|
|
231
|
+
</code>
|
|
232
|
+
</div>
|
|
233
|
+
)}
|
|
234
|
+
{result.txHash && (
|
|
235
|
+
<div className="flex items-center gap-2">
|
|
236
|
+
<span className="font-medium">Tx: </span>
|
|
237
|
+
<code className="flex-1 break-all">{result.txHash}</code>
|
|
238
|
+
<a
|
|
239
|
+
href={`${getExplorerUrl().replace('/address/', '/tx/')}/${result.txHash}`}
|
|
240
|
+
target="_blank"
|
|
241
|
+
rel="noopener noreferrer"
|
|
242
|
+
className="text-blue-400 hover:text-blue-300"
|
|
243
|
+
>
|
|
244
|
+
<ExternalLink className="h-3 w-3" />
|
|
245
|
+
</a>
|
|
246
|
+
</div>
|
|
247
|
+
)}
|
|
248
|
+
{result.status && (
|
|
249
|
+
<div>
|
|
250
|
+
<span className="font-medium">Status: </span>
|
|
251
|
+
{result.status === 'success' ? '✓ Success' : '✗ Failed'}
|
|
252
|
+
</div>
|
|
253
|
+
)}
|
|
254
|
+
</div>
|
|
255
|
+
) : (
|
|
256
|
+
<div className="text-red-400">{result.error}</div>
|
|
257
|
+
)}
|
|
258
|
+
</div>
|
|
259
|
+
)}
|
|
260
|
+
</CardContent>
|
|
261
|
+
</Card>
|
|
262
|
+
);
|
|
263
|
+
};
|
|
264
|
+
|
|
265
|
+
return (
|
|
266
|
+
<div className="h-full flex flex-col">
|
|
267
|
+
<div className="p-4 border-b border-border space-y-2">
|
|
268
|
+
<div className="flex items-center justify-between">
|
|
269
|
+
<h2 className="font-semibold">Contract Interaction</h2>
|
|
270
|
+
<a
|
|
271
|
+
href={getExplorerUrl()}
|
|
272
|
+
target="_blank"
|
|
273
|
+
rel="noopener noreferrer"
|
|
274
|
+
className="text-xs text-muted-foreground hover:text-foreground flex items-center gap-1"
|
|
275
|
+
>
|
|
276
|
+
View on Explorer
|
|
277
|
+
<ExternalLink className="h-3 w-3" />
|
|
278
|
+
</a>
|
|
279
|
+
</div>
|
|
280
|
+
<div className="text-xs font-mono text-muted-foreground break-all">
|
|
281
|
+
{contractAddress}
|
|
282
|
+
</div>
|
|
283
|
+
</div>
|
|
284
|
+
|
|
285
|
+
<Tabs defaultValue="read" className="flex-1 flex flex-col min-h-0">
|
|
286
|
+
<TabsList className="mx-4 mt-4 grid w-[calc(100%-2rem)] grid-cols-2">
|
|
287
|
+
<TabsTrigger value="read" className="flex items-center gap-2">
|
|
288
|
+
<BookOpen className="h-4 w-4" />
|
|
289
|
+
Read ({readFunctions.length})
|
|
290
|
+
</TabsTrigger>
|
|
291
|
+
<TabsTrigger value="write" className="flex items-center gap-2">
|
|
292
|
+
<Edit className="h-4 w-4" />
|
|
293
|
+
Write ({writeFunctions.length})
|
|
294
|
+
</TabsTrigger>
|
|
295
|
+
</TabsList>
|
|
296
|
+
|
|
297
|
+
<TabsContent value="read" className="flex-1 overflow-auto p-4 space-y-3 mt-0">
|
|
298
|
+
{readFunctions.length === 0 ? (
|
|
299
|
+
<p className="text-sm text-muted-foreground text-center py-8">
|
|
300
|
+
No read functions available
|
|
301
|
+
</p>
|
|
302
|
+
) : (
|
|
303
|
+
readFunctions.map((func) => renderFunctionCard(func, false))
|
|
304
|
+
)}
|
|
305
|
+
</TabsContent>
|
|
306
|
+
|
|
307
|
+
<TabsContent value="write" className="flex-1 overflow-auto p-4 space-y-3 mt-0">
|
|
308
|
+
{!isConnected ? (
|
|
309
|
+
<p className="text-sm text-muted-foreground text-center py-8">
|
|
310
|
+
Connect your wallet to write to the contract
|
|
311
|
+
</p>
|
|
312
|
+
) : writeFunctions.length === 0 ? (
|
|
313
|
+
<p className="text-sm text-muted-foreground text-center py-8">
|
|
314
|
+
No write functions available
|
|
315
|
+
</p>
|
|
316
|
+
) : (
|
|
317
|
+
writeFunctions.map((func) => renderFunctionCard(func, true))
|
|
318
|
+
)}
|
|
319
|
+
</TabsContent>
|
|
320
|
+
</Tabs>
|
|
321
|
+
</div>
|
|
322
|
+
);
|
|
323
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
'use client';
|
|
2
|
+
|
|
3
|
+
import { FileText, Zap } from 'lucide-react';
|
|
4
|
+
|
|
5
|
+
export function ContractPlaceholder() {
|
|
6
|
+
return (
|
|
7
|
+
<div className="h-full flex flex-col bg-card">
|
|
8
|
+
{/* Header */}
|
|
9
|
+
<div className="p-3 sm:p-4 border-b border-border flex items-center justify-between shrink-0">
|
|
10
|
+
<div className="flex items-center gap-2">
|
|
11
|
+
<FileText className="h-4 w-4 sm:h-5 sm:w-5 text-primary" />
|
|
12
|
+
<h2 className="font-semibold text-sm sm:text-base">Contract Interaction</h2>
|
|
13
|
+
</div>
|
|
14
|
+
</div>
|
|
15
|
+
|
|
16
|
+
{/* Placeholder Content */}
|
|
17
|
+
<div className="flex-1 flex items-center justify-center p-6">
|
|
18
|
+
<div className="text-center space-y-4 max-w-sm">
|
|
19
|
+
<div className="w-16 h-16 mx-auto bg-muted rounded-full flex items-center justify-center">
|
|
20
|
+
<Zap className="h-8 w-8 text-muted-foreground" />
|
|
21
|
+
</div>
|
|
22
|
+
<div className="space-y-2">
|
|
23
|
+
<h3 className="font-semibold text-lg">No Contract Deployed</h3>
|
|
24
|
+
<p className="text-sm text-muted-foreground">
|
|
25
|
+
Deploy a contract to interact with its functions. Once deployed, you'll be able to read from and write to your smart contract.
|
|
26
|
+
</p>
|
|
27
|
+
</div>
|
|
28
|
+
<div className="text-xs text-muted-foreground bg-muted/50 rounded-lg p-3">
|
|
29
|
+
<p className="font-medium mb-1">To get started:</p>
|
|
30
|
+
<ol className="text-left space-y-1">
|
|
31
|
+
<li>1. Write your Stylus contract</li>
|
|
32
|
+
<li>2. Compile successfully</li>
|
|
33
|
+
<li>3. Connect your wallet</li>
|
|
34
|
+
<li>4. Click Deploy</li>
|
|
35
|
+
</ol>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
</div>
|
|
39
|
+
</div>
|
|
40
|
+
);
|
|
41
|
+
}
|