diff --git a/src/app/components/Modal/index.tsx b/src/app/components/Modal/index.tsx index 9dfbded91a..dbd58a2c0c 100644 --- a/src/app/components/Modal/index.tsx +++ b/src/app/components/Modal/index.tsx @@ -1,29 +1,42 @@ +import { CrossIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; import ReactModal from "react-modal"; type Props = { children?: React.ReactNode; isOpen: boolean; close: () => void; - title: string; + contentLabel: string; + title?: string; }; export default function Modal({ children, isOpen, close: closeModal, + contentLabel, title, }: Props) { return ( + {title && ( +

{title}

+ )} + {children}
); diff --git a/src/app/components/PaymentSummary/index.test.tsx b/src/app/components/PaymentSummary/index.test.tsx index fbbf989798..ecac0672b8 100644 --- a/src/app/components/PaymentSummary/index.test.tsx +++ b/src/app/components/PaymentSummary/index.test.tsx @@ -1,5 +1,5 @@ import PaymentSummary, { Props } from "@components/PaymentSummary"; -import { render, screen, act } from "@testing-library/react"; +import { act, render, screen } from "@testing-library/react"; import { I18nextProvider } from "react-i18next"; import { BrowserRouter } from "react-router-dom"; import { settingsFixture as mockSettings } from "~/../tests/fixtures/settings"; diff --git a/src/app/components/SitePreferences/index.tsx b/src/app/components/SitePreferences/index.tsx index 23f7c0f2f4..d52fd4c39b 100644 --- a/src/app/components/SitePreferences/index.tsx +++ b/src/app/components/SitePreferences/index.tsx @@ -1,4 +1,3 @@ -import { CrossIcon } from "@bitcoin-design/bitcoin-icons-react/outline"; import Button from "@components/Button"; import Hyperlink from "@components/Hyperlink"; import Setting from "@components/Setting"; @@ -179,95 +178,82 @@ function SitePreferences({ launcherType, allowance, onEdit, onDelete }: Props) { -
-

- {t("edit_allowance.title")} -

- -
-
{ e.preventDefault(); updateAllowance(); }} > -
-
- setBudget(e.target.value)} +
+ setBudget(e.target.value)} + /> +
+
+ + setLnurlAuth(!lnurlAuth)} /> -
-
- - setLnurlAuth(!lnurlAuth)} - /> - -
+ +
- {hasPermissions && ( + {hasPermissions && ( +
+

+ {t("edit_permissions")} +

-

- {t("edit_permissions")} -

-
- {permissions.map((permission) => ( - - ( + + lnd.getinfo nostr/nip04decrypt --> nostr.nip04decrypt */ - > - { - setPermissions( - permissions.map((prm) => - prm.id === permission.id - ? { ...prm, enabled: !prm.enabled } - : prm - ) - ); - }} - /> - - - ))} -
+ > + { + setPermissions( + permissions.map((prm) => + prm.id === permission.id + ? { ...prm, enabled: !prm.enabled } + : prm + ) + ); + }} + /> + + + ))}
- )} -
+
+ )} -
+
{ if (window.confirm(t("confirm_delete"))) { diff --git a/src/app/components/TransactionsTable/TransactionModal.tsx b/src/app/components/TransactionsTable/TransactionModal.tsx new file mode 100644 index 0000000000..af3c4d7fe0 --- /dev/null +++ b/src/app/components/TransactionsTable/TransactionModal.tsx @@ -0,0 +1,197 @@ +import { + CaretDownIcon, + CaretUpIcon, +} from "@bitcoin-design/bitcoin-icons-react/filled"; +import { + ArrowDownIcon, + ArrowUpIcon, +} from "@bitcoin-design/bitcoin-icons-react/outline"; +import dayjs from "dayjs"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import Hyperlink from "~/app/components/Hyperlink"; +import Modal from "~/app/components/Modal"; +import { useSettings } from "~/app/context/SettingsContext"; +import { classNames } from "~/app/utils"; +import { Transaction } from "~/types"; + +type Props = { + transaction: Transaction; + onClose: () => void; + isOpen: boolean; +}; + +export default function TransactionModal({ + transaction, + isOpen, + onClose, +}: Props) { + const { t: tCommon } = useTranslation("common"); + const { t } = useTranslation("components", { + keyPrefix: "transactions_table", + }); + const [showMoreFields, setShowMoreFields] = useState(false); + const { getFormattedSats } = useSettings(); + + function toggleShowMoreFields() { + setShowMoreFields(!showMoreFields); + } + + useEffect(() => { + setShowMoreFields(false); + }, [transaction]); + + function getTransactionType(tx: Transaction): "incoming" | "outgoing" { + return [tx.type && "sent"].includes(tx.type) ? "outgoing" : "incoming"; + } + + return ( + { + onClose(); + }} + contentLabel={"Transactions"} + > +
+
+
+ {getTransactionType(transaction) == "outgoing" ? ( +
+ +
+ ) : ( +
+ +
+ )} +
+

+ {transaction.type == "received" ? t("received") : t("sent")} +

+
+
+
+

+ {transaction.type == "sent" ? "-" : "+"}{" "} + {getFormattedSats(transaction.totalAmount)} +

+ + {!!transaction.totalAmountFiat && ( +

+ ~{transaction.totalAmountFiat} +

+ )} +
+
+
+ + {transaction.totalFees?.toString && ( + + )} + {transaction.publisherLink && transaction.title && ( + + + {transaction.title} + + + } + /> + )} + {transaction.boostagram?.podcast && ( + + )} + {transaction.boostagram?.episode && ( + + )} + {(transaction.preimage || transaction.paymentHash) && ( + <> +
+ + {showMoreFields ? ( + <> + {tCommon("actions.hide")} + + + ) : ( + <> + {tCommon("actions.more")} + + + )} + +
+ {showMoreFields && ( +
+ {transaction.preimage && ( + + )} + {transaction.paymentHash && ( + + )} +
+ )} + + )} +
+
+
+ ); +} + +const Dt = ({ children }: { children: React.ReactNode }) => ( +
+ {children} +
+); + +const Dd = ({ children }: { children: React.ReactNode }) => ( +
+ {children} +
+); + +const TransactionDetailRow = ({ + title, + content, +}: { + title: React.ReactNode | string; + content: React.ReactNode | string; +}) => ( +
+
{title}
+
{content}
+
+); diff --git a/src/app/components/TransactionsTable/index.test.tsx b/src/app/components/TransactionsTable/index.test.tsx index c535df8d48..cf29b6bf12 100644 --- a/src/app/components/TransactionsTable/index.test.tsx +++ b/src/app/components/TransactionsTable/index.test.tsx @@ -1,14 +1,12 @@ import { render, screen } from "@testing-library/react"; -import userEvent from "@testing-library/user-event"; -import { act } from "react-dom/test-utils"; import { I18nextProvider } from "react-i18next"; import { BrowserRouter } from "react-router-dom"; import { settingsFixture as mockSettings } from "~/../tests/fixtures/settings"; import i18n from "~/../tests/unit/helpers/i18n"; import { SettingsProvider } from "~/app/context/SettingsContext"; -import TransactionsTable from "."; import type { Props } from "."; +import TransactionsTable from "."; jest.mock("~/common/lib/api", () => { const original = jest.requireActual("~/common/lib/api"); @@ -22,6 +20,7 @@ jest.mock("~/common/lib/api", () => { const transactions: Props = { transactions: [ { + timestamp: 1656573909064, createdAt: "1656573909064", date: "5 days ago", description: "Polar Invoice for bob", @@ -44,6 +43,7 @@ const invoices: Props = { transactions: [ { id: "lnbcrt666660n1p3tad0hpp5kkguywerj5lqspc4p2a7f53yfnkuywxmxnuawe3lu4gdg0ezc2tqdqjd3sk6cn0ypkxzmtzducqzpgxqyz5vqsp529wvk52ckjkrfkll9q3w6ep6lrsg35se66jjpm5ssmumck7xxy6s9qyyssqzq3zsstfs7gzklgkdnxy2hsp4jfavw8xj4hv5300yww3053jx76h57e3ypsuvg36zwd49xm2nfr2lrfvylwrxs7yhpckjytvlaju0hsq7p9wna", + timestamp: 1656573909064, type: "received", totalAmount: "66666", totalAmountFiat: "$13.02", @@ -53,6 +53,7 @@ const invoices: Props = { }, { id: "lnbcrt6543210n1p3tadjepp5rv6ufq4vumg66l9gcyxqhy89n6w90mx0mh6gcj0sawrf6xuep5ssdq5g9kxy7fqd9h8vmmfvdjscqzpgxqyz5vqsp5f9yzxeqjw33ule4rffuh0py32gjjsx8z48cd4xjl8ej3rn7zdtdq9qyyssqe6qvkfe260myc9ypgs5n63xzwcx82fderg8p5ysh6c2fvpz5xu4ksvhs5av0wwestk5pmucmhk8lpjhmy7wqyq9c29xgm9na2q5xv5spy5kukj", + timestamp: 1656573909064, type: "received", totalAmount: "654321", totalAmountFiat: "$127.80", @@ -67,6 +68,7 @@ const invoicesWithBoostagram: Props = { transactions: [ { id: "lnbcrt666660n1p3tad0hpp5kkguywerj5lqspc4p2a7f53yfnkuywxmxnuawe3lu4gdg0ezc2tqdqjd3sk6cn0ypkxzmtzducqzpgxqyz5vqsp529wvk52ckjkrfkll9q3w6ep6lrsg35se66jjpm5ssmumck7xxy6s9qyyssqzq3zsstfs7gzklgkdnxy2hsp4jfavw8xj4hv5300yww3053jx76h57e3ypsuvg36zwd49xm2nfr2lrfvylwrxs7yhpckjytvlaju0hsq7p9wna", + timestamp: 1656573909064, type: "received", totalAmount: "66666", totalAmountFiat: "$13.02", @@ -76,6 +78,7 @@ const invoicesWithBoostagram: Props = { }, { id: "lnbcrt888880n1p3tad30pp56j6g34wctydrfx4wwdwj3schell8uqug6jnlehlkpw02mdfd9wlqdq0v36k6urvd9hxwuccqzpgxqyz5vqsp5995q4egstsvnyetwvpax6jw8q0fnn4tyz3gp35k3yex29emhsylq9qyyssq0yxpx6peyn4vsepwj3l68w9sc5dqnkt07zff6aw4kqvcfs0fpu4jpfh929w6vqrgtjfkmrlwghq4s9t4mnwrh4dlkm6wjem5uq8eu4gpwqln0j", + timestamp: 1656573909064, type: "received", totalAmount: "88888", totalAmountFiat: "$17.36", @@ -103,8 +106,6 @@ const invoicesWithBoostagram: Props = { describe("TransactionsTable", () => { test("renders transactions", async () => { - const user = userEvent.setup(); - render( @@ -117,17 +118,8 @@ describe("TransactionsTable", () => { expect(screen.getByText("Alby")).toBeInTheDocument(); expect(screen.getByText(/5 days ago/)).toBeInTheDocument(); - expect(await screen.findByText(/-1,234,000 sats/)).toBeInTheDocument(); + expect(await screen.findByText(/- 1,234,000 sats/)).toBeInTheDocument(); expect(await screen.findByText(/~\$241.02/)).toBeInTheDocument(); - - const disclosureButton = screen.getByRole("button"); - - await act(() => { - user.click(disclosureButton); - }); - - expect(await screen.findByText("0 sats")).toBeInTheDocument(); - expect(await screen.findByText(/Open website/)).toBeInTheDocument(); }); test("renders invoice without boostagram", async () => { @@ -143,7 +135,7 @@ describe("TransactionsTable", () => { expect(await screen.findByText("lambo lambo")).toBeInTheDocument(); expect(await screen.findByText(/4 days ago/)).toBeInTheDocument(); - expect(await screen.findByText(/\+66,666 sats/)).toBeInTheDocument(); + expect(await screen.findByText(/\+ 66,666 sats/)).toBeInTheDocument(); expect(await screen.findByText(/~\$13.02/)).toBeInTheDocument(); const disclosureButtons = screen.queryByRole("button"); @@ -151,8 +143,6 @@ describe("TransactionsTable", () => { }); test("renders invoice with boostagram", async () => { - const user = userEvent.setup(); - render( @@ -165,23 +155,7 @@ describe("TransactionsTable", () => { expect(screen.getByText("dumplings")).toBeInTheDocument(); expect(screen.getByText(/5 days ago/)).toBeInTheDocument(); - expect(await screen.findByText(/\+88,888 sats/)).toBeInTheDocument(); + expect(await screen.findByText(/\+ 88,888 sats/)).toBeInTheDocument(); expect(await screen.findByText(/~\$17.36/)).toBeInTheDocument(); - - const disclosureButtons = screen.getAllByRole("button"); - expect(disclosureButtons).toHaveLength(1); - - await act(() => { - user.click(disclosureButtons[0]); - }); - - expect( - await screen.findByText(/Message: Du bist so 1 geiles podcast 100%/) - ).toBeInTheDocument(); - expect( - await screen.findByText(/Sender: bumi@getalby.com/) - ).toBeInTheDocument(); - expect(await screen.findByText(/App: Fountain/)).toBeInTheDocument(); - expect(await screen.findByText(/Podcast: Honigdachs/)).toBeInTheDocument(); }); }); diff --git a/src/app/components/TransactionsTable/index.tsx b/src/app/components/TransactionsTable/index.tsx index af143e6a92..53382dcbd7 100644 --- a/src/app/components/TransactionsTable/index.tsx +++ b/src/app/components/TransactionsTable/index.tsx @@ -1,9 +1,15 @@ -import { CaretDownIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; +import { + ArrowDownIcon, + ArrowUpIcon, +} from "@bitcoin-design/bitcoin-icons-react/outline"; import Loading from "@components/Loading"; -import { Disclosure } from "@headlessui/react"; + +import { useState } from "react"; import { useTranslation } from "react-i18next"; -import Button from "~/app/components/Button"; + +import TransactionModal from "~/app/components/TransactionsTable/TransactionModal"; import { useSettings } from "~/app/context/SettingsContext"; +import { classNames } from "~/app/utils"; import { Transaction } from "~/types"; export type Props = { @@ -18,143 +24,102 @@ export default function TransactionsTable({ loading = false, }: Props) { const { getFormattedSats } = useSettings(); - const { t } = useTranslation("components"); + const [modalOpen, setModalOpen] = useState(false); + const [transaction, setTransaction] = useState(); + const { t } = useTranslation("components", { + keyPrefix: "transactions_table", + }); - return loading ? ( -
- -
- ) : !transactions?.length && noResultMsg ? ( -

{noResultMsg}

- ) : ( - <> -
- {transactions?.map((tx) => ( -
- - {({ open }) => ( - <> -
+ function openDetails(transaction: Transaction) { + setTransaction(transaction); + setModalOpen(true); + } + + function getTransactionType(tx: Transaction): "incoming" | "outgoing" { + return [tx.type && "sent"].includes(tx.type) ? "outgoing" : "incoming"; + } + + return ( +
+ {loading ? ( +
+ +
+ ) : !transactions?.length && noResultMsg ? ( +

{noResultMsg}

+ ) : ( + <> +
+ {transactions?.map((tx) => { + const type = getTransactionType(tx); + + return ( +
openDetails(tx)} + > +
+
+ {type == "outgoing" ? ( +
+ +
+ ) : ( +
+ +
+ )} +
-
+

- {tx.publisherLink && tx.title ? ( - - {tx.title} - - ) : ( - tx.title || tx.boostagram?.message || "\u00A0" - )} + {tx.title || + tx.boostagram?.message || + (type == "incoming" ? t("received") : t("sent"))}

-

+

{tx.date}

-
+
-

- {[tx.type && "sent", "sending"].includes(tx.type) - ? "-" - : "+"} +

+ {type == "outgoing" ? "-" : "+"}{" "} {getFormattedSats(tx.totalAmount)}

+ {!!tx.totalAmountFiat && ( -

+

~{tx.totalAmountFiat}

)}
- {(!!tx.description || - [tx.type && "sent", "sending"].includes(tx.type) || - (tx.type === "received" && tx.boostagram)) && ( - - - - )}
- -
- {(tx.description || tx.boostagram) && ( -
- {tx.description &&

{tx.description}

} - {tx.boostagram && ( -
    -
  • - {t("transactionsTable.boostagram.sender")}:{" "} - {tx.boostagram.sender_name} -
  • -
  • - {t("transactionsTable.boostagram.message")}:{" "} - {tx.boostagram.message} -
  • -
  • - {t("transactionsTable.boostagram.app")}:{" "} - {tx.boostagram.app_name} -
  • -
  • - {t("transactionsTable.boostagram.podcast")}:{" "} - {tx.boostagram.podcast} -
  • -
- )} -
- )} - {(tx.totalFees !== undefined || tx.location) && ( -
- {tx.totalFees !== undefined && ( -

- - {t("transactionsTable.fee")} - -
- {getFormattedSats(tx.totalFees)} -

- )} - {tx.location && ( - -
- )} - {tx.preimage && ( -
-

- - {t("transactionsTable.preimage")} - -
- {tx.preimage} -

-
- )} -
-
- - )} - +
+ ); + })}
- ))} -
- + {transaction && ( + { + setModalOpen(false); + }} + /> + )} + + )} +
); } diff --git a/src/app/hooks/useInvoices.ts b/src/app/hooks/useInvoices.ts deleted file mode 100644 index a9c2bb3a1f..0000000000 --- a/src/app/hooks/useInvoices.ts +++ /dev/null @@ -1,52 +0,0 @@ -import dayjs from "dayjs"; -import { useCallback, useState } from "react"; -import toast from "~/app/components/Toast"; -import { useSettings } from "~/app/context/SettingsContext"; -import api from "~/common/lib/api"; -import { Transaction } from "~/types"; - -export const useInvoices = () => { - const { settings, getFormattedFiat } = useSettings(); - - const [isLoadingInvoices, setIsLoadingInvoices] = useState(false); - const [incomingTransactions, setIncomingTransactions] = useState< - Transaction[] - >([]); - - const loadInvoices = useCallback( - async (limit?: number) => { - setIsLoadingInvoices(true); - let result; - try { - result = await api.getInvoices({ isSettled: true, limit }); - } catch (e) { - if (e instanceof Error) toast.error(`Error: ${e.message}`); - setIsLoadingInvoices(false); - return; - } - - const invoices: Transaction[] = result.invoices.map((invoice) => ({ - ...invoice, - title: invoice.memo, - description: invoice.memo, - date: dayjs(invoice.settleDate).fromNow(), - })); - - for (const invoice of invoices) { - invoice.totalAmountFiat = settings.showFiat - ? await getFormattedFiat(invoice.totalAmount) - : ""; - } - - setIncomingTransactions(invoices); - setIsLoadingInvoices(false); - }, - [getFormattedFiat, settings.showFiat] - ); - - return { - isLoadingInvoices, - incomingTransactions, - loadInvoices, - }; -}; diff --git a/src/app/hooks/useTransactions.ts b/src/app/hooks/useTransactions.ts index 7fc62c0fc7..1d1f5cd001 100644 --- a/src/app/hooks/useTransactions.ts +++ b/src/app/hooks/useTransactions.ts @@ -1,7 +1,7 @@ +import dayjs from "dayjs"; import { useCallback, useState } from "react"; import toast from "~/app/components/Toast"; import { useSettings } from "~/app/context/SettingsContext"; -import { convertPaymentsToTransactions } from "~/app/utils/payments"; import api from "~/common/lib/api"; import { Transaction } from "~/types"; @@ -11,22 +11,28 @@ export const useTransactions = () => { const [isLoadingTransactions, setIsLoadingTransactions] = useState(true); const loadTransactions = useCallback( - async (accountId: string, limit?: number) => { + async (limit?: number) => { try { - const { payments } = await api.getPaymentsByAccount({ - accountId, + const getTransactionsResponse = await api.getTransactions({ limit, }); - const _transactions: Transaction[] = - await convertPaymentsToTransactions(payments); - for (const transaction of _transactions) { + const transactions = getTransactionsResponse.transactions.map( + (transaction) => ({ + ...transaction, + title: transaction.memo, + date: dayjs(transaction.settleDate).fromNow(), + timestamp: transaction.settleDate, + }) + ); + + for (const transaction of transactions) { transaction.totalAmountFiat = settings.showFiat ? await getFormattedFiat(transaction.totalAmount) : ""; } - setTransactions(_transactions); + setTransactions(transactions); setIsLoadingTransactions(false); } catch (e) { console.error(e); diff --git a/src/app/router/Options/Options.tsx b/src/app/router/Options/Options.tsx index af4acba192..2eabac9606 100644 --- a/src/app/router/Options/Options.tsx +++ b/src/app/router/Options/Options.tsx @@ -76,16 +76,7 @@ function Options() { } /> } /> } /> - - } - /> - } - /> - + } /> } /> } /> } /> diff --git a/src/app/screens/Accounts/Detail/index.tsx b/src/app/screens/Accounts/Detail/index.tsx index 54ad2b3a40..f2a5dff3c6 100644 --- a/src/app/screens/Accounts/Detail/index.tsx +++ b/src/app/screens/Accounts/Detail/index.tsx @@ -1,4 +1,3 @@ -import { CrossIcon } from "@bitcoin-design/bitcoin-icons-react/filled"; import Button from "@components/Button"; import Container from "@components/Container"; import Loading from "@components/Loading"; @@ -264,50 +263,42 @@ function AccountDetail() { -
-

- {t("export.title")} -

- -
- {exportLoading && ( -
+
{t("export.waiting")}
)} {!exportLoading && ( -
-
-

{t("export.scan_qr")}

+
+

{t("export.scan_qr")}

+
-
- -
- {lndHubData.lnAddress && ( -
-

- {t("export.your_ln_address")} -

- {lndHubData.lnAddress && ( -

{lndHubData.lnAddress}

- )} -
- )}
+
+ +
+ {lndHubData.lnAddress && ( +
+

+ {t("export.your_ln_address")} +

+ {lndHubData.lnAddress && ( +

{lndHubData.lnAddress}

+ )} +
+ )}
)} diff --git a/src/app/screens/Home/AllowanceView/index.tsx b/src/app/screens/Home/AllowanceView/index.tsx index e836e13b15..7f454498ae 100644 --- a/src/app/screens/Home/AllowanceView/index.tsx +++ b/src/app/screens/Home/AllowanceView/index.tsx @@ -139,9 +139,6 @@ const AllowanceView: FC = (props) => { />
)} -

- {t("allowance_view.recent_transactions")} -

{isLoadingTransactions && (
@@ -152,7 +149,7 @@ const AllowanceView: FC = (props) => { {hasTransactions && } {!isLoadingTransactions && !transactions?.length && ( -

+

= (props) => { + const itemsLimit = 8; + const { t } = useTranslation("translation", { keyPrefix: "home" }); const { t: tCommon } = useTranslation("common"); - const { t: tComponents } = useTranslation("components"); const navigate = useNavigate(); - const { account, balancesDecorated, accountLoading } = useAccount(); + const { account, accountLoading } = useAccount(); const lightningAddress = account?.lightningAddress || ""; @@ -48,31 +48,11 @@ const DefaultView: FC = (props) => { const { transactions, isLoadingTransactions, loadTransactions } = useTransactions(); - const { isLoadingInvoices, incomingTransactions, loadInvoices } = - useInvoices(); - - const isLoadingOutgoing = accountLoading || isLoadingTransactions; - const isLoadingIncoming = accountLoading || isLoadingInvoices; - - const itemsLimit = 8; + const isLoading = accountLoading || isLoadingTransactions; useEffect(() => { - if (account?.id) loadTransactions(account.id, itemsLimit); - }, [ - account?.id, - balancesDecorated?.accountBalance, - loadTransactions, - itemsLimit, - ]); - - useEffect(() => { - loadInvoices(itemsLimit); - }, [ - account?.id, - balancesDecorated?.accountBalance, - loadInvoices, - itemsLimit, - ]); + loadTransactions(itemsLimit); + }, [loadTransactions, itemsLimit]); // check if currentURL is blocked useEffect(() => { @@ -142,7 +122,7 @@ const DefaultView: FC = (props) => {

)} -
+
)} - {isLoadingTransactions && ( + {isLoading && (
)} - {!isLoadingTransactions && ( + {!isLoading && (
-

- {t("default_view.recent_transactions")} -

- - - - {[ - tComponents("transaction_list.tabs.outgoing"), - tComponents("transaction_list.tabs.incoming"), - ].map((category) => ( - - ))} - - - - - <> - - {!isLoadingOutgoing && transactions.length > 0 && ( -
- - handleViewAllLink("/transactions/outgoing") - } - > - {t("default_view.all_transactions_link")} - -
- )} - -
- - <> - - {!isLoadingIncoming && incomingTransactions.length > 0 && ( -
- - handleViewAllLink("/transactions/incoming") - } - > - {t("default_view.all_transactions_link")} - -
- )} - -
-
-
+ + + {!isLoading && transactions.length > 0 && ( +
+ handleViewAllLink("/transactions")} + className="flex justify-center items-center mt-2" + > + {t("default_view.see_all")} + + +
+ )}
)}
diff --git a/src/app/screens/Publishers/Detail/index.tsx b/src/app/screens/Publishers/Detail/index.tsx index 038c6c0639..c0909df09e 100644 --- a/src/app/screens/Publishers/Detail/index.tsx +++ b/src/app/screens/Publishers/Detail/index.tsx @@ -64,30 +64,25 @@ function PublisherDetail() { }, [fetchData, isLoadingSettings]); return ( -
-
- {allowance && ( - { - navigate("/publishers", { replace: true }); - }} - title={allowance?.name || ""} - image={allowance?.imageURL || ""} - url={allowance?.host} - isCard={false} - isSmall={false} - /> - )} -
+ <> + {allowance && ( + { + navigate("/publishers", { replace: true }); + }} + title={allowance?.name || ""} + image={allowance?.imageURL || ""} + url={allowance?.host} + isCard={false} + isSmall={false} + /> + )} {allowance && ( -
-

- {t("allowance_view.recent_transactions")} -

+
{transactions && transactions?.length > 0 ? ( ) : ( @@ -104,7 +99,7 @@ function PublisherDetail() {
)} -
+ ); } diff --git a/src/app/screens/Settings/index.tsx b/src/app/screens/Settings/index.tsx index 291ac9eb06..680f5c0242 100644 --- a/src/app/screens/Settings/index.tsx +++ b/src/app/screens/Settings/index.tsx @@ -1,4 +1,3 @@ -import { CrossIcon } from "@bitcoin-design/bitcoin-icons-react/outline"; import Button from "@components/Button"; import Container from "@components/Container"; import LocaleSwitcher from "@components/LocaleSwitcher/LocaleSwitcher"; @@ -295,32 +294,22 @@ function Settings() { -
-

- {t("change_password.title")} -

- -
- { e.preventDefault(); updateAccountPassword(formData.password); }} > -
- -
+ -
+
) : (
- {listItems && listItems.length > 0 && ( - + {transactions && transactions.length > 0 && ( + )}
)} diff --git a/src/app/utils/payments.ts b/src/app/utils/payments.ts index cfb0aa3941..16bca621e0 100644 --- a/src/app/utils/payments.ts +++ b/src/app/utils/payments.ts @@ -9,8 +9,9 @@ export const convertPaymentToTransaction = ( id: `${payment.id}`, type: "sent", date: dayjs(+payment.createdAt).fromNow(), - title: payment.name || payment.description, + title: payment.description || payment.name, publisherLink: publisherLink || payment.location, + timestamp: parseInt(payment.createdAt), }); export const convertPaymentsToTransactions = ( diff --git a/src/common/lib/api.ts b/src/common/lib/api.ts index 36101807fb..19ddfd1bfe 100644 --- a/src/common/lib/api.ts +++ b/src/common/lib/api.ts @@ -25,7 +25,7 @@ import type { LnurlAuthResponse, MessageAccountEdit, MessageAccountValidate, - MessageInvoices, + MessageGetTransactions, MessageLnurlAuth, MessageSettingsSet, NodeInfo, @@ -170,8 +170,8 @@ export const unlock = (password: string) => msg.request("unlock", { password }); export const getBlocklist = (host: string) => msg.request("getBlocklist", { host }); -export const getInvoices = (options?: MessageInvoices["args"]) => - msg.request<{ invoices: Invoice[] }>("getInvoices", options); +export const getTransactions = (options?: MessageGetTransactions["args"]) => + msg.request<{ transactions: Invoice[] }>("getTransactions", options); export const lnurlAuth = ( options: MessageLnurlAuth["args"] ): Promise => @@ -295,7 +295,7 @@ export default { removeAccount, unlock, getBlocklist, - getInvoices, + getTransactions, lnurlAuth, getCurrencyRate, sendPayment, diff --git a/src/common/lib/utils.ts b/src/common/lib/utils.ts index 53ef50922f..38ae048347 100644 --- a/src/common/lib/utils.ts +++ b/src/common/lib/utils.ts @@ -1,12 +1,8 @@ import browser, { Runtime } from "webextension-polyfill"; import { ABORT_PROMPT_ERROR } from "~/common/constants"; import { getPosition as getWindowPosition } from "~/common/utils/window"; -import type { - DeferredPromise, - Invoice, - OriginData, - OriginDataInternal, -} from "~/types"; +import { ConnectorTransaction } from "~/extension/background-script/connectors/connector.interface"; +import type { DeferredPromise, OriginData, OriginDataInternal } from "~/types"; const utils = { base64ToHex: (str: string) => { @@ -173,7 +169,7 @@ const utils = { }); }, getBoostagramFromInvoiceCustomRecords: ( - custom_records: Invoice["custom_records"] | undefined + custom_records: ConnectorTransaction["custom_records"] | undefined ) => { try { let boostagramDecoded: string | undefined; diff --git a/src/common/utils/currencyConvert.ts b/src/common/utils/currencyConvert.ts index 02d9f2b624..2a83b657d5 100644 --- a/src/common/utils/currencyConvert.ts +++ b/src/common/utils/currencyConvert.ts @@ -1,9 +1,9 @@ /** * Highly inspired by: https://github.com/AryanJ-NYC/bitcoin-conversion */ -import i18n from "~/i18n/i18nConfig"; -import type { CURRENCIES, ACCOUNT_CURRENCIES } from "../constants"; +import i18n from "~/i18n/i18nConfig"; +import type { ACCOUNT_CURRENCIES, CURRENCIES } from "../constants"; export const numSatsInBtc = 100_000_000; diff --git a/src/common/utils/helpers.ts b/src/common/utils/helpers.ts index 732dfbc16a..f104465a2d 100644 --- a/src/common/utils/helpers.ts +++ b/src/common/utils/helpers.ts @@ -1,5 +1,6 @@ import * as secp256k1 from "@noble/secp256k1"; import { bech32 } from "bech32"; +import { ConnectorTransaction } from "~/extension/background-script/connectors/connector.interface"; import { Sender } from "~/types"; export function bech32Decode(str: string, encoding: BufferEncoding = "utf-8") { @@ -61,3 +62,14 @@ export async function poll({ return new Promise(executePoll); } + +export function mergeTransactions( + invoices: ConnectorTransaction[], + payments: ConnectorTransaction[] +): ConnectorTransaction[] { + const mergedTransactions = [...invoices, ...payments].sort((a, b) => { + return b.settleDate - a.settleDate; + }); + + return mergedTransactions; +} diff --git a/src/extension/background-script/actions/ln/getTransactions.ts b/src/extension/background-script/actions/ln/getTransactions.ts new file mode 100644 index 0000000000..341014be6f --- /dev/null +++ b/src/extension/background-script/actions/ln/getTransactions.ts @@ -0,0 +1,43 @@ +import utils from "~/common/lib/utils"; +import { ConnectorTransaction } from "~/extension/background-script/connectors/connector.interface"; +import state from "~/extension/background-script/state"; +import { MessageGetTransactions } from "~/types"; + +const getTransactions = async (message: MessageGetTransactions) => { + const limit = message.args.limit; + + const connector = await state.getState().getConnector(); + try { + const result = await connector.getTransactions(); + + let transactions: ConnectorTransaction[] = result.data.transactions + .filter((transaction) => transaction.settled) + .map((transaction) => { + const boostagram = utils.getBoostagramFromInvoiceCustomRecords( + transaction.custom_records + ); + return { + ...transaction, + boostagram, + paymentHash: transaction.payment_hash, + }; + }); + + if (limit) { + transactions = transactions.slice(0, limit); + } + + return { + data: { + transactions, + }, + }; + } catch (e) { + console.error(e); + if (e instanceof Error) { + return { error: e.message }; + } + } +}; + +export default getTransactions; diff --git a/src/extension/background-script/actions/ln/index.ts b/src/extension/background-script/actions/ln/index.ts index ad3b3f7eb1..39a9cee671 100644 --- a/src/extension/background-script/actions/ln/index.ts +++ b/src/extension/background-script/actions/ln/index.ts @@ -1,7 +1,8 @@ import checkPayment from "./checkPayment"; import connectPeer from "./connectPeer"; import getInfo from "./getInfo"; -import invoices from "./invoices"; + +import getTransactions from "./getTransactions"; import keysend from "./keysend"; import makeInvoice from "./makeInvoice"; import request from "./request"; @@ -13,7 +14,7 @@ export { checkPayment, connectPeer, getInfo, - invoices, + getTransactions, keysend, makeInvoice, request, diff --git a/src/extension/background-script/actions/ln/invoices.ts b/src/extension/background-script/actions/ln/invoices.ts deleted file mode 100644 index 9f52557a28..0000000000 --- a/src/extension/background-script/actions/ln/invoices.ts +++ /dev/null @@ -1,38 +0,0 @@ -import utils from "~/common/lib/utils"; -import state from "~/extension/background-script/state"; -import type { Invoice, MessageInvoices } from "~/types"; - -const invoices = async (message: MessageInvoices) => { - const isSettled = message.args.isSettled; - const limit = message.args.limit; - - const connector = await state.getState().getConnector(); - try { - const result = await connector.getInvoices(); - let invoices: Invoice[] = result.data.invoices - .filter((invoice) => (isSettled ? invoice.settled : !invoice.settled)) - .map((invoice) => { - const boostagram = utils.getBoostagramFromInvoiceCustomRecords( - invoice.custom_records - ); - return { ...invoice, boostagram }; - }); - - if (limit) { - invoices = invoices.slice(0, limit); - } - - return { - data: { - invoices, - }, - }; - } catch (e) { - console.error(e); - if (e instanceof Error) { - return { error: e.message }; - } - } -}; - -export default invoices; diff --git a/src/extension/background-script/connectors/alby.ts b/src/extension/background-script/connectors/alby.ts index fa78ab8f39..38af1df487 100644 --- a/src/extension/background-script/connectors/alby.ts +++ b/src/extension/background-script/connectors/alby.ts @@ -15,11 +15,11 @@ import state from "../state"; import Connector, { CheckPaymentArgs, CheckPaymentResponse, - ConnectorInvoice, + ConnectorTransaction, ConnectPeerResponse, GetBalanceResponse, GetInfoResponse, - GetInvoicesResponse, + GetTransactionsResponse, KeysendArgs, MakeInvoiceArgs, MakeInvoiceResponse, @@ -76,6 +76,7 @@ export default class Alby implements Connector { "sendPayment", "sendPaymentAsync", "getBalance", + "getTransactions", ]; } @@ -87,26 +88,27 @@ export default class Alby implements Connector { throw new Error("Not yet supported with the currently used account."); } - async getInvoices(): Promise { - const incomingInvoices = (await this._request((client) => - client.incomingInvoices({}) + async getTransactions(): Promise { + const invoicesResponse = (await this._request((client) => + client.invoices({}) )) as Invoice[]; - const invoices: ConnectorInvoice[] = incomingInvoices.map( - (invoice, index): ConnectorInvoice => ({ + const transactions: ConnectorTransaction[] = invoicesResponse.map( + (invoice, index): ConnectorTransaction => ({ custom_records: invoice.custom_records, id: `${invoice.payment_request}-${index}`, memo: invoice.comment || invoice.memo, preimage: "", // alby wallet api doesn't support preimage (yet) + payment_hash: invoice.payment_hash, settled: invoice.settled, settleDate: new Date(invoice.settled_at).getTime(), - totalAmount: `${invoice.amount}`, - type: "received", + totalAmount: invoice.amount, + type: invoice.type == "incoming" ? "received" : "sent", }) ); return { data: { - invoices, + transactions, }, }; } diff --git a/src/extension/background-script/connectors/citadel.ts b/src/extension/background-script/connectors/citadel.ts index 1cde1135d9..7261ef46dd 100644 --- a/src/extension/background-script/connectors/citadel.ts +++ b/src/extension/background-script/connectors/citadel.ts @@ -6,7 +6,7 @@ import Connector, { ConnectPeerResponse, GetBalanceResponse, GetInfoResponse, - GetInvoicesResponse, + GetTransactionsResponse, KeysendArgs, MakeInvoiceArgs, MakeInvoiceResponse, @@ -76,14 +76,11 @@ class CitadelConnector implements Connector { }); } - // not yet implemented - async getInvoices(): Promise { + async getTransactions(): Promise { console.error( - `Not yet supported with the currently used account: ${this.constructor.name}` - ); - throw new Error( - `${this.constructor.name}: "getInvoices" is not yet supported. Contact us if you need it.` + `getTransactions() is not yet supported with the currently used account: ${this.constructor.name}` ); + return { data: { transactions: [] } }; } // not yet implemented diff --git a/src/extension/background-script/connectors/commando.ts b/src/extension/background-script/connectors/commando.ts index 9d7d71d6e0..bb4cce861c 100644 --- a/src/extension/background-script/connectors/commando.ts +++ b/src/extension/background-script/connectors/commando.ts @@ -4,16 +4,17 @@ import LnMessage from "lnmessage"; import { v4 as uuidv4 } from "uuid"; import { Account } from "~/types"; +import { mergeTransactions } from "~/common/utils/helpers"; import Connector, { CheckPaymentArgs, CheckPaymentResponse, - ConnectorInvoice, + ConnectorTransaction, ConnectPeerArgs, ConnectPeerResponse, flattenRequestMethods, GetBalanceResponse, GetInfoResponse, - GetInvoicesResponse, + GetTransactionsResponse, KeysendArgs, MakeInvoiceArgs, MakeInvoiceResponse, @@ -60,6 +61,11 @@ type CommandoListFundsResponse = { type CommandoListInvoicesResponse = { invoices: CommandoInvoice[]; }; + +type CommandoListSendPaysResponse = { + payments: CommandoPayment[]; +}; + type CommandoPayInvoiceResponse = { payment_preimage: string; payment_hash: string; @@ -81,6 +87,24 @@ type CommandoInvoice = { payment_hash: string; }; +type CommandoPayment = { + id: number; + partid?: number; + groupid: number; + created_at: number; + label?: string; + status: string; + description?: string; + amount_sent_msat: number; + amount_msat?: number; + bolt11?: string; + bolt12?: string; + payment_preimage: string; + payment_hash: string; + destination: string; + erroronion?: string; +}; + const supportedMethods: string[] = [ "bkpr-listbalances", "checkmessage", @@ -190,7 +214,7 @@ export default class Commando implements Connector { }); } - async getInvoices(): Promise { + private async getInvoices(): Promise { return this.ln .commando({ method: "listinvoices", @@ -199,26 +223,62 @@ export default class Commando implements Connector { }) .then((resp) => { const parsed = resp as CommandoListInvoicesResponse; - return { - data: { - invoices: parsed.invoices - .map( - (invoice, index): ConnectorInvoice => ({ - id: invoice.label, - memo: invoice.description, - settled: invoice.status === "paid", - preimage: invoice.payment_preimage, - settleDate: invoice.paid_at * 1000, - type: "received", - totalAmount: (invoice.msatoshi / 1000).toString(), - }) - ) - .filter((invoice) => invoice.settled) - .sort((a, b) => { - return b.settleDate - a.settleDate; - }), - }, - }; + return parsed.invoices + .map( + (invoice, index): ConnectorTransaction => ({ + id: invoice.label, + memo: invoice.description, + settled: invoice.status === "paid", + preimage: invoice.payment_preimage, + payment_hash: invoice.payment_hash, + settleDate: invoice.paid_at * 1000, + type: "received", + totalAmount: Math.floor(invoice.msatoshi / 1000), + }) + ) + .filter((invoice) => invoice.settled); + }); + } + + async getTransactions(): Promise { + const incomingInvoicesResponse = await this.getInvoices(); + const outgoingInvoicesResponse = await this.getPayments(); + + const transactions: ConnectorTransaction[] = mergeTransactions( + incomingInvoicesResponse, + outgoingInvoicesResponse + ); + + return { + data: { + transactions, + }, + }; + } + + private async getPayments(): Promise { + return await this.ln + .commando({ + method: "listsendpays", + params: {}, + rune: this.config.rune, + }) + .then((resp) => { + const parsed = resp as CommandoListSendPaysResponse; + return parsed.payments + .map( + (payment, index): ConnectorTransaction => ({ + id: `${payment.id}`, + memo: payment.description ?? "", + settled: payment.status === "complete", + preimage: payment.payment_preimage, + payment_hash: payment.payment_hash, + settleDate: payment.created_at * 1000, + type: "sent", + totalAmount: payment.amount_sent_msat / 1000, + }) + ) + .filter((payment) => payment.settled); }); } diff --git a/src/extension/background-script/connectors/connector.interface.ts b/src/extension/background-script/connectors/connector.interface.ts index cd08e97ef7..946ae6c5d2 100644 --- a/src/extension/background-script/connectors/connector.interface.ts +++ b/src/extension/background-script/connectors/connector.interface.ts @@ -17,19 +17,20 @@ interface Route { total_fees: number; } -export interface ConnectorInvoice { +export interface ConnectorTransaction { custom_records?: { "696969"?: string; "7629169"?: string; "5482373484"?: string; } & Record; id: string; - memo: string; + memo?: string; preimage: string; + payment_hash?: string; settled: boolean; settleDate: number; - totalAmount: string; - type: "received"; + totalAmount: number; + type: "received" | "sent"; } export interface MakeInvoiceArgs { @@ -55,9 +56,15 @@ export type GetBalanceResponse = { }; }; -export type GetInvoicesResponse = { +export type GetTransactionsResponse = { + data: { + transactions: ConnectorTransaction[]; + }; +}; + +export type GetPaymentsResponse = { data: { - invoices: ConnectorInvoice[]; + payments: ConnectorTransaction[]; }; }; @@ -124,7 +131,7 @@ export default interface Connector { unload(): Promise; getInfo(): Promise; getBalance(): Promise; - getInvoices(): Promise; + getTransactions(): Promise; makeInvoice(args: MakeInvoiceArgs): Promise; sendPayment(args: SendPaymentArgs): Promise; keysend(args: KeysendArgs): Promise; diff --git a/src/extension/background-script/connectors/eclair.ts b/src/extension/background-script/connectors/eclair.ts index 73a569e34d..a3a51c103a 100644 --- a/src/extension/background-script/connectors/eclair.ts +++ b/src/extension/background-script/connectors/eclair.ts @@ -5,11 +5,10 @@ import { Account } from "~/types"; import Connector, { CheckPaymentArgs, CheckPaymentResponse, - ConnectorInvoice, ConnectPeerResponse, GetBalanceResponse, GetInfoResponse, - GetInvoicesResponse, + GetTransactionsResponse, KeysendArgs, MakeInvoiceArgs, MakeInvoiceResponse, @@ -73,32 +72,11 @@ class Eclair implements Connector { throw new Error("Not yet supported with the currently used account."); } - async getInvoices(): Promise { - const response = await this.request("/listinvoices"); - const invoices: ConnectorInvoice[] = response - .map( - (invoice: { - paymentHash: string; - description: string; - timestamp: number; - amount: number; - }) => ({ - id: invoice.paymentHash, - memo: invoice.description, - settled: true, - settleDate: invoice.timestamp * 1000, - totalAmount: invoice.amount / 1000, - type: "received", - }) - ) - .sort((a: ConnectorInvoice, b: ConnectorInvoice) => { - return b.settleDate - a.settleDate; - }); - return { - data: { - invoices: invoices, - }, - }; + async getTransactions(): Promise { + console.error( + `getTransactions() is not yet supported with the currently used account: ${this.constructor.name}` + ); + return { data: { transactions: [] } }; } async getBalance(): Promise { diff --git a/src/extension/background-script/connectors/galoy.ts b/src/extension/background-script/connectors/galoy.ts index cd68376f04..556c3a5533 100644 --- a/src/extension/background-script/connectors/galoy.ts +++ b/src/extension/background-script/connectors/galoy.ts @@ -9,7 +9,7 @@ import Connector, { ConnectPeerResponse, GetBalanceResponse, GetInfoResponse, - GetInvoicesResponse, + GetTransactionsResponse, KeysendArgs, MakeInvoiceArgs, MakeInvoiceResponse, @@ -89,12 +89,11 @@ class Galoy implements Connector { throw new Error("Not yet supported with the currently used account."); } - // not yet implemented - async getInvoices(): Promise { + async getTransactions(): Promise { console.error( - `Not yet supported with the currently used account: ${this.constructor.name}` + `getTransactions() is not yet supported with the currently used account: ${this.constructor.name}` ); - return { data: { invoices: [] } }; + return { data: { transactions: [] } }; } async getBalance(): Promise { diff --git a/src/extension/background-script/connectors/kollider.ts b/src/extension/background-script/connectors/kollider.ts index d00c125097..4210854392 100644 --- a/src/extension/background-script/connectors/kollider.ts +++ b/src/extension/background-script/connectors/kollider.ts @@ -11,11 +11,10 @@ import { Account } from "~/types"; import Connector, { CheckPaymentArgs, CheckPaymentResponse, - ConnectorInvoice, ConnectPeerResponse, GetBalanceResponse, GetInfoResponse, - GetInvoicesResponse, + GetTransactionsResponse, KeysendArgs, MakeInvoiceArgs, MakeInvoiceResponse, @@ -85,52 +84,11 @@ export default class Kollider implements Connector { throw new Error("Not yet supported with the currently used account."); } - async getInvoices(): Promise { - const data = await this.request< - { - account_id: string; - add_index: number; - created_at: number; - currency: KolliderCurrencies; - expiry: number; - fees: null; // FIXME! Why is this null? - incoming: boolean; - owner: number; - payment_hash: string; - payment_request: string; - reference: string; - settled: boolean; - settled_date: number; - target_account_currency: KolliderCurrencies; - uid: number; - value: number; - value_msat: number; - }[] - >("GET", "/getuserinvoices", undefined); - - const invoices: ConnectorInvoice[] = data - .filter((i) => i.incoming) - .filter((i) => i.account_id === this.currentAccountId) - .map( - (invoice, index): ConnectorInvoice => ({ - id: `${invoice.payment_hash}-${index}`, - memo: invoice.reference, - preimage: "", // kollider doesn't support preimage (yet) - settled: invoice.settled, - settleDate: invoice.settled_date, - totalAmount: `${invoice.value}`, - type: "received", - }) - ) - .sort((a, b) => { - return b.settleDate - a.settleDate; - }); - - return { - data: { - invoices, - }, - }; + async getTransactions(): Promise { + console.error( + `getTransactions() is not yet supported with the currently used account: ${this.constructor.name}` + ); + return { data: { transactions: [] } }; } async getInfo(): Promise { diff --git a/src/extension/background-script/connectors/lnbits.ts b/src/extension/background-script/connectors/lnbits.ts index 4d2de70e9f..8c41139abd 100644 --- a/src/extension/background-script/connectors/lnbits.ts +++ b/src/extension/background-script/connectors/lnbits.ts @@ -9,11 +9,11 @@ import state from "../state"; import Connector, { CheckPaymentArgs, CheckPaymentResponse, - ConnectorInvoice, + ConnectorTransaction, ConnectPeerResponse, GetBalanceResponse, GetInfoResponse, - GetInvoicesResponse, + GetTransactionsResponse, KeysendArgs, MakeInvoiceArgs, MakeInvoiceResponse, @@ -100,7 +100,8 @@ class LnBits implements Connector { } ] */ - async getInvoices(): Promise { + + getTransactions(): Promise { return this.request( "GET", "/api/v1/payments", @@ -123,23 +124,28 @@ class LnBits implements Connector { webhook_status: string; }[] ) => { - const invoices: ConnectorInvoice[] = data - .filter((invoice) => invoice.amount > 0) - .map((invoice, index): ConnectorInvoice => { + const transactions: ConnectorTransaction[] = data.map( + (transaction, index): ConnectorTransaction => { return { - id: `${invoice.checking_id}-${index}`, - memo: invoice.memo, - preimage: invoice.preimage, - settled: !invoice.pending, - settleDate: invoice.time * 1000, - totalAmount: `${Math.floor(invoice.amount / 1000)}`, - type: "received", + id: `${transaction.checking_id}-${index}`, + memo: transaction.memo, + preimage: + transaction.preimage != + "0000000000000000000000000000000000000000000000000000000000000000" + ? transaction.preimage + : "", + payment_hash: transaction.payment_hash, + settled: !transaction.pending, + settleDate: transaction.time * 1000, + totalAmount: Math.abs(Math.floor(transaction.amount / 1000)), + type: transaction.amount > 0 ? "received" : "sent", }; - }); + } + ); return { data: { - invoices, + transactions, }, }; } diff --git a/src/extension/background-script/connectors/lnc.ts b/src/extension/background-script/connectors/lnc.ts index 062aef2f5b..c859aba264 100644 --- a/src/extension/background-script/connectors/lnc.ts +++ b/src/extension/background-script/connectors/lnc.ts @@ -1,4 +1,5 @@ import LNC from "@lightninglabs/lnc-web"; +import lightningPayReq from "bolt11"; import Base64 from "crypto-js/enc-base64"; import Hex from "crypto-js/enc-hex"; import UTF8 from "crypto-js/enc-utf8"; @@ -9,16 +10,17 @@ import { encryptData } from "~/common/lib/crypto"; import utils from "~/common/lib/utils"; import { Account } from "~/types"; +import { mergeTransactions } from "~/common/utils/helpers"; import state from "../state"; import Connector, { CheckPaymentArgs, CheckPaymentResponse, - ConnectorInvoice, + ConnectorTransaction, ConnectPeerResponse, flattenRequestMethods, GetBalanceResponse, GetInfoResponse, - GetInvoicesResponse, + GetTransactionsResponse, KeysendArgs, MakeInvoiceArgs, MakeInvoiceResponse, @@ -242,12 +244,12 @@ class Lnc implements Connector { }); } - async getInvoices(): Promise { + private async getInvoices(): Promise { this.checkConnection(); const data = await this.lnc.lnd.lightning.ListInvoices({ reversed: true }); - const invoices: ConnectorInvoice[] = data.invoices - .map((invoice: FixMe, index: number): ConnectorInvoice => { + const invoices: ConnectorTransaction[] = data.invoices + .map((invoice: FixMe, index: number): ConnectorTransaction => { const custom_records = invoice.htlcs[0] && invoice.htlcs[0].customRecords; @@ -264,13 +266,58 @@ class Lnc implements Connector { }) .reverse(); + return invoices; + } + + async getTransactions(): Promise { + const incomingInvoices = await this.getInvoices(); + const outgoingInvoices = await this.getPayments(); + + const transactions: ConnectorTransaction[] = mergeTransactions( + incomingInvoices, + outgoingInvoices + ); + return { data: { - invoices, + transactions, }, }; } + private async getPayments(): Promise { + const outgoingInvoicesResponse = await this.lnc.lnd.lightning.ListPayments({ + reversed: true, + max_payments: 100, + include_incomplete: false, + }); + + const outgoingInvoices: ConnectorTransaction[] = + outgoingInvoicesResponse.payments.map( + (payment: FixMe, index: number): ConnectorTransaction => { + let memo = "Sent"; + if (payment.payment_request) { + memo = ( + lightningPayReq + .decode(payment.payment_request) + .tags.find((tag) => tag.tagName === "description")?.data || + "Sent" + ).toString(); + } + return { + id: `${payment.payment_request}-${index}`, + memo: memo, + preimage: payment.payment_preimage, + settled: true, + settleDate: parseInt(payment.creation_date) * 1000, + totalAmount: payment.value_sat, + type: "sent", + }; + } + ); + return outgoingInvoices; + } + // not yet implemented async connectPeer(): Promise { console.error( diff --git a/src/extension/background-script/connectors/lnd.ts b/src/extension/background-script/connectors/lnd.ts index 7bf6968151..ba4cfe0ecb 100644 --- a/src/extension/background-script/connectors/lnd.ts +++ b/src/extension/background-script/connectors/lnd.ts @@ -1,3 +1,4 @@ +import lightningPayReq from "bolt11"; import Base64 from "crypto-js/enc-base64"; import Hex from "crypto-js/enc-hex"; import UTF8 from "crypto-js/enc-utf8"; @@ -6,16 +7,17 @@ import SHA256 from "crypto-js/sha256"; import utils from "~/common/lib/utils"; import { Account } from "~/types"; +import { mergeTransactions } from "~/common/utils/helpers"; import Connector, { CheckPaymentArgs, CheckPaymentResponse, - ConnectorInvoice, + ConnectorTransaction, ConnectPeerArgs, ConnectPeerResponse, flattenRequestMethods, GetBalanceResponse, GetInfoResponse, - GetInvoicesResponse, + GetTransactionsResponse, KeysendArgs, MakeInvoiceArgs, MakeInvoiceResponse, @@ -391,8 +393,8 @@ class Lnd implements Connector { }); }; - async getInvoices(): Promise { - const data = await this.request<{ + private async getInvoices(): Promise { + const lndInvoices = await this.request<{ invoices: { add_index: string; amt_paid_msat: string; @@ -413,7 +415,7 @@ class Lnd implements Connector { resolve_time: string; expiry_height: number; state: "SETTLED"; - custom_records: ConnectorInvoice["custom_records"]; + custom_records: ConnectorTransaction["custom_records"]; mpp_total_amt_msat: string; amp?: unknown; }[]; @@ -436,33 +438,96 @@ class Lnd implements Connector { first_index_offset: string; }>("GET", "/v1/invoices", { reversed: true }); - const invoices: ConnectorInvoice[] = data.invoices - .map((invoice, index): ConnectorInvoice => { + const invoices: ConnectorTransaction[] = lndInvoices.invoices.map( + (invoice, index): ConnectorTransaction => { const custom_records = invoice.htlcs[0] && invoice.htlcs[0].custom_records; return { - custom_records, id: `${invoice.payment_request}-${index}`, memo: invoice.memo, - preimage: invoice.r_preimage, + preimage: utils.base64ToHex(invoice.r_preimage), + payment_hash: utils.base64ToHex(invoice.r_hash), settled: invoice.settled, settleDate: parseInt(invoice.settle_date) * 1000, - totalAmount: invoice.value, + totalAmount: parseInt(invoice.value), type: "received", + custom_records, }; - }) - .sort((a, b) => { - return b.settleDate - a.settleDate; - }); + } + ); + + return invoices; + } + + async getTransactions(): Promise { + const invoices = await this.getInvoices(); + const payments = await this.getPayments(); + + const transactions: ConnectorTransaction[] = mergeTransactions( + invoices, + payments + ); return { data: { - invoices, + transactions, }, }; } + private async getPayments(): Promise { + const lndPayments = await this.request<{ + payments: { + payment_hash: string; + payment_preimage: string; + value_sat: number; + value_msat: number; + payment_request: string; + status: string; + fee_sat: string; + fee_msat: string; + creation_time_ns: string; + creation_date: string; + htlcs: Array; + payment_index: string; + failure_reason: string; + }[]; + last_index_offset: string; + first_index_offset: string; + total_num_payments: string; + }>("GET", "/v1/payments", { + reversed: true, + max_payments: 100, + include_incomplete: false, + }); + + const payments: ConnectorTransaction[] = lndPayments.payments.map( + (payment, index): ConnectorTransaction => { + let description: string | undefined; + if (payment.payment_request) { + description = lightningPayReq + .decode(payment.payment_request) + .tags.find((tag) => tag.tagName === "description") + ?.data.toString(); + } + + return { + id: `${payment.payment_request}-${index++}`, + memo: description, + preimage: payment.payment_preimage, + payment_hash: payment.payment_hash, + settled: true, + settleDate: parseInt(payment.creation_date) * 1000, + totalAmount: payment.value_sat, + type: "sent", + }; + } + ); + + return payments; + } + protected async request( method: string, path: string, diff --git a/src/extension/background-script/connectors/lndhub.ts b/src/extension/background-script/connectors/lndhub.ts index 871b899f8b..9d2da17f9e 100644 --- a/src/extension/background-script/connectors/lndhub.ts +++ b/src/extension/background-script/connectors/lndhub.ts @@ -10,15 +10,16 @@ import utils from "~/common/lib/utils"; import HashKeySigner from "~/common/utils/signer"; import { Account } from "~/types"; +import { mergeTransactions } from "~/common/utils/helpers"; import state from "../state"; import Connector, { CheckPaymentArgs, CheckPaymentResponse, - ConnectorInvoice, + ConnectorTransaction, ConnectPeerResponse, GetBalanceResponse, GetInfoResponse, - GetInvoicesResponse, + GetTransactionsResponse, KeysendArgs, MakeInvoiceArgs, MakeInvoiceResponse, @@ -85,7 +86,7 @@ export default class LndHub implements Connector { throw new Error("Not yet supported with the currently used account."); } - async getInvoices(): Promise { + private async getInvoices(): Promise { const data = await this.request< { r_hash: { @@ -93,7 +94,7 @@ export default class LndHub implements Connector { data: number[]; }; amt: number; - custom_records: ConnectorInvoice["custom_records"]; + custom_records: ConnectorTransaction["custom_records"]; description: string; expire_time: number; ispaid: boolean; @@ -106,16 +107,17 @@ export default class LndHub implements Connector { }[] >("GET", "/getuserinvoices", undefined); - const invoices: ConnectorInvoice[] = data + const invoices: ConnectorTransaction[] = data .map( - (invoice, index): ConnectorInvoice => ({ + (invoice, index): ConnectorTransaction => ({ custom_records: invoice.custom_records, id: `${invoice.payment_request}-${index}`, memo: invoice.description, preimage: "", // lndhub doesn't support preimage (yet) + payment_hash: invoice.payment_hash, settled: invoice.ispaid, settleDate: invoice.timestamp * 1000, - totalAmount: `${invoice.amt}`, + totalAmount: invoice.amt, type: "received", }) ) @@ -123,13 +125,65 @@ export default class LndHub implements Connector { return b.settleDate - a.settleDate; }); + return invoices; + } + + async getTransactions(): Promise { + const incomingInvoices = await this.getInvoices(); + const outgoingInvoices = await this.getPayments(); + + const transactions: ConnectorTransaction[] = mergeTransactions( + incomingInvoices, + outgoingInvoices + ); + return { data: { - invoices, + transactions, }, }; } + private async getPayments(): Promise { + const lndhubPayments = await this.request< + { + custom_records: ConnectorTransaction["custom_records"]; + fee: string; + keysend: boolean; + memo: string; + payment_hash: { + type: string; + data: ArrayBuffer; + }; + payment_preimage: string; + r_hash: { + type: "Buffer"; + data: number[]; + }; + timestamp: number; + type: "paid_invoice"; + value: number; + }[] + >("GET", "/gettxs", { limit: 100 }); + + // gettxs endpoint returns successfull outgoing transactions by default + const payments: ConnectorTransaction[] = lndhubPayments.map( + (transaction, index): ConnectorTransaction => ({ + id: `${index}`, + memo: transaction.memo, + preimage: transaction.payment_preimage, + payment_hash: Buffer.from(transaction.payment_hash.data).toString( + "hex" + ), + settled: true, + settleDate: transaction.timestamp * 1000, + totalAmount: transaction.value, + type: "sent", + }) + ); + return payments; + } + async getInfo(): Promise { const { alias } = await this.request<{ alias: string }>( "GET", diff --git a/src/extension/background-script/router.ts b/src/extension/background-script/router.ts index 643b98f94d..688d017536 100644 --- a/src/extension/background-script/router.ts +++ b/src/extension/background-script/router.ts @@ -30,7 +30,7 @@ const routes = { lock: accounts.lock, unlock: accounts.unlock, getInfo: ln.getInfo, - getInvoices: ln.invoices, + getTransactions: ln.getTransactions, sendPayment: ln.sendPayment, sendPaymentAsync: ln.sendPaymentAsync, keysend: ln.keysend, diff --git a/src/i18n/locales/cs/translation.json b/src/i18n/locales/cs/translation.json index 7486600627..8e5b84d8e4 100644 --- a/src/i18n/locales/cs/translation.json +++ b/src/i18n/locales/cs/translation.json @@ -820,7 +820,7 @@ "allow_camera_access": "Prosím povolte přístup kamery ve svém nastavení." } }, - "transactionsTable": { + "transactions_table": { "fee": "Poplatek", "preimage": "Preimage", "received": "Přijato", diff --git a/src/i18n/locales/da/translation.json b/src/i18n/locales/da/translation.json index fdfb7d51f4..3292b3ed4e 100644 --- a/src/i18n/locales/da/translation.json +++ b/src/i18n/locales/da/translation.json @@ -727,7 +727,7 @@ "allow_camera_access": "Giv adgang til kameraet i indstillingsvinduet." } }, - "transactionsTable": { + "transactions_table": { "fee": "Betaling", "preimage": "Præ-visning", "received": "Modtaget", diff --git a/src/i18n/locales/de/translation.json b/src/i18n/locales/de/translation.json index 96e89ec24d..b282c33e03 100644 --- a/src/i18n/locales/de/translation.json +++ b/src/i18n/locales/de/translation.json @@ -1056,7 +1056,7 @@ }, "title": "QR-Code scannen" }, - "transactionsTable": { + "transactions_table": { "sent": "Gesendet", "boostagram": { "sender": "Sender", diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index fb1b1da9b9..93da1faa5c 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -352,9 +352,7 @@ "send_satoshis": "⚡️ Send Satoshis ⚡️", "enable_now": "Enable Now" }, - "recent_transactions": "Recent Transactions", "allowance_view": { - "recent_transactions": "Recent Transactions", "budget_spent": "Budget spent", "sats": "sats", "no_transactions": "No transactions on <0>{{name}} yet.", @@ -363,12 +361,10 @@ "permissions": "Permissions" }, "default_view": { - "recent_transactions": "Recent Transactions", "is_blocked_hint": "Alby is currently disabled on {{host}}", "block_removed": "Enabled {{host}}. Please reload the website.", - "no_outgoing_transactions": "No outgoing transactions for this account yet.", - "no_incoming_transactions": "No incoming transactions for this account yet.", - "all_transactions_link": "See all transactions" + "no_transactions": "No transactions for this account yet.", + "see_all": "See all" } }, "accounts": { @@ -971,10 +967,7 @@ }, "transactions": { "title": "Transactions", - "description": { - "outgoing": "Outgoing transactions for this account", - "incoming": "Incoming transactions for this account" - }, + "description": "Transactions for this account", "list_empty": "No transactions available yet." }, "onboard": { @@ -1014,6 +1007,7 @@ "help": "Help", "balance": "Balance", "or": "or", + "website": "Website", "actions": { "back": "Back", "delete": "Delete", @@ -1044,6 +1038,7 @@ "log_in": "Log in", "remember": "Remember my choice and don't ask again", "more": "More", + "hide": "Hide", "disconnect": "Disconnect", "review": "Review", "download": "Download" @@ -1105,25 +1100,22 @@ "allow_camera_access": "Please allow camera access in the settings screen." } }, - "transactionsTable": { + "transactions_table": { "fee": "Fee", "preimage": "Preimage", + "payment_hash": "Payment hash", "received": "Received", "sent": "Sent", + "date_time": "Date & Time", "boostagram": { "sender": "Sender", "message": "Message", "app": "App", - "podcast": "Podcast" + "podcast": "Podcast", + "episode": "Episode" }, "open_location": "Open website" }, - "transaction_list": { - "tabs": { - "outgoing": "Outgoing", - "incoming": "Incoming" - } - }, "budget_control": { "remember": { "label": "Remember and set a budget", diff --git a/src/i18n/locales/eo/translation.json b/src/i18n/locales/eo/translation.json index d5bd956a6b..40c2e3fa67 100644 --- a/src/i18n/locales/eo/translation.json +++ b/src/i18n/locales/eo/translation.json @@ -578,7 +578,7 @@ "allow_camera_access": "" } }, - "transactionsTable": { + "transactions_table": { "fee": "Kotizo", "preimage": "", "received": "Ricevita", diff --git a/src/i18n/locales/es/translation.json b/src/i18n/locales/es/translation.json index 11540fcc32..f2dfa5c068 100644 --- a/src/i18n/locales/es/translation.json +++ b/src/i18n/locales/es/translation.json @@ -820,7 +820,7 @@ "allow_camera_access": "Permita el acceso a la cámara en la pantalla de configuración." } }, - "transactionsTable": { + "transactions_table": { "fee": "Comisión", "preimage": "Preimagen", "received": "Recibido", diff --git a/src/i18n/locales/fa/translation.json b/src/i18n/locales/fa/translation.json index 85ca212d69..3249888f96 100644 --- a/src/i18n/locales/fa/translation.json +++ b/src/i18n/locales/fa/translation.json @@ -1027,7 +1027,7 @@ "allow_camera_access": "لطفا در صفحه تنظیمات اجازه دسترسی به دوربین بدهید." } }, - "transactionsTable": { + "transactions_table": { "fee": "کارمزد", "preimage": "پیش نمایش", "received": "دریافت شد", diff --git a/src/i18n/locales/fi/translation.json b/src/i18n/locales/fi/translation.json index 2b6260f413..89c2af1063 100644 --- a/src/i18n/locales/fi/translation.json +++ b/src/i18n/locales/fi/translation.json @@ -573,7 +573,7 @@ "allow_camera_access": "Salli kameran käyttöoikeus asetusruudussa." } }, - "transactionsTable": { + "transactions_table": { "fee": "Kustannus", "preimage": "Esikuva", "received": "Vastaanotettu", diff --git a/src/i18n/locales/fr/translation.json b/src/i18n/locales/fr/translation.json index 004ca43e9b..6f5b62ef6b 100644 --- a/src/i18n/locales/fr/translation.json +++ b/src/i18n/locales/fr/translation.json @@ -733,7 +733,7 @@ "allow_camera_access": "Veuillez autoriser l'accès à la caméra dans l'écran des paramètres." } }, - "transactionsTable": { + "transactions_table": { "fee": "Frais", "preimage": "Préimage", "received": "Reçu", diff --git a/src/i18n/locales/hi/translation.json b/src/i18n/locales/hi/translation.json index 71ac701ec1..2356a6d5f5 100644 --- a/src/i18n/locales/hi/translation.json +++ b/src/i18n/locales/hi/translation.json @@ -691,7 +691,7 @@ "allow_camera_access": "कृपया सेटिंग स्क्रीन में कैमरा एक्सेस की अनुमति दें।" } }, - "transactionsTable": { + "transactions_table": { "fee": "शुल्क", "preimage": "Preimage", "received": "प्राप्त", diff --git a/src/i18n/locales/id/translation.json b/src/i18n/locales/id/translation.json index 8a95cbc789..d96cce801b 100644 --- a/src/i18n/locales/id/translation.json +++ b/src/i18n/locales/id/translation.json @@ -852,7 +852,7 @@ "screen_reader": "Toggle Dropdown", "title": "Ganti akun" }, - "transactionsTable": { + "transactions_table": { "sent": "Terkirim", "fee": "Biaya", "preimage": "Preimage", diff --git a/src/i18n/locales/it/translation.json b/src/i18n/locales/it/translation.json index d4d722c713..f2cc046c1c 100644 --- a/src/i18n/locales/it/translation.json +++ b/src/i18n/locales/it/translation.json @@ -727,7 +727,7 @@ "allow_camera_access": "Si prega di consentire l'accesso alla fotocamera nella schermata delle impostazioni." } }, - "transactionsTable": { + "transactions_table": { "fee": "Tassa", "preimage": "Preimmagine", "received": "Ricevuto", diff --git a/src/i18n/locales/mr/translation.json b/src/i18n/locales/mr/translation.json index 207bedbd2a..6204900062 100644 --- a/src/i18n/locales/mr/translation.json +++ b/src/i18n/locales/mr/translation.json @@ -754,7 +754,7 @@ "allow_camera_access": "कृपया सेटिंग्ज स्क्रीनमध्ये कॅमेरा प्रवेशास अनुमती द्या." } }, - "transactionsTable": { + "transactions_table": { "fee": "शुल्क", "preimage": "प्रीइमेज", "received": "प्राप्त", diff --git a/src/i18n/locales/nl/translation.json b/src/i18n/locales/nl/translation.json index 91d48859d3..343ef6ba74 100644 --- a/src/i18n/locales/nl/translation.json +++ b/src/i18n/locales/nl/translation.json @@ -587,7 +587,7 @@ "allow_camera_access": "" } }, - "transactionsTable": { + "transactions_table": { "fee": "", "preimage": "", "received": "", diff --git a/src/i18n/locales/pl/translation.json b/src/i18n/locales/pl/translation.json index 9813fa851a..6c0ab5e4a8 100644 --- a/src/i18n/locales/pl/translation.json +++ b/src/i18n/locales/pl/translation.json @@ -781,7 +781,7 @@ "allow_camera_access": "Proszę pozwolić na dostęp do kamery w ekranie ustawień." } }, - "transactionsTable": { + "transactions_table": { "fee": "Opłata", "preimage": "Obraz pierwotny (preimage)", "received": "Otrzymano", diff --git a/src/i18n/locales/pt_BR/translation.json b/src/i18n/locales/pt_BR/translation.json index 02a89ab367..9d3b5bd093 100644 --- a/src/i18n/locales/pt_BR/translation.json +++ b/src/i18n/locales/pt_BR/translation.json @@ -1080,7 +1080,7 @@ "allow_camera_access": "Permita o acesso à câmera na tela configurações." } }, - "transactionsTable": { + "transactions_table": { "fee": "Taxa", "preimage": "Pré-imagem", "received": "Recebida", diff --git a/src/i18n/locales/ro/translation.json b/src/i18n/locales/ro/translation.json index 9872ce37c0..0b985bb76d 100644 --- a/src/i18n/locales/ro/translation.json +++ b/src/i18n/locales/ro/translation.json @@ -727,7 +727,7 @@ "allow_camera_access": "" } }, - "transactionsTable": { + "transactions_table": { "fee": "", "preimage": "", "received": "", diff --git a/src/i18n/locales/ru/translation.json b/src/i18n/locales/ru/translation.json index 6914e56e98..05b051fa72 100644 --- a/src/i18n/locales/ru/translation.json +++ b/src/i18n/locales/ru/translation.json @@ -727,7 +727,7 @@ "allow_camera_access": "" } }, - "transactionsTable": { + "transactions_table": { "fee": "", "preimage": "", "received": "", diff --git a/src/i18n/locales/sv/translation.json b/src/i18n/locales/sv/translation.json index ae6214b1a5..b6bbfbf4f6 100644 --- a/src/i18n/locales/sv/translation.json +++ b/src/i18n/locales/sv/translation.json @@ -1105,7 +1105,7 @@ "allow_camera_access": "Tillåt kameraåtkomst under inställningar." } }, - "transactionsTable": { + "transactions_table": { "fee": "Avgift", "preimage": "Förhandsvy", "received": "Mottaget", diff --git a/src/i18n/locales/th/translation.json b/src/i18n/locales/th/translation.json index 0101d9009d..07295e3fd9 100644 --- a/src/i18n/locales/th/translation.json +++ b/src/i18n/locales/th/translation.json @@ -1120,7 +1120,7 @@ "invalid_credentials": "รหัสผ่านไม่ถูกต้อง โปรดเช็ครหัสผ่านและอีเมลของคุณ และลองอีกครั้ง" } }, - "transactionsTable": { + "transactions_table": { "fee": "ค่าทำเนียม", "preimage": "Preimage", "received": "ได้รับ", diff --git a/src/i18n/locales/tl/translation.json b/src/i18n/locales/tl/translation.json index 53b3b8bae4..5e2d11fa43 100644 --- a/src/i18n/locales/tl/translation.json +++ b/src/i18n/locales/tl/translation.json @@ -524,7 +524,7 @@ "allow_camera_access": "" } }, - "transactionsTable": { + "transactions_table": { "fee": "", "preimage": "", "received": "", diff --git a/src/i18n/locales/uk/translation.json b/src/i18n/locales/uk/translation.json index 3a7291508c..e8ad7f7bd1 100644 --- a/src/i18n/locales/uk/translation.json +++ b/src/i18n/locales/uk/translation.json @@ -727,7 +727,7 @@ "allow_camera_access": "" } }, - "transactionsTable": { + "transactions_table": { "fee": "", "preimage": "", "received": "", diff --git a/src/i18n/locales/zh_Hans/translation.json b/src/i18n/locales/zh_Hans/translation.json index 7cb616e30b..5380ff824d 100644 --- a/src/i18n/locales/zh_Hans/translation.json +++ b/src/i18n/locales/zh_Hans/translation.json @@ -1037,7 +1037,7 @@ }, "edit_permissions": "编辑权限" }, - "transactionsTable": { + "transactions_table": { "boostagram": { "podcast": "播客", "message": "消息", diff --git a/src/i18n/locales/zh_Hant/translation.json b/src/i18n/locales/zh_Hant/translation.json index 3e811613e1..471a6fd7a3 100644 --- a/src/i18n/locales/zh_Hant/translation.json +++ b/src/i18n/locales/zh_Hant/translation.json @@ -1024,7 +1024,7 @@ "allow_camera_access": "請在設置屏幕中允許攝像頭訪問。" } }, - "transactionsTable": { + "transactions_table": { "fee": "費用", "preimage": "原像", "received": "已收到", diff --git a/src/types.ts b/src/types.ts index 1bdb51b022..3693e446d8 100644 --- a/src/types.ts +++ b/src/types.ts @@ -4,7 +4,7 @@ import { Runtime } from "webextension-polyfill"; import { ACCOUNT_CURRENCIES, CURRENCIES, TIPS } from "~/common/constants"; import connectors from "~/extension/background-script/connectors"; import { - ConnectorInvoice, + ConnectorTransaction, SendPaymentResponse, WebLNNode, } from "~/extension/background-script/connectors/connector.interface"; @@ -342,9 +342,9 @@ export interface MessageAllowanceList extends MessageDefault { action: "listAllowances"; } -export interface MessageInvoices extends Omit { - args: { limit?: number; isSettled?: boolean }; - action: "getInvoices"; +export interface MessageGetTransactions extends Omit { + args: { limit?: number }; + action: "getTransactions"; } export interface MessageAllowanceEnable extends MessageDefault { @@ -707,11 +707,13 @@ export interface RequestInvoiceArgs { } export type Transaction = { + timestamp: number; amount?: string; boostagram?: Invoice["boostagram"]; createdAt?: string; currency?: string; date: string; + paymentHash?: string; description?: string; host?: string; id: string; @@ -722,7 +724,7 @@ export type Transaction = { totalAmount: Allowance["payments"][number]["totalAmount"]; totalAmountFiat?: string; totalFees?: Allowance["payments"][number]["totalFees"]; - type?: "sent" | "sending" | "received"; + type?: "sent" | "received"; value?: string; publisherLink?: string; // either the invoice URL if on PublisherSingleView, or the internal link to Publisher }; @@ -884,14 +886,15 @@ export type SupportedExchanges = "alby" | "coindesk" | "yadio"; export interface Invoice { id: string; - memo: string; - type: "received"; + memo?: string; + type: "received" | "sent"; settled: boolean; settleDate: number; - totalAmount: string; + totalAmount: number; totalAmountFiat?: string; preimage: string; - custom_records?: ConnectorInvoice["custom_records"]; + paymentHash?: string; + custom_records?: ConnectorTransaction["custom_records"]; boostagram?: { app_name: string; name: string;