Repository: stellar/laboratory
Pull request #1281: [Invoke contract] convert nativeToScVal and use `prepareTransaction` to submit
Author: login: jeesunikim
Created: 2025-03-18T20:19:46Z | Merged: 2025-03-19T21:33:49Z

- success case (happy path)
-- [stellar.expert link](https://stellar.expert/explorer/testnet/tx/a4efe2b07a4e093d6e7e1e0aefa05254aed0635b63a9014d8f12804dedb3c628)

https://github.com/user-attachments/assets/e17c417f-6545-4db7-9f81-d6677ae0aadc

- fail case on mainnet using the contract `CA6PUJLBYKZKUEKLZJMKBZLEKP2OTHANDEOWSFF44FTSYLKQPIICCJBE` ([link](https://stellar.expert/explorer/public/contract/CA6PUJLBYKZKUEKLZJMKBZLEKP2OTHANDEOWSFF44FTSYLKQPIICCJBE))
-- this one doesn't provide `debuginfo`

https://github.com/user-attachments/assets/a52068e4-a877-4070-995e-a04d0960283e

I need to test further with contracts to get the scenario with `debuginfo`



Diff:
diff --git a/src/app/(sidebar)/transaction/build/components/SorobanTransactionXdr.tsx b/src/app/(sidebar)/transaction/build/components/SorobanTransactionXdr.tsx
index 5b727d1bd..c7a7f4b4d 100644
--- a/src/app/(sidebar)/transaction/build/components/SorobanTransactionXdr.tsx
+++ b/src/app/(sidebar)/transaction/build/components/SorobanTransactionXdr.tsx
@@ -7,18 +7,23 @@ import { useRouter } from "next/navigation";
 
 import {
   getSorobanTxData,
-  buildSorobanTx,
+  buildTxWithSorobanData,
   getContractDataXDR,
 } from "@/helpers/sorobanUtils";
+import { TransactionBuildParams } from "@/store/createStore";
 
 import { Routes } from "@/constants/routes";
 
-import { SorobanOpType } from "@/types/types";
+import { SorobanOpType, TxnOperation } from "@/types/types";
 
 import { ValidationResponseCard } from "@/components/ValidationResponseCard";
 import { TransactionXdrDisplay } from "./TransactionXdrDisplay";
 
-export const SorobanTransactionXdr = () => {
+export const SorobanTransactionXdr = ({
+  hasPreBuiltXdr,
+}: {
+  hasPreBuiltXdr?: boolean;
+}) => {
   const { network, transaction } = useStore();
   const { updateSignActiveView, updateSignImportXdr, updateSorobanBuildXdr } =
     transaction;
@@ -26,6 +31,20 @@ export const SorobanTransactionXdr = () => {
   const { operation } = soroban;
   const router = useRouter();
 
+  const sorobanData = hasPreBuiltXdr
+    ? { xdr: soroban.xdr }
+    : getTxWithSorobanData({
+        operation,
+        txnParams,
+        networkPassphrase: network.passphrase,
+      });
+
+  useEffect(() => {
+    if (sorobanData.xdr && !hasPreBuiltXdr) {
+      updateSorobanBuildXdr(sorobanData.xdr);
+    }
+  }, [sorobanData.xdr, updateSorobanBuildXdr, hasPreBuiltXdr]);
+
   useEffect(() => {
     // Reset transaction.xdr if the transaction is not valid
     if (!(isValid.params && isValid.operations)) {
@@ -35,84 +54,102 @@ export const SorobanTransactionXdr = () => {
     // eslint-disable-next-line react-hooks/exhaustive-deps
   }, [isValid.params, isValid.operations]);
 
-  const getSorobanTxDataResult = (): { xdr: string; error?: string } => {
-    try {
-      const contractDataXDR = getContractDataXDR({
-        contractAddress: operation.params.contract,
-        dataKey: operation.params.key_xdr,
-        durability: operation.params.durability,
-      });
+  if (!(isValid.params && isValid.operations)) {
+    return null;
+  }
 
-      const sorobanData = getSorobanTxData({
-        contractDataXDR,
-        operationType: operation.operation_type as SorobanOpType,
-        fee: operation.params.resource_fee,
-      });
+  if (sorobanData?.xdr) {
+    return renderSorobanTxResultDisplay({
+      sorobanData: { xdr: sorobanData.xdr },
+      networkPassphrase: network.passphrase,
+      onSignClick: () => {
+        updateSignImportXdr(sorobanData.xdr);
+        updateSignActiveView("overview");
+        router.push(Routes.SIGN_TRANSACTION);
+      },
+    });
+  }
+  return null;
+};
 
-      if (sorobanData) {
-        const builtXdr = buildSorobanTx({
-          sorobanData,
-          params: txnParams,
-          sorobanOp: operation,
-          networkPassphrase: network.passphrase,
-        });
-
-        const builtXdrString = builtXdr.toXDR();
-
-        return { xdr: builtXdrString };
-      } else {
-        throw new Error("Failed to build Soroban transaction data");
-      }
-    } catch (e) {
-      return { xdr: "", error: `${e}` };
-    }
-  };
+const getTxWithSorobanData = ({
+  operation,
+  txnParams,
+  networkPassphrase,
+}: {
+  operation: TxnOperation;
+  txnParams: TransactionBuildParams;
+  networkPassphrase: string;
+}): { xdr: string; error?: string } => {
+  try {
+    const contractDataXDR = getContractDataXDR({
+      contractAddress: operation.params.contract,
+      dataKey: operation.params.key_xdr,
+      durability: operation.params.durability,
+    });
+
+    const sorobanData = getSorobanTxData({
+      contractDataXDR,
+      operationType: operation.operation_type as SorobanOpType,
+      fee: operation.params.resource_fee,
+    });
+
+    if (sorobanData) {
+      const sorobanTx = buildTxWithSorobanData({
+        sorobanData,
+        params: txnParams,
+        sorobanOp: operation,
+        networkPassphrase: networkPassphrase,
+      });
 
-  const sorobanData = getSorobanTxDataResult();
-  const sorobanDataXdr = sorobanData?.xdr || "";
+      const sorobanTxXdrString = sorobanTx.toXDR();
 
-  useEffect(() => {
-    if (sorobanDataXdr) {
-      updateSorobanBuildXdr(sorobanDataXdr);
+      return { xdr: sorobanTxXdrString, error: undefined };
+    } else {
+      throw new Error("Failed to build Soroban transaction data");
     }
-  }, [sorobanDataXdr, updateSorobanBuildXdr]);
-
-  if (!(isValid.params && isValid.operations)) {
-    return null;
+  } catch (e) {
+    return { xdr: "", error: `${e}` };
   }
+};
 
-  if (sorobanData?.xdr) {
-    try {
-      const txnHash = TransactionBuilder.fromXDR(
-        sorobanData.xdr,
-        network.passphrase,
-      )
-        .hash()
-        .toString("hex");
-
-      return (
-        <TransactionXdrDisplay
-          xdr={sorobanData.xdr}
-          networkPassphrase={network.passphrase}
-          txnHash={txnHash}
-          dataTestId="build-soroban-transaction-envelope-xdr"
-          onSignClick={() => {
-            updateSignImportXdr(sorobanData.xdr);
-            updateSignActiveView("overview");
-            router.push(Routes.SIGN_TRANSACTION);
-          }}
-        />
-      );
-    } catch (e: any) {
-      return (
-        <ValidationResponseCard
-          variant="error"
-          title="Transaction Error:"
-          response={e.toString()}
-        />
-      );
+export const renderSorobanTxResultDisplay = ({
+  sorobanData,
+  networkPassphrase,
+  onSignClick,
+}: {
+  sorobanData: { xdr: string };
+  networkPassphrase: string;
+  onSignClick: () => void;
+}) => {
+  try {
+    if (!sorobanData.xdr) {
+      return null;
     }
-  }
 
-  return null;
+    const txnHash = TransactionBuilder.fromXDR(
+      sorobanData.xdr,
+      networkPassphrase,
+    )
+      .hash()
+      .toString("hex");
+
+    return (
+      <TransactionXdrDisplay
+        xdr={sorobanData.xdr}
+        networkPassphrase={networkPassphrase}
+        txnHash={txnHash}
+        dataTestId="build-soroban-transaction-envelope-xdr"
+        onSignClick={onSignClick}
+      />
+    );
+  } catch (e: any) {
+    return (
+      <ValidationResponseCard
+        variant="error"
+        title="Transaction Error:"
+        response={e.toString()}
+      />
+    );
+  }
 };
diff --git a/src/app/(sidebar)/transaction/build/page.tsx b/src/app/(sidebar)/transaction/build/page.tsx
index c2cf4fbaa..5c6cc62fb 100644
--- a/src/app/(sidebar)/transaction/build/page.tsx
+++ b/src/app/(sidebar)/transaction/build/page.tsx
@@ -87,7 +87,20 @@ export default function BuildTransaction() {
 
       <>{renderError()}</>
 
-      {IS_SOROBAN_TX ? <SorobanTransactionXdr /> : <ClassicTransactionXdr />}
+      {IS_SOROBAN_TX ? (
+        renderSorobanTransactionXdr(soroban.operation.operation_type)
+      ) : (
+        <ClassicTransactionXdr />
+      )}
     </Box>
   );
 }
+
+const renderSorobanTransactionXdr = (sorobanOperation: string) => {
+  // "invoke_contract_function" operation builds its tx from the component level
+  if (sorobanOperation === "invoke_contract_function") {
+    return <SorobanTransactionXdr hasPreBuiltXdr={true} />;
+  }
+
+  return <SorobanTransactionXdr />;
+};
diff --git a/src/app/(sidebar)/transaction/submit/page.tsx b/src/app/(sidebar)/transaction/submit/page.tsx
index 3fe9076de..9898e9caf 100644
--- a/src/app/(sidebar)/transaction/submit/page.tsx
+++ b/src/app/(sidebar)/transaction/submit/page.tsx
@@ -45,12 +45,13 @@ import { TransactionHashReadOnlyField } from "@/components/TransactionHashReadOn
 import { PageCard } from "@/components/layout/PageCard";
 import { SaveToLocalStorageModal } from "@/components/SaveToLocalStorageModal";
 import { JsonCodeWrapToggle } from "@/components/JsonCodeWrapToggle";
-
 import {
   HorizonErrorResponse,
   RpcErrorResponse,
 } from "./components/ErrorResponse";
 
+import { parseJsonString } from "@/helpers/parseJsonString";
+
 const SUBMIT_OPTIONS = [
   {
     id: "rpc",
@@ -75,7 +76,7 @@ const isSorobanXdr = (xdrJsonString: string) => {
   }
 
   try {
-    const parsedJson = JSON.parse(xdrJsonString);
+    const parsedJson = parseJsonString(xdrJsonString);
     const operations = parsedJson?.tx?.tx?.operations || [];
 
     return operations.some((op: any) => {
@@ -83,7 +84,7 @@ const isSorobanXdr = (xdrJsonString: string) => {
       return (
         "extend_footprint_ttl" in body ||
         "restore_footprint" in body ||
-        "invoke_contract_function" in body
+        "invoke_host_function" in body
       );
     });
   } catch (e) {
@@ -662,7 +663,7 @@ export default function SubmitTransaction() {
                 </Text>
                 <div className="PageBody__content PageBody__scrollable">
                   <PrettyJsonTransaction
-                    json={JSON.parse(xdrJson.jsonString)}
+                    json={parseJsonString(xdrJson.jsonString)}
                     xdr={blob}
                     isCodeWrapped={isCodeWrapped}
                   />
diff --git a/src/components/FormElements/ContractMethodSelectPicker.tsx b/src/components/FormElements/ContractMethodSelectPicker.tsx
index 952dc741a..bb2471ae2 100644
--- a/src/components/FormElements/ContractMethodSelectPicker.tsx
+++ b/src/components/FormElements/ContractMethodSelectPicker.tsx
@@ -1,7 +1,6 @@
 import { useState } from "react";
 import { Select } from "@stellar/design-system";
 import { contract } from "@stellar/stellar-sdk";
-import type { JSONSchema7 } from "json-schema";
 
 import { Box } from "@/components/layout/Box";
 import { JsonSchemaForm } from "@/components/JsonSchemaForm";
@@ -12,16 +11,8 @@ import { SorobanInvokeValue } from "@/types/types";
 // @todo for testing
 // comment it out before committing
 // remove it before merging into main
-import Form from "@rjsf/core";
-import validator from "@rjsf/validator-ajv8";
-
-const cleanupData = (data: any) => {
-  if (!data) return {};
-  // Only keep function_method if it exists, reset all other fields
-  return {
-    function_method: data.function_method || "",
-  };
-};
+// import Form from "@rjsf/core";
+// import validator from "@rjsf/validator-ajv8";
 
 export const ContractMethodSelectPicker = ({
   value,
@@ -37,23 +28,18 @@ export const ContractMethodSelectPicker = ({
   onChange: (val: SorobanInvokeValue) => void;
 }) => {
   const [selectedValue, setSelectedValue] = useState<string>("");
-  const [funcSpec, setFuncSpec] = useState<JSONSchema7 | undefined>(undefined);
 
   const onSelectChange = (e: React.ChangeEvent<HTMLSelectElement>) => {
     if (e.target.value) {
-      const selectedFuncSchema = spec.jsonSchema(e.target.value);
       setSelectedValue(e.target.value);
-      setFuncSpec(selectedFuncSchema);
 
       onChange({
         ...value,
-        data: {
-          ...cleanupData(value.data),
-          function_method: e.target.value,
+        function_name: e.target.value,
+        args: {
+          ...value.args,
         },
       });
-    } else {
-      setFuncSpec(undefined);
     }
   };
 
@@ -77,20 +63,21 @@ export const ContractMethodSelectPicker = ({
         ))}
       </Select>
       <ExpandBox isExpanded={Boolean(selectedValue)} offsetTop="sm">
-        {selectedValue && funcSpec ? (
+        {selectedValue && spec.jsonSchema(selectedValue) ? (
           <>
             <JsonSchemaForm
-              schema={funcSpec}
               name={selectedValue}
               value={value}
               onChange={onChange}
+              spec={spec}
+              funcSchema={spec.jsonSchema(selectedValue)}
             />
             {/* 
             // @todo for testing
             // comment it out before committing
             // remove it before merging into main
             */}
-            <Form schema={funcSpec} validator={validator} />
+            {/* <Form schema={spec.jsonSchema(selectedValue)} validator={validator} /> */}
           </>
         ) : null}
       </ExpandBox>
diff --git a/src/components/FormElements/FetchContractMethodPickerWithQuery.tsx b/src/components/FormElements/FetchContractMethodPickerWithQuery.tsx
index f544c6e5c..77b3ff2ad 100644
--- a/src/components/FormElements/FetchContractMethodPickerWithQuery.tsx
+++ b/src/components/FormElements/FetchContractMethodPickerWithQuery.tsx
@@ -1,6 +1,5 @@
 import React, { useState } from "react";
 import { Button } from "@stellar/design-system";
-import { Spec } from "@stellar/stellar-sdk/contract";
 
 import { SorobanInvokeValue } from "@/types/types";
 
@@ -37,9 +36,6 @@ export const FetchContractMethodPickerWithQuery = ({
   const { network } = useStore();
   const [contractIdError, setContractIdError] = useState<string>("");
   const [contractMethods, setContractMethods] = useState<string[]>([]);
-  const [contractMethodsSpec, setContractMethodsSpec] = useState<Spec>(
-    {} as Spec,
-  );
   const [fetchError, setFetchError] = useState<string | null>(null);
 
   const {
@@ -72,7 +68,8 @@ export const FetchContractMethodPickerWithQuery = ({
 
     const newValue: SorobanInvokeValue = {
       contract_id: e.target.value || "",
-      data: value?.data || {},
+      function_name: value?.function_name || "",
+      args: value?.args || {},
     };
 
     onChange(newValue);
@@ -87,8 +84,7 @@ export const FetchContractMethodPickerWithQuery = ({
     }
 
     if (isSuccess) {
-      const contractSpec = contractClient?.spec;
-      const contractMethods = contractSpec?.funcs();
+      const contractMethods = contractClient?.spec?.funcs();
 
       if (contractMethods) {
         const methodNames = contractMethods.map((method) =>
@@ -96,10 +92,6 @@ export const FetchContractMethodPickerWithQuery = ({
         );
         setContractMethods(methodNames);
       }
-
-      if (contractSpec) {
-        setContractMethodsSpec(contractSpec);
-      }
     }
 
     if (isError) {
@@ -135,11 +127,11 @@ export const FetchContractMethodPickerWithQuery = ({
       </Box>
 
       <>
-        {contractMethods.length && value ? (
+        {contractMethods.length && contractClient?.spec && value ? (
           <ContractMethodSelectPicker
             value={value}
             onChange={onChange}
-            spec={contractMethodsSpec}
+            spec={contractClient.spec}
             methods={contractMethods}
             id={`${id}-method`}
           />
diff --git a/src/components/FormElements/ResourceFeePickerWithQuery.tsx b/src/components/FormElements/ResourceFeePickerWithQuery.tsx
index 2ab7cf61c..15ade15b4 100644
--- a/src/components/FormElements/ResourceFeePickerWithQuery.tsx
+++ b/src/components/FormElements/ResourceFeePickerWithQuery.tsx
@@ -1,14 +1,14 @@
 "use client";
 
 import React, { useEffect, useState } from "react";
-
 import { useSimulateTx } from "@/query/useSimulateTx";
+import { BASE_FEE } from "@stellar/stellar-sdk";
 
 import { useStore } from "@/store/useStore";
 import { SorobanOpType } from "@/types/types";
 import { getNetworkHeaders } from "@/helpers/getNetworkHeaders";
 import {
-  buildSorobanTx,
+  buildTxWithSorobanData,
   getContractDataXDR,
   getSorobanTxData,
 } from "@/helpers/sorobanUtils";
@@ -18,7 +18,8 @@ import { PositiveIntPicker } from "@/components/FormElements/PositiveIntPicker";
 
 const isAllParamsExceptResourceFeeValid = (params: Record<string, any>) => {
   // Create a copy of params without resource_fee
-  const { ...requiredParams } = params;
+  // eslint-disable-next-line @typescript-eslint/no-unused-vars
+  const { resource_fee, ...requiredParams } = params;
 
   // Check if all remaining fields have truthy values
   return Object.values(requiredParams).every((value) => Boolean(value));
@@ -66,6 +67,11 @@ export const ResourceFeePickerWithQuery = ({
     undefined,
   );
 
+  useEffect(() => {
+    // Reset error message when operation params change
+    setErrorMessage(undefined);
+  }, [soroban.operation]);
+
   // operation.params.resource_fee is required for submitting; however,
   // we don't check for it here in case user doesn't have one and we still want to simulate
   // @TODO should update this from isValid.operations point of view
@@ -91,20 +97,11 @@ export const ResourceFeePickerWithQuery = ({
   // Create a sample transaction to simulate to get the min resource fee
   const buildTxToSimulate = () => {
     try {
-      // We add a bogus fee to simualte to fetch the min resource fee from the RPC
-      const BOGUS_RESOURCE_FEE = "100";
-
-      let builtXdr, contractDataXDR;
-
-      try {
-        contractDataXDR = getContractDataXDR({
-          contractAddress: operation.params.contract,
-          dataKey: operation.params.key_xdr,
-          durability: operation.params.durability,
-        });
-      } catch (e) {
-        throw new Error(`Failed to generate contract data XDR: ${e}`);
-      }
+      const contractDataXDR = getContractDataXDR({
+        contractAddress: operation.params.contract,
+        dataKey: operation.params.key_xdr,
+        durability: operation.params.durability,
+      });
 
       if (!contractDataXDR) {
         throw new Error("Failed to fetch contract data XDR");
@@ -113,25 +110,26 @@ export const ResourceFeePickerWithQuery = ({
       const sorobanData = getSorobanTxData({
         contractDataXDR,
         operationType: operation.operation_type as SorobanOpType,
-        fee: BOGUS_RESOURCE_FEE, // simulate purpose only
+        fee: BASE_FEE, // simulate purpose only
       });
 
-      if (sorobanData) {
-        builtXdr = buildSorobanTx({
-          sorobanData,
-          params: txnParams,
-          sorobanOp: {
-            ...operation,
-            params: {
-              ...operation.params,
-              resource_fee: BOGUS_RESOURCE_FEE,
-            },
-          },
-          networkPassphrase: network.passphrase,
-        }).toXDR();
-      } else {
+      if (!sorobanData) {
         throw new Error("Failed to build Soroban transaction data");
       }
+
+      const builtXdr = buildTxWithSorobanData({
+        sorobanData,
+        params: txnParams,
+        sorobanOp: {
+          ...operation,
+          params: {
+            ...operation.params,
+            resource_fee: BASE_FEE,
+          },
+        },
+        networkPassphrase: network.passphrase,
+      }).toXDR();
+
       return builtXdr;
     } catch (e) {
       setErrorMessage(
diff --git a/src/components/JsonSchemaForm/index.tsx b/src/components/JsonSchemaForm/index.tsx
index c753d595e..7baf6a48a 100644
--- a/src/components/JsonSchemaForm/index.tsx
+++ b/src/components/JsonSchemaForm/index.tsx
@@ -1,54 +1,89 @@
 import React, { useEffect, useState } from "react";
-import type { JSONSchema7 } from "json-schema";
+import { BASE_FEE, contract } from "@stellar/stellar-sdk";
 import { Button, Card, Icon, Input, Text } from "@stellar/design-system";
 
+import type { JSONSchema7 } from "json-schema";
+
+import { TransactionBuildParams } from "@/store/createStore";
+import { useStore } from "@/store/useStore";
+import { AnyObject, SorobanInvokeValue, TxnOperation } from "@/types/types";
+import { validate } from "@/validate";
+
 import {
-  getDereferenceSchema,
-  DereferencedSchema,
+  dereferenceSchema,
+  DereferencedSchemaType,
 } from "@/helpers/dereferenceSchema";
 import { removeLeadingZeroes } from "@/helpers/removeLeadingZeroes";
+import { getScValsFromSpec } from "@/helpers/getScValsFromSpec";
+import { buildTxWithSorobanData } from "@/helpers/sorobanUtils";
+import { getNetworkHeaders } from "@/helpers/getNetworkHeaders";
+
+import { useRpcPrepareTx } from "@/query/useRpcPrepareTx";
 
 import { Box } from "@/components/layout/Box";
 import { PositiveIntPicker } from "@/components/FormElements/PositiveIntPicker";
 import { LabelHeading } from "@/components/LabelHeading";
-import { AnyObject, SorobanInvokeValue } from "@/types/types";
-import { validate } from "@/validate";
+import { ErrorText } from "../ErrorText";
 
 export const JsonSchemaForm = ({
-  schema,
   name,
   value,
   onChange,
+  spec,
+  funcSchema,
 }: {
-  schema: JSONSchema7;
   name: string;
   value: SorobanInvokeValue;
   onChange: (value: SorobanInvokeValue) => void;
+  spec: contract.Spec;
+  funcSchema: JSONSchema7;
 }) => {
-  const [dereferencedSchema, setDereferencedSchema] =
-    useState<DereferencedSchema>({
-      properties: {},
-      required: [],
-      additionalProperties: false,
-    });
+  const { network, transaction } = useStore();
+  const { updateSorobanBuildXdr } = transaction;
+  const { params: txnParams, soroban } = transaction.build;
+  const { operation } = soroban;
+  const {
+    mutate: prepareTx,
+    isPending: isPrepareTxPending,
+    isError: isPrepareTxError,
+    error: prepareTxError,
+    data: prepareTxData,
+    reset: resetPrepareTx,
+  } = useRpcPrepareTx();
+
+  const dereferencedSchema: DereferencedSchemaType = dereferenceSchema(
+    funcSchema,
+    name,
+  );
+  const requiredFields = dereferencedSchema.required;
+  const missingFields =
+    requiredFields && requiredFields.filter((field) => !value.args[field]);
+
+  const [formError, setFormError] = useState<AnyObject>({});
 
   useEffect(() => {
-    const dereferencedSchema = getDereferenceSchema(schema, name);
-    setDereferencedSchema(dereferencedSchema);
-  }, [schema, name]);
+    if (prepareTxData) {
+      updateSorobanBuildXdr(prepareTxData.transactionXdr);
+    } else {
+      updateSorobanBuildXdr("");
+    }
+    // eslint-disable-next-line react-hooks/exhaustive-deps
+  }, [prepareTxData]);
+
+  const handleChange = (key: string, newVal: any) => {
+    if (soroban.xdr) {
+      updateSorobanBuildXdr("");
+    }
 
-  const handleChange = (key: string, newVal: string) => {
     onChange({
       ...value,
-      data: {
-        ...value.data,
+      args: {
+        ...value.args,
         [key]: newVal,
       },
     });
   };
 
-  const [formError, setFormError] = useState<AnyObject>({});
-
   // key: argument label
   // prop: argument object
   //   // value: argument value that includes the key
@@ -78,8 +113,9 @@ export const JsonSchemaForm = ({
             key={label}
             fieldSize="md"
             label={label}
-            value={value.data[label] || ""}
+            value={value.args[label] || ""}
             error={formError[label] || ""}
+            required={requiredFields.includes(key)}
             onChange={(e) => {
               handleChange(label, e.target.value);
 
@@ -96,6 +132,21 @@ export const JsonSchemaForm = ({
           />
         );
       case "ScString":
+        return (
+          <Input
+            id={key}
+            key={label}
+            fieldSize="md"
+            label={label}
+            value={value.args[label] || ""}
+            error={""}
+            required={requiredFields.includes(key)}
+            onChange={(e) => {
+              handleChange(label, e.target.value);
+            }}
+            note={<>{prop.description}</>}
+          />
+        );
       case "ScSymbol":
         return (
           <Input
@@ -103,10 +154,13 @@ export const JsonSchemaForm = ({
             key={label}
             fieldSize="md"
             label={label}
-            value={value.data[label] || ""}
+            value={value.args[label] || ""}
             error={""}
+            required={requiredFields.includes(key)}
             onChange={(e) => {
               handleChange(label, e.target.value);
+
+              // @TODO add an error handling
             }}
             note={<>{prop.description}</>}
           />
@@ -118,13 +172,14 @@ export const JsonSchemaForm = ({
             key={label}
             fieldSize="md"
             label={label}
-            value={value.data[label] || ""}
+            value={value.args[label] || ""}
             error={formError[label] || ""}
+            required={requiredFields.includes(key)}
             onChange={(e) => {
               handleChange(label, e.target.value);
 
               // validate the value
-              const error = validate.getDataUrlError(e.target.value);
+              const error = Boolean(validate.getDataUrlError(e.target.value));
               setFormError({
                 ...formError,
                 [label]: error,
@@ -138,12 +193,12 @@ export const JsonSchemaForm = ({
             id={key}
             key={label}
             label={label}
-            value={removeLeadingZeroes(value.data[label] || "")}
+            value={value.args[label] || ""}
             error={formError[label] || ""}
             onChange={(e) => {
+              // validate the value
               handleChange(label, e.target.value);
 
-              // validate the value
               const error = validate.getU32Error(e.target.value);
               setFormError({
                 ...formError,
@@ -158,10 +213,10 @@ export const JsonSchemaForm = ({
             id={key}
             key={label}
             label={label}
-            value={removeLeadingZeroes(value.data[label] || "")}
+            value={removeLeadingZeroes(value.args[label] || "")}
             error={formError[label] || ""}
             onChange={(e) => {
-              handleChange(label, e.target.value);
+              handleChange(label, removeLeadingZeroes(e.target.value));
 
               // validate the value
               const error = validate.getU64Error(e.target.value);
@@ -178,10 +233,10 @@ export const JsonSchemaForm = ({
             id={key}
             key={label}
             label={label}
-            value={removeLeadingZeroes(value.data[label] || "")}
+            value={removeLeadingZeroes(value.args[label] || "")}
             error={formError[label] || ""}
             onChange={(e) => {
-              handleChange(label, e.target.value);
+              handleChange(label, removeLeadingZeroes(e.target.value));
 
               // validate the value
               const error = validate.getU128Error(e.target.value);
@@ -198,7 +253,7 @@ export const JsonSchemaForm = ({
             id={key}
             key={label}
             label={label}
-            value={value.data[label] || ""}
+            value={value.args[label] || ""}
             error={formError[label] || ""}
             onChange={(e) => {
               handleChange(label, e.target.value);
@@ -220,8 +275,9 @@ export const JsonSchemaForm = ({
             key={label}
             fieldSize="md"
             label={label}
-            value={value.data[label] || ""}
+            value={value.args[label] || ""}
             error={formError[label] || ""}
+            required={requiredFields.includes(key)}
             onChange={(e) => {
               handleChange(label, e.target.value);
 
@@ -241,8 +297,9 @@ export const JsonSchemaForm = ({
             key={label}
             fieldSize="md"
             label={label}
-            value={value.data[label] || ""}
+            value={value.args[label] || ""}
             error={formError[label] || ""}
+            required={requiredFields.includes(key)}
             onChange={(e) => {
               handleChange(label, e.target.value);
 
@@ -262,8 +319,9 @@ export const JsonSchemaForm = ({
             key={label}
             fieldSize="md"
             label={label}
-            value={value.data[label] || ""}
+            value={value.args[label] || ""}
             error={formError[label] || ""}
+            required={requiredFields.includes(key)}
             onChange={(e) => {
               handleChange(label, e.target.value);
 
@@ -283,8 +341,9 @@ export const JsonSchemaForm = ({
             key={label}
             fieldSize="md"
             label={label}
-            value={value.data[label] || ""}
+            value={value.args[label] || ""}
             error={formError[label] || ""}
+            required={requiredFields.includes(key)}
             onChange={(e) => {
               handleChange(label, e.target.value);
 
@@ -305,6 +364,7 @@ export const JsonSchemaForm = ({
             renderComponent(key, item, index),
           );
         }
+
         // render a button to add an item to the array
         return (
           <Box gap="sm" key={label}>
@@ -321,26 +381,74 @@ export const JsonSchemaForm = ({
             </div>
           </Box>
         );
-
       default:
         return null;
     }
   };
 
+  const handlePrepareTx = () => {
+    resetPrepareTx();
+
+    const sampleTxnXdr = getTxnToSimulate(
+      value,
+      spec,
+      txnParams,
+      operation,
+      network.passphrase,
+    );
+
+    if (sampleTxnXdr) {
+      prepareTx({
+        rpcUrl: network.rpcUrl,
+        transactionXdr: sampleTxnXdr,
+        networkPassphrase: network.passphrase,
+        headers: getNetworkHeaders(network, "rpc"),
+      });
+    } else {
+      // @TODO write an error
+    }
+  };
+
   const render = (item: any): React.ReactElement => {
     const argLabels = item.properties ? Object.keys(item.properties) : [];
     const fields = item.properties ? Object.entries(item.properties) : [];
+    const { name, description } = dereferencedSchema;
 
     return (
-      <>
-        {renderTitle(name, argLabels, fields)}
+      <Box gap="md">
+        {renderTitle(name, description, argLabels, fields)}
 
         <Box gap="md" key={name}>
           {fields.map(([key, prop]) => (
             <>{renderComponent(key, prop)}</>
           ))}
         </Box>
-      </>
+
+        <Box gap="md" direction="row" wrap="wrap">
+          <Button
+            variant="secondary"
+            disabled={
+              (missingFields && missingFields.length > 0) ||
+              Object.values(formError).filter(Boolean).length > 0
+            }
+            isLoading={isPrepareTxPending}
+            size="md"
+            onClick={handlePrepareTx}
+            type="button"
+          >
+            Prepare Transaction
+          </Button>
+        </Box>
+
+        {isPrepareTxError && prepareTxError?.result.toString() ? (
+          <ErrorText
+            errorMessage={prepareTxError?.result.toString()}
+            size="sm"
+          />
+        ) : (
+          <></>
+        )}
+      </Box>
     );
   };
 
@@ -370,44 +478,78 @@ const getTupleLabel = (items: any[]): string => {
 
 const renderTitle = (
   name: string,
+  description: string,
   labels: string[],
   fields: [string, any][],
 ) => {
-  if (labels.length) {
-    const mappedLabels = labels.map((label) => {
-      const field = fields.find(([key]) => key === label);
+  const mappedLabels = labels.map((label) => {
+    const field = fields.find(([key]) => key === label);
 
-      if (field) {
-        if (field?.[1]?.specType) {
-          return `${label}: ${field?.[1]?.specType}`;
+    if (field) {
+      if (field?.[1]?.specType) {
+        return `${label}: ${field?.[1]?.specType}`;
+      }
+      // for an array type that doesn't have a specType
+      if (field?.[1]?.type === "array") {
+        if (field?.[1]?.items?.$ref) {
+          // ex: will output 'Address[]' for `"#/definitions/Address"`
+          const refPath = field?.[1]?.items?.$ref.replace("#/definitions/", "");
+          return `${label}: ${refPath}[]`;
         }
-        // for an array type that doesn't have a specType
-        if (field?.[1]?.type === "array") {
-          if (field?.[1]?.items?.$ref) {
-            // ex: will output 'Address[]' for `"#/definitions/Address"`
-            const refPath = field?.[1]?.items?.$ref.replace(
-              "#/definitions/",
-              "",
-            );
-            return `${label}: ${refPath}[]`;
-          }
-          if (Array.isArray(field?.[1]?.items)) {
-            // Handle tuple case
-            const tupleLabel = getTupleLabel(field[1].items);
-            return `${label}: ${tupleLabel}`;
-          }
-          return `${label}: array`;
+        if (Array.isArray(field?.[1]?.items)) {
+          // Handle tuple case
+          const tupleLabel = getTupleLabel(field[1].items);
+          return `${label}: ${tupleLabel}`;
         }
+        return `${label}: array`;
       }
-      return label;
-    });
+    }
+    return label;
+  });
 
-    return (
-      <Text size="lg" as="p">
-        {name}({mappedLabels.join(", ")})
+  return (
+    <>
+      <Text size="lg" as="h2">
+        {name} ({mappedLabels.join(", ")})
       </Text>
-    );
+      {description ? (
+        <Text size="sm" as="h3">
+          {description}
+        </Text>
+      ) : null}
+    </>
+  );
+};
+
+const getTxnToSimulate = (
+  value: SorobanInvokeValue,
+  spec: contract.Spec,
+  txnParams: TransactionBuildParams,
+  operation: TxnOperation,
+  networkPassphrase: string,
+) => {
+  const scVals = getScValsFromSpec(value.function_name, spec, value);
+
+  try {
+    const builtXdr = buildTxWithSorobanData({
+      params: txnParams,
+      sorobanOp: {
+        ...operation,
+        params: {
+          ...operation.params,
+          contract_id: value.contract_id,
+          function_name: value.function_name,
+          args: scVals,
+          resource_fee: BASE_FEE, // bogus resource fee for simulation purpose
+        },
+      },
+      networkPassphrase,
+    });
+
+    return builtXdr.toXDR();
+  } catch (e) {
+    console.log("e :", e);
   }
 
-  return null;
+  return undefined;
 };
diff --git a/src/helpers/dereferenceSchema.ts b/src/helpers/dereferenceSchema.ts
index e444c304c..8ef30f2c0 100644
--- a/src/helpers/dereferenceSchema.ts
+++ b/src/helpers/dereferenceSchema.ts
@@ -15,7 +15,9 @@ type PrimitiveType =
 type XdrSpecialType = "Address" | "ScString" | "ScSymbol" | "DataUrl";
 type CustomXdrSpecType = PrimitiveType | XdrSpecialType;
 
-export type DereferencedSchema = {
+export type DereferencedSchemaType = {
+  name: string;
+  description: string;
   properties: Record<
     string,
     JSONSchema7Definition & { specType?: CustomXdrSpecType }
@@ -29,10 +31,10 @@ export type DereferencedSchema = {
  * @param methodName - the method name
  * @returns the dereferenced schema
  */
-export const getDereferenceSchema = (
+export const dereferenceSchema = (
   fullSchema: any,
   methodName: string,
-): DereferencedSchema => {
+): DereferencedSchemaType => {
   // Get the method schema
   const methodSchema = fullSchema.definitions[methodName];
   if (!methodSchema) {
@@ -101,6 +103,8 @@ export const getDereferenceSchema = (
   });
 
   return {
+    name: methodName,
+    description: methodSchema.description,
     properties,
     required: requiredFields,
     additionalProperties: false,
diff --git a/src/helpers/getScValsFromSpec.ts b/src/helpers/getScValsFromSpec.ts
new file mode 100644
index 000000000..95027295f
--- /dev/null
+++ b/src/helpers/getScValsFromSpec.ts
@@ -0,0 +1,66 @@
+import { contract, nativeToScVal, ScInt, xdr } from "@stellar/stellar-sdk";
+
+import { isSpecTypeNumber } from "@/helpers/isSpecTypeNumber";
+import { readObj } from "@/helpers/readObj";
+
+import { SorobanInvokeValue } from "@/types/types";
+
+// Used in the JsonSchemaForm component:
+// To convert the string value to ScVal
+// based on its function name and spec
+export const getScValsFromSpec = (
+  function_name: string,
+  spec: contract.Spec,
+  value: SorobanInvokeValue,
+) => {
+  const fn = spec.getFunc(function_name);
+
+  return fn.inputs().map((input: xdr.ScSpecFunctionInputV0) => {
+    const rawInputValue = readObj(value.args, input);
+    const { name: inputFieldType } = input.type().switch();
+
+    if (isSpecTypeNumber(input.type().switch())) {
+      const parsedIntValue = new ScInt(rawInputValue);
+      // without the following check, nativeToScVal makes it
+      // a U64 / I64 instead of a U32 / I32
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeU32().name) {
+        return xdr.ScVal.scvU32(Number(rawInputValue));
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeI32().name) {
+        return xdr.ScVal.scvI32(Number(rawInputValue));
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeU64().name) {
+        return parsedIntValue.toU64();
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeI64().name) {
+        return parsedIntValue.toI64();
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeI128().name) {
+        return parsedIntValue.toI128();
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeU128().name) {
+        return parsedIntValue.toU128();
+      }
+
+      return nativeToScVal(parsedIntValue, { type: inputFieldType });
+    }
+
+    if (inputFieldType === xdr.ScSpecType.scSpecTypeAddress().name) {
+      return nativeToScVal(rawInputValue, {
+        type: "address",
+      });
+    }
+
+    // the below is how funcArgsToScVals is implemented
+    // we need to customize a bit since the saved number value is a string
+    // https://stellar.github.io/js-soroban-client/ContractSpec.html#funcArgsToScVals
+    return nativeToScVal(rawInputValue, {
+      type: inputFieldType,
+    });
+  });
+};
diff --git a/src/helpers/isSpecTypeNumber.ts b/src/helpers/isSpecTypeNumber.ts
new file mode 100644
index 000000000..2b1e48fcb
--- /dev/null
+++ b/src/helpers/isSpecTypeNumber.ts
@@ -0,0 +1,17 @@
+import { xdr } from "@stellar/stellar-sdk";
+
+export const isSpecTypeNumber = (specType: xdr.ScSpecType): boolean => {
+  switch (specType.name) {
+    case xdr.ScSpecType.scSpecTypeU32().name:
+    case xdr.ScSpecType.scSpecTypeI32().name:
+    case xdr.ScSpecType.scSpecTypeU64().name:
+    case xdr.ScSpecType.scSpecTypeI64().name:
+    case xdr.ScSpecType.scSpecTypeU128().name:
+    case xdr.ScSpecType.scSpecTypeI128().name:
+    case xdr.ScSpecType.scSpecTypeU256().name:
+    case xdr.ScSpecType.scSpecTypeI256().name:
+      return true; // It's a valid number spec type
+    default:
+      return false; // Not a valid number spec type
+  }
+};
diff --git a/src/helpers/readObj.ts b/src/helpers/readObj.ts
new file mode 100644
index 000000000..beba8fe5c
--- /dev/null
+++ b/src/helpers/readObj.ts
@@ -0,0 +1,11 @@
+import { AnyObject } from "@/types/types";
+import { xdr } from "@stellar/stellar-sdk";
+
+export const readObj = (args: AnyObject, input: xdr.ScSpecFunctionInputV0) => {
+  const inputName = input.name().toString();
+  const entry = Object.entries(args).find(([name]) => name === inputName);
+  if (!entry) {
+    throw new Error(`Missing field ${inputName}`);
+  }
+  return entry[1];
+};
diff --git a/src/helpers/sorobanUtils.ts b/src/helpers/sorobanUtils.ts
index d86ac0357..3bb83861d 100644
--- a/src/helpers/sorobanUtils.ts
+++ b/src/helpers/sorobanUtils.ts
@@ -7,6 +7,7 @@ import {
   Account,
   Memo,
   SorobanDataBuilder,
+  Transaction,
 } from "@stellar/stellar-sdk";
 
 import { TransactionBuildParams } from "@/store/createStore";
@@ -22,6 +23,8 @@ export const isSorobanOperationType = (operationType: string) => {
 // https://developers.stellar.org/docs/learn/glossary#ledgerkey
 // https://developers.stellar.org/docs/build/guides/archival/restore-data-js
 // Setup contract data xdr that will be used to build Soroban Transaction Data
+// Used for Soroban Operation "restore_footprint" and "extend_footprint_ttl"
+// "invoke_contract_function" uses the returned soroban transaction data from simulateTransaction
 export const getContractDataXDR = ({
   contractAddress,
   dataKey,
@@ -79,17 +82,17 @@ export const getSorobanTxData = ({
   }
 };
 
-export const buildSorobanTx = ({
+export const buildTxWithSorobanData = ({
   sorobanData,
   params,
   sorobanOp,
   networkPassphrase,
 }: {
-  sorobanData: xdr.SorobanTransactionData;
+  sorobanData?: xdr.SorobanTransactionData | string;
   params: TransactionBuildParams;
   sorobanOp: TxnOperation;
   networkPassphrase: string;
-}) => {
+}): Transaction => {
   // decrement seq number by 1 because TransactionBuilder.build()
   // will increment the seq number by 1 automatically
   const txSeq = (BigInt(params.seq_num) - BigInt(1)).toString();
@@ -131,6 +134,14 @@ export const buildSorobanTx = ({
           source: sorobanOp.source_account,
         });
       // case "restore_footprint":
+      case "invoke_contract_function":
+        return Operation.invokeContractFunction({
+          contract: sorobanOp.params.contract_id,
+          function: sorobanOp.params.function_name,
+          args: sorobanOp.params.args,
+          auth: sorobanOp.params.auth,
+          source: sorobanOp.source_account,
+        });
       default:
         throw new Error(`Unsupported Soroban operation type: ${operationType}`);
     }
@@ -148,12 +159,15 @@ export const buildSorobanTx = ({
 
   return transaction
     .setNetworkPassphrase(networkPassphrase)
-    .setSorobanData(sorobanData)
+    .setSorobanData(sorobanData || "")
     .addOperation(getSorobanOp(sorobanOp.operation_type))
     .build();
 };
 
-// Preparing Soroban Transaction Data
+// supports building xdr.SorobanTransactionData that
+// will be included in TransactionBuilder.setSorobanData()
+// used in "extend_footprint_ttl" and "restore_footprint" operation
+// https://stellar.github.io/js-stellar-sdk/SorobanDataBuilder.html
 const buildSorobanData = ({
   readOnlyXdrLedgerKey = [],
   readWriteXdrLedgerKey = [],
@@ -165,14 +179,6 @@ const buildSorobanData = ({
   readWriteXdrLedgerKey?: xdr.LedgerKey[];
   resourceFee: string;
 }) => {
-  // one of the two must be provided
-  if (!(readOnlyXdrLedgerKey && readWriteXdrLedgerKey)) {
-    return;
-  }
-
-  // https://stellar.github.io/js-stellar-sdk/SorobanDataBuilder.html
-  // SorobanDataBuilder is a builder for xdr.SorobanTransactionData structures
-  // that will be used in tx builder
   return new SorobanDataBuilder()
     .setReadOnly(readOnlyXdrLedgerKey)
     .setReadWrite(readWriteXdrLedgerKey)
diff --git a/src/query/useRpcPrepareTx.ts b/src/query/useRpcPrepareTx.ts
new file mode 100644
index 000000000..fed880fdd
--- /dev/null
+++ b/src/query/useRpcPrepareTx.ts
@@ -0,0 +1,55 @@
+import { useMutation } from "@tanstack/react-query";
+import { rpc as StellarRpc, TransactionBuilder } from "@stellar/stellar-sdk";
+
+import { isEmptyObject } from "@/helpers/isEmptyObject";
+import {
+  NetworkHeaders,
+  PrepareRpcErrorResponse,
+  PrepareRpcResponse,
+} from "@/types/types";
+
+type PrepareRpcTxProps = {
+  rpcUrl: string;
+  transactionXdr: string;
+  networkPassphrase: string;
+  headers: NetworkHeaders;
+};
+
+// RPC's prepareTransaction method handles both
+// simulating and assembling transactions
+export const useRpcPrepareTx = () => {
+  const mutation = useMutation<
+    PrepareRpcResponse,
+    PrepareRpcErrorResponse,
+    PrepareRpcTxProps
+  >({
+    mutationFn: async ({
+      rpcUrl,
+      transactionXdr,
+      networkPassphrase,
+      headers,
+    }: PrepareRpcTxProps) => {
+      try {
+        const transaction = TransactionBuilder.fromXDR(
+          transactionXdr,
+          networkPassphrase,
+        );
+        const rpcServer = new StellarRpc.Server(rpcUrl, {
+          headers: isEmptyObject(headers) ? undefined : { ...headers },
+          allowHttp: new URL(rpcUrl).hostname === "localhost",
+        });
+        const preparedTx = await rpcServer.prepareTransaction(transaction);
+
+        return {
+          transactionXdr: preparedTx.toXDR(),
+        };
+      } catch (e) {
+        throw {
+          result: e,
+        };
+      }
+    },
+  });
+
+  return mutation;
+};
diff --git a/src/types/types.ts b/src/types/types.ts
index dc242dd1b..7c95011d1 100644
--- a/src/types/types.ts
+++ b/src/types/types.ts
@@ -152,6 +152,14 @@ export type LedgerErrorResponse = {
   errorCode: number;
 };
 
+export type PrepareRpcResponse = {
+  transactionXdr: string;
+};
+
+export type PrepareRpcErrorResponse = {
+  result: StellarRpc.Api.SimulateTransactionErrorResponse;
+};
+
 export type SubmitRpcResponse = {
   hash: string;
   result: StellarRpc.Api.GetSuccessfulTransactionResponse;
@@ -253,7 +261,9 @@ export type SorobanOpType = "extend_footprint_ttl" | "invoke_contract_function";
 
 export type SorobanInvokeValue = {
   contract_id: string;
-  data: AnyObject;
+  function_name: string;
+  args: AnyObject;
+  scValsXdr?: string[];
 };
 
 // =============================================================================


Reviews:
user: login: quietbits
state: APPROVED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T17:33:40Z
user: login: Shaptic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T18:54:32Z
user: login: Shaptic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T18:56:43Z
user: login: jeesunikim
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T19:38:21Z
user: login: Shaptic
state: COMMENTED
author_association: CONTRIBUTOR
submitted_at: 2025-03-19T21:21:29Z

Review comments:
diff_hunk: @@ -370,44 +478,79 @@ const getTupleLabel = (items: any[]): string => {
 
 const renderTitle = (
   name: string,
+  description: string,
   labels: string[],
   fields: [string, any][],
 ) => {
-  if (labels.length) {
-    const mappedLabels = labels.map((label) => {
-      const field = fields.find(([key]) => key === label);
+  // if (labels.length) {
path: src/components/JsonSchemaForm/index.tsx
user: login: quietbits
body: Do we need to keep this for now?
created_at: 2025-03-19T17:26:03Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,56 @@
+import { useMutation } from "@tanstack/react-query";
+import { rpc as StellarRpc, TransactionBuilder } from "@stellar/stellar-sdk";
+
+import { isEmptyObject } from "@/helpers/isEmptyObject";
+import {
+  NetworkHeaders,
+  PrepareRpcErrorResponse,
+  PrepareRpcResponse,
+} from "@/types/types";
+
+type PrepareRpcTxProps = {
+  rpcUrl: string;
+  transactionXdr: string;
+  networkPassphrase: string;
+  headers: NetworkHeaders;
+};
+
+// RPC's prepareTransaction method handles both
+// simulating and assembling transactions
+export const useRpcPrepareTx = () => {
+  const mutation = useMutation<
+    PrepareRpcResponse,
+    PrepareRpcErrorResponse,
+    PrepareRpcTxProps
+  >({
+    mutationFn: async ({
+      rpcUrl,
+      transactionXdr,
+      networkPassphrase,
+      headers,
+    }: PrepareRpcTxProps) => {
+      try {
+        const transaction = TransactionBuilder.fromXDR(
+          transactionXdr,
+          networkPassphrase,
+        );
+        console.log("transaction :", transaction);
path: src/query/useRpcPrepareTx.ts
user: login: quietbits
body: `console.log`
created_at: 2025-03-19T17:33:08Z
author_association: CONTRIBUTOR
diff_hunk: @@ -1,24 +1,27 @@
 "use client";
 
 import React, { useEffect, useState } from "react";
-
 import { useSimulateTx } from "@/query/useSimulateTx";
 
 import { useStore } from "@/store/useStore";
 import { SorobanOpType } from "@/types/types";
 import { getNetworkHeaders } from "@/helpers/getNetworkHeaders";
 import {
-  buildSorobanTx,
+  buildTxWithSorobanData,
   getContractDataXDR,
   getSorobanTxData,
 } from "@/helpers/sorobanUtils";
 
 import { InputSideElement } from "@/components/InputSideElement";
 import { PositiveIntPicker } from "@/components/FormElements/PositiveIntPicker";
 
+// We add a bogus fee to simualte to fetch the min resource fee from the RPC
+const BOGUS_RESOURCE_FEE = "100";
path: src/components/FormElements/ResourceFeePickerWithQuery.tsx
user: login: Shaptic
body: could use the SDK constant `sdk.BASE_FEE` here
created_at: 2025-03-19T18:54:32Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { contract, nativeToScVal, ScInt, xdr } from "@stellar/stellar-sdk";
+
+import { isSpecTypeNumber } from "@/helpers/isSpecTypeNumber";
+import { readObj } from "@/helpers/readObj";
+
+import { SorobanInvokeValue } from "@/types/types";
+
+// Used in the JsonSchemaForm component:
+// To convert the string value to ScVal
+// based on its function name and spec
+export const getScValsFromSpec = (
+  function_name: string,
+  spec: contract.Spec,
+  value: SorobanInvokeValue,
+) => {
+  const fn = spec.getFunc(function_name);
+
+  return fn.inputs().map((input: xdr.ScSpecFunctionInputV0) => {
+    const rawInputValue = readObj(value.args, input);
+    const { name: inputFieldType } = input.type().switch();
+
+    if (isSpecTypeNumber(input.type().switch())) {
+      const parsedIntValue = new ScInt(rawInputValue);
+      // without the following check, nativeToScVal makes it
+      // a U64 / I64 instead of a U32 / I32
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeU32().name) {
+        return xdr.ScVal.scvU32(Number(rawInputValue));
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeI32().name) {
+        return xdr.ScVal.scvI32(Number(rawInputValue));
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeU64().name) {
+        return parsedIntValue.toU64();
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeI64().name) {
+        return parsedIntValue.toI64();
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeI128().name) {
+        return parsedIntValue.toI128();
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeU128().name) {
+        return parsedIntValue.toU128();
+      }
+
+      return nativeToScVal(parsedIntValue, { type: inputFieldType });
path: src/helpers/getScValsFromSpec.ts
user: login: Shaptic
body: I think you can use `return parsedIntValue.toScVal()` here?
created_at: 2025-03-19T18:56:43Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { contract, nativeToScVal, ScInt, xdr } from "@stellar/stellar-sdk";
+
+import { isSpecTypeNumber } from "@/helpers/isSpecTypeNumber";
+import { readObj } from "@/helpers/readObj";
+
+import { SorobanInvokeValue } from "@/types/types";
+
+// Used in the JsonSchemaForm component:
+// To convert the string value to ScVal
+// based on its function name and spec
+export const getScValsFromSpec = (
+  function_name: string,
+  spec: contract.Spec,
+  value: SorobanInvokeValue,
+) => {
+  const fn = spec.getFunc(function_name);
+
+  return fn.inputs().map((input: xdr.ScSpecFunctionInputV0) => {
+    const rawInputValue = readObj(value.args, input);
+    const { name: inputFieldType } = input.type().switch();
+
+    if (isSpecTypeNumber(input.type().switch())) {
+      const parsedIntValue = new ScInt(rawInputValue);
+      // without the following check, nativeToScVal makes it
+      // a U64 / I64 instead of a U32 / I32
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeU32().name) {
+        return xdr.ScVal.scvU32(Number(rawInputValue));
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeI32().name) {
+        return xdr.ScVal.scvI32(Number(rawInputValue));
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeU64().name) {
+        return parsedIntValue.toU64();
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeI64().name) {
+        return parsedIntValue.toI64();
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeI128().name) {
+        return parsedIntValue.toI128();
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeU128().name) {
+        return parsedIntValue.toU128();
+      }
+
+      return nativeToScVal(parsedIntValue, { type: inputFieldType });
path: src/helpers/getScValsFromSpec.ts
user: login: jeesunikim
body: `parseIntValue.toScVal` returns `u64` even for the required `u128` type. I think it's because the saved value type is in `string` type so it returns the smallest possible XDR integer type when I use `parsedIntValue.toScVal()`, which is `u64` in this case even though the contract requires `u128` in the case of swap func in contract `CA6PUJLBYKZKUEKLZJMKBZLEKP2OTHANDEOWSFF44FTSYLKQPIICCJBE`
created_at: 2025-03-19T19:38:21Z
author_association: CONTRIBUTOR
diff_hunk: @@ -0,0 +1,66 @@
+import { contract, nativeToScVal, ScInt, xdr } from "@stellar/stellar-sdk";
+
+import { isSpecTypeNumber } from "@/helpers/isSpecTypeNumber";
+import { readObj } from "@/helpers/readObj";
+
+import { SorobanInvokeValue } from "@/types/types";
+
+// Used in the JsonSchemaForm component:
+// To convert the string value to ScVal
+// based on its function name and spec
+export const getScValsFromSpec = (
+  function_name: string,
+  spec: contract.Spec,
+  value: SorobanInvokeValue,
+) => {
+  const fn = spec.getFunc(function_name);
+
+  return fn.inputs().map((input: xdr.ScSpecFunctionInputV0) => {
+    const rawInputValue = readObj(value.args, input);
+    const { name: inputFieldType } = input.type().switch();
+
+    if (isSpecTypeNumber(input.type().switch())) {
+      const parsedIntValue = new ScInt(rawInputValue);
+      // without the following check, nativeToScVal makes it
+      // a U64 / I64 instead of a U32 / I32
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeU32().name) {
+        return xdr.ScVal.scvU32(Number(rawInputValue));
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeI32().name) {
+        return xdr.ScVal.scvI32(Number(rawInputValue));
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeU64().name) {
+        return parsedIntValue.toU64();
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeI64().name) {
+        return parsedIntValue.toI64();
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeI128().name) {
+        return parsedIntValue.toI128();
+      }
+
+      if (inputFieldType === xdr.ScSpecType.scSpecTypeU128().name) {
+        return parsedIntValue.toU128();
+      }
+
+      return nativeToScVal(parsedIntValue, { type: inputFieldType });
path: src/helpers/getScValsFromSpec.ts
user: login: Shaptic
body: Ahhh yes that makes sense - you can pass an optional `{ type: "" }` but that'd be the same amount of code to compare the input field type.
created_at: 2025-03-19T21:21:29Z
author_association: CONTRIBUTOR

PR comments:
user: login: stellar-jenkins
created_at: 2025-03-18T20:22:56Z
body: **Something went wrong with PR preview build please check**
author_association: NONE
user: login: stellar-jenkins
created_at: 2025-03-18T20:41:01Z
body: **Something went wrong with PR preview build please check**
author_association: NONE
user: login: stellar-jenkins
created_at: 2025-03-18T23:48:03Z
body: **Something went wrong with PR preview build please check**
author_association: NONE
user: login: stellar-jenkins
created_at: 2025-03-19T00:44:52Z
body: **Something went wrong with PR preview build please check**
author_association: NONE
user: login: stellar-jenkins
created_at: 2025-03-19T05:36:30Z
body: **Something went wrong with PR preview build please check**
author_association: NONE
user: login: stellar-jenkins
created_at: 2025-03-19T05:59:27Z
body: **Preview is available here:**<br>http://laboratory-pr1281.previews.kube001.services.stellar-ops.com/
author_association: NONE
user: login: janewang
created_at: 2025-03-19T15:54:00Z
body: @jeesunikim This is super neat! 
author_association: CONTRIBUTOR
user: login: stellar-jenkins
created_at: 2025-03-19T18:55:17Z
body: **Preview is available here:**<br>http://laboratory-pr1281.previews.kube001.services.stellar-ops.com/
author_association: NONE
user: login: stellar-jenkins
created_at: 2025-03-19T20:06:56Z
body: **Preview is available here:**<br>http://laboratory-pr1281.previews.kube001.services.stellar-ops.com/
author_association: NONE