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,374 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blockchain explorer API client
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import {
|
|
6
|
+
ExplorerAPIResponse,
|
|
7
|
+
ContractSourceCode,
|
|
8
|
+
ParsedContractSource,
|
|
9
|
+
} from "@/types/blockchain";
|
|
10
|
+
import { getExplorerConfig } from "@/lib/blockchain-explorers";
|
|
11
|
+
|
|
12
|
+
export class BlockchainAPIError extends Error {
|
|
13
|
+
constructor(
|
|
14
|
+
message: string,
|
|
15
|
+
public statusCode?: number,
|
|
16
|
+
public isNotVerified: boolean = false,
|
|
17
|
+
public isInvalidAddress: boolean = false,
|
|
18
|
+
public isAPIKeyMissing: boolean = false
|
|
19
|
+
) {
|
|
20
|
+
super(message);
|
|
21
|
+
this.name = "BlockchainAPIError";
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Blockchain API Client
|
|
27
|
+
*/
|
|
28
|
+
export class BlockchainAPIClient {
|
|
29
|
+
private apiKeys: Record<string, string> = {};
|
|
30
|
+
|
|
31
|
+
constructor() {
|
|
32
|
+
// Load API keys from environment (client-side only)
|
|
33
|
+
if (typeof window !== "undefined") {
|
|
34
|
+
console.log("🔑 Loading API keys from environment...");
|
|
35
|
+
|
|
36
|
+
const arbiscanKey = process.env.NEXT_PUBLIC_ARBISCAN_API_KEY;
|
|
37
|
+
const etherscanKey = process.env.NEXT_PUBLIC_ETHERSCAN_API_KEY;
|
|
38
|
+
const basescanKey = process.env.NEXT_PUBLIC_BASESCAN_API_KEY;
|
|
39
|
+
|
|
40
|
+
console.log("Available keys:", {
|
|
41
|
+
arbiscan: !!arbiscanKey,
|
|
42
|
+
etherscan: !!etherscanKey,
|
|
43
|
+
basescan: !!basescanKey,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
this.apiKeys = {
|
|
47
|
+
"arbiscan.io": arbiscanKey || "",
|
|
48
|
+
"sepolia.arbiscan.io": arbiscanKey || "", // Use same key for testnet
|
|
49
|
+
"etherscan.io": etherscanKey || "",
|
|
50
|
+
"sepolia.etherscan.io": etherscanKey || "",
|
|
51
|
+
"basescan.org": basescanKey || "",
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
/**
|
|
57
|
+
* Fetch contract source code from explorer
|
|
58
|
+
*/
|
|
59
|
+
async getContractSource(
|
|
60
|
+
explorerDomain: string,
|
|
61
|
+
address: string
|
|
62
|
+
): Promise<ContractSourceCode> {
|
|
63
|
+
const config = getExplorerConfig(explorerDomain);
|
|
64
|
+
|
|
65
|
+
if (!config) {
|
|
66
|
+
throw new BlockchainAPIError(
|
|
67
|
+
`Unsupported blockchain explorer: ${explorerDomain}`,
|
|
68
|
+
0,
|
|
69
|
+
false,
|
|
70
|
+
false,
|
|
71
|
+
false
|
|
72
|
+
);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Get API key for this explorer (for V2: typically your Etherscan API key)
|
|
76
|
+
const apiKey = this.apiKeys[explorerDomain] || "";
|
|
77
|
+
|
|
78
|
+
console.log("🔍 API Key check:", {
|
|
79
|
+
domain: explorerDomain,
|
|
80
|
+
hasKey: !!apiKey,
|
|
81
|
+
keyPreview: apiKey ? apiKey.slice(0, 10) + "..." : "none",
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
if (!apiKey && config.apiKeyRequired) {
|
|
85
|
+
throw new BlockchainAPIError(
|
|
86
|
+
`API key required for ${config.name}. Please add NEXT_PUBLIC_ETHERSCAN_API_KEY to your .env.local file.`,
|
|
87
|
+
0,
|
|
88
|
+
false,
|
|
89
|
+
false,
|
|
90
|
+
true
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// --- V2 IMPORTANT: include chainid (default is 1, but you should always pass it) ---
|
|
95
|
+
// Ensure your BlockchainExplorerConfig includes chainId:number
|
|
96
|
+
const chainId =
|
|
97
|
+
(config as any).chainId ??
|
|
98
|
+
(config.network === "sepolia" && config.chain === "ethereum"
|
|
99
|
+
? 11155111
|
|
100
|
+
: config.network === "mainnet" && config.chain === "ethereum"
|
|
101
|
+
? 1
|
|
102
|
+
: undefined);
|
|
103
|
+
|
|
104
|
+
if (!chainId) {
|
|
105
|
+
throw new BlockchainAPIError(
|
|
106
|
+
`Missing chainId for explorer ${config.name}. Add chainId to BLOCKCHAIN_EXPLORERS config.`,
|
|
107
|
+
0
|
|
108
|
+
);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
const params = new URLSearchParams({
|
|
112
|
+
chainid: String(chainId), // ✅ V2
|
|
113
|
+
module: "contract",
|
|
114
|
+
action: "getsourcecode",
|
|
115
|
+
address,
|
|
116
|
+
});
|
|
117
|
+
|
|
118
|
+
if (apiKey) params.set("apikey", apiKey);
|
|
119
|
+
|
|
120
|
+
const url = `${config.apiUrl}?${params.toString()}`;
|
|
121
|
+
|
|
122
|
+
console.log("📡 API Request:", {
|
|
123
|
+
url: apiKey ? url.replace(apiKey, "API_KEY_HIDDEN") : url,
|
|
124
|
+
address,
|
|
125
|
+
chainId,
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
try {
|
|
129
|
+
const response = await fetch(url);
|
|
130
|
+
|
|
131
|
+
if (!response.ok) {
|
|
132
|
+
throw new BlockchainAPIError(
|
|
133
|
+
`Failed to fetch from ${config.name}: ${response.statusText}`,
|
|
134
|
+
response.status
|
|
135
|
+
);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
const data: ExplorerAPIResponse = await response.json();
|
|
139
|
+
|
|
140
|
+
console.log("📦 API Response:", {
|
|
141
|
+
status: data.status,
|
|
142
|
+
message: data.message,
|
|
143
|
+
hasResult: !!data.result,
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
// Check API response status
|
|
147
|
+
if (data.status !== "1") {
|
|
148
|
+
console.error("❌ API Error Details:", data);
|
|
149
|
+
|
|
150
|
+
const resultText = typeof data.result === "string" ? data.result : "";
|
|
151
|
+
const combinedText = `${data.message || ""} ${resultText || ""}`.trim();
|
|
152
|
+
|
|
153
|
+
// V1 deprecation message (means you're still hitting a V1 endpoint somewhere)
|
|
154
|
+
if (combinedText.toLowerCase().includes("deprecated v1 endpoint")) {
|
|
155
|
+
throw new BlockchainAPIError(
|
|
156
|
+
`You are hitting a deprecated V1 endpoint. Ensure config.apiUrl is "https://api.etherscan.io/v2/api" and you pass "chainid" for ${config.chain} ${config.network}.`,
|
|
157
|
+
0
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Invalid API key (often comes via result text in V2)
|
|
162
|
+
if (combinedText.toLowerCase().includes("invalid api key")) {
|
|
163
|
+
throw new BlockchainAPIError(
|
|
164
|
+
`Invalid API key for ${config.name}. Please check NEXT_PUBLIC_ETHERSCAN_API_KEY.`,
|
|
165
|
+
0,
|
|
166
|
+
false,
|
|
167
|
+
false,
|
|
168
|
+
true
|
|
169
|
+
);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Some V2 errors are returned as NOTOK with details in `result`
|
|
173
|
+
if (data.message === "NOTOK") {
|
|
174
|
+
throw new BlockchainAPIError(
|
|
175
|
+
`API request failed: ${
|
|
176
|
+
resultText || data.message
|
|
177
|
+
}\n\nChecks:\n1) API key is valid\n2) Contract address is correct\n3) Correct chainid (${chainId}) for ${
|
|
178
|
+
config.chain
|
|
179
|
+
} ${config.network}`,
|
|
180
|
+
0
|
|
181
|
+
);
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
throw new BlockchainAPIError(
|
|
185
|
+
`API Error: ${combinedText || "Unknown error"}`,
|
|
186
|
+
0
|
|
187
|
+
);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
// Parse result
|
|
191
|
+
const result = Array.isArray(data.result) ? data.result[0] : data.result;
|
|
192
|
+
|
|
193
|
+
if (!result || typeof result === "string") {
|
|
194
|
+
throw new BlockchainAPIError("Invalid response from explorer API", 0);
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
const sourceCode = result as ContractSourceCode;
|
|
198
|
+
|
|
199
|
+
// Check if contract is verified
|
|
200
|
+
if (!sourceCode.SourceCode || sourceCode.SourceCode === "") {
|
|
201
|
+
throw new BlockchainAPIError(
|
|
202
|
+
"Contract is not verified on this explorer",
|
|
203
|
+
0,
|
|
204
|
+
true
|
|
205
|
+
);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
console.log("✅ Contract loaded:", sourceCode.ContractName);
|
|
209
|
+
|
|
210
|
+
return sourceCode;
|
|
211
|
+
} catch (error) {
|
|
212
|
+
if (error instanceof BlockchainAPIError) {
|
|
213
|
+
throw error;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
console.error("❌ Network/Unknown error:", error);
|
|
217
|
+
throw new BlockchainAPIError(
|
|
218
|
+
"Network error. Please check your connection and try again.",
|
|
219
|
+
0
|
|
220
|
+
);
|
|
221
|
+
}
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Parse contract source code into files
|
|
226
|
+
*/
|
|
227
|
+
parseContractSource(sourceCode: ContractSourceCode): ParsedContractSource {
|
|
228
|
+
const rawSource = sourceCode.SourceCode;
|
|
229
|
+
|
|
230
|
+
console.log("🔍 Parsing contract source...");
|
|
231
|
+
console.log("Raw source length:", rawSource.length);
|
|
232
|
+
console.log("First 100 chars:", rawSource.substring(0, 100));
|
|
233
|
+
|
|
234
|
+
// Check if it's multi-file (JSON format)
|
|
235
|
+
if (rawSource.startsWith("{")) {
|
|
236
|
+
try {
|
|
237
|
+
let parsed: any;
|
|
238
|
+
|
|
239
|
+
// Try double-brace format first: {{...}}
|
|
240
|
+
if (rawSource.startsWith("{{") && rawSource.endsWith("}}")) {
|
|
241
|
+
console.log("📦 Detected double-brace format");
|
|
242
|
+
const jsonStr = rawSource.slice(1, -1);
|
|
243
|
+
parsed = JSON.parse(jsonStr);
|
|
244
|
+
} else {
|
|
245
|
+
console.log("📦 Detected standard JSON format");
|
|
246
|
+
parsed = JSON.parse(rawSource);
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
console.log("Parsed keys:", Object.keys(parsed));
|
|
250
|
+
|
|
251
|
+
// Standard JSON format: { "language": "Solidity", "sources": {...} }
|
|
252
|
+
if (parsed.sources) {
|
|
253
|
+
console.log("✅ Using standard JSON sources format");
|
|
254
|
+
console.log("Source files:", Object.keys(parsed.sources));
|
|
255
|
+
|
|
256
|
+
const files = Object.entries(parsed.sources).map(
|
|
257
|
+
([path, sourceObj]: [string, any]) => {
|
|
258
|
+
// Clean path
|
|
259
|
+
const cleanPath = path.startsWith("/") ? path.slice(1) : path;
|
|
260
|
+
|
|
261
|
+
// Extract content - handle different structures
|
|
262
|
+
let content = "";
|
|
263
|
+
if (typeof sourceObj === "string") {
|
|
264
|
+
content = sourceObj;
|
|
265
|
+
} else if (sourceObj && typeof sourceObj === "object") {
|
|
266
|
+
content = sourceObj.content || sourceObj.source || "";
|
|
267
|
+
}
|
|
268
|
+
|
|
269
|
+
console.log(` - ${cleanPath}: ${content.length} bytes`);
|
|
270
|
+
|
|
271
|
+
return {
|
|
272
|
+
path: cleanPath,
|
|
273
|
+
content,
|
|
274
|
+
};
|
|
275
|
+
}
|
|
276
|
+
);
|
|
277
|
+
|
|
278
|
+
// Also include language and settings as files if present
|
|
279
|
+
if (parsed.language) {
|
|
280
|
+
files.push({
|
|
281
|
+
path: "metadata/language.txt",
|
|
282
|
+
content: parsed.language,
|
|
283
|
+
});
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
if (parsed.settings) {
|
|
287
|
+
files.push({
|
|
288
|
+
path: "metadata/settings.json",
|
|
289
|
+
content: JSON.stringify(parsed.settings, null, 2),
|
|
290
|
+
});
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
return {
|
|
294
|
+
type: "multi-file",
|
|
295
|
+
files: files.filter((f) => f.content && f.content.length > 0), // Filter empty files
|
|
296
|
+
mainFile: files[0]?.path || "Contract.sol",
|
|
297
|
+
};
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
// Legacy format: { "Contract.sol": {...}, "OtherFile.sol": {...} }
|
|
301
|
+
console.log("✅ Using legacy JSON format");
|
|
302
|
+
const files = Object.entries(parsed)
|
|
303
|
+
.filter(([key]) => !["language", "settings"].includes(key))
|
|
304
|
+
.map(([path, sourceObj]: [string, any]) => {
|
|
305
|
+
const cleanPath = path.startsWith("/") ? path.slice(1) : path;
|
|
306
|
+
|
|
307
|
+
let content = "";
|
|
308
|
+
if (typeof sourceObj === "string") {
|
|
309
|
+
content = sourceObj;
|
|
310
|
+
} else if (sourceObj && typeof sourceObj === "object") {
|
|
311
|
+
content = sourceObj.content || sourceObj.source || "";
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
console.log(` - ${cleanPath}: ${content.length} bytes`);
|
|
315
|
+
|
|
316
|
+
return {
|
|
317
|
+
path: cleanPath,
|
|
318
|
+
content,
|
|
319
|
+
};
|
|
320
|
+
})
|
|
321
|
+
.filter((f) => f.content && f.content.length > 0);
|
|
322
|
+
|
|
323
|
+
if (files.length === 0) {
|
|
324
|
+
throw new Error("No valid source files found in JSON");
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
type: "multi-file",
|
|
329
|
+
files,
|
|
330
|
+
mainFile: files[0]?.path || "Contract.sol",
|
|
331
|
+
};
|
|
332
|
+
} catch (error) {
|
|
333
|
+
console.error("❌ Failed to parse JSON:", error);
|
|
334
|
+
console.log("Falling back to single file mode");
|
|
335
|
+
}
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
// Single file or flattened contract
|
|
339
|
+
console.log("📄 Using single file format");
|
|
340
|
+
const fileName = `${sourceCode.ContractName}.sol`;
|
|
341
|
+
|
|
342
|
+
if (!rawSource || rawSource.length === 0) {
|
|
343
|
+
throw new Error("Source code is empty");
|
|
344
|
+
}
|
|
345
|
+
|
|
346
|
+
return {
|
|
347
|
+
type: rawSource.includes("// File:") ? "flattened" : "single",
|
|
348
|
+
files: [
|
|
349
|
+
{
|
|
350
|
+
path: fileName,
|
|
351
|
+
content: rawSource,
|
|
352
|
+
},
|
|
353
|
+
],
|
|
354
|
+
mainFile: fileName,
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
/**
|
|
359
|
+
* Set API key for an explorer
|
|
360
|
+
*/
|
|
361
|
+
setAPIKey(explorerDomain: string, apiKey: string): void {
|
|
362
|
+
this.apiKeys[explorerDomain] = apiKey;
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
/**
|
|
366
|
+
* Check if API key is set for explorer
|
|
367
|
+
*/
|
|
368
|
+
hasAPIKey(explorerDomain: string): boolean {
|
|
369
|
+
return !!this.apiKeys[explorerDomain];
|
|
370
|
+
}
|
|
371
|
+
}
|
|
372
|
+
|
|
373
|
+
// Export singleton instance
|
|
374
|
+
export const blockchainAPI = new BlockchainAPIClient();
|
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
// lib/blockchain-explorers.ts
|
|
2
|
+
import { BlockchainExplorerConfig } from "@/types/blockchain";
|
|
3
|
+
|
|
4
|
+
const ETHERSCAN_V2_BASE = "https://api.etherscan.io/v2/api";
|
|
5
|
+
|
|
6
|
+
export const BLOCKCHAIN_EXPLORERS: Record<string, BlockchainExplorerConfig> = {
|
|
7
|
+
"etherscan.io": {
|
|
8
|
+
name: "Etherscan",
|
|
9
|
+
chain: "ethereum",
|
|
10
|
+
network: "mainnet",
|
|
11
|
+
chainId: 1,
|
|
12
|
+
apiUrl: ETHERSCAN_V2_BASE,
|
|
13
|
+
explorerUrl: "https://etherscan.io",
|
|
14
|
+
apiKeyRequired: true,
|
|
15
|
+
},
|
|
16
|
+
"sepolia.etherscan.io": {
|
|
17
|
+
name: "Sepolia Etherscan",
|
|
18
|
+
chain: "ethereum",
|
|
19
|
+
network: "sepolia",
|
|
20
|
+
chainId: 11155111,
|
|
21
|
+
apiUrl: ETHERSCAN_V2_BASE, // ✅ don't use api-sepolia host for V2
|
|
22
|
+
explorerUrl: "https://sepolia.etherscan.io",
|
|
23
|
+
apiKeyRequired: true,
|
|
24
|
+
},
|
|
25
|
+
"arbiscan.io": {
|
|
26
|
+
name: "Arbiscan",
|
|
27
|
+
chain: "arbitrum",
|
|
28
|
+
network: "mainnet",
|
|
29
|
+
chainId: 42161,
|
|
30
|
+
apiUrl: ETHERSCAN_V2_BASE, // ✅ unified V2 base
|
|
31
|
+
explorerUrl: "https://arbiscan.io",
|
|
32
|
+
apiKeyRequired: true,
|
|
33
|
+
},
|
|
34
|
+
"sepolia.arbiscan.io": {
|
|
35
|
+
name: "Arbiscan Sepolia",
|
|
36
|
+
chain: "arbitrum",
|
|
37
|
+
network: "sepolia",
|
|
38
|
+
chainId: 421614,
|
|
39
|
+
apiUrl: ETHERSCAN_V2_BASE,
|
|
40
|
+
explorerUrl: "https://sepolia.arbiscan.io",
|
|
41
|
+
apiKeyRequired: true,
|
|
42
|
+
},
|
|
43
|
+
"basescan.org": {
|
|
44
|
+
name: "Basescan",
|
|
45
|
+
chain: "base",
|
|
46
|
+
network: "mainnet",
|
|
47
|
+
chainId: 8453,
|
|
48
|
+
apiUrl: ETHERSCAN_V2_BASE,
|
|
49
|
+
explorerUrl: "https://basescan.org",
|
|
50
|
+
apiKeyRequired: true,
|
|
51
|
+
},
|
|
52
|
+
};
|
|
53
|
+
|
|
54
|
+
/**
|
|
55
|
+
* Get explorer config by domain
|
|
56
|
+
*/
|
|
57
|
+
export function getExplorerConfig(
|
|
58
|
+
domain: string
|
|
59
|
+
): BlockchainExplorerConfig | null {
|
|
60
|
+
return BLOCKCHAIN_EXPLORERS[domain] || null;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
/**
|
|
64
|
+
* Get all supported explorers
|
|
65
|
+
*/
|
|
66
|
+
export function getSupportedExplorers(): BlockchainExplorerConfig[] {
|
|
67
|
+
return Object.values(BLOCKCHAIN_EXPLORERS);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Check if domain is a supported explorer
|
|
72
|
+
*/
|
|
73
|
+
export function isSupportedExplorer(domain: string): boolean {
|
|
74
|
+
return domain in BLOCKCHAIN_EXPLORERS;
|
|
75
|
+
}
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Blockchain contract loader - for interaction panel, not editor
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { ContractInteractionData } from "@/types/blockchain";
|
|
6
|
+
import { BlockchainURLInfo } from "@/lib/url-parser";
|
|
7
|
+
import { blockchainAPI } from "@/lib/blockchain-api";
|
|
8
|
+
import { getExplorerConfig } from "@/lib/blockchain-explorers";
|
|
9
|
+
|
|
10
|
+
export interface BlockchainLoadProgress {
|
|
11
|
+
stage: "validating" | "fetching" | "parsing" | "complete" | "error";
|
|
12
|
+
message: string;
|
|
13
|
+
progress: number; // 0-100
|
|
14
|
+
contractName?: string;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
export type BlockchainProgressCallback = (
|
|
18
|
+
progress: BlockchainLoadProgress
|
|
19
|
+
) => void;
|
|
20
|
+
|
|
21
|
+
/**
|
|
22
|
+
* Fetch contract data for interaction (not for editor)
|
|
23
|
+
*/
|
|
24
|
+
export async function fetchContractForInteraction(
|
|
25
|
+
urlInfo: BlockchainURLInfo,
|
|
26
|
+
onProgress?: BlockchainProgressCallback
|
|
27
|
+
): Promise<ContractInteractionData> {
|
|
28
|
+
const { explorerDomain, address, chain, network } = urlInfo;
|
|
29
|
+
|
|
30
|
+
try {
|
|
31
|
+
// Stage 1: Validate address format
|
|
32
|
+
onProgress?.({
|
|
33
|
+
stage: "validating",
|
|
34
|
+
message: "Validating contract address...",
|
|
35
|
+
progress: 10,
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
if (!address.match(/^0x[a-fA-F0-9]{40}$/)) {
|
|
39
|
+
throw new Error("Invalid Ethereum address format");
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
// Stage 2: Fetch contract source + ABI
|
|
43
|
+
onProgress?.({
|
|
44
|
+
stage: "fetching",
|
|
45
|
+
message: `Fetching contract from ${chain}...`,
|
|
46
|
+
progress: 40,
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
const sourceCode = await blockchainAPI.getContractSource(
|
|
50
|
+
explorerDomain,
|
|
51
|
+
address
|
|
52
|
+
);
|
|
53
|
+
|
|
54
|
+
onProgress?.({
|
|
55
|
+
stage: "fetching",
|
|
56
|
+
message: `Found contract: ${sourceCode.ContractName}`,
|
|
57
|
+
progress: 70,
|
|
58
|
+
contractName: sourceCode.ContractName,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
// Stage 3: Extract ABI
|
|
62
|
+
onProgress?.({
|
|
63
|
+
stage: "parsing",
|
|
64
|
+
message: "Extracting contract ABI...",
|
|
65
|
+
progress: 85,
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
if (!sourceCode.ABI || sourceCode.ABI === "") {
|
|
69
|
+
throw new Error("Contract ABI not available");
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
// Get explorer config for URL
|
|
73
|
+
const explorerConfig = getExplorerConfig(explorerDomain);
|
|
74
|
+
const explorerUrl = explorerConfig
|
|
75
|
+
? `${explorerConfig.explorerUrl}/address/${address}`
|
|
76
|
+
: urlInfo.rawUrl;
|
|
77
|
+
|
|
78
|
+
// Build contract interaction data
|
|
79
|
+
const contractData: ContractInteractionData = {
|
|
80
|
+
address,
|
|
81
|
+
name: sourceCode.ContractName,
|
|
82
|
+
chain,
|
|
83
|
+
network,
|
|
84
|
+
chainId: explorerConfig?.chainId || 0,
|
|
85
|
+
abi: sourceCode.ABI,
|
|
86
|
+
verified: true,
|
|
87
|
+
compiler: sourceCode.CompilerVersion,
|
|
88
|
+
optimization: sourceCode.OptimizationUsed === "1",
|
|
89
|
+
explorerUrl,
|
|
90
|
+
};
|
|
91
|
+
|
|
92
|
+
onProgress?.({
|
|
93
|
+
stage: "complete",
|
|
94
|
+
message: `Ready to interact with ${sourceCode.ContractName}`,
|
|
95
|
+
progress: 100,
|
|
96
|
+
contractName: sourceCode.ContractName,
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
return contractData;
|
|
100
|
+
} catch (error) {
|
|
101
|
+
const errorMessage =
|
|
102
|
+
error instanceof Error ? error.message : "Unknown error";
|
|
103
|
+
|
|
104
|
+
onProgress?.({
|
|
105
|
+
stage: "error",
|
|
106
|
+
message: errorMessage,
|
|
107
|
+
progress: 0,
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
throw error;
|
|
111
|
+
}
|
|
112
|
+
}
|
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
export const CARGO_TOML_TEMPLATE = `[package]
|
|
2
|
+
name = "stylus-hello-world"
|
|
3
|
+
version = "0.1.11"
|
|
4
|
+
edition = "2021"
|
|
5
|
+
license = "MIT OR Apache-2.0"
|
|
6
|
+
homepage = "https://github.com/OffchainLabs/stylus-hello-world"
|
|
7
|
+
repository = "https://github.com/OffchainLabs/stylus-hello-world"
|
|
8
|
+
keywords = ["arbitrum", "ethereum", "stylus", "alloy"]
|
|
9
|
+
description = "Stylus hello world example"
|
|
10
|
+
|
|
11
|
+
[dependencies]
|
|
12
|
+
alloy-primitives = "=0.8.20"
|
|
13
|
+
alloy-sol-types = "=0.8.20"
|
|
14
|
+
stylus-sdk = "0.9.0"
|
|
15
|
+
hex = { version = "0.4", default-features = false }
|
|
16
|
+
|
|
17
|
+
[dev-dependencies]
|
|
18
|
+
alloy-primitives = { version = "=0.8.20", features = ["sha3-keccak"] }
|
|
19
|
+
tokio = { version = "1.12.0", features = ["full"] }
|
|
20
|
+
ethers = "2.0"
|
|
21
|
+
eyre = "0.6.8"
|
|
22
|
+
stylus-sdk = { version = "0.9.0", features = ["stylus-test"] }
|
|
23
|
+
dotenv = "0.15.0"
|
|
24
|
+
|
|
25
|
+
[features]
|
|
26
|
+
default = ["mini-alloc"]
|
|
27
|
+
export-abi = ["stylus-sdk/export-abi"]
|
|
28
|
+
debug = ["stylus-sdk/debug"]
|
|
29
|
+
mini-alloc = ["stylus-sdk/mini-alloc"]
|
|
30
|
+
|
|
31
|
+
[[bin]]
|
|
32
|
+
name = "stylus-hello-world"
|
|
33
|
+
path = "src/main.rs"
|
|
34
|
+
required-features = ["export-abi"]
|
|
35
|
+
|
|
36
|
+
[lib]
|
|
37
|
+
crate-type = ["lib", "cdylib"]
|
|
38
|
+
|
|
39
|
+
[profile.release]
|
|
40
|
+
codegen-units = 1
|
|
41
|
+
strip = true
|
|
42
|
+
lto = true
|
|
43
|
+
panic = "abort"
|
|
44
|
+
opt-level = 3
|
|
45
|
+
`;
|
|
46
|
+
|
|
47
|
+
export const RUST_TOOLCHAIN_TOML = `[toolchain]
|
|
48
|
+
channel = "1.87.0"
|
|
49
|
+
targets = ["wasm32-unknown-unknown"]
|
|
50
|
+
`;
|
|
51
|
+
|
|
52
|
+
export const MAIN_RS_TEMPLATE = `#![cfg_attr(not(feature = "export-abi"), no_main)]
|
|
53
|
+
|
|
54
|
+
#[cfg(feature = "export-abi")]
|
|
55
|
+
fn main() {
|
|
56
|
+
// Generated by Stylus when #[entrypoint] + #[public] exist in src/lib.rs
|
|
57
|
+
stylus_hello_world::print_abi("MIT-OR-APACHE-2.0", "pragma solidity ^0.8.23;");
|
|
58
|
+
}
|
|
59
|
+
`;
|
|
60
|
+
|
|
61
|
+
export const GITIGNORE_TEMPLATE = `target/
|
|
62
|
+
.DS_Store
|
|
63
|
+
Cargo.lock
|
|
64
|
+
`;
|