Skip to content

Commit

Permalink
Merge pull request #3292 from getAlby/nwc-transaction-states
Browse files Browse the repository at this point in the history
feat: show pending and failed transactions for nwc
  • Loading branch information
pavanjoshi914 authored Jan 23, 2025
2 parents 4c4bd74 + ffcc1eb commit c109f45
Show file tree
Hide file tree
Showing 21 changed files with 244 additions and 102 deletions.
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@
},
"dependencies": {
"@bitcoinerlab/secp256k1": "^1.1.1",
"@getalby/sdk": "^3.6.0",
"@getalby/sdk": "^3.9.0",
"@headlessui/react": "^1.7.18",
"@lightninglabs/lnc-web": "^0.3.1-alpha",
"@noble/ciphers": "^0.5.1",
Expand Down
41 changes: 35 additions & 6 deletions src/app/components/TransactionsTable/TransactionModal.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import {
PopiconsArrowUpSolid,
PopiconsChevronBottomLine,
PopiconsChevronTopLine,
PopiconsXSolid,
} from "@popicons/react";
import dayjs from "dayjs";
import { useEffect, useState } from "react";
Expand Down Expand Up @@ -56,17 +57,43 @@ export default function TransactionModal({
<div>
<div className="flex items-center justify-center">
{getTransactionType(transaction) == "outgoing" ? (
<div className="flex justify-center items-center bg-orange-100 dark:bg-orange-950 rounded-full p-3">
<PopiconsArrowUpSolid className="w-10 h-10 text-orange-400 dark:text-amber-600 stroke-[1px] stroke-orange-400 dark:stroke-amber-600" />
</div>
transaction.state === "pending" ? (
<div className="flex justify-center items-center bg-blue-100 dark:bg-sky-950 rounded-full p-3 animate-pulse">
<PopiconsArrowUpSolid className="w-10 h-10 rotate-45 text-blue-500 dark:text-sky-500 stroke-[1px] stroke-blue-500 dark:stroke-sky-500" />
</div>
) : transaction.state === "failed" ? (
<div className="flex justify-center items-center bg-red-100 dark:bg-rose-950 rounded-full p-3">
<PopiconsXSolid className="w-10 h-10 text-red-500 dark:text-rose-500 stroke-[1px] stroke-red-500 dark:stroke-rose-500" />
</div>
) : (
<div className="flex justify-center items-center bg-orange-100 dark:bg-amber-950 rounded-full p-3">
<PopiconsArrowUpSolid className="w-10 h-10 text-orange-500 dark:text-amber-500 stroke-[1px] stroke-orange-500 dark:stroke-amber-500" />
</div>
)
) : (
<div className="flex justify-center items-center bg-green-100 dark:bg-emerald-950 rounded-full p-3">
<PopiconsArrowDownSolid className="w-10 h-10 text-green-500 dark:text-emerald-500 stroke-[1px] stroke-green-400 dark:stroke-emerald-500" />
<PopiconsArrowDownSolid className="w-10 h-10 text-green-500 dark:text-teal-500 stroke-[1px] stroke-green-500 dark:stroke-teal-500" />
</div>
)}
</div>
<h2 className="mt-4 text-md text-gray-900 font-bold dark:text-white text-center">
{transaction.type == "received" ? t("received") : t("sent")}

<h2
className={classNames(
"mt-4 text-md text-gray-900 font-bold dark:text-white text-center",
transaction.state == "pending" && "animate-pulse text-gray-400"
)}
>
{transaction.type == "received"
? t("received")
: t(
transaction.state === "settled"
? "sent"
: transaction.state === "pending"
? "sending"
: transaction.state === "failed"
? "failed"
: "sent"
)}
</h2>
</div>
<div className="flex items-center text-center justify-center dark:text-white">
Expand All @@ -76,6 +103,8 @@ export default function TransactionModal({
"text-3xl font-medium",
transaction.type == "received"
? "text-green-600 dark:text-emerald-500"
: transaction.state == "failed"
? "text-red-400 dark:text-rose-600"
: "text-orange-600 dark:text-amber-600"
)}
>
Expand Down
10 changes: 5 additions & 5 deletions src/app/components/TransactionsTable/index.test.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ const transactions: Props = {
{
timestamp: 1656573909064,
createdAt: "1656573909064",
date: "5 days ago",
timeAgo: "5 days ago",
description: "Polar Invoice for bob",
host: "https://openai.com/dall-e-2/",
id: "1",
Expand All @@ -49,7 +49,7 @@ const invoices: Props = {
totalAmountFiat: "$13.02",
preimage: "",
title: "lambo lambo",
date: "4 days ago",
timeAgo: "4 days ago",
},
{
id: "lnbcrt6543210n1p3tadjepp5rv6ufq4vumg66l9gcyxqhy89n6w90mx0mh6gcj0sawrf6xuep5ssdq5g9kxy7fqd9h8vmmfvdjscqzpgxqyz5vqsp5f9yzxeqjw33ule4rffuh0py32gjjsx8z48cd4xjl8ej3rn7zdtdq9qyyssqe6qvkfe260myc9ypgs5n63xzwcx82fderg8p5ysh6c2fvpz5xu4ksvhs5av0wwestk5pmucmhk8lpjhmy7wqyq9c29xgm9na2q5xv5spy5kukj",
Expand All @@ -59,7 +59,7 @@ const invoices: Props = {
totalAmountFiat: "$127.80",
preimage: "",
title: "Alby invoice",
date: "6 days ago",
timeAgo: "6 days ago",
},
],
};
Expand All @@ -74,7 +74,7 @@ const invoicesWithBoostagram: Props = {
totalAmountFiat: "$13.02",
preimage: "",
title: "lambo lambo",
date: "4 days ago",
timeAgo: "4 days ago",
},
{
id: "lnbcrt888880n1p3tad30pp56j6g34wctydrfx4wwdwj3schell8uqug6jnlehlkpw02mdfd9wlqdq0v36k6urvd9hxwuccqzpgxqyz5vqsp5995q4egstsvnyetwvpax6jw8q0fnn4tyz3gp35k3yex29emhsylq9qyyssq0yxpx6peyn4vsepwj3l68w9sc5dqnkt07zff6aw4kqvcfs0fpu4jpfh929w6vqrgtjfkmrlwghq4s9t4mnwrh4dlkm6wjem5uq8eu4gpwqln0j",
Expand All @@ -84,7 +84,7 @@ const invoicesWithBoostagram: Props = {
totalAmountFiat: "$17.36",
preimage: "",
title: "dumplings",
date: "5 days ago",
timeAgo: "5 days ago",
boostagram: {
app_name: "Fountain",
name: "Friedemann",
Expand Down
47 changes: 39 additions & 8 deletions src/app/components/TransactionsTable/index.tsx
Original file line number Diff line number Diff line change
@@ -1,5 +1,9 @@
import Loading from "@components/Loading";
import { PopiconsArrowDownSolid, PopiconsArrowUpSolid } from "@popicons/react";
import {
PopiconsArrowDownSolid,
PopiconsArrowUpSolid,
PopiconsXSolid,
} from "@popicons/react";

import { useState } from "react";
import { useTranslation } from "react-i18next";
Expand Down Expand Up @@ -60,25 +64,50 @@ export default function TransactionsTable({
<div className="flex gap-3">
<div className="flex items-center">
{type == "outgoing" ? (
<div className="flex justify-center items-center bg-orange-100 dark:bg-orange-950 rounded-full w-8 h-8">
<PopiconsArrowUpSolid className="w-5 h-5 text-orange-400 dark:text-amber-600 stroke-[1px] stroke-orange-400 dark:stroke-amber-600" />
</div>
tx.state === "pending" ? (
<div className="flex justify-center items-center bg-blue-100 dark:bg-sky-950 rounded-full w-8 h-8 animate-pulse">
<PopiconsArrowUpSolid className="w-5 h-5 rotate-45 text-blue-500 dark:text-sky-500 stroke-[1px] stroke-blue-500 dark:stroke-sky-500" />
</div>
) : tx.state === "failed" ? (
<div className="flex justify-center items-center bg-red-100 dark:bg-rose-950 rounded-full w-8 h-8">
<PopiconsXSolid className="w-5 h-5 text-red-500 dark:text-rose-500 stroke-[1px] stroke-red-500 dark:stroke-rose-500" />
</div>
) : (
<div className="flex justify-center items-center bg-orange-100 dark:bg-amber-950 rounded-full w-8 h-8">
<PopiconsArrowUpSolid className="w-5 h-5 text-orange-500 dark:text-amber-500 stroke-[1px] stroke-orange-500 dark:stroke-amber-500" />
</div>
)
) : (
<div className="flex justify-center items-center bg-green-100 dark:bg-emerald-950 rounded-full w-8 h-8">
<PopiconsArrowDownSolid className="w-5 h-5 text-green-500 dark:text-emerald-500 stroke-[1px] stroke-green-400 dark:stroke-emerald-500" />
<PopiconsArrowDownSolid className="w-5 h-5 text-green-500 dark:text-teal-500 stroke-[1px] stroke-green-500 dark:stroke-teal-500" />
</div>
)}
</div>
<div className="overflow-hidden mr-3">
<div className="text-sm font-medium text-black truncate dark:text-white">
<p className="truncate">
<p
className={classNames(
"truncate",
tx.state == "pending" && "animate-pulse"
)}
>
{tx.title ||
tx.boostagram?.message ||
(type == "incoming" ? t("received") : t("sent"))}
(type == "incoming"
? t("received")
: t(
tx.state === "settled"
? "sent"
: tx.state === "pending"
? "sending"
: tx.state === "failed"
? "failed"
: "sent"
))}
</p>
</div>
<p className="text-xs text-gray-400 dark:text-neutral-500">
{tx.date}
{tx.timeAgo}
</p>
</div>
<div className="flex ml-auto text-right space-x-3 shrink-0 dark:text-white">
Expand All @@ -88,6 +117,8 @@ export default function TransactionsTable({
"text-sm",
type == "incoming"
? "text-green-600 dark:text-emerald-500"
: tx.state == "failed"
? "text-red-600 dark:text-rose-500"
: "text-orange-600 dark:text-amber-600"
)}
>
Expand Down
7 changes: 4 additions & 3 deletions src/app/hooks/useTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,13 +16,14 @@ export const useTransactions = () => {
const getTransactionsResponse = await api.getTransactions({
limit,
});

const transactions = getTransactionsResponse.transactions.map(
(transaction) => ({
...transaction,
title: transaction.memo,
date: dayjs(transaction.settleDate).fromNow(),
timestamp: transaction.settleDate,
timeAgo: dayjs(
transaction.settleDate || transaction.creationDate
).fromNow(),
timestamp: transaction.settleDate || transaction.creationDate,
})
);

Expand Down
2 changes: 1 addition & 1 deletion src/app/utils/payments.ts
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ export const convertPaymentToTransaction = (
...payment,
id: `${payment.id}`,
type: "sent",
date: dayjs(+payment.createdAt).fromNow(),
timeAgo: dayjs(+payment.createdAt).fromNow(),
title: payment.description || payment.name,
publisherLink: publisherLink || payment.location,
timestamp: parseInt(payment.createdAt),
Expand Down
2 changes: 1 addition & 1 deletion src/common/utils/helpers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export function mergeTransactions(
payments: ConnectorTransaction[]
): ConnectorTransaction[] {
const mergedTransactions = [...invoices, ...payments].sort((a, b) => {
return b.settleDate - a.settleDate;
return (b.settleDate ?? 0) - (a.settleDate ?? 0);
});

return mergedTransactions;
Expand Down
8 changes: 4 additions & 4 deletions src/extension/background-script/actions/ln/getTransactions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,8 @@ const getTransactions = async (message: MessageGetTransactions) => {
try {
const result = await connector.getTransactions();

let transactions: ConnectorTransaction[] = result.data.transactions
.filter((transaction) => transaction.settled)
.map((transaction) => {
let transactions: ConnectorTransaction[] = result.data.transactions.map(
(transaction) => {
const boostagram = utils.getBoostagramFromInvoiceCustomRecords(
transaction.custom_records
);
Expand All @@ -21,7 +20,8 @@ const getTransactions = async (message: MessageGetTransactions) => {
boostagram,
paymentHash: transaction.payment_hash,
};
});
}
);

if (limit) {
transactions = transactions.slice(0, limit);
Expand Down
29 changes: 16 additions & 13 deletions src/extension/background-script/connectors/alby.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,19 +113,22 @@ export default class Alby implements Connector {
client.invoices({})
)) as Invoice[];

const transactions: ConnectorTransaction[] = invoicesResponse.map(
(invoice, index): ConnectorTransaction => ({
custom_records: invoice.custom_records,
id: `${invoice.payment_request}-${index}`,
memo: invoice.comment || invoice.memo,
preimage: invoice.preimage ?? "",
payment_hash: invoice.payment_hash,
settled: invoice.settled,
settleDate: new Date(invoice.settled_at).getTime(),
totalAmount: invoice.amount,
type: invoice.type == "incoming" ? "received" : "sent",
})
);
const transactions: ConnectorTransaction[] = invoicesResponse
.map(
(invoice, index): ConnectorTransaction => ({
custom_records: invoice.custom_records,
id: `${invoice.payment_request}-${index}`,
memo: invoice.comment || invoice.memo,
preimage: invoice.preimage ?? "",
payment_hash: invoice.payment_hash,
settled: invoice.settled,
settleDate: new Date(invoice.settled_at).getTime(),
creationDate: new Date(invoice.created_at).getTime(),
totalAmount: invoice.amount,
type: invoice.type == "incoming" ? "received" : "sent",
})
)
.filter((transaction) => transaction.settled);

return {
data: {
Expand Down
39 changes: 30 additions & 9 deletions src/extension/background-script/connectors/commando.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import LnMessage from "lnmessage";
import { v4 as uuidv4 } from "uuid";
import { Account } from "~/types";

import lightningPayReq from "bolt11-signet";
import { mergeTransactions } from "~/common/utils/helpers";
import Connector, {
CheckPaymentArgs,
Expand Down Expand Up @@ -238,18 +239,28 @@ export default class Commando implements Connector {
.then((resp) => {
const parsed = resp as CommandoListInvoicesResponse;
return parsed.invoices
.map(
(invoice, index): ConnectorTransaction => ({
.map((invoice, index): ConnectorTransaction => {
const decoded = invoice.bolt11
? lightningPayReq.decode(invoice.bolt11)
: null;

const creationDate =
decoded && decoded.timestamp
? decoded.timestamp * 1000
: new Date(0).getTime();

return {
id: invoice.label,
memo: invoice.description,
settled: invoice.status === "paid",
creationDate: creationDate,
preimage: invoice.payment_preimage,
payment_hash: invoice.payment_hash,
settleDate: invoice.paid_at * 1000,
type: "received",
totalAmount: Math.floor(invoice.amount_received_msat / 1000),
})
)
};
})
.filter((invoice) => invoice.settled);
});
}
Expand All @@ -261,7 +272,7 @@ export default class Commando implements Connector {
const transactions: ConnectorTransaction[] = mergeTransactions(
incomingInvoicesResponse,
outgoingInvoicesResponse
);
).filter((transaction) => transaction.settled);

return {
data: {
Expand All @@ -280,18 +291,28 @@ export default class Commando implements Connector {
.then((resp) => {
const parsed = resp as CommandoListSendPaysResponse;
return parsed.payments
.map(
(payment, index): ConnectorTransaction => ({
.map((payment, index): ConnectorTransaction => {
const decoded = payment.bolt11
? lightningPayReq.decode(payment.bolt11)
: null;

const creationDate =
decoded && decoded.timestamp
? decoded.timestamp * 1000
: new Date(0).getTime();

return {
id: `${payment.id}`,
memo: payment.description ?? "",
settled: payment.status === "complete",
preimage: payment.payment_preimage,
creationDate: creationDate,
payment_hash: payment.payment_hash,
settleDate: payment.created_at * 1000,
type: "sent",
totalAmount: payment.amount_sent_msat / 1000,
})
)
};
})
.filter((payment) => payment.settled);
});
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,10 +31,12 @@ export interface ConnectorTransaction {
/**
* Settle date in UNIX milliseconds
*/
settleDate: number;
settleDate: number | null;
creationDate: number;
totalAmount: number;
displayAmount?: [number, ACCOUNT_CURRENCIES];
type: "received" | "sent";
state?: "settled" | "pending" | "failed";
}

export interface MakeInvoiceArgs {
Expand Down
Loading

0 comments on commit c109f45

Please sign in to comment.