-
- toggleCollapsed(e, k)}>
- {`${k.substring(0, 5)}..${k.substring(50)}`}
-
- handleCopy(k)} />
- {/* {copied && Copied!} */}
- handleRemoveDeployed(k)}/>
-
-
- {
- deployed[k] && deployed[k].map((item, i) => (
-
- ))
- }
+
+
+ toggleCollapsed(e, k)}>
+ {`${k.substring(0, 5)}..${k.substring(50)}`}
+
+ handleCopy(k)} />
+ {/* {copied && Copied!} */}
+ handleRemoveDeployed(k)} />
+
+
+ {
+ deployed[k] && deployed[k].methods && deployed[k].methods.map((item, i) => (
+
+ ))
+ }
+
-
)
- )
+ )
}
diff --git a/packages/frontend/src/components/Editor/Editor.tsx b/packages/frontend/src/components/Editor/Editor.tsx
index 785e16b..2cac606 100644
--- a/packages/frontend/src/components/Editor/Editor.tsx
+++ b/packages/frontend/src/components/Editor/Editor.tsx
@@ -15,17 +15,30 @@ function Editor() {
const { fontSize } = useSelector(store, (state) => state.context.preferences);
return (
-
+
}
onChange={(value) => store.send({ type: "changeContent", content: value || "" })}
- options={{ fontSize }}
+ options={{
+ fontSize,
+ minimap: { enabled: false },
+ scrollBeyondLastLine: false,
+ automaticLayout: true,
+ theme: "vs-dark",
+ padding: { top: 0, bottom: 0 },
+ renderWhitespace: 'none',
+ wordWrap: 'off',
+ selectOnLineNumbers: true,
+ roundedSelection: false,
+ readOnly: false,
+ cursorStyle: 'line'
+ }}
/>
);
diff --git a/packages/frontend/src/components/ErrorModal.tsx b/packages/frontend/src/components/ErrorModal.tsx
new file mode 100644
index 0000000..69bf728
--- /dev/null
+++ b/packages/frontend/src/components/ErrorModal.tsx
@@ -0,0 +1,52 @@
+"use client";
+
+import { Dialog, DialogContent, DialogHeader, DialogTitle, DialogDescription } from "./ui/dialog";
+import { Button } from "./ui/button";
+import { FaTimes } from "react-icons/fa";
+
+interface ErrorModalProps {
+ isOpen: boolean;
+ onClose: () => void;
+ title: string;
+ message: string;
+}
+
+function ErrorModal({ isOpen, onClose, title, message }: ErrorModalProps) {
+ return (
+
+ );
+}
+
+export default ErrorModal;
\ No newline at end of file
diff --git a/packages/frontend/src/components/FileExplorer/index.tsx b/packages/frontend/src/components/FileExplorer/index.tsx
index 0aade6f..529f6b6 100644
--- a/packages/frontend/src/components/FileExplorer/index.tsx
+++ b/packages/frontend/src/components/FileExplorer/index.tsx
@@ -3,9 +3,8 @@ import RenderNode from "./components/RenderNode";
function FileExplorer({ root }: { root: FolderType }) {
return (
-
-
File Explorer
-
+
diff --git a/packages/frontend/src/components/FunctionOutputDisplay.tsx b/packages/frontend/src/components/FunctionOutputDisplay.tsx
new file mode 100644
index 0000000..b5a6f10
--- /dev/null
+++ b/packages/frontend/src/components/FunctionOutputDisplay.tsx
@@ -0,0 +1,89 @@
+"use client";
+
+import { useState } from "react";
+import { Button } from "./ui/button";
+import { FaCopy, FaCheck, FaTimes } from "react-icons/fa";
+import { safeStringify } from "@/utils";
+
+interface FunctionOutputDisplayProps {
+ functionName: string;
+ returnValue: any;
+ logs: string[];
+ onClose: () => void;
+}
+
+function FunctionOutputDisplay({ functionName, returnValue, logs, onClose }: FunctionOutputDisplayProps) {
+ const [copied, setCopied] = useState(false);
+
+ const handleCopy = async () => {
+ try {
+ const outputText = `Function: ${functionName}\nReturn Value: ${safeStringify(returnValue)}\nLogs: ${logs.join('\n')}`;
+ await navigator.clipboard.writeText(outputText);
+ setCopied(true);
+ setTimeout(() => setCopied(false), 2000);
+ } catch (err) {
+ console.error("Copy failed", err);
+ }
+ };
+
+ return (
+
+
+
Function Output
+
+
+
+
+
+
+
+ {/* Function Name */}
+
+
+
+ {functionName}
+
+
+
+ {/* Return Value */}
+
+
+
+ {returnValue !== null ? safeStringify(returnValue) : "No return value"}
+
+
+
+ {/* Logs */}
+ {logs.length > 0 && (
+
+
+
+ {logs.map((log, index) => (
+
+ {log}
+
+ ))}
+
+
+ )}
+
+
+ );
+}
+
+export default FunctionOutputDisplay;
diff --git a/packages/frontend/src/components/Header.tsx b/packages/frontend/src/components/Header.tsx
index cf1a625..63904bc 100644
--- a/packages/frontend/src/components/Header.tsx
+++ b/packages/frontend/src/components/Header.tsx
@@ -1,7 +1,9 @@
"use client";
import { useEffect, useRef, useState } from "react";
-import { FaPlay, FaTimes } from "react-icons/fa";
+import { FaPlay, FaTimes, FaRocket, FaCode, FaMoon, FaSun } from "react-icons/fa";
+import Image from "next/image";
+import SolangLogo from "@/assets/image/solang-logo.png";
import { useSelector } from "@xstate/store/react";
import { cn } from "@/lib/utils";
import { store } from "@/state";
@@ -9,9 +11,13 @@ import { useExplorerItem, useFileContent } from "@/state/hooks";
import Hide from "./Hide";
import IconButton from "./IconButton";
import useCompile from "@/hooks/useCompile";
+import CompilerOptionsModal from "./CompilerOptionsModal";
import useDeploy from "@/hooks/useDeploy";
import { FileType } from "@/types/explorer";
+import DeployContractModal from "./DeployContractModal";
+import InvokeFunctionModal from "./InvokeFunctionModal";
import { get } from "lodash";
+import ErrorModal from "./ErrorModal";
function TabItem({ path }: { path: string }) {
const file = useExplorerItem(path);
@@ -29,16 +35,18 @@ function TabItem({ path }: { path: string }) {
ref={itemRef}
onClick={() => store.send({ type: "setCurrentPath", path })}
className={cn(
- "bg-foreground/10 px-3 py-1 w-max h-full flex items-center gap-32 border-r duration-150 active:opacity-50",
- active && "border-t border-t-primary bg-background/20",
+ "px-4 py-2 w-max h-full flex items-center gap-2 border-r border-[#2d2d2d] duration-150 active:opacity-50 cursor-pointer",
+ active
+ ? "bg-[#0F0F23] text-white border-t-2 border-t-[#8b5cf6]"
+ : "bg-[#1A1B3A] text-[#cccccc] hover:bg-[#2a2b5a]",
)}
>
-
{file?.name}
+
{file?.name}
store.send({ type: "removeTab", path })}
>
-
+
);
@@ -61,16 +69,18 @@ function TabHome({ path }: { path: string }) {
ref={itemRef}
onClick={() => store.send({ type: "setCurrentPath", path })}
className={cn(
- "bg-foreground/10 px-3 py-1 w-max h-full flex items-center gap-32 border-r duration-150 active:opacity-50 select-none",
- active && "border-t border-t-primary bg-background/20",
+ "px-4 py-2 w-max h-full flex items-center gap-2 border-r border-[#2d2d2d] duration-150 active:opacity-50 select-none cursor-pointer",
+ active
+ ? "bg-[#0F0F23] text-white border-t-2 border-t-[#8b5cf6]"
+ : "bg-[#1A1B3A] text-[#cccccc] hover:bg-[#2a2b5a]",
)}
>
-
Home
+
Home
store.send({ type: "removeTab", path })}
>
-
+
);
@@ -85,31 +95,132 @@ function Header() {
const [contract, setContract] = useState
(null);
const selected = useSelector(store, (state) => state.context.currentFile);
// const showSpinnerDialog = useSelector(store, (state) => state.context.showSpinnerDialog);
-const [name, setName] = useState('');
+ const [name, setName] = useState('');
const obj = useSelector(store, (state) => get(state.context, selected || '')) as FileType;
console.log('[header] tabs', tabs)
useEffect(() => {
- if(selected && selected !== 'home') {
- setName(obj.name);
- }
- }, [selected])
+ if (selected && selected !== 'home') {
+ setName(obj.name);
+ }
+ }, [selected])
const handleCompile = async () => {
const result = await compileFile()
- if(selected && selected !== 'home')
- store.send({ type: "addCompiled", path: selected, name });
+
+ // Check if compilation failed
+ if (result.err) {
+ setCompileErrorMessage(result.err);
+ setShowCompileErrorModal(true);
+ return;
+ }
+
+ // Only add to compiled list if compilation was successful
+ if (selected && selected !== 'home' && result.data) {
+ store.send({ type: "addCompiled", path: selected, name });
+ }
+
console.log('[-] compilation result', result)
}
+ const [isDarkMode, setIsDarkMode] = useState(true);
+ const [openCompile, setOpenCompile] = useState(false);
+ const [openDeploy, setOpenDeploy] = useState(false);
+ const [openInvoke, setOpenInvoke] = useState(false);
+ const [showCompileErrorModal, setShowCompileErrorModal] = useState(false);
+ const [compileErrorMessage, setCompileErrorMessage] = useState("");
+
return (
-
- {/*
-
+ );
+}
+
+// File Tabs Component (to be used below the header)
+export function FileTabs() {
+ const tabs = useSelector(store, (state) => state.context.tabs);
+ const containerRef = useRef
(null);
+
+ return (
+
{[...tabs].map((tab) => (
@@ -123,4 +234,5 @@ const [name, setName] = useState
('');
);
}
+export { Header };
export default Header;
diff --git a/packages/frontend/src/components/InvokeFunction.tsx b/packages/frontend/src/components/InvokeFunction.tsx
index f62e12c..c1c9705 100644
--- a/packages/frontend/src/components/InvokeFunction.tsx
+++ b/packages/frontend/src/components/InvokeFunction.tsx
@@ -14,6 +14,7 @@ import { scValToNative, xdr } from "@stellar/stellar-base";
import Spinner from "./Spinner";
import ContractService from "@/lib/services/server/contract";
import { Network_Url } from "@/constants";
+import { safeStringify } from "@/utils";
function transformValue(type: string, value: any) {
switch (type) {
@@ -22,7 +23,7 @@ function transformValue(type: string, value: any) {
case "bool":
return value ? "true" : "false";
case "vec":
- return typeof value === "string" ? value : JSON.stringify(value);
+ return typeof value === "string" ? value : safeStringify(value);
default:
return value;
}
@@ -91,7 +92,7 @@ function InvokeFunction({ contractAddress, method }: { contractAddress: string,
logger.info("Invoking Contract function...");
setBlock(true);
- logger.info(JSON.stringify(requestData, null, 2));
+ logger.info(safeStringify(requestData, 2));
toast.loading("Invoking function...", { id: toastId });
console.log("Invoke Data", requestData);
@@ -104,6 +105,9 @@ function InvokeFunction({ contractAddress, method }: { contractAddress: string,
let retVal: any = null;
let logs: string[] = [];
+ // Track error events to write to terminal
+ let errorMessages: string[] = [];
+
for (const eventXdr of Array.from(diagnosticEventsXdr || [])) {
try {
const diagnosticEvent = xdr.DiagnosticEvent.fromXDR(eventXdr as any, "base64");
@@ -119,16 +123,30 @@ function InvokeFunction({ contractAddress, method }: { contractAddress: string,
if (topics.includes("log")) {
try {
- logs.push(JSON.stringify(eventData));
+ logs.push(safeStringify(eventData));
} catch {
logs.push(String(eventData));
}
}
+
+ // Check for error events
+ if (topics.includes("error")) {
+ const errorMsg = typeof eventData === 'string' ? eventData : safeStringify(eventData);
+ errorMessages.push(errorMsg);
+ logger.error(`Contract Error: ${errorMsg}`);
+ console.log("Error Event", topics, eventData);
+ }
} catch (e) {
logger.error(`Error parsing diagnostic event: ${String(e)}`);
}
}
+ // Write errors to terminal if any
+ if (errorMessages.length > 0) {
+ const errorDetails = `${errorMessages.join('\n')}\nDiagnostic Events:\n${safeStringify(diagnosticEventsXdr, 2)}`;
+ logger.error(errorDetails);
+ }
+
logs = logs.filter(Boolean);
setLogs(logs);
@@ -180,7 +198,7 @@ function InvokeFunction({ contractAddress, method }: { contractAddress: string,
-
-
Solang playground
+
+ {/* Tabs */}
+
+ setSidebar(SidebarView.FILE_EXPLORER)}
+ className={`flex-1 py-3 px-4 text-sm font-medium transition-colors ${sidebar === SidebarView.FILE_EXPLORER
+ ? 'bg-[#0F0F23] text-white border-b-2 border-b-[#8b5cf6]'
+ : 'bg-[#1A1B3A] text-[#cccccc] hover:bg-[#2a2b5a]'
+ }`}
+ >
+ Files
+
+ setSidebar(SidebarView.COMPILE)}
+ className={`flex-1 py-3 px-4 text-sm font-medium transition-colors ${sidebar === SidebarView.COMPILE
+ ? 'bg-[#0F0F23] text-white border-b-2 border-b-[#8b5cf6]'
+ : 'bg-[#1A1B3A] text-[#cccccc] hover:bg-[#2a2b5a]'
+ }`}
+ >
+ Examples
+
+
diff --git a/packages/frontend/src/hooks/useCompile.tsx b/packages/frontend/src/hooks/useCompile.tsx
index 9c7aad0..09b214b 100644
--- a/packages/frontend/src/hooks/useCompile.tsx
+++ b/packages/frontend/src/hooks/useCompile.tsx
@@ -14,72 +14,78 @@ function useCompile() {
const code = useFileContent();
const selected = useSelector(store, (state) => state.context.currentFile);
- const compileFile = async (): Promise
=> {
+ const compileFile = async (targetFilePath?: string): Promise => {
try {
store.send({ type: "setDialogSpinner", show: true });
- console.log('[tur] [compileFile] code:', code)
- if (!code) {
- const err ="Error: No Source Code Found"
+ // Determine which file's code to compile
+ const state = store.getSnapshot().context;
+ const path = targetFilePath || selected || '';
+ const codeToCompile = path ? state.files[path] : code;
+
+ console.log('[tur] [compileFile] path:', path)
+ if (!codeToCompile) {
+ const err = "Error: No Source Code Found"
logger.error(err);
- return {
+ return {
data: null,
err
}
}
-
+
logger.info("Compiling contract...");
-
+
const opts: RequestInit = {
method: "POST",
mode: "cors",
credentials: "same-origin",
headers: { "Content-Type": "application/json" },
body: JSON.stringify({
- source: code,
+ source: codeToCompile,
}),
};
-
+
const { result, success, message } = await fetchWithTimeout(`${Network_Url.BACKEND_SERVER}/compile`, opts, async (res) => {
const result = await res.json().catch(() => null);
console.log('compilation result', result);
if (!result) {
- return {
- success: false,
- message: res.statusText,
- result: null,
- };
+ return {
+ success: false,
+ message: res.statusText,
+ result: null,
+ };
}
-
+
return {
- success: res.ok,
- message: res.statusText,
- result: result,
+ success: res.ok,
+ message: res.statusText,
+ result: result,
};
});
-
+
let err = "";
-
+
if (success) {
if (result.type === "SUCCESS") {
- const wasm = result.payload.wasm;
- store.send({ type: "updateCurrentWasm", path: selected || '', buff: wasm });
- logger.info("Contract compiled successfully!");
- return {
- data: wasm,
- err: null
- };
+ const wasm = result.payload.wasm;
+ // Persist the compiled WASM against the target path (or current selection)
+ store.send({ type: "updateCurrentWasm", path: path, buff: wasm });
+ logger.info("Contract compiled successfully!");
+ return {
+ data: wasm,
+ err: null
+ };
} else {
- const message = result.payload.compile_stderr;
- logger.error(message);
- err = message
+ const message = result.payload.compile_stderr;
+ logger.error(message);
+ err = message
}
} else {
logger.error(message);
err = message
}
console.log('[tur] compilation error:', err)
- return {
+ return {
data: null,
err
}
diff --git a/packages/frontend/src/hooks/useDeploy.tsx b/packages/frontend/src/hooks/useDeploy.tsx
index d073a0c..4b3a53c 100644
--- a/packages/frontend/src/hooks/useDeploy.tsx
+++ b/packages/frontend/src/hooks/useDeploy.tsx
@@ -10,43 +10,90 @@ import ContractService from "@/lib/services/server/contract";
import { IParam } from "@/lib/services/types/common";
import { Network_Url } from "@/constants";
import { logger } from "@/state/utils";
+import { get } from "lodash";
+import { FileType } from "@/types/explorer";
function useDeploy() {
- const {compileFile} = useCompile();
-
+ const { compileFile } = useCompile();
+
const selected = useSelector(store, (state) => state.context.currentFile);
const currWasm = useSelector(store, (state) => state.context.currentWasm);
- const deployWasm = async (wasmBuf: null | Buffer, ctorParamList: IParam[]) => {
+ const deployWasm = async (wasmBuf: null | Buffer, ctorParamList: IParam[], targetFilePath?: string) => {
console.log('[tur] deploying', wasmBuf)
-
- if(currWasm.path.indexOf(selected || '') > -1) {
+
+ // Use the provided target file path or fall back to current tab
+ const fileToDeploy = targetFilePath || selected;
+ console.log('[tur] deploying file:', fileToDeploy, 'current tab:', selected);
+
+ if (currWasm.path && fileToDeploy && currWasm.path === fileToDeploy) {
wasmBuf = currWasm.buff
- } else if(!wasmBuf && selected && selected !== 'explorer') {
- const r = await compileFile();
- wasmBuf = r.data
- }
-
- if (!wasmBuf) {
- return;
}
try {
store.send({ type: "setDialogSpinner", show: true });
+ logger.info(`Deploying contract from file: ${fileToDeploy}`);
const contractService = new ContractService(Network_Url.TEST_NET)
-
+
+ // If we don't have WASM buffer, compile the target file
+ if (!wasmBuf && fileToDeploy && fileToDeploy !== 'explorer') {
+ // Store original current file
+ const originalCurrentFile = selected;
+
+ // Temporarily switch to target file for compilation
+ if (fileToDeploy !== originalCurrentFile) {
+ store.send({ type: "setCurrentPath", path: fileToDeploy });
+ }
+
+ try {
+ const r = await compileFile(fileToDeploy);
+ wasmBuf = r.data;
+ } finally {
+ // Restore original file
+ if (originalCurrentFile && fileToDeploy !== originalCurrentFile) {
+ store.send({ type: "setCurrentPath", path: originalCurrentFile });
+ }
+ }
+ }
+
+ if (!wasmBuf) {
+ logger.error("No WASM buffer available for deployment after compilation attempt");
+ throw new Error("No WASM buffer available for deployment");
+ }
+
const idl = await generateIdl(wasmBuf);
const fltrd = idl.filter((i: FunctionSpec) => i.name.indexOf('constructor') == -1);
store.send({ type: "updateContract", methods: fltrd });
const contractAddress = await contractService.deployContract(wasmBuf, ctorParamList);
console.log("Contract deployed successfully!", contractAddress);
- if(contractAddress) store.send({ type: "updateContract", address: contractAddress });
+ if (contractAddress) {
+ // Extract file name from the file object (path format: "explorer.items.src.items['main.sol']")
+ let fileName = 'Unknown';
+ if (fileToDeploy) {
+ try {
+ const state = store.getSnapshot().context;
+ const fileObj = get(state, fileToDeploy) as FileType;
+ fileName = fileObj?.name || 'Unknown';
+ } catch {
+ // Fallback: extract from path using regex
+ const match = fileToDeploy.match(/\['(.+?)'\]$/);
+ fileName = match ? match[1] : 'Unknown';
+ }
+ }
+ store.send({ type: "updateContract", address: contractAddress, fileName });
+ logger.info(`Contract deployed successfully! Address: ${contractAddress}`);
+ } else {
+ logger.error("Deployment completed but no address was returned");
+ }
} catch (e) {
- logger.error('Deployment failed')
- console.log('deployment error', e)
- return !1
+ const errorMessage = e instanceof Error ? e.message : String(e);
+ logger.error(`Deployment failed: ${errorMessage}`);
+ console.log('deployment error', e);
+
+ // Throw the error so it can be caught by the caller (DeployContractModal)
+ throw e;
} finally {
store.send({ type: "setDialogSpinner", show: false });
}
diff --git a/packages/frontend/src/lib/editor/language/index.ts b/packages/frontend/src/lib/editor/language/index.ts
index bb8def0..44aff92 100644
--- a/packages/frontend/src/lib/editor/language/index.ts
+++ b/packages/frontend/src/lib/editor/language/index.ts
@@ -81,22 +81,27 @@ export default class Language implements monaco.languages.ILanguageExtensionPoin
monaco.languages.registerCompletionItemProvider(this.id, {
async provideCompletionItems(model, position, context, token): Promise {
void token;
- const response = await (client.request(proto.CompletionRequest.type.method, {
- textDocument: monacoToProtocol.asTextDocumentIdentifier(model),
- position: monacoToProtocol.asPosition(position.column, position.lineNumber),
- context: monacoToProtocol.asCompletionContext(context),
- } as proto.CompletionParams) as Promise);
- console.log(response);
-
- const word = model.getWordUntilPosition(position);
- const result: monaco.languages.CompletionList = protocolToMonaco.asCompletionResult(response, {
- startLineNumber: position.lineNumber,
- startColumn: word.startColumn,
- endLineNumber: position.lineNumber,
- endColumn: word.endColumn,
- });
-
- return result;
+ try {
+ const response = await (client.request(proto.CompletionRequest.type.method, {
+ textDocument: monacoToProtocol.asTextDocumentIdentifier(model),
+ position: monacoToProtocol.asPosition(position.column, position.lineNumber),
+ context: monacoToProtocol.asCompletionContext(context),
+ } as proto.CompletionParams) as Promise);
+ console.log(response);
+
+ const word = model.getWordUntilPosition(position);
+ const result: monaco.languages.CompletionList = protocolToMonaco.asCompletionResult(response, {
+ startLineNumber: position.lineNumber,
+ startColumn: word.startColumn,
+ endLineNumber: position.lineNumber,
+ endColumn: word.endColumn,
+ });
+
+ return result;
+ } catch (e) {
+ console.warn("Completion request failed", e);
+ return { suggestions: [] } as unknown as monaco.languages.CompletionList;
+ }
},
});
@@ -191,56 +196,66 @@ export default class Language implements monaco.languages.ILanguageExtensionPoin
// eslint-disable-next-line
async provideDocumentSymbols(model, token): Promise {
void token;
- const response = await (client.request(proto.DocumentSymbolRequest.type.method, {
- textDocument: monacoToProtocol.asTextDocumentIdentifier(model),
- } as proto.DocumentSymbolParams) as Promise);
+ try {
+ const response = await (client.request(proto.DocumentSymbolRequest.type.method, {
+ textDocument: monacoToProtocol.asTextDocumentIdentifier(model),
+ } as proto.DocumentSymbolParams) as Promise);
- console.log({ response });
+ console.log({ response });
- const uri = model.uri;
+ const uri = model.uri;
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- const result: monaco.languages.DocumentSymbol[] = protocolToMonaco.asSymbolInformations(response, uri);
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ const result: monaco.languages.DocumentSymbol[] = protocolToMonaco.asSymbolInformations(response, uri);
- return result;
+ return result;
+ } catch (e) {
+ console.warn("Document symbols request failed", e);
+ return [];
+ }
},
});
monaco.languages.registerHoverProvider(this.id, {
// eslint-disable-next-line
- async provideHover(model, position, token): Promise {
+ async provideHover(model, position, token): Promise {
void token;
- const response = await (client.request(proto.HoverRequest.type.method, {
- textDocument: monacoToProtocol.asTextDocumentIdentifier(model),
- position: monacoToProtocol.asPosition(position.column, position.lineNumber),
- } as proto.HoverParams) as Promise);
- console.log(response);
-
- // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
- const result: monaco.languages.Hover = protocolToMonaco.asHover(response);
-
- console.log("Hover result: ", result);
-
- // add handler if hover result is null
- let message = "";
- if (result == null) {
- message = "";
- } else {
- message = result.contents[0].value;
+ try {
+ const response = await (client.request(proto.HoverRequest.type.method, {
+ textDocument: monacoToProtocol.asTextDocumentIdentifier(model),
+ position: monacoToProtocol.asPosition(position.column, position.lineNumber),
+ } as proto.HoverParams) as Promise);
+ console.log(response);
+
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
+ const result: monaco.languages.Hover = protocolToMonaco.asHover(response);
+
+ console.log("Hover result: ", result);
+
+ // add handler if hover result is null
+ let message = "";
+ if (result == null) {
+ message = "";
+ } else {
+ message = (result.contents?.[0] as any)?.value ?? "";
+ }
+
+ // Create a decoration with the hover result
+ const decoration: monaco.editor.IModelDeltaDecoration = {
+ range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
+ options: {
+ hoverMessage: { value: message },
+ },
+ };
+
+ // Apply the decoration to the editor
+ model.deltaDecorations([], [decoration]);
+
+ return result;
+ } catch (e) {
+ console.warn("Hover request failed", e);
+ return null;
}
-
- // Create a decoration with the hover result
- const decoration: monaco.editor.IModelDeltaDecoration = {
- range: new monaco.Range(position.lineNumber, position.column, position.lineNumber, position.column),
- options: {
- hoverMessage: { value: message },
- },
- };
-
- // Apply the decoration to the editor
- model.deltaDecorations([], [decoration]);
-
- return result;
},
});
diff --git a/packages/frontend/src/lib/services/server/contract.ts b/packages/frontend/src/lib/services/server/contract.ts
index 23e792e..314a1ce 100644
--- a/packages/frontend/src/lib/services/server/contract.ts
+++ b/packages/frontend/src/lib/services/server/contract.ts
@@ -1,219 +1,299 @@
"use client";
-import { Account, Address, BASE_FEE, Contract, Keypair, nativeToScVal, Networks, OperationOptions as oo, Operation, scValToNative, TransactionBuilder, xdr } from "@stellar/stellar-base";
-import { IParam, } from "../types/common";
+import {
+ Account,
+ Address,
+ BASE_FEE,
+ Contract,
+ Keypair,
+ nativeToScVal,
+ Networks,
+ OperationOptions as oo,
+ Operation,
+ scValToNative,
+ TransactionBuilder,
+ xdr,
+} from "@stellar/stellar-base";
+import { IParam } from "../types/common";
import { RpcService } from "../rpc";
import { Api, Server } from "@stellar/stellar-sdk/rpc";
import { matchEnum, safeParseJson, sha256Buffer } from "../utils";
import { ContractArgumentI, ContractInvokeI, Nullable, Op_Type, OperationOptionI } from "../types/server";
import { mapIfValid } from "@/utils";
-
+import generateIdl from "../../idl-wasm";
+import type { IDL, FunctionSpec } from "@/types/idl";
class ContractService {
- private acc: Account;
- private rpcUrl: string;
- private wasmHash: string;
- private keyPair: Keypair;
- private rpcServer: Server;
- private curTxnHash: string;
- private friendBotUrl: string;
- private nwPassphrase: string;
- private rpcService: RpcService;
- private isSetupDone = false;
-
- constructor(rpcUrl: string, keyPair: Keypair = Keypair.random()) {
- this.rpcUrl = rpcUrl;
- this.keyPair = keyPair;
- this.rpcService = new RpcService(rpcUrl);
- this.rpcServer = new Server(rpcUrl, {allowHttp: true});
-
- this.acc = {} as Account;
- this.wasmHash = "";
- this.curTxnHash = "";
- this.friendBotUrl = "";
- this.nwPassphrase = "";
- }
+ private acc: Account;
+ private rpcUrl: string;
+ private wasmHash: string;
+ private keyPair: Keypair;
+ private rpcServer: Server;
+ private curTxnHash: string;
+ private friendBotUrl: string;
+ private nwPassphrase: string;
+ private rpcService: RpcService;
+ private isSetupDone = false;
- async setup() {
- const nw = await this.rpcServer.getNetwork();
- this.friendBotUrl = nw.friendbotUrl || "";
- this.nwPassphrase = nw.passphrase;
- this.isSetupDone = true;
- }
+ constructor(rpcUrl: string, keyPair: Keypair = Keypair.random()) {
+ this.rpcUrl = rpcUrl;
+ this.keyPair = keyPair;
+ this.rpcService = new RpcService(rpcUrl);
+ this.rpcServer = new Server(rpcUrl, { allowHttp: true });
- genKeyPairRandom() {
- return Keypair.random();
- }
+ this.acc = {} as Account;
+ this.wasmHash = "";
+ this.curTxnHash = "";
+ this.friendBotUrl = "";
+ this.nwPassphrase = "";
+ }
- async fundAccount(pubKey = this.pubKey()) {
- await this.rpcService.fundAccount(pubKey, this.friendBotUrl);
- this.acc = await this.account(pubKey);
- }
+ async setup() {
+ const nw = await this.rpcServer.getNetwork();
+ this.friendBotUrl = nw.friendbotUrl || "";
+ this.nwPassphrase = nw.passphrase;
+ this.isSetupDone = true;
+ }
- pubKey() {
- return this.keyPair.publicKey();
- }
+ genKeyPairRandom() {
+ return Keypair.random();
+ }
- async account(pubKey = this.pubKey()) {
- return await this.rpcServer.getAccount(pubKey);
- }
-
- async invokeContract(ciData: ContractInvokeI): Promise {
- if(!this.isSetupDone) {
- await this.setup();
- }
- console.log("Invoking contract:", ciData.contractId, ciData.method, ciData.args);
- await this.fundAccount();
- const op = this.makeOperation(Op_Type.INVOKE_CT_FUNC, ciData);
- const [resp, _] = await this.doTransaction([op]);
+ async fundAccount(pubKey = this.pubKey()) {
+ await this.rpcService.fundAccount(pubKey, this.friendBotUrl);
+ this.acc = await this.account(pubKey);
+ }
- return resp;
- }
-
- // stellar contract upload --wasm \
- // --source-account \
- // --network-passphrase <'Test SDF Network ; September 2015'> \
- // --rpc-url
- async uploadByWasmBuffer(wasm: Buffer): Promise {
- if(!this.isSetupDone) {
- await this.setup();
- }
- this.wasmHash = await sha256Buffer(wasm);
- await this.fundAccount();
- const op = this.makeOperation(Op_Type.UP_CT_WASM, { wasm });
- const [_, addr] = await this.doTransaction([op]);
+ pubKey() {
+ return this.keyPair.publicKey();
+ }
+
+ async account(pubKey = this.pubKey()) {
+ return await this.rpcServer.getAccount(pubKey);
+ }
- return addr;
+ async invokeContract(ciData: ContractInvokeI): Promise {
+ if (!this.isSetupDone) {
+ await this.setup();
}
-
- // stellar contract deploy --wasm-hash \
- // --source-account \
- // --network-passphrase <'Test SDF Network ; September 2015'> \
- // --rpc-url
- async deployByWasmHash(ctorParamList: IParam[]) {
- console.log("Deploying contract:", this.wasmHash.length, ctorParamList);
- const constructorArgs = ctorParamList.map((param) => nativeToScVal(param.value, { type: param.type }));
- const op = this.makeOperation(Op_Type.DEP_CT_WASM, {
- wasmHash: Buffer.from(this.wasmHash, "hex"),
- address: Address.fromString(this.pubKey()),
- salt: Buffer.from(this.curTxnHash, "hex"),
- constructorArgs,
- });
+ console.log("Invoking contract:", ciData.contractId, ciData.method, ciData.args);
+ await this.fundAccount();
+ const op = this.makeOperation(Op_Type.INVOKE_CT_FUNC, ciData);
+ const [resp, _] = await this.doTransaction([op]);
- const [_, addr] = await this.doTransaction([op]);
+ return resp;
+ }
- return addr;
+ // stellar contract upload --wasm \
+ // --source-account \
+ // --network-passphrase <'Test SDF Network ; September 2015'> \
+ // --rpc-url
+ async uploadByWasmBuffer(wasm: Buffer): Promise {
+ if (!this.isSetupDone) {
+ await this.setup();
}
-
- // 1. upload wasm
- // 2. deploy wasm-hash
- async deployContract(
- wasm: Buffer,
- ctorParamList: IParam[]
- ): Promise {
- await this.uploadByWasmBuffer(wasm);
-
- const addr = await this.deployByWasmHash(ctorParamList);
- return addr;
+ this.wasmHash = await sha256Buffer(wasm);
+ await this.fundAccount();
+ const op = this.makeOperation(Op_Type.UP_CT_WASM, { wasm });
+ const [_, addr] = await this.doTransaction([op]);
+
+ return addr;
+ }
+
+ // stellar contract deploy --wasm-hash \
+ // --source-account \
+ // --network-passphrase <'Test SDF Network ; September 2015'> \
+ // --rpc-url
+ async deployByWasmHashEncoded(constructorArgs: xdr.ScVal[]) {
+ console.log("Deploying contract:", this.wasmHash.length, constructorArgs.length);
+ // Soroban requires a 32-byte salt. Generate if not present.
+ const saltU8 = this.curTxnHash
+ ? Uint8Array.from(Buffer.from(this.curTxnHash, "hex"))
+ : crypto.getRandomValues(new Uint8Array(32));
+
+ const op = this.makeOperation(Op_Type.DEP_CT_WASM, {
+ wasmHash: Buffer.from(this.wasmHash, "hex"),
+ address: Address.fromString(this.pubKey()),
+ salt: Buffer.from(saltU8),
+ constructorArgs,
+ });
+
+ const [_, addr] = await this.doTransaction([op]);
+
+ return addr;
+ }
+
+ // 1. upload wasm
+ // 2. deploy wasm-hash
+ async deployContract(wasm: Buffer, ctorParamList: IParam[]): Promise {
+ console.log("Starting deployContract with wasm:", wasm.length, "bytes");
+ console.log("Constructor params:", ctorParamList);
+
+ // Check what's actually in the constructor params
+ if (ctorParamList.length > 0) {
+ console.log("First param details:", {
+ type: ctorParamList[0].type,
+ value: ctorParamList[0].value,
+ seq: ctorParamList[0].seq,
+ });
}
- async pollTxnByHash(hash = this.curTxnHash): Promise {
- let rsp;
- let retryTime = 5000;
+ // 1) Upload wasm
+ await this.uploadByWasmBuffer(wasm);
+ console.log("WASM uploaded successfully");
+
+ // 2) Build constructor args based on IDL of this wasm (robust to presence/absence)
+ let encodedCtorArgs: xdr.ScVal[] = [];
+ try {
+ const idl: IDL = await generateIdl(new Uint8Array(wasm));
+ const ctor = Array.isArray(idl) ? idl.find((i: FunctionSpec) => i.name.includes("constructor")) : undefined;
+
+ if (ctor && Array.isArray(ctor.inputs) && ctor.inputs.length > 0) {
+ encodedCtorArgs = ctor.inputs.map((input, idx) => {
+ const expectedType = input.value.type as string; // e.g., 'u32', 'i64', 'vec', ...
- while(retryTime > 0) {
- rsp = await this.rpcService.getTransactionByHash(hash);
- if (rsp?.status !== "NOT_FOUND") {
- break;
+ if (expectedType === "vec") {
+ const sub = (input.value as any).element?.type as string;
+ const raw = ctorParamList[idx]?.value ?? "[]";
+ const parsed = safeParseJson(String(raw));
+ if (parsed === null || !Array.isArray(parsed)) {
+ throw new Error(`Invalid constructor arg at ${idx}. Expected JSON array of ${sub}`);
}
- retryTime -= 1000;
- await new Promise((resolve) => setTimeout(resolve, 1000));
- }
- if(rsp?.status !== "SUCCESS") {
- throw new Error("Transaction failed");
- }
+ return nativeToScVal(
+ parsed.map((v) => {
+ const [vv, tt] = mapIfValid(String(v), sub);
+ return nativeToScVal(vv, { type: tt });
+ }),
+ { type: "vec" },
+ );
+ }
- return rsp;
+ const userProvided = ctorParamList[idx]?.value ?? "";
+ const [val, mapped] = mapIfValid(String(userProvided), expectedType);
+ if (val === null || mapped === "") {
+ throw new Error(
+ `Invalid constructor arg at index ${idx}. Expected ${expectedType}, received "${String(userProvided)}"`,
+ );
+ }
+ return nativeToScVal(val, { type: mapped });
+ });
+ } else {
+ encodedCtorArgs = [];
+ }
+ } catch (e) {
+ console.warn("Failed to derive constructor args from IDL; defaulting to empty.", e);
+ encodedCtorArgs = [];
}
- async doTransaction(
- ops: xdr.Operation[],
- acc = this.acc
- ): Promise<[any, string]> {
- const txnBuilder = new TransactionBuilder(acc, {
- fee: BASE_FEE,
- networkPassphrase: this.nwPassphrase,
- });
+ // 3) Deploy using encoded args (or none if no constructor)
+ console.log("Starting deployByWasmHash...");
+ const addr = await this.deployByWasmHashEncoded(encodedCtorArgs);
+ return addr;
+ }
- for(const op of ops) {
- txnBuilder.addOperation(op);
- }
+ async pollTxnByHash(hash = this.curTxnHash): Promise {
+ let rsp;
+ let retryTime = 5000;
- const txn = txnBuilder.setTimeout(30).build();
- const preparedTx = await this.rpcServer.prepareTransaction(txn);
- preparedTx.sign(this.keyPair);
-
- const sim = await this.rpcServer.simulateTransaction(preparedTx) as any;
- const rawReturn = sim.result.retval;
- const addr = scValToNative(rawReturn);
- const stResp = await this.rpcServer.sendTransaction(preparedTx);
-
- this.curTxnHash = stResp.hash;
-
- const resp = await this.pollTxnByHash();
-
- return [resp, addr];
+ while (retryTime > 0) {
+ rsp = await this.rpcService.getTransactionByHash(hash);
+ if (rsp?.status !== "NOT_FOUND") {
+ break;
+ }
+ retryTime -= 1000;
+ await new Promise((resolve) => setTimeout(resolve, 1000));
+ }
+ if (rsp?.status !== "SUCCESS") {
+ throw new Error("Transaction failed");
}
- makeOperation(opType: Op_Type, data: OperationOptionI): xdr.Operation {
- switch(opType) {
- case Op_Type.UP_CT_WASM:
- return Operation.uploadContractWasm(data as oo.UploadContractWasm);
-
- case Op_Type.DEP_CT_WASM:
- return Operation.createCustomContract(data as oo.CreateCustomContract);
-
- case Op_Type.INVOKE_CT_FUNC:
- return this.buildInvokeOperation(data as ContractInvokeI);
-
- default:
- throw new Error(`Unknown operation type: ${opType}`);
- }
+ return rsp;
+ }
+
+ async doTransaction(ops: xdr.Operation[], acc = this.acc): Promise<[any, string]> {
+ const txnBuilder = new TransactionBuilder(acc, {
+ fee: BASE_FEE,
+ networkPassphrase: this.nwPassphrase,
+ });
+
+ for (const op of ops) {
+ txnBuilder.addOperation(op);
}
- private buildInvokeOperation(ciData: ContractInvokeI) {
- const mapFn = (val: any, type: string) => {
- try {
- const [v, t] = mapIfValid(val, type);
- return nativeToScVal(v, { type: t });
- } catch {
- throw new Error(`Invalid argument "${val}". Expected a valid ${type}`);
- }
- };
-
- const scArgs = ciData.args.map((arg: ContractArgumentI) => {
- const { value, type, subType } = arg;
-
- if (type === "vec") {
- const parsed = safeParseJson(value);
- if (parsed === null || !Array.isArray(parsed)) {
- throw new Error(`Invalid argument "${value}". Expected a JSON array of ${subType}`);
- }
- if (!subType) {
- throw new Error(`Missing subType for vec argument`);
- }
- return nativeToScVal(parsed.map(v => mapFn(v, subType)), { type });
- }
+ const txn = txnBuilder.setTimeout(30).build();
+ const preparedTx = await this.rpcServer.prepareTransaction(txn);
+ preparedTx.sign(this.keyPair);
- return mapFn(value, type);
- });
+ let stResp;
+ let addr;
+ try {
+ const sim = (await this.rpcServer.simulateTransaction(preparedTx)) as any;
+ const rawReturn = sim.result.retval;
+ addr = scValToNative(rawReturn);
+ stResp = await this.rpcServer.sendTransaction(preparedTx);
+ } catch (error) {
+ console.error("Error in doTransaction:", error);
+ throw error;
+ }
+
+ this.curTxnHash = stResp.hash;
- console.log("[-] scArgs", scArgs);
+ const resp = await this.pollTxnByHash();
- const contract = new Contract(ciData.contractId);
- return contract.call(ciData.method, ...scArgs);
+ return [resp, addr];
+ }
+
+ makeOperation(opType: Op_Type, data: OperationOptionI): xdr.Operation {
+ switch (opType) {
+ case Op_Type.UP_CT_WASM:
+ return Operation.uploadContractWasm(data as oo.UploadContractWasm);
+
+ case Op_Type.DEP_CT_WASM:
+ return Operation.createCustomContract(data as oo.CreateCustomContract);
+
+ case Op_Type.INVOKE_CT_FUNC:
+ return this.buildInvokeOperation(data as ContractInvokeI);
+
+ default:
+ throw new Error(`Unknown operation type: ${opType}`);
}
+ }
+
+ private buildInvokeOperation(ciData: ContractInvokeI) {
+ const mapFn = (val: any, type: string) => {
+ try {
+ const [v, t] = mapIfValid(val, type);
+ return nativeToScVal(v, { type: t });
+ } catch {
+ throw new Error(`Invalid argument "${val}". Expected a valid ${type}`);
+ }
+ };
+
+ const scArgs = ciData.args.map((arg: ContractArgumentI) => {
+ const { value, type, subType } = arg;
+
+ if (type === "vec") {
+ const parsed = safeParseJson(value);
+ if (parsed === null || !Array.isArray(parsed)) {
+ throw new Error(`Invalid argument "${value}". Expected a JSON array of ${subType}`);
+ }
+ if (!subType) {
+ throw new Error(`Missing subType for vec argument`);
+ }
+ return nativeToScVal(
+ parsed.map((v) => mapFn(v, subType)),
+ { type },
+ );
+ }
+
+ return mapFn(value, type);
+ });
+
+ console.log("[-] scArgs", scArgs);
+ const contract = new Contract(ciData.contractId);
+ return contract.call(ciData.method, ...scArgs);
+ }
}
-export default ContractService;
\ No newline at end of file
+export default ContractService;
diff --git a/packages/frontend/src/state/events.ts b/packages/frontend/src/state/events.ts
index 46c668b..877599e 100644
--- a/packages/frontend/src/state/events.ts
+++ b/packages/frontend/src/state/events.ts
@@ -13,7 +13,6 @@ import { ICompiled, ICurrentWasm } from "@/types/contracts";
export const events = {
setDialogSpinner: (context: Context, event: { show: boolean }) => {
context.showSpinnerDialog = event.show;
-
},
toggleFolder: (context: Context, event: { path: string }) => {
const folder = get(context, event.path) as FolderType;
@@ -41,7 +40,7 @@ export const events = {
}
}
},
- addDeployedContract(context: Context, event: { basePath: string; name: string, contract: any }) {},
+ addDeployedContract(context: Context, event: { basePath: string; name: string; contract: any }) {},
addFile(context: Context, event: { basePath: string; name: string; content: string }) {
const path = createPath(event.basePath, event.name);
const file = {
@@ -71,7 +70,6 @@ export const events = {
} satisfies FolderType;
},
-
deleteFile(context: Context, event: { path: string; basePath: string }) {
const folder = get(context, event.basePath) as FolderType;
const file = get(context, event.path) as FileType;
@@ -79,7 +77,7 @@ export const events = {
events.removeTab(context, { path: event.path });
delete folder.items[file.name];
delete context.files[event.path];
- context.compiled = context.compiled.filter(c => c.path !== event.path)
+ context.compiled = context.compiled.filter((c) => c.path !== event.path);
},
deleteFolder(context: Context, event: { path: string }) {
unset(context, event.path);
@@ -137,33 +135,35 @@ export const events = {
// setContractIdl(context: Context, event: { idl: IDL }) {
// context.contract.methods = event.idl;
// },
- updateContract(context: Context, event: Partial) {
+ updateContract(context: Context, event: Partial & { fileName?: string }) {
const addr = event.address;
- if(addr && Object.keys(context.contract.deployed).indexOf(addr || '') == -1) {
- context.contract.deployed[addr] = context.contract.methods
+ if (addr && Object.keys(context.contract.deployed).indexOf(addr || "") == -1) {
+ context.contract.deployed[addr] = {
+ methods: context.contract.methods,
+ fileName: event.fileName || "Unknown Contract",
+ };
}
Object.assign(context.contract, event);
},
- deleteDeployed(context: Context, event: {addr: string}) {
- console.log('[tur] remove deployed:', event.addr);
+ deleteDeployed(context: Context, event: { addr: string }) {
+ console.log("[tur] remove deployed:", event.addr);
const copy = { ...context.contract.deployed };
- console.log('[tur] copy:', copy, copy[event.addr]);
+ console.log("[tur] copy:", copy, copy[event.addr]);
delete copy[event.addr];
context.contract.deployed = copy;
-
},
-
+
addCompiled(context: Context, event: Partial) {
const d = {} as ICompiled;
Object.assign(d, event);
- const x = context.compiled.filter(c => c.path == event.path);
- if(x.length == 0) context.compiled.push(d);
+ const x = context.compiled.filter((c) => c.path == event.path);
+ if (x.length == 0) context.compiled.push(d);
},
updateCurrentWasm(context: Context, event: Partial) {
- console.log('[tur] event updateCurrentWasm:', event)
- Object.assign(context.currentWasm, event)
- }
+ console.log("[tur] event updateCurrentWasm:", event);
+ Object.assign(context.currentWasm, event);
+ },
};
diff --git a/packages/frontend/src/state/initstate.ts b/packages/frontend/src/state/initstate.ts
index 57120d0..d39f77e 100644
--- a/packages/frontend/src/state/initstate.ts
+++ b/packages/frontend/src/state/initstate.ts
@@ -1,20 +1,20 @@
-export const defaultCode = `contract flipper {
- bool private value;
+export const defaultCode = `pragma solidity 0;
- /// Constructor that initializes the \`bool\` value to the given \`init_value\`.
- constructor(bool initvalue) {
+contract incrementer {
+ uint32 private value;
+
+ /// Constructor that initializes the int32 value to the given init_value.
+ constructor(uint32 initvalue) {
value = initvalue;
}
- /// A message that can be called on instantiated contracts.
- /// This one flips the value of the stored \`bool\` from \`true\`
- /// to \`false\` and vice versa.
- function flip() public {
- value = !value;
+ /// This increments the value by by.
+ function inc(uint32 by) public {
+ value += by;
}
- /// Simply returns the current value of our \`bool\`.
- function get() public view returns (bool) {
+ /// Simply returns the current value of our uint32.
+ function get() public view returns (uint32) {
return value;
}
}
diff --git a/packages/frontend/src/types/idl.ts b/packages/frontend/src/types/idl.ts
index 5add947..3f47ded 100644
--- a/packages/frontend/src/types/idl.ts
+++ b/packages/frontend/src/types/idl.ts
@@ -8,8 +8,13 @@ export interface FunctionSpec {
outputs: OutputSpec[];
}
+export interface DeployedContractInfo {
+ methods: IDL;
+ fileName: string;
+}
+
export interface ContractsDeployed {
- [key: string]: IDL;
+ [key: string]: DeployedContractInfo;
}
interface InputSpec {
diff --git a/packages/frontend/src/utils.ts b/packages/frontend/src/utils.ts
index 573a062..5165cbd 100644
--- a/packages/frontend/src/utils.ts
+++ b/packages/frontend/src/utils.ts
@@ -1,120 +1,136 @@
import { DefaultFetchCallback, DefaultTimeout } from "./constants";
import { FetchOptionsWithTimeoutI } from "./types/common";
+/**
+ * Safely stringify objects that may contain BigInt values
+ * Converts BigInt to string representation to avoid JSON serialization errors
+ */
+export function safeStringify(obj: any, space?: number): string {
+ return JSON.stringify(
+ obj,
+ (key, value) => {
+ if (typeof value === "bigint") {
+ return value.toString();
+ }
+ return value;
+ },
+ space,
+ );
+}
+
export function mapIfValid(v: string, t: string): [any, string] {
// Normalize type for matching
- type SolType = keyof typeof typeMap;
-
- const typeMap = {
- uint8: "u32",
- uint16: "u32",
- uint32: "u32",
- uint64: "u64",
- uint128: "u128",
- uint256: "u256",
- int8: "i32",
- int16: "i32",
- int32: "i32",
- int64: "i64",
- int128: "i128",
- int256: "i256",
- bool: "bool",
- address: "address",
- bytes: "bytes",
- bytes1: "bytes",
- bytes2: "bytes",
- bytes3: "bytes",
- bytes4: "bytes",
- bytes5: "bytes",
- bytes6: "bytes",
- bytes7: "bytes",
- bytes8: "bytes",
- bytes9: "bytes",
- bytes10: "bytes",
- bytes11: "bytes",
- bytes12: "bytes",
- bytes13: "bytes",
- bytes14: "bytes",
- bytes15: "bytes",
- bytes16: "bytes",
- bytes17: "bytes",
- bytes18: "bytes",
- bytes19: "bytes",
- bytes20: "bytes",
- bytes21: "bytes",
- bytes22: "bytes",
- bytes23: "bytes",
- bytes24: "bytes",
- bytes25: "bytes",
- bytes26: "bytes",
- bytes27: "bytes",
- bytes28: "bytes",
- bytes29: "bytes",
- bytes30: "bytes",
- bytes31: "bytes",
- bytes32: "bytes",
- string: "string"
- };
-
- const sorobanTypes = new Set(Object.values(typeMap));
-
- const type = t.trim().toLowerCase() as SolType;
- const typ = !sorobanTypes.has(type) ? typeMap[type] : type;
-
- console.log('[tur] type:', type, 'typ:', typ)
-
- const out: [any, string] = [null, typ || ""] // Fallback — no valid mapping
-
- // Boolean mapping
- if (/^bool$/.test(type) && (v === "true" || v === "false")) {
- out[0] = v === "true";
- } else
-
- // Unsigned integer mapping (uint8, uint16, uint32, uint64, uint128, uint256)
- if (/^(uint(8|16|32|64|128|256)?|u(8|16|32|64|128|256))$/.test(type) && /^[0-9]+$/.test(v)) {
- out[0] = Number(v); // Keep original type (e.g., uint32)
- } else
-
- // Signed integer mapping (int8, int16, int32, int64, int128, int256)
- if (/^(int(8|16|32|64|128|256)?|i(8|16|32|64|128|256))$/.test(type) && /^-?[0-9]+$/.test(v)) {
- out[0] = Number(v); // Keep original type (e.g., int32)
- } else
-
- // Address mapping
- if (type === "address" && /^0x[0-9a-f]{40}$/i.test(v)) {
- out[0] = v;
- } else
-
- // Bytes mapping (bytes, bytes1..bytes32)
- if (/^bytes([1-9]|[12][0-9]|3[0-2])?$/.test(type) && /^0x[0-9a-f]+$/i.test(v)) {
- out[0] = v;
- } else
-
- // String mapping
- if (type === "string") {
- out[0] = v;
- }
- console.log('mapping:', 'incoming:', type, v, 'outgoing:', ...out);
-
- return out;
-}
+ type SolType = keyof typeof typeMap;
+
+ const typeMap = {
+ uint8: "u32",
+ uint16: "u32",
+ uint32: "u32",
+ uint64: "u64",
+ uint128: "u128",
+ uint256: "u256",
+ int8: "i32",
+ int16: "i32",
+ int32: "i32",
+ int64: "i64",
+ int128: "i128",
+ int256: "i256",
+ bool: "bool",
+ address: "address",
+ bytes: "bytes",
+ bytes1: "bytes",
+ bytes2: "bytes",
+ bytes3: "bytes",
+ bytes4: "bytes",
+ bytes5: "bytes",
+ bytes6: "bytes",
+ bytes7: "bytes",
+ bytes8: "bytes",
+ bytes9: "bytes",
+ bytes10: "bytes",
+ bytes11: "bytes",
+ bytes12: "bytes",
+ bytes13: "bytes",
+ bytes14: "bytes",
+ bytes15: "bytes",
+ bytes16: "bytes",
+ bytes17: "bytes",
+ bytes18: "bytes",
+ bytes19: "bytes",
+ bytes20: "bytes",
+ bytes21: "bytes",
+ bytes22: "bytes",
+ bytes23: "bytes",
+ bytes24: "bytes",
+ bytes25: "bytes",
+ bytes26: "bytes",
+ bytes27: "bytes",
+ bytes28: "bytes",
+ bytes29: "bytes",
+ bytes30: "bytes",
+ bytes31: "bytes",
+ bytes32: "bytes",
+ string: "string",
+ };
+
+ const sorobanTypes = new Set(Object.values(typeMap));
+
+ const type = t.trim().toLowerCase() as SolType;
+ const typ = !sorobanTypes.has(type) ? typeMap[type] : type;
+
+ console.log("[tur] type:", type, "typ:", typ);
+
+ const out: [any, string] = [null, typ || ""]; // Fallback — no valid mapping
+
+ // Boolean mapping
+ if (/^bool$/.test(type) && (v === "true" || v === "false")) {
+ out[0] = v === "true";
+ }
+ // Unsigned integer mapping (uint8, uint16, uint32, uint64, uint128, uint256)
+ else if (/^(uint(8|16|32|64|128|256)?|u(8|16|32|64|128|256))$/.test(type) && /^[0-9]+$/.test(v)) {
+ out[0] = Number(v); // Keep original type (e.g., uint32)
+ }
+
+ // Signed integer mapping (int8, int16, int32, int64, int128, int256)
+ else if (/^(int(8|16|32|64|128|256)?|i(8|16|32|64|128|256))$/.test(type) && /^-?[0-9]+$/.test(v)) {
+ out[0] = Number(v); // Keep original type (e.g., int32)
+ }
+
+ // Address mapping
+ else if (type === "address" && /^0x[0-9a-f]{40}$/i.test(v)) {
+ out[0] = v;
+ }
+
+ // Bytes mapping (bytes, bytes1..bytes32)
+ else if (/^bytes([1-9]|[12][0-9]|3[0-2])?$/.test(type) && /^0x[0-9a-f]+$/i.test(v)) {
+ out[0] = v;
+ }
+
+ // String mapping
+ else if (type === "string") {
+ out[0] = v;
+ }
+ console.log("mapping:", "incoming:", type, v, "outgoing:", ...out);
+
+ return out;
+}
export async function fetchWithTimeout(
- resource: string,
- options: FetchOptionsWithTimeoutI,
- callback = DefaultFetchCallback
+ resource: string,
+ options: FetchOptionsWithTimeoutI,
+ callback = DefaultFetchCallback,
) {
- const { timeout = DefaultTimeout } = options;
-
- const controller = new AbortController();
- const id = setTimeout(() => controller.abort(), timeout);
-
- const response = await fetch(resource, {
- ...options,
- signal: controller.signal
- }).then(callback);
- clearTimeout(id);
-
- return response;
+ const { timeout = DefaultTimeout } = options;
+
+ const controller = new AbortController();
+ const id = setTimeout(() => controller.abort(), timeout);
+
+ const response = await fetch(resource, {
+ ...options,
+ signal: controller.signal,
+ }).then(callback);
+ clearTimeout(id);
+
+ return response;
}
diff --git a/sysbox/on-start.sh b/sysbox/on-start.sh
old mode 100755
new mode 100644
diff --git a/yarn.lock b/yarn.lock
index 37fc53a..631b36e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -3809,7 +3809,7 @@ fresh@0.5.2:
resolved "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz"
integrity sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==
-"frontend@file:/home/spectre/Documents/WWW/solang-playground/packages/frontend":
+"frontend@file:/home/spectre/Documents/donny/solang/packages/frontend":
version "0.1.0"
resolved "file:packages/frontend"
dependencies:
@@ -5485,7 +5485,7 @@ monaco-languageclient@^1.0.1:
vscode-languageserver-textdocument "1.0.5"
vscode-uri "3.0.3"
-"monaco-lsp-streams@file:/home/spectre/Documents/WWW/solang-playground/packages/app":
+"monaco-lsp-streams@file:/home/spectre/Documents/donny/solang/packages/app":
version "0.0.0"
resolved "file:packages/app"
dependencies: