diff --git a/src/components/dashboard/TransactionBuilder.jsx b/src/components/dashboard/TransactionBuilder.jsx
index a16f466b..5be10f59 100644
--- a/src/components/dashboard/TransactionBuilder.jsx
+++ b/src/components/dashboard/TransactionBuilder.jsx
@@ -7,6 +7,7 @@ import {
getCachedUserTransactionTemplates,
upsertUserTransactionTemplate,
} from "../../lib/transactionTemplateVault.ts";
+import { fetchContractData } from "../../lib/stellar";
import { Copy, Play, Download, AlertCircle, CheckCircle, ArrowDown, GripVertical, Trash2, Plus, Zap } from "lucide-react";
function Panel({ title, subtitle, children }) {
@@ -153,6 +154,11 @@ export default function TransactionBuilder() {
const [isSimulating, setIsSimulating] = useState(false);
const [showXDR, setShowXDR] = useState(false);
const [draggedIndex, setDraggedIndex] = useState(null);
+ const [inspectContractId, setInspectContractId] = useState("");
+ const [inspectContractKey, setInspectContractKey] = useState("");
+ const [inspectContractData, setInspectContractData] = useState(null);
+ const [inspectContractLoading, setInspectContractLoading] = useState(false);
+ const [inspectContractError, setInspectContractError] = useState("");
function addOperation() {
setOperations([
@@ -208,6 +214,27 @@ export default function TransactionBuilder() {
loadTemplate(selectedTemplateId);
setSelectedTemplateId(null);
}, [selectedTemplateId]);
+
+ async function handleFetchContractData() {
+ if (!inspectContractId.trim()) return;
+
+ setInspectContractError("");
+ setInspectContractData(null);
+ setInspectContractLoading(true);
+
+ try {
+ const data = await fetchContractData(
+ inspectContractId,
+ inspectContractKey,
+ network
+ );
+ setInspectContractData(data);
+ } catch (error) {
+ setInspectContractError(error.message || "Failed to fetch contract data");
+ } finally {
+ setInspectContractLoading(false);
+ }
+ }
// Drag and drop handlers
function handleDragStart(index) {
@@ -645,6 +672,112 @@ export default function TransactionBuilder() {
>
);
+ case "invokeHostFunction": {
+ const args = op.params.args || [];
+ return (
+ <>
+
+
+ updateOperation(op.id, "contractId", e.target.value)
+ }
+ placeholder="C... contract address"
+ style={textInputStyle(hasErrors)}
+ />
+
+
+
+ updateOperation(op.id, "functionName", e.target.value)
+ }
+ placeholder="increment"
+ style={textInputStyle()}
+ />
+
+
+
+
+
+
+
+ {args.map((arg, idx) => (
+
+
+ {
+ const newArgs = [...args];
+ newArgs[idx] = { ...arg, value: e.target.value };
+ updateOperation(op.id, "args", newArgs);
+ }}
+ placeholder={arg.type === "bool" ? "true/false" : "Argument value"}
+ style={textInputStyle()}
+ />
+
+
+ ))}
+
+ >
+ );
+ }
+
default:
return (
@@ -715,6 +848,90 @@ export default function TransactionBuilder() {
+ {/* Contract State Inspection */}
+
+
+
+
+ setInspectContractId(e.target.value)}
+ placeholder="C... contract address"
+ style={textInputStyle()}
+ />
+
+
+
+
+ setInspectContractKey(e.target.value)}
+ placeholder="Storage key (string or JSON)"
+ style={textInputStyle()}
+ />
+
+
+
+
+ {inspectContractError && (
+
+ {inspectContractError}
+
+ )}
+ {inspectContractData && (
+
+
+
+ Key
+
+
+ {JSON.stringify(inspectContractData.key, null, 2)}
+
+
+
+
+ Value
+
+
+ {JSON.stringify(inspectContractData.value, null, 2)}
+
+
+
+ )}
+
+
{/* Transaction Settings */}
diff --git a/src/lib/stellar.ts b/src/lib/stellar.ts
index 6f7b7456..7226e520 100644
--- a/src/lib/stellar.ts
+++ b/src/lib/stellar.ts
@@ -914,6 +914,40 @@ export async function fetchContractInfo(
}
}
+export async function fetchContractData(
+ contractId: string,
+ key: StellarSdk.xdr.ScVal | string,
+ network: NetworkName = 'testnet',
+ durability: StellarSdk.SorobanRpc.Durability = StellarSdk.SorobanRpc.Durability.Persistent
+): Promise
{
+ const server = getSorobanServer(network);
+
+ let scValKey;
+ if (typeof key === "string") {
+ try {
+ // Try to parse from JSON first
+ const parsed = JSON.parse(key);
+ scValKey = StellarSdk.nativeToScVal(parsed);
+ } catch {
+ // If JSON fails, treat as string
+ scValKey = StellarSdk.nativeToScVal(key, { type: "string" });
+ }
+ } else {
+ scValKey = key;
+ }
+
+ try {
+ const result = await server.getContractData(contractId, scValKey, durability);
+ return {
+ key: StellarSdk.scValToNative(result.key),
+ value: StellarSdk.scValToNative(result.val),
+ xdr: result.xdr
+ };
+ } catch (e) {
+ throw new Error(`Failed to fetch contract data: ${(e as Error).message}`);
+ }
+}
+
export interface ContractInvocationArg {
type: 'string' | 'int' | 'address' | 'bool';
value: string;
diff --git a/src/lib/transactionBuilder.js b/src/lib/transactionBuilder.js
index 8fdd81ed..2a0ceb3e 100644
--- a/src/lib/transactionBuilder.js
+++ b/src/lib/transactionBuilder.js
@@ -23,6 +23,8 @@ export const OPERATION_TYPES = [
// Fee-bump, sponsorship, and clawback operations (#196)
{ value: "feeBump", label: "Fee-Bump Transaction" },
{ value: "clawback", label: "Clawback" },
+ // Contract invocation
+ { value: "invokeHostFunction", label: "Invoke Host Function (Contract Call)" },
];
export function createOperation(type, params) {
@@ -201,6 +203,25 @@ export function createOperation(type, params) {
return op;
}
+ case "invokeHostFunction": {
+ const contract = new StellarSdk.Contract(params.contractId);
+ const args = (params.args || []).map(arg => {
+ switch (arg.type) {
+ case "string":
+ return StellarSdk.nativeToScVal(arg.value, { type: "string" });
+ case "int":
+ return StellarSdk.nativeToScVal(BigInt(arg.value), { type: "i128" });
+ case "address":
+ return StellarSdk.Address.fromString(arg.value).toScVal();
+ case "bool":
+ return StellarSdk.nativeToScVal(arg.value === "true", { type: "bool" });
+ default:
+ throw new Error(`Unsupported argument type: ${arg.type}`);
+ }
+ });
+ return contract.call(params.functionName, ...args);
+ }
+
default:
throw new Error(`Unsupported operation type: ${type}`);
}