create-stylus-ide 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (135) hide show
  1. package/Readme.MD +1515 -0
  2. package/cli.js +28 -0
  3. package/frontend/.vscode/settings.json +9 -0
  4. package/frontend/app/api/chat/route.ts +101 -0
  5. package/frontend/app/api/check-setup/route.ts +93 -0
  6. package/frontend/app/api/cleanup/route.ts +14 -0
  7. package/frontend/app/api/compile/route.ts +95 -0
  8. package/frontend/app/api/compile-stream/route.ts +98 -0
  9. package/frontend/app/api/complete/route.ts +86 -0
  10. package/frontend/app/api/deploy/route.ts +118 -0
  11. package/frontend/app/api/export-abi/route.ts +58 -0
  12. package/frontend/app/favicon.ico +0 -0
  13. package/frontend/app/globals.css +177 -0
  14. package/frontend/app/layout.tsx +29 -0
  15. package/frontend/app/ml/page.tsx +694 -0
  16. package/frontend/app/page.tsx +1132 -0
  17. package/frontend/app/providers.tsx +18 -0
  18. package/frontend/app/qlearning/page.tsx +188 -0
  19. package/frontend/app/raytracing/page.tsx +268 -0
  20. package/frontend/components/abi/ABIDialog.tsx +132 -0
  21. package/frontend/components/ai/AICompletionPopup.tsx +76 -0
  22. package/frontend/components/ai/ChatPanel.tsx +292 -0
  23. package/frontend/components/ai/QuickActions.tsx +128 -0
  24. package/frontend/components/blockchain/BlockchainContractBanner.tsx +64 -0
  25. package/frontend/components/blockchain/BlockchainLoadingDialog.tsx +188 -0
  26. package/frontend/components/deploy/DeployDialog.tsx +334 -0
  27. package/frontend/components/editor/FileTabs.tsx +181 -0
  28. package/frontend/components/editor/MonacoEditor.tsx +306 -0
  29. package/frontend/components/file-tree/ContextMenu.tsx +110 -0
  30. package/frontend/components/file-tree/DeleteConfirmDialog.tsx +61 -0
  31. package/frontend/components/file-tree/FileInputDialog.tsx +97 -0
  32. package/frontend/components/file-tree/FileNode.tsx +60 -0
  33. package/frontend/components/file-tree/FileTree.tsx +259 -0
  34. package/frontend/components/file-tree/FileTreeSkeleton.tsx +26 -0
  35. package/frontend/components/file-tree/FolderNode.tsx +105 -0
  36. package/frontend/components/github/GitHubLoadingDialog.tsx +201 -0
  37. package/frontend/components/github/GitHubMetadataBanner.tsx +61 -0
  38. package/frontend/components/github/LoadFromGitHubDialog.tsx +125 -0
  39. package/frontend/components/github/URLCopyButton.tsx +60 -0
  40. package/frontend/components/interact/ContractInteraction.tsx +323 -0
  41. package/frontend/components/interact/ContractPlaceholder.tsx +41 -0
  42. package/frontend/components/orbit/BenchmarkDialog.tsx +342 -0
  43. package/frontend/components/orbit/OrbitExplorer.tsx +273 -0
  44. package/frontend/components/project/ProjectActions.tsx +176 -0
  45. package/frontend/components/q-learning/ContractConfig.tsx +172 -0
  46. package/frontend/components/q-learning/MazeGrid.tsx +346 -0
  47. package/frontend/components/q-learning/PathAnimation.tsx +384 -0
  48. package/frontend/components/q-learning/QTableHeatmap.tsx +300 -0
  49. package/frontend/components/q-learning/TrainingForm.tsx +349 -0
  50. package/frontend/components/ray-tracing/ContractConfig.tsx +245 -0
  51. package/frontend/components/ray-tracing/MintingForm.tsx +280 -0
  52. package/frontend/components/ray-tracing/RenderCanvas.tsx +228 -0
  53. package/frontend/components/ray-tracing/RenderingPanel.tsx +259 -0
  54. package/frontend/components/ray-tracing/StyleControls.tsx +217 -0
  55. package/frontend/components/setup/SetupGuide.tsx +290 -0
  56. package/frontend/components/ui/KeyboardShortcutHint.tsx +74 -0
  57. package/frontend/components/ui/alert-dialog.tsx +157 -0
  58. package/frontend/components/ui/alert.tsx +66 -0
  59. package/frontend/components/ui/badge.tsx +46 -0
  60. package/frontend/components/ui/button.tsx +62 -0
  61. package/frontend/components/ui/card.tsx +92 -0
  62. package/frontend/components/ui/context-menu.tsx +252 -0
  63. package/frontend/components/ui/dialog.tsx +143 -0
  64. package/frontend/components/ui/dropdown-menu.tsx +257 -0
  65. package/frontend/components/ui/input.tsx +21 -0
  66. package/frontend/components/ui/label.tsx +24 -0
  67. package/frontend/components/ui/progress.tsx +31 -0
  68. package/frontend/components/ui/scroll-area.tsx +58 -0
  69. package/frontend/components/ui/select.tsx +190 -0
  70. package/frontend/components/ui/separator.tsx +28 -0
  71. package/frontend/components/ui/sheet.tsx +139 -0
  72. package/frontend/components/ui/skeleton.tsx +13 -0
  73. package/frontend/components/ui/slider.tsx +63 -0
  74. package/frontend/components/ui/sonner.tsx +40 -0
  75. package/frontend/components/ui/tabs.tsx +66 -0
  76. package/frontend/components/ui/textarea.tsx +18 -0
  77. package/frontend/components/wallet/ConnectButton.tsx +167 -0
  78. package/frontend/components/wallet/FaucetButton.tsx +256 -0
  79. package/frontend/components.json +22 -0
  80. package/frontend/eslint.config.mjs +18 -0
  81. package/frontend/hooks/useAICompletion.ts +75 -0
  82. package/frontend/hooks/useBlockchainLoader.ts +58 -0
  83. package/frontend/hooks/useChats.ts +137 -0
  84. package/frontend/hooks/useCompilation.ts +173 -0
  85. package/frontend/hooks/useFileTabs.ts +178 -0
  86. package/frontend/hooks/useGitHubLoader.ts +50 -0
  87. package/frontend/hooks/useKeyboardShortcuts.ts +47 -0
  88. package/frontend/hooks/usePanelState.ts +115 -0
  89. package/frontend/hooks/useProjectState.ts +276 -0
  90. package/frontend/hooks/useResponsive.ts +29 -0
  91. package/frontend/lib/abi-parser.ts +58 -0
  92. package/frontend/lib/blockchain-api.ts +374 -0
  93. package/frontend/lib/blockchain-explorers.ts +75 -0
  94. package/frontend/lib/blockchain-loader.ts +112 -0
  95. package/frontend/lib/cargo-template.ts +64 -0
  96. package/frontend/lib/compilation.ts +529 -0
  97. package/frontend/lib/constants.ts +31 -0
  98. package/frontend/lib/deployment.ts +176 -0
  99. package/frontend/lib/file-utils.ts +83 -0
  100. package/frontend/lib/github-api.ts +246 -0
  101. package/frontend/lib/github-loader.ts +369 -0
  102. package/frontend/lib/ml-contract-template.txt +900 -0
  103. package/frontend/lib/orbit-chains.ts +181 -0
  104. package/frontend/lib/output-formatter.ts +68 -0
  105. package/frontend/lib/project-manager.ts +632 -0
  106. package/frontend/lib/ray-tracing-abi.ts +206 -0
  107. package/frontend/lib/storage.ts +189 -0
  108. package/frontend/lib/templates.ts +1662 -0
  109. package/frontend/lib/url-parser.ts +188 -0
  110. package/frontend/lib/utils.ts +6 -0
  111. package/frontend/lib/wagmi-config.ts +24 -0
  112. package/frontend/next.config.ts +7 -0
  113. package/frontend/package-lock.json +16259 -0
  114. package/frontend/package.json +60 -0
  115. package/frontend/postcss.config.mjs +7 -0
  116. package/frontend/public/file.svg +1 -0
  117. package/frontend/public/globe.svg +1 -0
  118. package/frontend/public/ml-weights/.gitkeep +0 -0
  119. package/frontend/public/ml-weights/model.pkl +0 -0
  120. package/frontend/public/ml-weights/model_weights.json +27102 -0
  121. package/frontend/public/ml-weights/test_samples.json +7888 -0
  122. package/frontend/public/next.svg +1 -0
  123. package/frontend/public/vercel.svg +1 -0
  124. package/frontend/public/window.svg +1 -0
  125. package/frontend/scripts/check-env.js +52 -0
  126. package/frontend/scripts/setup.js +285 -0
  127. package/frontend/tailwind.config.ts +64 -0
  128. package/frontend/tsconfig.json +34 -0
  129. package/frontend/types/blockchain.ts +63 -0
  130. package/frontend/types/github.ts +54 -0
  131. package/frontend/types/project.ts +106 -0
  132. package/ml-training/README.md +56 -0
  133. package/ml-training/train_tiny_model.py +325 -0
  134. package/ml-training/update_template.py +59 -0
  135. package/package.json +30 -0
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,9 @@
1
+ {
2
+ "cSpell.words": [
3
+ "cdylib",
4
+ "Chainlink",
5
+ "codegen",
6
+ "parseable",
7
+ "rustc"
8
+ ]
9
+ }
@@ -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
+ }