Skip to content

Commit c109f45

Browse files
Merge pull request #3292 from getAlby/nwc-transaction-states
feat: show pending and failed transactions for nwc
2 parents 4c4bd74 + ffcc1eb commit c109f45

File tree

21 files changed

+244
-102
lines changed

21 files changed

+244
-102
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@
3636
},
3737
"dependencies": {
3838
"@bitcoinerlab/secp256k1": "^1.1.1",
39-
"@getalby/sdk": "^3.6.0",
39+
"@getalby/sdk": "^3.9.0",
4040
"@headlessui/react": "^1.7.18",
4141
"@lightninglabs/lnc-web": "^0.3.1-alpha",
4242
"@noble/ciphers": "^0.5.1",

src/app/components/TransactionsTable/TransactionModal.tsx

Lines changed: 35 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ import {
33
PopiconsArrowUpSolid,
44
PopiconsChevronBottomLine,
55
PopiconsChevronTopLine,
6+
PopiconsXSolid,
67
} from "@popicons/react";
78
import dayjs from "dayjs";
89
import { useEffect, useState } from "react";
@@ -56,17 +57,43 @@ export default function TransactionModal({
5657
<div>
5758
<div className="flex items-center justify-center">
5859
{getTransactionType(transaction) == "outgoing" ? (
59-
<div className="flex justify-center items-center bg-orange-100 dark:bg-orange-950 rounded-full p-3">
60-
<PopiconsArrowUpSolid className="w-10 h-10 text-orange-400 dark:text-amber-600 stroke-[1px] stroke-orange-400 dark:stroke-amber-600" />
61-
</div>
60+
transaction.state === "pending" ? (
61+
<div className="flex justify-center items-center bg-blue-100 dark:bg-sky-950 rounded-full p-3 animate-pulse">
62+
<PopiconsArrowUpSolid className="w-10 h-10 rotate-45 text-blue-500 dark:text-sky-500 stroke-[1px] stroke-blue-500 dark:stroke-sky-500" />
63+
</div>
64+
) : transaction.state === "failed" ? (
65+
<div className="flex justify-center items-center bg-red-100 dark:bg-rose-950 rounded-full p-3">
66+
<PopiconsXSolid className="w-10 h-10 text-red-500 dark:text-rose-500 stroke-[1px] stroke-red-500 dark:stroke-rose-500" />
67+
</div>
68+
) : (
69+
<div className="flex justify-center items-center bg-orange-100 dark:bg-amber-950 rounded-full p-3">
70+
<PopiconsArrowUpSolid className="w-10 h-10 text-orange-500 dark:text-amber-500 stroke-[1px] stroke-orange-500 dark:stroke-amber-500" />
71+
</div>
72+
)
6273
) : (
6374
<div className="flex justify-center items-center bg-green-100 dark:bg-emerald-950 rounded-full p-3">
64-
<PopiconsArrowDownSolid className="w-10 h-10 text-green-500 dark:text-emerald-500 stroke-[1px] stroke-green-400 dark:stroke-emerald-500" />
75+
<PopiconsArrowDownSolid className="w-10 h-10 text-green-500 dark:text-teal-500 stroke-[1px] stroke-green-500 dark:stroke-teal-500" />
6576
</div>
6677
)}
6778
</div>
68-
<h2 className="mt-4 text-md text-gray-900 font-bold dark:text-white text-center">
69-
{transaction.type == "received" ? t("received") : t("sent")}
79+
80+
<h2
81+
className={classNames(
82+
"mt-4 text-md text-gray-900 font-bold dark:text-white text-center",
83+
transaction.state == "pending" && "animate-pulse text-gray-400"
84+
)}
85+
>
86+
{transaction.type == "received"
87+
? t("received")
88+
: t(
89+
transaction.state === "settled"
90+
? "sent"
91+
: transaction.state === "pending"
92+
? "sending"
93+
: transaction.state === "failed"
94+
? "failed"
95+
: "sent"
96+
)}
7097
</h2>
7198
</div>
7299
<div className="flex items-center text-center justify-center dark:text-white">
@@ -76,6 +103,8 @@ export default function TransactionModal({
76103
"text-3xl font-medium",
77104
transaction.type == "received"
78105
? "text-green-600 dark:text-emerald-500"
106+
: transaction.state == "failed"
107+
? "text-red-400 dark:text-rose-600"
79108
: "text-orange-600 dark:text-amber-600"
80109
)}
81110
>

src/app/components/TransactionsTable/index.test.tsx

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ const transactions: Props = {
2222
{
2323
timestamp: 1656573909064,
2424
createdAt: "1656573909064",
25-
date: "5 days ago",
25+
timeAgo: "5 days ago",
2626
description: "Polar Invoice for bob",
2727
host: "https://openai.com/dall-e-2/",
2828
id: "1",
@@ -49,7 +49,7 @@ const invoices: Props = {
4949
totalAmountFiat: "$13.02",
5050
preimage: "",
5151
title: "lambo lambo",
52-
date: "4 days ago",
52+
timeAgo: "4 days ago",
5353
},
5454
{
5555
id: "lnbcrt6543210n1p3tadjepp5rv6ufq4vumg66l9gcyxqhy89n6w90mx0mh6gcj0sawrf6xuep5ssdq5g9kxy7fqd9h8vmmfvdjscqzpgxqyz5vqsp5f9yzxeqjw33ule4rffuh0py32gjjsx8z48cd4xjl8ej3rn7zdtdq9qyyssqe6qvkfe260myc9ypgs5n63xzwcx82fderg8p5ysh6c2fvpz5xu4ksvhs5av0wwestk5pmucmhk8lpjhmy7wqyq9c29xgm9na2q5xv5spy5kukj",
@@ -59,7 +59,7 @@ const invoices: Props = {
5959
totalAmountFiat: "$127.80",
6060
preimage: "",
6161
title: "Alby invoice",
62-
date: "6 days ago",
62+
timeAgo: "6 days ago",
6363
},
6464
],
6565
};
@@ -74,7 +74,7 @@ const invoicesWithBoostagram: Props = {
7474
totalAmountFiat: "$13.02",
7575
preimage: "",
7676
title: "lambo lambo",
77-
date: "4 days ago",
77+
timeAgo: "4 days ago",
7878
},
7979
{
8080
id: "lnbcrt888880n1p3tad30pp56j6g34wctydrfx4wwdwj3schell8uqug6jnlehlkpw02mdfd9wlqdq0v36k6urvd9hxwuccqzpgxqyz5vqsp5995q4egstsvnyetwvpax6jw8q0fnn4tyz3gp35k3yex29emhsylq9qyyssq0yxpx6peyn4vsepwj3l68w9sc5dqnkt07zff6aw4kqvcfs0fpu4jpfh929w6vqrgtjfkmrlwghq4s9t4mnwrh4dlkm6wjem5uq8eu4gpwqln0j",
@@ -84,7 +84,7 @@ const invoicesWithBoostagram: Props = {
8484
totalAmountFiat: "$17.36",
8585
preimage: "",
8686
title: "dumplings",
87-
date: "5 days ago",
87+
timeAgo: "5 days ago",
8888
boostagram: {
8989
app_name: "Fountain",
9090
name: "Friedemann",

src/app/components/TransactionsTable/index.tsx

Lines changed: 39 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
import Loading from "@components/Loading";
2-
import { PopiconsArrowDownSolid, PopiconsArrowUpSolid } from "@popicons/react";
2+
import {
3+
PopiconsArrowDownSolid,
4+
PopiconsArrowUpSolid,
5+
PopiconsXSolid,
6+
} from "@popicons/react";
37

48
import { useState } from "react";
59
import { useTranslation } from "react-i18next";
@@ -60,25 +64,50 @@ export default function TransactionsTable({
6064
<div className="flex gap-3">
6165
<div className="flex items-center">
6266
{type == "outgoing" ? (
63-
<div className="flex justify-center items-center bg-orange-100 dark:bg-orange-950 rounded-full w-8 h-8">
64-
<PopiconsArrowUpSolid className="w-5 h-5 text-orange-400 dark:text-amber-600 stroke-[1px] stroke-orange-400 dark:stroke-amber-600" />
65-
</div>
67+
tx.state === "pending" ? (
68+
<div className="flex justify-center items-center bg-blue-100 dark:bg-sky-950 rounded-full w-8 h-8 animate-pulse">
69+
<PopiconsArrowUpSolid className="w-5 h-5 rotate-45 text-blue-500 dark:text-sky-500 stroke-[1px] stroke-blue-500 dark:stroke-sky-500" />
70+
</div>
71+
) : tx.state === "failed" ? (
72+
<div className="flex justify-center items-center bg-red-100 dark:bg-rose-950 rounded-full w-8 h-8">
73+
<PopiconsXSolid className="w-5 h-5 text-red-500 dark:text-rose-500 stroke-[1px] stroke-red-500 dark:stroke-rose-500" />
74+
</div>
75+
) : (
76+
<div className="flex justify-center items-center bg-orange-100 dark:bg-amber-950 rounded-full w-8 h-8">
77+
<PopiconsArrowUpSolid className="w-5 h-5 text-orange-500 dark:text-amber-500 stroke-[1px] stroke-orange-500 dark:stroke-amber-500" />
78+
</div>
79+
)
6680
) : (
6781
<div className="flex justify-center items-center bg-green-100 dark:bg-emerald-950 rounded-full w-8 h-8">
68-
<PopiconsArrowDownSolid className="w-5 h-5 text-green-500 dark:text-emerald-500 stroke-[1px] stroke-green-400 dark:stroke-emerald-500" />
82+
<PopiconsArrowDownSolid className="w-5 h-5 text-green-500 dark:text-teal-500 stroke-[1px] stroke-green-500 dark:stroke-teal-500" />
6983
</div>
7084
)}
7185
</div>
7286
<div className="overflow-hidden mr-3">
7387
<div className="text-sm font-medium text-black truncate dark:text-white">
74-
<p className="truncate">
88+
<p
89+
className={classNames(
90+
"truncate",
91+
tx.state == "pending" && "animate-pulse"
92+
)}
93+
>
7594
{tx.title ||
7695
tx.boostagram?.message ||
77-
(type == "incoming" ? t("received") : t("sent"))}
96+
(type == "incoming"
97+
? t("received")
98+
: t(
99+
tx.state === "settled"
100+
? "sent"
101+
: tx.state === "pending"
102+
? "sending"
103+
: tx.state === "failed"
104+
? "failed"
105+
: "sent"
106+
))}
78107
</p>
79108
</div>
80109
<p className="text-xs text-gray-400 dark:text-neutral-500">
81-
{tx.date}
110+
{tx.timeAgo}
82111
</p>
83112
</div>
84113
<div className="flex ml-auto text-right space-x-3 shrink-0 dark:text-white">
@@ -88,6 +117,8 @@ export default function TransactionsTable({
88117
"text-sm",
89118
type == "incoming"
90119
? "text-green-600 dark:text-emerald-500"
120+
: tx.state == "failed"
121+
? "text-red-600 dark:text-rose-500"
91122
: "text-orange-600 dark:text-amber-600"
92123
)}
93124
>

src/app/hooks/useTransactions.ts

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,13 +16,14 @@ export const useTransactions = () => {
1616
const getTransactionsResponse = await api.getTransactions({
1717
limit,
1818
});
19-
2019
const transactions = getTransactionsResponse.transactions.map(
2120
(transaction) => ({
2221
...transaction,
2322
title: transaction.memo,
24-
date: dayjs(transaction.settleDate).fromNow(),
25-
timestamp: transaction.settleDate,
23+
timeAgo: dayjs(
24+
transaction.settleDate || transaction.creationDate
25+
).fromNow(),
26+
timestamp: transaction.settleDate || transaction.creationDate,
2627
})
2728
);
2829

src/app/utils/payments.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ export const convertPaymentToTransaction = (
88
...payment,
99
id: `${payment.id}`,
1010
type: "sent",
11-
date: dayjs(+payment.createdAt).fromNow(),
11+
timeAgo: dayjs(+payment.createdAt).fromNow(),
1212
title: payment.description || payment.name,
1313
publisherLink: publisherLink || payment.location,
1414
timestamp: parseInt(payment.createdAt),

src/common/utils/helpers.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,7 @@ export function mergeTransactions(
6868
payments: ConnectorTransaction[]
6969
): ConnectorTransaction[] {
7070
const mergedTransactions = [...invoices, ...payments].sort((a, b) => {
71-
return b.settleDate - a.settleDate;
71+
return (b.settleDate ?? 0) - (a.settleDate ?? 0);
7272
});
7373

7474
return mergedTransactions;

src/extension/background-script/actions/ln/getTransactions.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,9 +10,8 @@ const getTransactions = async (message: MessageGetTransactions) => {
1010
try {
1111
const result = await connector.getTransactions();
1212

13-
let transactions: ConnectorTransaction[] = result.data.transactions
14-
.filter((transaction) => transaction.settled)
15-
.map((transaction) => {
13+
let transactions: ConnectorTransaction[] = result.data.transactions.map(
14+
(transaction) => {
1615
const boostagram = utils.getBoostagramFromInvoiceCustomRecords(
1716
transaction.custom_records
1817
);
@@ -21,7 +20,8 @@ const getTransactions = async (message: MessageGetTransactions) => {
2120
boostagram,
2221
paymentHash: transaction.payment_hash,
2322
};
24-
});
23+
}
24+
);
2525

2626
if (limit) {
2727
transactions = transactions.slice(0, limit);

src/extension/background-script/connectors/alby.ts

Lines changed: 16 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -113,19 +113,22 @@ export default class Alby implements Connector {
113113
client.invoices({})
114114
)) as Invoice[];
115115

116-
const transactions: ConnectorTransaction[] = invoicesResponse.map(
117-
(invoice, index): ConnectorTransaction => ({
118-
custom_records: invoice.custom_records,
119-
id: `${invoice.payment_request}-${index}`,
120-
memo: invoice.comment || invoice.memo,
121-
preimage: invoice.preimage ?? "",
122-
payment_hash: invoice.payment_hash,
123-
settled: invoice.settled,
124-
settleDate: new Date(invoice.settled_at).getTime(),
125-
totalAmount: invoice.amount,
126-
type: invoice.type == "incoming" ? "received" : "sent",
127-
})
128-
);
116+
const transactions: ConnectorTransaction[] = invoicesResponse
117+
.map(
118+
(invoice, index): ConnectorTransaction => ({
119+
custom_records: invoice.custom_records,
120+
id: `${invoice.payment_request}-${index}`,
121+
memo: invoice.comment || invoice.memo,
122+
preimage: invoice.preimage ?? "",
123+
payment_hash: invoice.payment_hash,
124+
settled: invoice.settled,
125+
settleDate: new Date(invoice.settled_at).getTime(),
126+
creationDate: new Date(invoice.created_at).getTime(),
127+
totalAmount: invoice.amount,
128+
type: invoice.type == "incoming" ? "received" : "sent",
129+
})
130+
)
131+
.filter((transaction) => transaction.settled);
129132

130133
return {
131134
data: {

src/extension/background-script/connectors/commando.ts

Lines changed: 30 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import LnMessage from "lnmessage";
44
import { v4 as uuidv4 } from "uuid";
55
import { Account } from "~/types";
66

7+
import lightningPayReq from "bolt11-signet";
78
import { mergeTransactions } from "~/common/utils/helpers";
89
import Connector, {
910
CheckPaymentArgs,
@@ -238,18 +239,28 @@ export default class Commando implements Connector {
238239
.then((resp) => {
239240
const parsed = resp as CommandoListInvoicesResponse;
240241
return parsed.invoices
241-
.map(
242-
(invoice, index): ConnectorTransaction => ({
242+
.map((invoice, index): ConnectorTransaction => {
243+
const decoded = invoice.bolt11
244+
? lightningPayReq.decode(invoice.bolt11)
245+
: null;
246+
247+
const creationDate =
248+
decoded && decoded.timestamp
249+
? decoded.timestamp * 1000
250+
: new Date(0).getTime();
251+
252+
return {
243253
id: invoice.label,
244254
memo: invoice.description,
245255
settled: invoice.status === "paid",
256+
creationDate: creationDate,
246257
preimage: invoice.payment_preimage,
247258
payment_hash: invoice.payment_hash,
248259
settleDate: invoice.paid_at * 1000,
249260
type: "received",
250261
totalAmount: Math.floor(invoice.amount_received_msat / 1000),
251-
})
252-
)
262+
};
263+
})
253264
.filter((invoice) => invoice.settled);
254265
});
255266
}
@@ -261,7 +272,7 @@ export default class Commando implements Connector {
261272
const transactions: ConnectorTransaction[] = mergeTransactions(
262273
incomingInvoicesResponse,
263274
outgoingInvoicesResponse
264-
);
275+
).filter((transaction) => transaction.settled);
265276

266277
return {
267278
data: {
@@ -280,18 +291,28 @@ export default class Commando implements Connector {
280291
.then((resp) => {
281292
const parsed = resp as CommandoListSendPaysResponse;
282293
return parsed.payments
283-
.map(
284-
(payment, index): ConnectorTransaction => ({
294+
.map((payment, index): ConnectorTransaction => {
295+
const decoded = payment.bolt11
296+
? lightningPayReq.decode(payment.bolt11)
297+
: null;
298+
299+
const creationDate =
300+
decoded && decoded.timestamp
301+
? decoded.timestamp * 1000
302+
: new Date(0).getTime();
303+
304+
return {
285305
id: `${payment.id}`,
286306
memo: payment.description ?? "",
287307
settled: payment.status === "complete",
288308
preimage: payment.payment_preimage,
309+
creationDate: creationDate,
289310
payment_hash: payment.payment_hash,
290311
settleDate: payment.created_at * 1000,
291312
type: "sent",
292313
totalAmount: payment.amount_sent_msat / 1000,
293-
})
294-
)
314+
};
315+
})
295316
.filter((payment) => payment.settled);
296317
});
297318
}

0 commit comments

Comments
 (0)