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
package/cli.js
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const { spawn } = require('child_process');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
console.log('🚀 Starting Stylus IDE...');
|
|
7
|
+
|
|
8
|
+
// Point to the frontend directory
|
|
9
|
+
const frontendPath = path.join(__dirname, 'frontend');
|
|
10
|
+
|
|
11
|
+
// Run 'npm run dev' inside the frontend folder
|
|
12
|
+
// Note: For a production package, you might want to run 'npm run start'
|
|
13
|
+
// after building, but for "as is" source, 'dev' is safest.
|
|
14
|
+
const child = spawn('npm', ['run', 'dev'], {
|
|
15
|
+
cwd: frontendPath,
|
|
16
|
+
stdio: 'inherit', // This pipes the output (logs) to the user's terminal
|
|
17
|
+
shell: true
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
child.on('error', (err) => {
|
|
21
|
+
console.error('Failed to start Stylus IDE:', err);
|
|
22
|
+
});
|
|
23
|
+
|
|
24
|
+
child.on('close', (code) => {
|
|
25
|
+
if (code !== 0) {
|
|
26
|
+
console.log(`Stylus IDE process exited with code ${code}`);
|
|
27
|
+
}
|
|
28
|
+
});
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
import { OpenAI } from "openai";
|
|
2
|
+
|
|
3
|
+
export const runtime = "edge";
|
|
4
|
+
export const maxDuration = 60;
|
|
5
|
+
|
|
6
|
+
const openai = new OpenAI({
|
|
7
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
8
|
+
});
|
|
9
|
+
|
|
10
|
+
const SYSTEM_PROMPT = `You are an expert Rust and Arbitrum Stylus smart contract developer. You help developers write, debug, and optimize Stylus contracts.
|
|
11
|
+
|
|
12
|
+
Key knowledge:
|
|
13
|
+
- Stylus uses Rust + stylus-sdk (currently v0.9.x)
|
|
14
|
+
- Contracts use sol_storage! macro for storage
|
|
15
|
+
- #[public] macro for external functions
|
|
16
|
+
- alloy_primitives for types (U256, Address, etc.)
|
|
17
|
+
- View functions (read-only) vs write functions (state changes)
|
|
18
|
+
- WASM deployment with 24KB limit
|
|
19
|
+
- Common patterns: ERC-20, Counter, Storage examples
|
|
20
|
+
|
|
21
|
+
Response guidelines:
|
|
22
|
+
- Provide concise, actionable code
|
|
23
|
+
- Explain Stylus-specific patterns
|
|
24
|
+
- Point out common mistakes
|
|
25
|
+
- Suggest gas optimizations
|
|
26
|
+
- Format code in markdown with \`\`\`rust blocks
|
|
27
|
+
- Keep explanations brief but clear`;
|
|
28
|
+
|
|
29
|
+
export async function POST(req: Request) {
|
|
30
|
+
try {
|
|
31
|
+
const { messages, context } = await req.json();
|
|
32
|
+
|
|
33
|
+
if (!process.env.OPENAI_API_KEY) {
|
|
34
|
+
return new Response(
|
|
35
|
+
JSON.stringify({ error: "OpenAI API key not configured" }),
|
|
36
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
37
|
+
);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Build messages with context
|
|
41
|
+
const contextMessage = context
|
|
42
|
+
? {
|
|
43
|
+
role: "system" as const,
|
|
44
|
+
content: `Current context:\n${context}`,
|
|
45
|
+
}
|
|
46
|
+
: null;
|
|
47
|
+
|
|
48
|
+
const allMessages = [
|
|
49
|
+
{ role: "system" as const, content: SYSTEM_PROMPT },
|
|
50
|
+
...(contextMessage ? [contextMessage] : []),
|
|
51
|
+
...messages,
|
|
52
|
+
];
|
|
53
|
+
|
|
54
|
+
// 1. Create the completion with stream: true
|
|
55
|
+
const response = await openai.chat.completions.create({
|
|
56
|
+
model: "gpt-4o-mini",
|
|
57
|
+
messages: allMessages,
|
|
58
|
+
temperature: 0.7,
|
|
59
|
+
max_tokens: 2000,
|
|
60
|
+
stream: true,
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
// 2. Create a native ReadableStream to handle the response chunks
|
|
64
|
+
const stream = new ReadableStream({
|
|
65
|
+
async start(controller) {
|
|
66
|
+
const encoder = new TextEncoder();
|
|
67
|
+
try {
|
|
68
|
+
// Iterate over the async iterable provided by the OpenAI SDK
|
|
69
|
+
for await (const chunk of response) {
|
|
70
|
+
const content = chunk.choices[0]?.delta?.content || "";
|
|
71
|
+
if (content) {
|
|
72
|
+
// Encode string to Uint8Array and enqueue
|
|
73
|
+
controller.enqueue(encoder.encode(content));
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
} catch (err) {
|
|
77
|
+
controller.error(err);
|
|
78
|
+
} finally {
|
|
79
|
+
controller.close();
|
|
80
|
+
}
|
|
81
|
+
},
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
// 3. Return a standard Response with the stream
|
|
85
|
+
return new Response(stream, {
|
|
86
|
+
headers: {
|
|
87
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
88
|
+
"Cache-Control": "no-cache",
|
|
89
|
+
Connection: "keep-alive",
|
|
90
|
+
},
|
|
91
|
+
});
|
|
92
|
+
} catch (error) {
|
|
93
|
+
console.error("Chat API error:", error);
|
|
94
|
+
return new Response(
|
|
95
|
+
JSON.stringify({
|
|
96
|
+
error: error instanceof Error ? error.message : "Chat failed",
|
|
97
|
+
}),
|
|
98
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
99
|
+
);
|
|
100
|
+
}
|
|
101
|
+
}
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { exec } from "child_process";
|
|
2
|
+
import { promisify } from "util";
|
|
3
|
+
import { NextResponse } from "next/server";
|
|
4
|
+
|
|
5
|
+
const execAsync = promisify(exec);
|
|
6
|
+
|
|
7
|
+
interface SetupStatus {
|
|
8
|
+
rust: boolean;
|
|
9
|
+
cargo: boolean;
|
|
10
|
+
wasmTarget: boolean;
|
|
11
|
+
cargoStylus: boolean;
|
|
12
|
+
platform: "darwin" | "linux" | "win32" | "other";
|
|
13
|
+
rustVersion?: string;
|
|
14
|
+
needsUpdate?: boolean;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
async function checkCommand(command: string): Promise<boolean> {
|
|
18
|
+
try {
|
|
19
|
+
await execAsync(command, { timeout: 5000 });
|
|
20
|
+
return true;
|
|
21
|
+
} catch {
|
|
22
|
+
return false;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
async function getRustVersion(): Promise<{
|
|
27
|
+
version: string;
|
|
28
|
+
major: number;
|
|
29
|
+
minor: number;
|
|
30
|
+
} | null> {
|
|
31
|
+
try {
|
|
32
|
+
const { stdout } = await execAsync("rustc --version", { timeout: 5000 });
|
|
33
|
+
const match = stdout.match(/rustc (\d+)\.(\d+)\.(\d+)/);
|
|
34
|
+
if (match) {
|
|
35
|
+
return {
|
|
36
|
+
version: `${match[1]}.${match[2]}.${match[3]}`,
|
|
37
|
+
major: parseInt(match[1]),
|
|
38
|
+
minor: parseInt(match[2]),
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
} catch {}
|
|
42
|
+
return null;
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
async function checkWasmTarget(): Promise<boolean> {
|
|
46
|
+
try {
|
|
47
|
+
const { stdout } = await execAsync("rustup target list", { timeout: 5000 });
|
|
48
|
+
return stdout.includes("wasm32-unknown-unknown (installed)");
|
|
49
|
+
} catch {
|
|
50
|
+
return false;
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export async function GET() {
|
|
55
|
+
try {
|
|
56
|
+
const platform = process.platform as SetupStatus["platform"];
|
|
57
|
+
|
|
58
|
+
const [rust, cargo, wasmTarget, cargoStylus] = await Promise.all([
|
|
59
|
+
checkCommand("rustc --version"),
|
|
60
|
+
checkCommand("cargo --version"),
|
|
61
|
+
checkWasmTarget(),
|
|
62
|
+
checkCommand("cargo stylus --version"),
|
|
63
|
+
]);
|
|
64
|
+
|
|
65
|
+
const rustVersionInfo = await getRustVersion();
|
|
66
|
+
const needsUpdate = rustVersionInfo
|
|
67
|
+
? rustVersionInfo.major < 1 ||
|
|
68
|
+
(rustVersionInfo.major === 1 && rustVersionInfo.minor < 88)
|
|
69
|
+
: false;
|
|
70
|
+
|
|
71
|
+
const status: SetupStatus = {
|
|
72
|
+
rust,
|
|
73
|
+
cargo,
|
|
74
|
+
wasmTarget,
|
|
75
|
+
cargoStylus,
|
|
76
|
+
platform: ["darwin", "linux", "win32"].includes(platform)
|
|
77
|
+
? (platform as "darwin" | "linux" | "win32")
|
|
78
|
+
: "other",
|
|
79
|
+
rustVersion: rustVersionInfo?.version,
|
|
80
|
+
needsUpdate,
|
|
81
|
+
};
|
|
82
|
+
|
|
83
|
+
return NextResponse.json(status);
|
|
84
|
+
} catch (error) {
|
|
85
|
+
return NextResponse.json(
|
|
86
|
+
{
|
|
87
|
+
error: "Failed to check setup",
|
|
88
|
+
details: error instanceof Error ? error.message : "Unknown error",
|
|
89
|
+
},
|
|
90
|
+
{ status: 500 }
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { cleanupOldProjects } from "@/lib/file-utils";
|
|
3
|
+
|
|
4
|
+
export const runtime = "nodejs";
|
|
5
|
+
|
|
6
|
+
export async function POST(request: NextRequest) {
|
|
7
|
+
try {
|
|
8
|
+
await cleanupOldProjects(30); // Cleanup projects older than 30 minutes
|
|
9
|
+
return NextResponse.json({ success: true });
|
|
10
|
+
} catch (error) {
|
|
11
|
+
console.error("Cleanup error:", error);
|
|
12
|
+
return NextResponse.json({ error: "Cleanup failed" }, { status: 500 });
|
|
13
|
+
}
|
|
14
|
+
}
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { getProjectPath } from "@/lib/file-utils";
|
|
4
|
+
import {
|
|
5
|
+
runCargoStylusCheck,
|
|
6
|
+
parseCompilationErrors,
|
|
7
|
+
ProjectFile, // Import the interface
|
|
8
|
+
} from "@/lib/compilation";
|
|
9
|
+
|
|
10
|
+
export const runtime = "nodejs";
|
|
11
|
+
export const maxDuration = 120;
|
|
12
|
+
|
|
13
|
+
export async function POST(request: NextRequest) {
|
|
14
|
+
const sessionId = randomUUID();
|
|
15
|
+
const projectPath = getProjectPath(sessionId);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const body = await request.json();
|
|
19
|
+
const { code, projectFiles } = body;
|
|
20
|
+
|
|
21
|
+
// Validate input
|
|
22
|
+
let filesToCompile: ProjectFile[] | undefined;
|
|
23
|
+
let mainCode = code;
|
|
24
|
+
|
|
25
|
+
if (
|
|
26
|
+
projectFiles &&
|
|
27
|
+
Array.isArray(projectFiles) &&
|
|
28
|
+
projectFiles.length > 0
|
|
29
|
+
) {
|
|
30
|
+
// Multi-file mode
|
|
31
|
+
filesToCompile = projectFiles;
|
|
32
|
+
|
|
33
|
+
// Find lib.rs for backward compatibility (used as fallback)
|
|
34
|
+
const libFile = projectFiles.find(
|
|
35
|
+
(f: ProjectFile) => f.path === "src/lib.rs" || f.path === "./src/lib.rs"
|
|
36
|
+
);
|
|
37
|
+
if (libFile) {
|
|
38
|
+
mainCode = libFile.content;
|
|
39
|
+
} else {
|
|
40
|
+
// If no lib.rs, use first .rs file
|
|
41
|
+
const firstRsFile = projectFiles.find((f: ProjectFile) =>
|
|
42
|
+
f.path.endsWith(".rs")
|
|
43
|
+
);
|
|
44
|
+
if (firstRsFile) {
|
|
45
|
+
mainCode = firstRsFile.content;
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
} else if (!code || typeof code !== "string") {
|
|
49
|
+
return NextResponse.json(
|
|
50
|
+
{
|
|
51
|
+
error: "Code or projectFiles required",
|
|
52
|
+
},
|
|
53
|
+
{ status: 400 }
|
|
54
|
+
);
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Run compilation with multi-file support
|
|
58
|
+
// The runCargoStylusCheck function now handles ALL file writing internally
|
|
59
|
+
const result = await runCargoStylusCheck(
|
|
60
|
+
projectPath,
|
|
61
|
+
mainCode,
|
|
62
|
+
undefined, // onOutput callback (optional)
|
|
63
|
+
filesToCompile // NEW: Pass project files (optional)
|
|
64
|
+
);
|
|
65
|
+
|
|
66
|
+
// Parse errors from stderr
|
|
67
|
+
const stderrContent = result.output
|
|
68
|
+
.filter((o) => o.type === "stderr")
|
|
69
|
+
.map((o) => o.data)
|
|
70
|
+
.join("\n");
|
|
71
|
+
|
|
72
|
+
const parsedErrors =
|
|
73
|
+
!result.success && stderrContent.length > 0
|
|
74
|
+
? parseCompilationErrors(stderrContent)
|
|
75
|
+
: [];
|
|
76
|
+
|
|
77
|
+
return NextResponse.json({
|
|
78
|
+
success: result.success,
|
|
79
|
+
exitCode: result.exitCode,
|
|
80
|
+
output: result.output, // Already includes file count from runCargoStylusCheck
|
|
81
|
+
errors: parsedErrors,
|
|
82
|
+
sessionId,
|
|
83
|
+
wasmSize: result.wasmSize, // Include WASM size
|
|
84
|
+
});
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.error("Compilation error:", error);
|
|
87
|
+
return NextResponse.json(
|
|
88
|
+
{
|
|
89
|
+
error: "Compilation failed",
|
|
90
|
+
details: error instanceof Error ? error.message : "Unknown error",
|
|
91
|
+
},
|
|
92
|
+
{ status: 500 }
|
|
93
|
+
);
|
|
94
|
+
}
|
|
95
|
+
}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import { NextRequest } from "next/server";
|
|
2
|
+
import { randomUUID } from "crypto";
|
|
3
|
+
import { getProjectPath } from "@/lib/file-utils";
|
|
4
|
+
import {
|
|
5
|
+
runCargoStylusCheck,
|
|
6
|
+
CompilationOutput,
|
|
7
|
+
parseCompilationErrors,
|
|
8
|
+
} from "@/lib/compilation";
|
|
9
|
+
|
|
10
|
+
export const runtime = "nodejs";
|
|
11
|
+
export const maxDuration = 120;
|
|
12
|
+
|
|
13
|
+
export async function POST(request: NextRequest) {
|
|
14
|
+
const sessionId = randomUUID();
|
|
15
|
+
const projectPath = getProjectPath(sessionId);
|
|
16
|
+
|
|
17
|
+
try {
|
|
18
|
+
const body = await request.json();
|
|
19
|
+
const { code } = body;
|
|
20
|
+
|
|
21
|
+
if (!code || typeof code !== "string") {
|
|
22
|
+
return new Response("Code is required", { status: 400 });
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
const encoder = new TextEncoder();
|
|
26
|
+
const stream = new TransformStream();
|
|
27
|
+
const writer = stream.writable.getWriter();
|
|
28
|
+
|
|
29
|
+
(async () => {
|
|
30
|
+
const stderrChunks: string[] = [];
|
|
31
|
+
|
|
32
|
+
try {
|
|
33
|
+
await writer.write(
|
|
34
|
+
encoder.encode(
|
|
35
|
+
`data: ${JSON.stringify({ type: "start", sessionId })}\n\n`
|
|
36
|
+
)
|
|
37
|
+
);
|
|
38
|
+
|
|
39
|
+
const result = await runCargoStylusCheck(
|
|
40
|
+
projectPath,
|
|
41
|
+
code,
|
|
42
|
+
async (out: CompilationOutput) => {
|
|
43
|
+
if (out.type === "stderr") stderrChunks.push(out.data);
|
|
44
|
+
await writer.write(
|
|
45
|
+
encoder.encode(`data: ${JSON.stringify(out)}\n\n`)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
);
|
|
49
|
+
|
|
50
|
+
const stderr = stderrChunks.join("\n");
|
|
51
|
+
const errors =
|
|
52
|
+
!result.success && stderr.length > 0
|
|
53
|
+
? parseCompilationErrors(stderr)
|
|
54
|
+
: [];
|
|
55
|
+
|
|
56
|
+
await writer.write(
|
|
57
|
+
encoder.encode(
|
|
58
|
+
`data: ${JSON.stringify({
|
|
59
|
+
type: "result",
|
|
60
|
+
success: result.success,
|
|
61
|
+
exitCode: result.exitCode,
|
|
62
|
+
errors,
|
|
63
|
+
})}\n\n`
|
|
64
|
+
)
|
|
65
|
+
);
|
|
66
|
+
} catch (error) {
|
|
67
|
+
await writer.write(
|
|
68
|
+
encoder.encode(
|
|
69
|
+
`data: ${JSON.stringify({
|
|
70
|
+
type: "error",
|
|
71
|
+
data: error instanceof Error ? error.message : "Unknown error",
|
|
72
|
+
})}\n\n`
|
|
73
|
+
)
|
|
74
|
+
);
|
|
75
|
+
} finally {
|
|
76
|
+
// DON'T cleanup here - keep project for ABI export
|
|
77
|
+
// Cleanup will happen on next compilation or via separate cleanup route
|
|
78
|
+
await writer.close();
|
|
79
|
+
}
|
|
80
|
+
})();
|
|
81
|
+
|
|
82
|
+
return new Response(stream.readable, {
|
|
83
|
+
headers: {
|
|
84
|
+
"Content-Type": "text/event-stream",
|
|
85
|
+
"Cache-Control": "no-cache",
|
|
86
|
+
Connection: "keep-alive",
|
|
87
|
+
},
|
|
88
|
+
});
|
|
89
|
+
} catch (error) {
|
|
90
|
+
return new Response(
|
|
91
|
+
JSON.stringify({
|
|
92
|
+
error: "Failed to start compilation",
|
|
93
|
+
details: error instanceof Error ? error.message : "Unknown error",
|
|
94
|
+
}),
|
|
95
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
96
|
+
);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import { OpenAI } from "openai";
|
|
2
|
+
|
|
3
|
+
export const runtime = "edge";
|
|
4
|
+
// Increased maxDuration for more complex completions if needed
|
|
5
|
+
export const maxDuration = 60;
|
|
6
|
+
|
|
7
|
+
const openai = new OpenAI({
|
|
8
|
+
apiKey: process.env.OPENAI_API_KEY,
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
const COMPLETION_SYSTEM_PROMPT = `You are an AI code completion assistant for Arbitrum Stylus smart contracts.
|
|
12
|
+
|
|
13
|
+
Rules:
|
|
14
|
+
- ONLY output the code completion, no explanations
|
|
15
|
+
- Match the existing code style and indentation
|
|
16
|
+
- Complete the function, type, or statement being written
|
|
17
|
+
- Use Stylus SDK v0.9.x patterns
|
|
18
|
+
- Keep completions concise and relevant
|
|
19
|
+
- NO markdown formatting, just raw code`;
|
|
20
|
+
|
|
21
|
+
export async function POST(req: Request) {
|
|
22
|
+
try {
|
|
23
|
+
const { prompt, context } = await req.json();
|
|
24
|
+
|
|
25
|
+
if (!process.env.OPENAI_API_KEY) {
|
|
26
|
+
return new Response(
|
|
27
|
+
JSON.stringify({ error: "OpenAI API key not configured" }),
|
|
28
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
29
|
+
);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
const fullPrompt = `${context}\n\n// Complete this:\n${prompt}`;
|
|
33
|
+
|
|
34
|
+
// 1. Create the completion with stream: true
|
|
35
|
+
const response = await openai.chat.completions.create({
|
|
36
|
+
model: "gpt-4o-mini",
|
|
37
|
+
messages: [
|
|
38
|
+
{ role: "system", content: COMPLETION_SYSTEM_PROMPT },
|
|
39
|
+
{ role: "user", content: fullPrompt },
|
|
40
|
+
],
|
|
41
|
+
temperature: 0.3,
|
|
42
|
+
max_tokens: 500,
|
|
43
|
+
stream: true,
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
// 2. Create a native ReadableStream to handle the response chunks
|
|
47
|
+
//
|
|
48
|
+
const stream = new ReadableStream({
|
|
49
|
+
async start(controller) {
|
|
50
|
+
const encoder = new TextEncoder();
|
|
51
|
+
try {
|
|
52
|
+
// Iterate over the async iterable provided by the OpenAI SDK
|
|
53
|
+
for await (const chunk of response) {
|
|
54
|
+
// Note: Completion responses are similar to chat, using 'content'
|
|
55
|
+
const content = chunk.choices[0]?.delta?.content || "";
|
|
56
|
+
if (content) {
|
|
57
|
+
// Encode string to Uint8Array and enqueue
|
|
58
|
+
controller.enqueue(encoder.encode(content));
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
controller.error(err);
|
|
63
|
+
} finally {
|
|
64
|
+
controller.close();
|
|
65
|
+
}
|
|
66
|
+
},
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// 3. Return a standard Response with the stream
|
|
70
|
+
return new Response(stream, {
|
|
71
|
+
headers: {
|
|
72
|
+
"Content-Type": "text/plain; charset=utf-8",
|
|
73
|
+
"Cache-Control": "no-cache",
|
|
74
|
+
Connection: "keep-alive",
|
|
75
|
+
},
|
|
76
|
+
});
|
|
77
|
+
} catch (error) {
|
|
78
|
+
console.error("Completion API error:", error);
|
|
79
|
+
return new Response(
|
|
80
|
+
JSON.stringify({
|
|
81
|
+
error: error instanceof Error ? error.message : "Completion failed",
|
|
82
|
+
}),
|
|
83
|
+
{ status: 500, headers: { "Content-Type": "application/json" } }
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
//deploy/route.ts <- api
|
|
2
|
+
import { NextRequest, NextResponse } from "next/server";
|
|
3
|
+
import { getProjectPath, projectExists } from "@/lib/file-utils";
|
|
4
|
+
import { deployContract } from "@/lib/deployment";
|
|
5
|
+
|
|
6
|
+
export const runtime = "nodejs";
|
|
7
|
+
export const maxDuration = 180;
|
|
8
|
+
|
|
9
|
+
// Normalize and validate private key format
|
|
10
|
+
function normalizePrivateKey(key: string): {
|
|
11
|
+
normalized: string;
|
|
12
|
+
error?: string;
|
|
13
|
+
} {
|
|
14
|
+
// Remove whitespace and newlines
|
|
15
|
+
let normalized = key.trim().replace(/\s/g, "");
|
|
16
|
+
|
|
17
|
+
// Remove 0x prefix if present for validation
|
|
18
|
+
const withoutPrefix =
|
|
19
|
+
normalized.startsWith("0x") || normalized.startsWith("0X")
|
|
20
|
+
? normalized.slice(2)
|
|
21
|
+
: normalized;
|
|
22
|
+
|
|
23
|
+
// Validate it's valid hex
|
|
24
|
+
if (!/^[a-fA-F0-9]+$/.test(withoutPrefix)) {
|
|
25
|
+
return {
|
|
26
|
+
normalized: "",
|
|
27
|
+
error:
|
|
28
|
+
"Private key contains invalid characters. Must be hexadecimal (0-9, a-f).",
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Validate correct length (64 hex chars = 32 bytes)
|
|
33
|
+
if (withoutPrefix.length !== 64) {
|
|
34
|
+
return {
|
|
35
|
+
normalized: "",
|
|
36
|
+
error: `Private key must be exactly 64 hex characters (got ${withoutPrefix.length}). This causes the "Odd number of digits" error.`,
|
|
37
|
+
};
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// Always return with 0x prefix (required by cargo-stylus)
|
|
41
|
+
return { normalized: `0x${withoutPrefix}` };
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export async function POST(request: NextRequest) {
|
|
45
|
+
try {
|
|
46
|
+
const body = await request.json();
|
|
47
|
+
const { sessionId, privateKey, rpcUrl } = body;
|
|
48
|
+
|
|
49
|
+
if (!sessionId || typeof sessionId !== "string") {
|
|
50
|
+
return NextResponse.json(
|
|
51
|
+
{ error: "Session ID is required" },
|
|
52
|
+
{ status: 400 }
|
|
53
|
+
);
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
if (!privateKey || typeof privateKey !== "string") {
|
|
57
|
+
return NextResponse.json(
|
|
58
|
+
{ error: "Private key is required" },
|
|
59
|
+
{ status: 400 }
|
|
60
|
+
);
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
if (!rpcUrl || typeof rpcUrl !== "string") {
|
|
64
|
+
return NextResponse.json(
|
|
65
|
+
{ error: "RPC URL is required" },
|
|
66
|
+
{ status: 400 }
|
|
67
|
+
);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
// Normalize and validate private key
|
|
71
|
+
const { normalized: normalizedKey, error: keyError } =
|
|
72
|
+
normalizePrivateKey(privateKey);
|
|
73
|
+
if (keyError) {
|
|
74
|
+
return NextResponse.json(
|
|
75
|
+
{ error: keyError, success: false },
|
|
76
|
+
{ status: 400 }
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
const projectPath = getProjectPath(sessionId);
|
|
81
|
+
|
|
82
|
+
// Check if project exists
|
|
83
|
+
const exists = await projectExists(sessionId);
|
|
84
|
+
if (!exists) {
|
|
85
|
+
return NextResponse.json(
|
|
86
|
+
{
|
|
87
|
+
error: "Project not found. Please compile your contract first.",
|
|
88
|
+
success: false,
|
|
89
|
+
},
|
|
90
|
+
{ status: 404 }
|
|
91
|
+
);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Use normalized key for deployment
|
|
95
|
+
const result = await deployContract(projectPath, normalizedKey, rpcUrl);
|
|
96
|
+
|
|
97
|
+
return NextResponse.json({
|
|
98
|
+
success: result.success,
|
|
99
|
+
contractAddress: result.contractAddress,
|
|
100
|
+
txHash: result.activationTxHash ?? result.deploymentTxHash,
|
|
101
|
+
deploymentTxHash: result.deploymentTxHash,
|
|
102
|
+
activationTxHash: result.activationTxHash,
|
|
103
|
+
rpcUsed: result.rpcUsed,
|
|
104
|
+
error: result.error,
|
|
105
|
+
output: result.output,
|
|
106
|
+
});
|
|
107
|
+
} catch (error) {
|
|
108
|
+
console.error("Deployment error:", error);
|
|
109
|
+
return NextResponse.json(
|
|
110
|
+
{
|
|
111
|
+
error: "Deployment failed",
|
|
112
|
+
details: error instanceof Error ? error.message : "Unknown error",
|
|
113
|
+
success: false,
|
|
114
|
+
},
|
|
115
|
+
{ status: 500 }
|
|
116
|
+
);
|
|
117
|
+
}
|
|
118
|
+
}
|