Repository: cstenglein/raspiblitz-web (ts)

Issue:
Add sweeping functionality for on-chain
The blitz_api provides a flag for spend all funds on-chain

Implement a checkbox to send all sats on-chain

Pull request:
add sweep functionality for on-chain; refactor Send components
fixes #517 

refactored the send components as well (SendModal, SendOnchain, SendLn), to reduce unnecessary complexity

Will need to test more, maybe refactor a bit more, but should work like this.

TODO: 

- [x] when going back on the ConfirmModal, the entered inputs should not be cleared

Patch:
diff --git a/src/components/AmountInput.tsx b/src/components/AmountInput.tsx
index 72ef410..c54ea3f 100644
--- a/src/components/AmountInput.tsx
+++ b/src/components/AmountInput.tsx
@@ -13,9 +13,15 @@ export type Props = {
   amount?: number;
   register: UseFormRegisterReturn;
   errorMessage?: FieldError;
+  disabled?: boolean;
 };
 
-const AmountInput: FC<Props> = ({ amount, register, errorMessage }) => {
+const AmountInput: FC<Props> = ({
+  amount,
+  register,
+  errorMessage,
+  disabled = false,
+}) => {
   const { t } = useTranslation();
   const [amountInput, setAmountInput] = useState<string>(
     amount ? `${amount}` : ""
@@ -71,6 +77,7 @@ const AmountInput: FC<Props> = ({ amount, register, errorMessage }) => {
           type="text"
           value={amountInput}
           onChange={onChangeHandler}
+          disabled={disabled}
         />
         <span
           className="ml-6 flex w-4/12 items-center justify-center rounded p-1 shadow-md dark:bg-gray-600"
diff --git a/src/i18n/langs/en.json b/src/i18n/langs/en.json
index 8b0b02d..0a75257 100644
--- a/src/i18n/langs/en.json
+++ b/src/i18n/langs/en.json
@@ -293,6 +293,7 @@
       "included_block": "Included in Block",
       "mempool": "View on mempool",
       "sent": "Transaction sent!",
+      "spend_all": "Spend all funds",
       "transactions": "Transactions",
       "transactions_none": "No transactions yet, time to make your first one",
       "tx_details": "Transaction Details",
diff --git a/src/models/decode-pay-req.ts b/src/models/decode-pay-req.ts
new file mode 100644
index 0000000..f221397
--- /dev/null
+++ b/src/models/decode-pay-req.ts
@@ -0,0 +1,17 @@
+export interface DecodePayRequest {
+  destination: string;
+  payment_hash: string;
+  /** epoch time in seconds */
+  timestamp: number;
+  /** number in seconds */
+  expiry: number;
+  description: string;
+  description_hash: string;
+  fallback_addr: string;
+  cltv_expiry: number;
+  route_hints: [];
+  payment_addr: string;
+  num_msat: number;
+  features: [];
+  currency: string;
+}
diff --git a/src/pages/Home/SendModal/ConfirmSendModal.tsx b/src/pages/Home/SendModal/ConfirmSendModal.tsx
index 7a96738..bdbca71 100644
--- a/src/pages/Home/SendModal/ConfirmSendModal.tsx
+++ b/src/pages/Home/SendModal/ConfirmSendModal.tsx
@@ -15,60 +15,50 @@ import { checkError } from "../../../utils/checkError";
 import {
   convertBtcToSat,
   convertMSatToSat,
+  formatAmount,
   stringToNumber,
 } from "../../../utils/format";
 import { instance } from "../../../utils/interceptor";
 import { TxType } from "../SwitchTxType";
+import { SendLnForm } from "./SendModal";
+import { SendOnChainForm } from "./SendOnChain";
+
 interface IFormInputs {
   amountInput: string;
 }
 
 export type Props = {
-  address: string;
-  back: () => void;
+  confirmData: SendOnChainForm | SendLnForm;
+  back: (data: SendOnChainForm | SendLnForm) => void;
   balance: number;
   close: (confirmed: boolean) => void;
-  comment: string;
-  expiry: number;
-  fee: string;
-  /** amount in mSat */
-  invoiceAmount: number;
-  invoiceType: TxType;
-  /** epoch time in seconds */
-  timestamp: number;
 };
 
-const ConfirmSendModal: FC<Props> = ({
-  address,
-  back,
-  balance,
-  close,
-  comment,
-  expiry,
-  fee,
-  invoiceAmount,
-  invoiceType,
-  timestamp,
-}) => {
+const ConfirmSendModal: FC<Props> = ({ confirmData, back, balance, close }) => {
   const { t } = useTranslation();
   const { unit } = useContext(AppContext);
   const [amountInput, setAmountInput] = useState(0);
   const [isLoading, setIsLoading] = useState(false);
   const [error, setError] = useState<string | null>(null);
-  const isLnTx = invoiceType === TxType.LIGHTNING;
+  const isLnTx = confirmData.invoiceType === TxType.LIGHTNING;
 
   const amountChangeHandler = (event: ChangeEvent<HTMLInputElement>) => {
     setAmountInput(+event.target.value);
   };
 
-  const invoiceExpiryDate = (timestamp + expiry) * 1000;
+  const lnData = confirmData as SendLnForm;
+  const onChainData = confirmData as SendOnChainForm;
+
+  const invoiceExpiryDate = isLnTx
+    ? (lnData.timestamp + lnData.expiry) * 1000
+    : 0;
   const invoiceExpiryDateDecorated = new Intl.DateTimeFormat("default", {
     dateStyle: "medium",
     timeStyle: "medium",
   }).format(new Date(invoiceExpiryDate));
-  const isInvoiceExpired: boolean = isLnTx && invoiceExpiryDate < Date.now();
-  const isInvoiceAmountBiggerThanBalance: boolean = invoiceAmount > balance;
-  const isValidLnInvoice: boolean =
+  const isInvoiceExpired = isLnTx && invoiceExpiryDate < Date.now();
+  const isInvoiceAmountBiggerThanBalance = confirmData.amount > balance;
+  const isValidLnInvoice =
     !isLnTx ||
     (isLnTx && !isInvoiceExpired && !isInvoiceAmountBiggerThanBalance);
 
@@ -98,7 +88,9 @@ const ConfirmSendModal: FC<Props> = ({
       }
 
       instance
-        .post(`lightning/send-payment?pay_req=${address}${msatQuery}`)
+        .post(
+          `lightning/send-payment?pay_req=${confirmData?.address}${msatQuery}`
+        )
         .then(() => {
           setIsLoading(false);
           close(true);
@@ -108,11 +100,15 @@ const ConfirmSendModal: FC<Props> = ({
           setIsLoading(false);
         });
     } else {
+      const { spendAll, fee, address, comment, amount } =
+        confirmData as SendOnChainForm;
+      const amountBody = spendAll ? 0 : amount;
       const body = {
-        amount: invoiceAmount === 0 ? amountInput : invoiceAmount,
+        amount: amountBody,
         address,
-        sat_per_vbyte: fee,
+        sat_per_vbyte: +fee,
         label: comment,
+        send_all: spendAll,
         unit,
       };
 
@@ -135,7 +131,8 @@ const ConfirmSendModal: FC<Props> = ({
   return (
     <form onSubmit={handleSubmit(sendTransactionHandler)}>
       <button
-        onClick={back}
+        type="button"
+        onClick={() => back(confirmData)}
         className="flex items-center justify-center font-bold outline-none"
       >
         <ChevronLeftIcon className="inline-block h-4 w-4" />
@@ -148,7 +145,7 @@ const ConfirmSendModal: FC<Props> = ({
       <div className="my-2">
         <h4 className="font-bold">{addressTitle}: </h4>
         <p className="w-full break-all text-gray-600 dark:text-gray-200">
-          {address}
+          {confirmData.address}
         </p>
         {isInvoiceExpired && (
           <p className="text-red-500">
@@ -160,15 +157,27 @@ const ConfirmSendModal: FC<Props> = ({
 
       <div className="my-2">
         <h4 className="font-bold">{t("wallet.amount")}:</h4>
-        {Number(invoiceAmount) !== 0 && (
-          <span>{convertMSatToSat(invoiceAmount)} Sat</span>
+        {isLnTx && Number(confirmData.amount) !== 0 && (
+          <span>
+            {formatAmount(
+              convertMSatToSat(+confirmData.amount)?.toString()!,
+              Unit.SAT
+            )}{" "}
+            Sat
+          </span>
+        )}
+
+        {!isLnTx && (
+          <span>
+            {formatAmount(confirmData.amount.toString(), Unit.SAT)} Sat
+          </span>
         )}
 
         {isInvoiceAmountBiggerThanBalance && (
           <p className="text-red-500">{t("forms.validation.lnInvoice.max")}</p>
         )}
 
-        {Number(invoiceAmount) === 0 && (
+        {Number(confirmData.amount) === 0 && !onChainData.spendAll && (
           <div>
             <p>{t("forms.hint.invoiceAmountZero")}</p>
 
@@ -191,17 +200,19 @@ const ConfirmSendModal: FC<Props> = ({
             />
           </div>
         )}
+        {onChainData.spendAll && <div>Spend All</div>}
       </div>
 
       {!isLnTx && (
         <div className="my-2">
-          <h4 className="font-bold">{t("tx.fee")}:</h4> {fee} sat/vByte
+          <h4 className="font-bold">{t("tx.fee")}:</h4> {confirmData.fee}{" "}
+          sat/vByte
         </div>
       )}
 
-      {comment && (
+      {confirmData.comment && (
         <div className="my-2">
-          <h4 className="font-bold">{commentHeading}:</h4> {comment}
+          <h4 className="font-bold">{commentHeading}:</h4> {confirmData.comment}
         </div>
       )}
 
diff --git a/src/pages/Home/SendModal/SendLN.tsx b/src/pages/Home/SendModal/SendLN.tsx
index dd6f1ff..85360c4 100644
--- a/src/pages/Home/SendModal/SendLN.tsx
+++ b/src/pages/Home/SendModal/SendLN.tsx
@@ -1,7 +1,6 @@
 import { ShareIcon } from "@bitcoin-design/bitcoin-icons-react/filled";
 import AvailableBalance from "components/AvailableBalance";
-import type { ChangeEvent } from "react";
-import { FC } from "react";
+import { FC, useState } from "react";
 import type { SubmitHandler } from "react-hook-form";
 import { useForm } from "react-hook-form";
 import { useTranslation } from "react-i18next";
@@ -9,36 +8,50 @@ import { convertMSatToSat } from "utils/format";
 import ButtonWithSpinner from "../../../components/ButtonWithSpinner/ButtonWithSpinner";
 import InputField from "../../../components/InputField";
 import Message from "../../../components/Message";
+import { TxType } from "../SwitchTxType";
+import { SendLnForm } from "./SendModal";
+import { SendOnChainForm } from "./SendOnChain";
 
 export type Props = {
   lnBalance: number;
   loading: boolean;
-  onConfirm: () => void;
-  onChangeInvoice: (event: ChangeEvent<HTMLInputElement>) => void;
+  onConfirm: (data: LnInvoiceForm) => void;
   error: string;
+  confirmData?: SendOnChainForm | SendLnForm | null;
 };
-interface IFormInputs {
-  invoiceInput: string;
+
+export interface LnInvoiceForm {
+  invoice: string;
 }
 
 const SendLn: FC<Props> = ({
   loading,
   lnBalance,
   onConfirm,
-  onChangeInvoice,
   error,
+  confirmData,
 }) => {
   const { t } = useTranslation();
 
   const {
     register,
     handleSubmit,
+    reset,
     formState: { errors, isValid },
-  } = useForm<IFormInputs>({
+  } = useForm<LnInvoiceForm>({
     mode: "onChange",
   });
 
-  const onSubmit: SubmitHandler<IFormInputs> = (_) => onConfirm();
+  const [updated, setUpdated] = useState(false);
+
+  if (!updated && confirmData?.invoiceType === TxType.LIGHTNING) {
+    setUpdated(true);
+    reset({
+      invoice: confirmData.invoice,
+    });
+  }
+
+  const onSubmit: SubmitHandler<LnInvoiceForm> = (data) => onConfirm(data);
 
   const convertedBalance = lnBalance ? convertMSatToSat(lnBalance) : 0;
 
@@ -49,16 +62,15 @@ const SendLn: FC<Props> = ({
       <AvailableBalance balance={convertedBalance!} />
 
       <InputField
-        {...register("invoiceInput", {
+        {...register("invoice", {
           required: t("forms.validation.lnInvoice.required"),
           pattern: {
             value: /^(lnbc|lntb)\w+/i,
             message: t("forms.validation.lnInvoice.patternMismatch"),
           },
-          onChange: onChangeInvoice,
         })}
         label={t("wallet.invoice")}
-        errorMessage={errors.invoiceInput}
+        errorMessage={errors.invoice}
         placeholder="lnbc..."
         disabled={loading}
       />
diff --git a/src/pages/Home/SendModal/SendModal.tsx b/src/pages/Home/SendModal/SendModal.tsx
index 368b6da..4c730c5 100644
--- a/src/pages/Home/SendModal/SendModal.tsx
+++ b/src/pages/Home/SendModal/SendModal.tsx
@@ -1,4 +1,6 @@
-import { ChangeEvent, FC, useState } from "react";
+import { AxiosResponse } from "axios";
+import { DecodePayRequest } from "models/decode-pay-req";
+import { FC, useState } from "react";
 import { createPortal } from "react-dom";
 import ModalDialog from "../../../layouts/ModalDialog";
 import { MODAL_ROOT } from "../../../utils";
@@ -6,8 +8,8 @@ import { checkError } from "../../../utils/checkError";
 import { instance } from "../../../utils/interceptor";
 import SwitchTxType, { TxType } from "../SwitchTxType";
 import ConfirmSendModal from "./ConfirmSendModal";
-import SendLn from "./SendLN";
-import SendOnChain from "./SendOnChain";
+import SendLn, { LnInvoiceForm } from "./SendLN";
+import SendOnChain, { SendOnChainForm } from "./SendOnChain";
 
 export type Props = {
   lnBalance: number;
@@ -15,34 +17,45 @@ export type Props = {
   onClose: (confirmed: boolean) => void;
 };
 
+export interface SendLnForm {
+  invoiceType: TxType.LIGHTNING;
+  invoice: string;
+  address: string;
+  amount: number;
+  comment: string;
+  timestamp: number;
+  expiry: number;
+}
+
 const SendModal: FC<Props> = ({ lnBalance, onClose, onchainBalance }) => {
   const [invoiceType, setInvoiceType] = useState<TxType>(TxType.LIGHTNING);
-  const [invoice, setInvoice] = useState("");
-  const [confirm, setConfirm] = useState(false);
-  const [address, setAddress] = useState("");
-  const [amount, setAmount] = useState(0);
-  const [fee, setFee] = useState("");
-  const [comment, setComment] = useState("");
-  const [timestamp, setTimestamp] = useState(0);
-  const [expiry, setExpiry] = useState(0);
+  const [confirmData, setConfirmData] = useState<
+    SendOnChainForm | SendLnForm | null
+  >(null);
+  const [isBack, setIsBack] = useState(false);
   const [loading, setIsLoading] = useState(false);
   const [error, setError] = useState("");
 
-  const confirmLnHandler = async () => {
+  const confirmLnHandler = async (data: LnInvoiceForm) => {
     setIsLoading(true);
+    setIsBack(false);
     setError("");
     await instance
       .get("/lightning/decode-pay-req", {
         params: {
-          pay_req: invoice,
+          pay_req: data.invoice,
         },
       })
-      .then((resp) => {
-        setAmount(resp.data.num_msat);
-        setComment(resp.data.description);
-        setTimestamp(resp.data.timestamp);
-        setExpiry(resp.data.expiry);
-        setConfirm(true);
+      .then((resp: AxiosResponse<DecodePayRequest>) => {
+        setConfirmData({
+          invoiceType: TxType.LIGHTNING,
+          invoice: data.invoice,
+          address: resp.data.payment_addr,
+          amount: resp.data.num_msat,
+          timestamp: resp.data.timestamp,
+          expiry: resp.data.expiry,
+          comment: resp.data.description,
+        });
       })
       .catch((err) => {
         setError(checkError(err));
@@ -52,58 +65,33 @@ const SendModal: FC<Props> = ({ lnBalance, onClose, onchainBalance }) => {
       });
   };
 
-  const confirmOnChainHandler = () => {
-    setConfirm(true);
-  };
-
-  const changeTransactionHandler = (txType: TxType) => {
-    setInvoiceType(txType);
-    setInvoice("");
-    setAddress("");
-    setAmount(0);
-    setFee("");
-    setComment("");
-    setError("");
-  };
-
-  const changeAmountHandler = (event: ChangeEvent<HTMLInputElement>) => {
-    setAmount(+event.target.value);
+  const confirmOnChainHandler = (data: SendOnChainForm): void => {
+    setIsBack(false);
+    setConfirmData(data);
   };
 
-  const changeAddressHandler = (event: ChangeEvent<HTMLInputElement>) => {
-    setAddress(event.target.value);
+  const backHandler = (data: SendOnChainForm | SendLnForm): void => {
+    setIsBack(true);
+    setConfirmData(data);
   };
 
-  const changeCommentHandler = (event: ChangeEvent<HTMLInputElement>) => {
-    setComment(event.target.value);
-  };
-
-  const changeFeeHandler = (event: ChangeEvent<HTMLInputElement>) => {
-    setFee(event.target.value);
-  };
-
-  const changeInvoiceHandler = (event: ChangeEvent<HTMLInputElement>) => {
-    setInvoice(event.target.value);
+  const changeTransactionHandler = (txType: TxType): void => {
+    setInvoiceType(txType);
+    setConfirmData(null);
+    setError("");
   };
 
   // confirm send
-  if (confirm) {
-    const addr = invoiceType === TxType.LIGHTNING ? invoice : address;
+  if (!isBack && confirmData) {
     return (
       <ModalDialog close={() => onClose(false)}>
         <ConfirmSendModal
-          address={addr}
-          back={() => setConfirm(false)}
+          confirmData={confirmData}
+          back={backHandler}
           balance={
             invoiceType === TxType.LIGHTNING ? lnBalance : onchainBalance
           }
           close={onClose}
-          comment={comment}
-          expiry={expiry}
-          fee={fee}
-          invoiceAmount={amount}
-          invoiceType={invoiceType}
-          timestamp={timestamp}
         />
       </ModalDialog>
     );
@@ -121,9 +109,9 @@ const SendModal: FC<Props> = ({ lnBalance, onClose, onchainBalance }) => {
 
         <SendLn
           loading={loading}
-          onChangeInvoice={changeInvoiceHandler}
           onConfirm={confirmLnHandler}
           lnBalance={lnBalance}
+          confirmData={confirmData}
           error={error}
         />
       </ModalDialog>,
@@ -141,15 +129,8 @@ const SendModal: FC<Props> = ({ lnBalance, onClose, onchainBalance }) => {
       />
 
       <SendOnChain
-        address={address}
-        amount={amount}
         balance={onchainBalance}
-        comment={comment}
-        fee={fee}
-        onChangeAmount={changeAmountHandler}
-        onChangeAddress={changeAddressHandler}
-        onChangeComment={changeCommentHandler}
-        onChangeFee={changeFeeHandler}
+        confirmData={confirmData}
         onConfirm={confirmOnChainHandler}
       />
     </ModalDialog>,
diff --git a/src/pages/Home/SendModal/SendOnChain.tsx b/src/pages/Home/SendModal/SendOnChain.tsx
index 6b103c6..e75bb80 100644
--- a/src/pages/Home/SendModal/SendOnChain.tsx
+++ b/src/pages/Home/SendModal/SendOnChain.tsx
@@ -1,53 +1,63 @@
 import AvailableBalance from "components/AvailableBalance";
-import { ChangeEvent, FC } from "react";
+import { ChangeEvent, FC, useState } from "react";
 import type { SubmitHandler } from "react-hook-form";
 import { useForm } from "react-hook-form";
 import { useTranslation } from "react-i18next";
+import { stringToNumber } from "utils/format";
 import AmountInput from "../../../components/AmountInput";
 import InputField from "../../../components/InputField";
-import { stringToNumber } from "../../../utils/format";
+import { TxType } from "../SwitchTxType";
+import { SendLnForm } from "./SendModal";
 
 export type Props = {
+  balance: number;
+  onConfirm: (data: SendOnChainForm) => void;
+  confirmData?: SendOnChainForm | SendLnForm | null;
+};
+
+export interface SendOnChainForm {
+  invoiceType: TxType.ONCHAIN;
   address: string;
+  fee: string;
+  /** amount in satoshi */
   amount: number;
-  balance: number;
   comment: string;
-  fee: string;
-  onChangeAmount: (event: ChangeEvent<HTMLInputElement>) => void;
-  onChangeAddress: (event: ChangeEvent<HTMLInputElement>) => void;
-  onChangeComment: (event: ChangeEvent<HTMLInputElement>) => void;
-  onChangeFee: (event: ChangeEvent<HTMLInputElement>) => void;
-  onConfirm: () => void;
-};
-interface IFormInputs {
-  addressInput: string;
-  feeInput: number;
-  amountInput: string;
-  commentInput: string;
+  spendAll: boolean;
 }
 
-const SendOnChain: FC<Props> = ({
-  amount,
-  address,
-  balance,
-  comment,
-  fee,
-  onChangeAmount,
-  onChangeAddress,
-  onChangeComment,
-  onChangeFee,
-  onConfirm,
-}) => {
+const SendOnChain: FC<Props> = ({ balance, onConfirm, confirmData }) => {
   const { t } = useTranslation();
   const {
     register,
     handleSubmit,
+    watch,
+    reset,
     formState: { errors, isValid },
-  } = useForm<IFormInputs>({
+  } = useForm<SendOnChainForm>({
     mode: "onChange",
   });
+  const [updated, setUpdated] = useState(false);
+  const [amount, setAmount] = useState(0);
+
+  if (!updated && confirmData?.invoiceType === TxType.ONCHAIN) {
+    console.log("updating");
+    setUpdated(true);
+    setAmount(confirmData.amount);
+    reset({
+      address: confirmData.address,
+      comment: confirmData.comment,
+      fee: confirmData.fee,
+      spendAll: confirmData.spendAll,
+    });
+  }
 
-  const onSubmit: SubmitHandler<IFormInputs> = (_data) => onConfirm();
+  const spendAll = watch("spendAll", false);
+  const onSubmit: SubmitHandler<SendOnChainForm> = (data) =>
+    // overwrite amount to submit number instead of string
+    onConfirm({ ...data, invoiceType: TxType.ONCHAIN, amount });
+  const changeAmountHandler = (event: ChangeEvent<HTMLInputElement>) => {
+    setAmount(+event.target.value);
+  };
 
   return (
     <form className="px-5" onSubmit={handleSubmit(onSubmit)}>
@@ -58,50 +68,59 @@ const SendOnChain: FC<Props> = ({
       <fieldset className="my-5 flex flex-col items-center justify-center text-center">
         <div className="w-full py-1 md:w-10/12">
           <InputField
-            {...register("addressInput", {
+            {...register("address", {
               required: t("forms.validation.chainAddress.required"),
               pattern: {
                 value: /^(1|3|bc1|tb1|tpub|bcrt)\w+/i,
                 message: t("forms.validation.chainAddress.patternMismatch"),
               },
-              onChange: onChangeAddress,
             })}
             placeholder="bc1..."
             label={t("wallet.address")}
-            errorMessage={errors.addressInput}
-            value={address}
+            errorMessage={errors.address}
           />
         </div>
 
-        <div className="w-full py-1 md:w-10/12">
-          <AmountInput
-            amount={amount}
-            errorMessage={errors?.amountInput}
-            register={register("amountInput", {
-              required: t("forms.validation.chainAmount.required"),
-              max: {
-                value: balance || 0,
-                message: t("forms.validation.chainAmount.max"),
-              },
-              validate: {
-                greaterThanZero: (val) =>
-                  stringToNumber(val) > 0 ||
-                  t("forms.validation.chainAmount.required"),
-              },
-              onChange: onChangeAmount,
-            })}
+        {!spendAll && (
+          <div className="w-full pt-1 md:w-10/12">
+            <AmountInput
+              errorMessage={errors?.amount}
+              disabled={spendAll}
+              amount={amount}
+              register={register("amount", {
+                required: t("forms.validation.chainAmount.required"),
+                max: {
+                  value: balance || 0,
+                  message: t("forms.validation.chainAmount.max"),
+                },
+                validate: {
+                  greaterThanZero: (val) =>
+                    //@ts-ignore
+                    stringToNumber(val) > 0 ||
+                    t("forms.validation.chainAmount.required"),
+                },
+                onChange: changeAmountHandler,
+              })}
+            />
+          </div>
+        )}
+
+        <div className="flex w-full justify-start gap-2 pb-1 md:w-10/12">
+          <InputField
+            {...register("spendAll", {})}
+            label={t("tx.spend_all")}
+            errorMessage={errors.spendAll}
+            type="checkbox"
           />
         </div>
 
         <div className="w-full py-1 md:w-10/12">
           <InputField
-            {...register("feeInput", {
+            {...register("fee", {
               required: t("forms.validation.chainFee.required"),
-              onChange: onChangeFee,
             })}
             label={t("tx.fee")}
-            errorMessage={errors.feeInput}
-            value={fee}
+            errorMessage={errors.fee}
             inputRightAddon="sat / vByte"
             type="number"
           />
@@ -109,17 +128,14 @@ const SendOnChain: FC<Props> = ({
 
         <div className="w-full py-1 md:w-10/12">
           <InputField
-            {...register("commentInput", {
-              onChange: onChangeComment,
-            })}
+            {...register("comment")}
             label={t("tx.comment")}
-            value={comment}
             placeholder={t("tx.comment_placeholder")}
           />
         </div>
       </fieldset>
 
-      <div className="mb-5 inline-block w-4/5 align-top lg:w-3/12">
+      <div className="inline-block w-4/5 align-top lg:w-3/12">
         <button
           type="submit"
           className="bd-button my-3 p-3"


Test patch:
diff --git a/src/pages/Home/SendModal/__tests__/ConfirmSendModal.test.tsx b/src/pages/Home/SendModal/__tests__/ConfirmSendModal.test.tsx
index 7574005..7b0f3a4 100644
--- a/src/pages/Home/SendModal/__tests__/ConfirmSendModal.test.tsx
+++ b/src/pages/Home/SendModal/__tests__/ConfirmSendModal.test.tsx
@@ -1,39 +1,41 @@
-import { render, screen, waitFor } from "test-utils";
 import userEvent from "@testing-library/user-event";
 import { I18nextProvider } from "react-i18next";
-import { TxType } from "../../SwitchTxType";
+import { render, screen, waitFor } from "test-utils";
 import i18n from "../../../../i18n/test_config";
 import { rest, server } from "../../../../testServer";
+import { TxType } from "../../SwitchTxType";
 import type { Props } from "../ConfirmSendModal";
 import ConfirmSendModal from "../ConfirmSendModal";
+import { SendLnForm } from "../SendModal";
+import { SendOnChainForm } from "../SendOnChain";
 
 const closeSpy = vi.fn();
 
 const basicLnTxProps: Props = {
-  address:
-    "lnbcrt10u1pscxuktpp5k4hp6wxafdaqfhk84krlt26q80dfdg5df3cdagwjpr5v8xc7s5qqdpz2phkcctjypykuan0d93k2grxdaezqcn0vgxqyjw5qcqp2sp5ndav50eqfh32xxpwd4wa645hevumj7ze5meuajjs40vtgkucdams9qy9qsqc34r4wlyytf68xvt540gz7yq80wsdhyy93dgetv2d2x44dhtg4fysu9k8v0aec8r649tcgtu5s9xths93nuxklvf93px6gnlw2h7u0gq602rww",
+  confirmData: {
+    address:
+      "lnbcrt10u1pscxuktpp5k4hp6wxafdaqfhk84krlt26q80dfdg5df3cdagwjpr5v8xc7s5qqdpz2phkcctjypykuan0d93k2grxdaezqcn0vgxqyjw5qcqp2sp5ndav50eqfh32xxpwd4wa645hevumj7ze5meuajjs40vtgkucdams9qy9qsqc34r4wlyytf68xvt540gz7yq80wsdhyy93dgetv2d2x44dhtg4fysu9k8v0aec8r649tcgtu5s9xths93nuxklvf93px6gnlw2h7u0gq602rww",
+    invoiceType: TxType.LIGHTNING,
+    comment: "",
+    expiry: 36000,
+    amount: 0,
+    timestamp: 1893456000, // 01 Jan 2030 00:00:00 GMT
+  } as SendLnForm,
   back: () => {},
   balance: 100,
   close: closeSpy,
-  comment: "",
-  expiry: 36000,
-  fee: "",
-  invoiceAmount: 0,
-  invoiceType: TxType.LIGHTNING,
-  timestamp: 1893456000, // 01 Jan 2030 00:00:00 GMT
 };
 
 const basicOnChainTxProps: Props = {
-  address: "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
+  confirmData: {
+    address: "bc1qar0srrr7xfkvy5l643lydnw9re59gtzzwf5mdq",
+    fee: "1",
+    amount: 50000,
+    invoiceType: TxType.ONCHAIN,
+  } as SendOnChainForm,
   back: () => {},
   balance: 100,
   close: closeSpy,
-  comment: "",
-  expiry: 36000,
-  fee: "1",
-  invoiceAmount: 50,
-  invoiceType: TxType.ONCHAIN,
-  timestamp: 1893456000, // 01 Jan 2030 00:00:00 GMT
 };
 
 describe("ConfirmSendModal", () => {
@@ -130,13 +132,14 @@ describe("ConfirmSendModal", () => {
 
   describe("ln-invoice with amount above zero", () => {
     test("show error if invoice is expired", async () => {
+      const confirmData = {
+        ...basicLnTxProps.confirmData,
+        timestamp: 1640995200, // Sat Jan 01 2022 08:00:00
+        expiry: 36000,
+      };
       render(
         <I18nextProvider i18n={i18n}>
-          <ConfirmSendModal
-            {...basicLnTxProps}
-            timestamp={1640995200} // "Sat Jan 01 2022 08:00:00
-            expiry={36000}
-          />
+          <ConfirmSendModal {...basicLnTxProps} confirmData={confirmData} />
         </I18nextProvider>
       );
 
@@ -152,9 +155,13 @@ describe("ConfirmSendModal", () => {
     });
 
     test("show error if amount is bigger than balance", async () => {
+      const confirmData = {
+        ...basicLnTxProps.confirmData,
+        amount: 111,
+      };
       render(
         <I18nextProvider i18n={i18n}>
-          <ConfirmSendModal {...basicLnTxProps} invoiceAmount={111} />
+          <ConfirmSendModal {...basicLnTxProps} confirmData={confirmData} />
         </I18nextProvider>
       );
 
@@ -167,9 +174,13 @@ describe("ConfirmSendModal", () => {
     });
 
     test("valid form passes", async () => {
+      const confirmData = {
+        ...basicLnTxProps.confirmData,
+        amount: 100,
+      };
       render(
         <I18nextProvider i18n={i18n}>
-          <ConfirmSendModal {...basicLnTxProps} invoiceAmount={100} />
+          <ConfirmSendModal {...basicLnTxProps} confirmData={confirmData} />
         </I18nextProvider>
       );
 
@@ -186,9 +197,16 @@ describe("ConfirmSendModal", () => {
 
   describe("on chain tx with amount above zero", () => {
     test("show error if amount is bigger than balance", async () => {
+      const confirmData = {
+        ...basicOnChainTxProps.confirmData,
+        amount: 111,
+      };
       render(
         <I18nextProvider i18n={i18n}>
-          <ConfirmSendModal {...basicOnChainTxProps} invoiceAmount={111} />
+          <ConfirmSendModal
+            {...basicOnChainTxProps}
+            confirmData={confirmData}
+          />
         </I18nextProvider>
       );
 
@@ -201,9 +219,16 @@ describe("ConfirmSendModal", () => {
     });
 
     test("valid form passes", async () => {
+      const confirmData = {
+        ...basicOnChainTxProps.confirmData,
+        amount: 50,
+      };
       render(
         <I18nextProvider i18n={i18n}>
-          <ConfirmSendModal {...basicOnChainTxProps} invoiceAmount={50} />
+          <ConfirmSendModal
+            {...basicOnChainTxProps}
+            confirmData={confirmData}
+          />
         </I18nextProvider>
       );
 
diff --git a/src/pages/Home/SendModal/__tests__/SendModal.test.tsx b/src/pages/Home/SendModal/__tests__/SendModal.test.tsx
index f3f5083..8d57aef 100644
--- a/src/pages/Home/SendModal/__tests__/SendModal.test.tsx
+++ b/src/pages/Home/SendModal/__tests__/SendModal.test.tsx
@@ -160,7 +160,7 @@ describe("SendModal", () => {
       expect(confirmBtn).toBeEnabled();
       await user.click(confirmBtn);
       // Should display confirm modal with amount
-      expect(await screen.findByText("20.123 Sat")).toBeInTheDocument();
+      expect(await screen.findByText("20,123 Sat")).toBeInTheDocument();
     });
   });
 });