diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 94bc9369..b74e1e50 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -28,3 +28,5 @@ jobs: run: yarn build - name: Run Tests run: yarn test + - name: Run Typecheck + run: yarn tsc diff --git a/README.md b/README.md index 53fc5a14..ae867054 100644 --- a/README.md +++ b/README.md @@ -130,3 +130,5 @@ Please do not commit the above changes. - [MIT License (?)](https://github.com/lightningnetwork/lnd/blob/master/LICENSE) - Core Lightning Logo from [Blockstream](https://blockstream.com/) - [Blockstream Corporate Brand Guideline](https://blockstream.com/brand-assets/) +- Alby Logo from [Alby media repo](https://github.com/getAlby/media) + - License unclear diff --git a/backend-mock/system.js b/backend-mock/system.js index 47e50a7b..1a5eb511 100644 --- a/backend-mock/system.js +++ b/backend-mock/system.js @@ -51,4 +51,27 @@ router.get("/get-debug-logs-raw", (req, res) => { res.status(200).send(logs.toString()); }); +router.get("/connection-info", (req, res) => { + console.info(`call to ${req.originalUrl}`); + const lndInfo = { + lnd_admin_macaroon: + "0201036C6E6402F801030A10B9BC5746E0F52F441BE2BF3B75A1C1EC1201301A160A0761646472657373120472656164120577726974651A130A04696E666F120472656164120577726974651A170A08696E766F69636573120472656164120577726974651A210A086D616361726F6FD634804926E441E8C730F92AE0C10E5732EE20EB6D6DE", + lnd_invoice_macaroon: + "0201036C6E640258030A10B7BC5746E0F52F441BE2BF3B75A1C1EC1201301A160A0761646472657373120472656164120577726974651A170A08696E766F6963657312047", + lnd_readonly_macaroon: + "0201036C6E6402AC01030A10B8BC5746E0F52F441BE2BF3B75A1C1EC1201301A0F0A07616464726573731204726561641A0C0A04696E666F1204726561641A100A08696E766F696365731204726561641A100A086D616361726F6F6E1204726561641A0F0A076D6573736167651204726561641A100A086F6666636861696E1204726561641A0F0A076F6E636861696E1204726561641A0D0A0570656572731204726561641A0E0A067369676E65721204726561640000062065950AD6F3E7C234B5E76B9EC58838180AB9296F99D49F9C0396C9138", + lnd_rest_onion: + "7hyube57cxcd47lcgrihujaevtsqlgd5ga34l637rgsyvd32x6.onion:8080", + lnd_tls_cert: + "2D2D2D2D2D424547494E2043455254494649434154452D2D2D2D2D0A4D494943436A434341624367417749424167495156505242734D556F4D37443532634574762B6671696A414B42676771686B6A4F50515144416A41314D5238770A485159445651514B45785A73626D5167595856306232646C626D56795958526C5A43426A5A584A304D52497745415944565151444760A633351774868634E4D6A45774F4449334D546B304D7A51775768634E4D6A49784D4449794D546B304D7A5177576A41314D523877485159445651514B45785A730A626D5167595856306232646C626D56795958526C5A43426A5A584A304D524977454159445651514445776C7362324E68624768766333517757544154426763710A686B6A4F5051494242676771686B6A4F50514D4242774E434141512B4A42564C363634484C58346F4F78482F534C705A61584E4E474F775657496E44373058310A4745426F425341492F3177372B354D635855694F6E554552793041524641304E366D354E4B666B502B346130586E79566F3447684D4947654D413447413155640A447745422F775145417749437044415442674E56485355454444414B4267677242674546425163444154415042674E5648524D4241663845425441444151482F0A4D42304741315564446751574242513946686E4B31517256705670344E68746A4A66323567584262687A424842674E56485245455144412B67676C7362324E680A62476876633353434248567561586943436E56756158687759574E725A56D364842483841414147484541414141414141414141410A414141414141414141414577436759494B6F5A497A6A3045417749445341417752514968414A726D33786478504C737444382F466B61554A68534846375375740A7A2B344776524A512F637A4876326836416941383677627747782F4C624A5074716342476E773639626C73507A6646496F2F3072434F636C4F754F5248773D3D0A2D2D2D2D2D454E44204345525449464943415445", + lnd_btcpay_connection_string: "", + lnd_zeus_connection_string: + "lndconnect://7hyube57cxcd47lcgrihujaevtsqlgd5ga34l637rgsyvd32x6.onion:8080?macaroon=AgEDbG5kAvgBAwoQubxXRuD1L0Qb4r87daHB7BIBMBoWCgdhZGRyZXNzEgRyZWFkEgV3cml0ZRoTCgRpbmZvEgRyZWFkEgV3cml0ZRoXCghpbnZvaWNlcxIEcmVhZBIFd3JpdGUaIQoIbWFjYXJvb24SCGdlbmVyYXRlEgRyZWFkEgV3cml0ZRoWCgdtZXNzYWdlEgRyZWFkEgV3cml0ZRoXCghvZmZjaGFgoHb25jaGFpbhIEcmVhZBIFd3JpdGUaFAoFcGVlcnMSBHJlYWQSBXdyaXRlGhgKBnNpZ25lchIIZ2VuZXJhdGUSBHJlYWQAAAYg0JENEUVinuZ2_WNIBJJuRB6Mcw-SrgwQ5XMu4g621t4&cert=MIICCjCCAbCgAwIBAgIQVPRBsMUoM7D52cEtv-fqijAKBggqhkjOPQQDAjA1MR8wHQYDVQQKExZsbmQgYXV0b2dlbmVyYXRlZCBjZXJ0MRIwEAYDVQQDEwlsb2NhbGhvc3QwHhcNMjEwODI3MTk0MzQwWhcNMjIxMDIyMTk0MzQwWjA1MR8wHQYDVQQKExZsbmQgYXV0b2dlbmVyYXRlZCBjZXJ0MRIwEAYDVQQDEwlsb2NhbGhvc3QwWTATBgcqhkjOPQIBBggqhkjOPQMBBwNCAAQ-JBVL664HLX4oOxH_SLpZaXD70X1GEBoBSAI_1w7-5McXUiOnUERy0ARFA0N6m5NKfkP-4a0XnyVo4GhMIGeMwICpDATBgNVHSUEDDAKBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH_MB0GA1UdDgQWBBQ9FhnK1QrVpVp4NhtjJf25gXBbhzBHBgNVHREEQDA-gglsb2NhbGhvc3SCBHVuaXiCCnVuaXhwYWNrZXSCB2J1ZmNvbm6HBH8AAAGHEAAAAAAAAAAAAAAAAAAAAAEwCgYIKoZIzj0EAwIDSAAwRQIhAJrm3xdxPLstD8_FkaUJhSHF7Sutz-4GvRJQ_czHv2h6AiA86wbwGx_LbJPtqcBGnw69blsPzfFIo", + cl_rest_zeus_connection_string: "", + cl_rest_macaroon: "", + cl_rest_onion: "", + }; + res.status(200).send(lndInfo); +}); + module.exports = router; diff --git a/package.json b/package.json index dc9efebc..391c1e77 100644 --- a/package.json +++ b/package.json @@ -11,6 +11,7 @@ "postinstall": "husky install", "start": "vite", "build": "vite build", + "tsc": "tsc --noEmit", "test": "vitest run", "test:watch": "vitest watch", "coverage": "vitest run --coverage" diff --git a/public/assets/apps/logos/alby.png b/public/assets/apps/logos/alby.png new file mode 100644 index 00000000..54e16936 Binary files /dev/null and b/public/assets/apps/logos/alby.png differ diff --git a/src/globals.d.ts b/src/globals.d.ts new file mode 100644 index 00000000..f0ef45b6 --- /dev/null +++ b/src/globals.d.ts @@ -0,0 +1,16 @@ +declare global { + // for more detailed type-info see: + // https://github.com/getAlby/lightning-browser-extension/blob/master/src/extension/providers/alby/index.ts#L3 + interface Window { + alby?: { + enable: () => Promise; + addAccount: (params: { + name: string; + connector: string; + config: Record; + }) => Promise<{ success: boolean }>; + }; + } +} + +export {}; diff --git a/src/i18n/langs/en.json b/src/i18n/langs/en.json index 73e3dba3..24542090 100644 --- a/src/i18n/langs/en.json +++ b/src/i18n/langs/en.json @@ -32,6 +32,20 @@ "jam": { "about": "Jam is a web interface for JoinMarket focusing on user-friendliness and ease-of-use. It aims to provide sensible defaults and be easy to use for beginners while still having the features advanced users expect.", "shortDescription": "Jam - A web interface for JoinMarket" + }, + "alby": { + "about": "Alby Browser Extension", + "shortDescription": "Connect your RaspiBlitz LND node and use Bitcoin & Nostr apps with the Alby Extension", + "action": { + "install": "Install Alby", + "addAccount": "Connect", + "connection_info_error": "Could not get connection info", + "connection": { + "hint": "Please install the Alby extension first", + "success": "Raspiblitz account was added to Alby", + "error": "Adding Raspiblitz to Alby didn't work, please try again" + } + } } }, "apps": { diff --git a/src/i18n/langs/en_appInfo.json b/src/i18n/langs/en_appInfo.json deleted file mode 100644 index d497007e..00000000 --- a/src/i18n/langs/en_appInfo.json +++ /dev/null @@ -1,38 +0,0 @@ -{ - "translation": { - "appInfo": { - "btc-rpc-explorer": { - "about": "This is a self-hosted explorer for the Bitcoin blockchain, driven by RPC calls to your own Bitcoin node. It is easy to run and can be connected to other tools (like Electrum servers) to achieve a full-featured explorer.", - "shortDescription": "Bitcoin RPC Explorer" - }, - "btcpayserver": { - "about": "BTCPay Server is a free and open-source Bitcoin payment processor which allows you to accept bitcoin without fees or intermediaries.", - "shortDescription": "Accept Bitcoin payments. Free, open-source & self-hosted, Bitcoin payment processor." - }, - "lnbits": { - "about": "LNbits is a very simple Python server that sits on top of any funding source, and can be used as an accounts system to mitigate the risk of exposing applications to your full balance, via unique API keys for each wallet.", - "shortDescription": "Free and open-source lightning-network wallet/accounts system" - }, - "mempool": { - "about": " An open-source explorer developed for the Bitcoin community, focusing on the emerging transaction fee market to help our transition into a multi-layer ecosystem.", - "shortDescription": "An open-source explorer developed for the Bitcoin community" - }, - "rtl": { - "about": "RTL is a full function, device agnostic, web user interface to help manage Lightning node operations. RTL is available on LND, Core Lightning and Eclair implementations.", - "shortDescription": "A full function web browser app for LND, Core Lightning and Eclair" - }, - "specter": { - "about": "Bitcoin Core has a very powerful command line interface and a wonderful daemon. Using PSBT and HWI it can also work with hardware wallets, but at the moment it is too linux-way. The same applies to multisignature setups. The goal of this project is to make a convenient and user-friendly GUI around Bitcoin Core with a focus on multisignature setup with hardware wallets and airgapped devices.", - "shortDescription": "A desktop GUI for Bitcoin Core optimised to work with hardware wallets" - }, - "thunderhub": { - "about": "ThunderHub is an open-source LND node manager where you can manage and monitor your node on any device or browser. It allows you to take control of the lightning network with a simple and intuitive UX and the most up-to-date tech stack.", - "shortDescription": "LND Lightning Node Manager in your Browser" - }, - "jam": { - "about": "Jam is a web interface for JoinMarket focusing on user-friendliness and ease-of-use. It aims to provide sensible defaults and be easy to use for beginners while still having the features advanced users expect.", - "shortDescription": "Jam - A web interface for JoinMarket" - } - } - } -} diff --git a/src/pages/Apps/AppCardAlby.tsx b/src/pages/Apps/AppCardAlby.tsx new file mode 100644 index 00000000..b2b86907 --- /dev/null +++ b/src/pages/Apps/AppCardAlby.tsx @@ -0,0 +1,101 @@ +import { PlusIcon, LinkIcon } from "@heroicons/react/24/outline"; +import { FC } from "react"; +import { useTranslation } from "react-i18next"; +import AppIcon from "../../components/AppIcon"; +import { toast } from "react-toastify"; +import { instance } from "../../utils/interceptor"; + +export const AppCardAlby: FC = () => { + const { id, name } = { + id: "alby", + name: "Alby", + }; + + const { t } = useTranslation(); + + const addAlbyAccountHandler = async () => { + const resp = await instance.get("/system/connection-info"); + + if ( + resp.status !== 200 || + !resp.data.lnd_admin_macaroon || + !resp.data.lnd_rest_onion + ) { + toast.error(t(`appInfo.${id}.action.connection_info_error`)); + return; + } + + const { lnd_admin_macaroon, lnd_rest_onion } = resp.data; + + const albyProvider = window.alby; + + if (!albyProvider) { + toast.error(t(`appInfo.${id}.action.connection.hint`)); + return; + } + + try { + await albyProvider.enable(); + + const result = await albyProvider.addAccount({ + name: "⚡️ Raspiblitz", + connector: "lnd", + config: { + adminkey: lnd_admin_macaroon, + url: lnd_rest_onion, + }, + }); + + if (result.success) { + toast.success(t(`appInfo.${id}.action.connection.success`)); + } else { + toast.error(t(`appInfo.${id}.action.connection.error`)); + } + } catch (e) { + toast.error(t(`appInfo.${id}.action.connection.error`)); + } + }; + + return ( +
+
+ {/* Icon */} +
+ +
+ {/* Content */} +
+

{name}

+

+ {t(`appInfo.${id}.shortDescription`)} +

+
+
+
+ {window.alby && ( + + )} + + {!window.alby && ( + + + {t(`appInfo.${id}.action.install`)} + + )} +
+
+ ); +}; + +export default AppCardAlby; diff --git a/src/pages/Apps/index.tsx b/src/pages/Apps/index.tsx index ff38e4c0..8b7eeefd 100644 --- a/src/pages/Apps/index.tsx +++ b/src/pages/Apps/index.tsx @@ -11,6 +11,7 @@ import { availableApps } from "../../utils/availableApps"; import { checkError } from "../../utils/checkError"; import { instance } from "../../utils/interceptor"; import AppCard from "./AppCard"; +import AppCardAlby from "./AppCardAlby"; import AppInfo from "./AppInfo"; export const Apps: FC = () => { @@ -121,6 +122,10 @@ export const Apps: FC = () => { ); })} + +
+ +
diff --git a/src/pages/Home/SendModal/__tests__/SendModal.test.tsx b/src/pages/Home/SendModal/__tests__/SendModal.test.tsx index 8d57aef8..a4ffde2d 100644 --- a/src/pages/Home/SendModal/__tests__/SendModal.test.tsx +++ b/src/pages/Home/SendModal/__tests__/SendModal.test.tsx @@ -1,5 +1,5 @@ import userEvent from "@testing-library/user-event"; -import { UserEvent } from "@testing-library/user-event/dist/types/setup"; +import type { UserEvent } from "@testing-library/user-event/dist/types/setup/setup"; import { render, screen } from "test-utils"; import { rest, server } from "../../../../testServer"; import SendModal, { Props } from "../SendModal"; @@ -115,6 +115,7 @@ describe("SendModal", () => { describe("SendOnChain", () => { let user: UserEvent; // switch to onchain modal + beforeEach(async () => { user = userEvent.setup(); setup(); diff --git a/src/pages/Settings/ActionBox.tsx b/src/pages/Settings/ActionBox.tsx index 3d3925b9..ee8eadcc 100644 --- a/src/pages/Settings/ActionBox.tsx +++ b/src/pages/Settings/ActionBox.tsx @@ -1,7 +1,7 @@ -import type { FC } from "react"; +import type { FC, ReactElement } from "react"; export type Props = { - name: string; + name: string | ReactElement; actionName: string; action: () => void; }; @@ -11,7 +11,9 @@ const ActionBox: FC = ({ name, action, actionName }) => {
-

{name}

+

+ {name} +

diff --git a/src/utils/__tests__/checkError.test.ts b/src/utils/__tests__/checkError.test.ts index 8df0112a..c39068ae 100644 --- a/src/utils/__tests__/checkError.test.ts +++ b/src/utils/__tests__/checkError.test.ts @@ -7,6 +7,7 @@ vi.mock("i18next", () => ({ describe("checkError", () => { it("should display the message with basic detail object", () => { const errorMsg = checkError({ + // @ts-ignore response is not a full "AxiosResponse" type currently response: { data: { detail: "old password format invalid", @@ -18,6 +19,7 @@ describe("checkError", () => { it("should display the message with detail.msg object", () => { const errorMsg = checkError({ + // @ts-ignore response is not a full "AxiosResponse" type currently response: { data: { detail: [ @@ -40,6 +42,7 @@ describe("checkError", () => { it("should display the message with detail as an array", () => { const errorMsg = checkError({ + // @ts-ignore response: { data: { detail: [