From b1c76525d3fedca8fe74f46ec8fd076692056000 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Fri, 30 Jun 2023 13:33:51 +0700 Subject: [PATCH 01/73] re-add signPSBT changes --- package.json | 3 + src/app/router/Prompt/Prompt.tsx | 2 + .../screens/ConfirmSignPsbt/index.test.tsx | 118 ++++++++++++ src/app/screens/ConfirmSignPsbt/index.tsx | 173 ++++++++++++++++++ src/common/lib/psbt.ts | 76 ++++++++ .../actions/webbtc/__tests__/signPsbt.test.ts | 100 ++++++++++ .../actions/webbtc/getInfo.ts | 2 +- .../background-script/actions/webbtc/index.ts | 15 +- .../actions/webbtc/signPsbt.ts | 126 +++++++++++++ .../actions/webbtc/signPsbtWithPrompt.ts | 26 +++ src/extension/background-script/router.ts | 2 + src/extension/content-script/onendwebbtc.js | 1 + src/extension/providers/webbtc/index.ts | 8 + src/fixtures/btc.ts | 11 ++ src/types.ts | 7 + webpack.config.js | 5 + yarn.lock | 46 ++++- 17 files changed, 714 insertions(+), 7 deletions(-) create mode 100644 src/app/screens/ConfirmSignPsbt/index.test.tsx create mode 100644 src/app/screens/ConfirmSignPsbt/index.tsx create mode 100644 src/common/lib/psbt.ts create mode 100644 src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts create mode 100644 src/extension/background-script/actions/webbtc/signPsbt.ts create mode 100644 src/extension/background-script/actions/webbtc/signPsbtWithPrompt.ts diff --git a/package.json b/package.json index ef96434c9a..01f8861d4d 100644 --- a/package.json +++ b/package.json @@ -46,10 +46,12 @@ "@vespaiach/axios-fetch-adapter": "^0.3.0", "axios": "^0.27.2", "bech32": "^2.0.0", + "bitcoinjs-lib": "^6.1.0", "bolt11": "^1.4.1", "crypto-js": "^4.1.1", "dayjs": "^1.11.7", "dexie": "^3.2.3", + "ecpair": "^2.1.0", "elliptic": "^6.5.4", "html5-qrcode": "^2.3.8", "i18next": "^22.4.15", @@ -70,6 +72,7 @@ "react-toastify": "^9.1.3", "stream": "^0.0.2", "tailwindcss": "^3.3.2", + "tiny-secp256k1": "^2.2.1", "uuid": "^9.0.0", "webextension-polyfill": "^0.10.0", "zustand": "^3.7.2" diff --git a/src/app/router/Prompt/Prompt.tsx b/src/app/router/Prompt/Prompt.tsx index 536e19458a..6ce0f6a358 100644 --- a/src/app/router/Prompt/Prompt.tsx +++ b/src/app/router/Prompt/Prompt.tsx @@ -20,6 +20,7 @@ import { ToastContainer } from "react-toastify"; import Providers from "~/app/context/Providers"; import RequireAuth from "~/app/router/RequireAuth"; import ConfirmGetAddress from "~/app/screens/ConfirmGetAddress"; +import ConfirmSignPsbt from "~/app/screens/ConfirmSignPsbt"; import type { NavigationState, OriginData } from "~/types"; // Parse out the parameters from the querystring. @@ -107,6 +108,7 @@ function Prompt() { } /> } /> } /> + } /> } /> { + return { + useNavigationState: jest.fn(() => ({ + origin: mockOrigin, + args: { + psbt: btcFixture.regtestTaprootPsbt, + }, + })), + }; +}); + +const passwordMock = jest.fn; + +const mockState = { + password: passwordMock, + currentAccountId: "1e1e8ea6-493e-480b-9855-303d37506e97", + getAccount: () => ({ + bitcoinNetwork: "regtest", + }), + getConnector: jest.fn(), +}; + +state.getState = jest.fn().mockReturnValue(mockState); + +// mock get settings +msg.request = jest.fn().mockReturnValue({ + bitcoinNetwork: "regtest", +}); + +describe("ConfirmSignMessage", () => { + test("render", async () => { + await act(async () => { + render( + + + + ); + }); + + const user = userEvent.setup(); + + await act(async () => { + await user.click(screen.getByText("View addresses")); + }); + await act(async () => { + await user.click(screen.getByText("View PSBT hex")); + }); + + // TODO: update copy + expect( + await screen.findByText( + "This website asks you to sign a Partially Signed Bitcoin Transaction:" + ) + ).toBeInTheDocument(); + + expect( + await screen.findByText(btcFixture.regtestTaprootPsbt) + ).toBeInTheDocument(); + + // Check inputs + const inputsContainer = (await screen.getByText("Input") + .parentElement) as HTMLElement; + expect(inputsContainer).toBeInTheDocument(); + const inputsRef = within(inputsContainer); + expect( + await inputsRef.findByText( + "bcrt1p8wpt9v4frpf3tkn0srd97pksgsxc5hs52lafxwru9kgeephvs7rqjeprhg" + ) + ).toBeInTheDocument(); + + // Check outputs + const outputsContainer = screen.getByText("Outputs") + .parentElement as HTMLElement; + expect(outputsContainer).toBeInTheDocument(); + + const outputsRef = within(outputsContainer); + expect( + await outputsRef.findByText( + "bcrt1p6uav7en8k7zsumsqugdmg5j6930zmzy4dg7jcddshsr0fvxlqx7qnc7l22" + ) + ).toBeInTheDocument(); + + expect( + await outputsRef.findByText( + "bcrt1p90h6z3p36n9hrzy7580h5l429uwchyg8uc9sz4jwzhdtuhqdl5eqkcyx0f" + ) + ).toBeInTheDocument(); + }); +}); diff --git a/src/app/screens/ConfirmSignPsbt/index.tsx b/src/app/screens/ConfirmSignPsbt/index.tsx new file mode 100644 index 0000000000..b64222ee76 --- /dev/null +++ b/src/app/screens/ConfirmSignPsbt/index.tsx @@ -0,0 +1,173 @@ +import ConfirmOrCancel from "@components/ConfirmOrCancel"; +import Container from "@components/Container"; +import PublisherCard from "@components/PublisherCard"; +import SuccessMessage from "@components/SuccessMessage"; +import { TFunction } from "i18next"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import { toast } from "react-toastify"; +import Hyperlink from "~/app/components/Hyperlink"; +import Loading from "~/app/components/Loading"; +import ScreenHeader from "~/app/components/ScreenHeader"; +import { useNavigationState } from "~/app/hooks/useNavigationState"; +import { USER_REJECTED_ERROR } from "~/common/constants"; +import api from "~/common/lib/api"; +import msg from "~/common/lib/msg"; +import { Address, PsbtPreview, getPsbtPreview } from "~/common/lib/psbt"; +import type { OriginData } from "~/types"; + +function ConfirmSignPsbt() { + const navState = useNavigationState(); + const { t: tCommon } = useTranslation("common"); + const { t } = useTranslation("translation", { + keyPrefix: "confirm_sign_psbt", + }); + const navigate = useNavigate(); + + const psbt = navState.args?.psbt as string; + const origin = navState.origin as OriginData; + const [loading, setLoading] = useState(false); + const [successMessage, setSuccessMessage] = useState(""); + const [preview, setPreview] = useState(undefined); + const [showAddresses, setShowAddresses] = useState(false); + const [showHex, setShowHex] = useState(false); + + useEffect(() => { + (async () => { + const settings = await api.getSettings(); + setPreview(getPsbtPreview(psbt, settings.bitcoinNetwork)); + })(); + }, [origin, psbt]); + + async function confirm() { + try { + setLoading(true); + const response = await msg.request("signPsbt", { psbt }, { origin }); + msg.reply(response); + setSuccessMessage(tCommon("success")); + } catch (e) { + console.error(e); + if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); + } finally { + setLoading(false); + } + } + + function reject(e: React.MouseEvent) { + e.preventDefault(); + msg.error(USER_REJECTED_ERROR); + } + + function close(e: React.MouseEvent) { + if (navState.isPrompt) { + window.close(); + } else { + e.preventDefault(); + navigate(-1); + } + } + + function toggleShowAddresses() { + setShowAddresses((current) => !current); + } + function toggleShowHex() { + setShowHex((current) => !current); + } + + if (!preview) { + return ; + } + + return ( +
+ + {!successMessage ? ( + +
+ +
+ {t("warning")} +
+
+

+ {t("allow_sign", { host: origin.host })} +

+
+ + {showAddresses ? t("hide_addresses") : t("view_addresses")} + + {"•"} + + {showHex ? t("hide_hex") : t("view_hex")} + +
+ + {showAddresses && ( +
+

{t("input")}

+ +
+ )} + + {showAddresses && ( +
+

{t("outputs")}

+
+ {preview.outputs.map((output) => ( + + ))} +
+
+ )} +
+ + {showHex && ( +
+ {psbt} +
+ )} +
+ +
+ ) : ( + + + + + )} +
+ ); +} + +function AddressPreview({ + address, + amount, + t, +}: Address & { + t: TFunction<"translation", "confirm_sign_psbt", "translation">; +}) { + return ( +
+

{address}

+

+ {t("amount", { amount })} +

+
+ ); +} + +export default ConfirmSignPsbt; diff --git a/src/common/lib/psbt.ts b/src/common/lib/psbt.ts new file mode 100644 index 0000000000..b92366005c --- /dev/null +++ b/src/common/lib/psbt.ts @@ -0,0 +1,76 @@ +import * as btc from "@scure/btc-signer"; +import { Psbt, networks } from "bitcoinjs-lib"; + +export type Address = { amount: number; address: string }; + +export type PsbtPreview = { + inputs: Address[]; + outputs: Address[]; +}; + +export function getPsbtPreview( + psbt: string, + networkType?: keyof typeof networks +): PsbtPreview { + const network = networkType ? networks[networkType] : undefined; + + const unsignedPsbt = Psbt.fromHex(psbt, { + network, + }); + + const preview: PsbtPreview = { + inputs: [], + outputs: [], + }; + + for (let i = 0; i < unsignedPsbt.data.inputs.length; i++) { + if (i > 0) { + throw new Error("Multiple inputs currently unsupported"); + } + + const tapBip32Derivation = unsignedPsbt.data.inputs[i].tapBip32Derivation; + if (!tapBip32Derivation) { + throw new Error("No bip32Derivation in input " + i); + } + const address = btc.p2tr( + tapBip32Derivation[0].pubkey, + undefined, + network + ).address; + + if (!address) { + throw new Error("No address found in input " + i); + } + const witnessUtxo = unsignedPsbt.data.inputs[i].witnessUtxo; + if (!witnessUtxo) { + throw new Error("No witnessUtxo in input " + i); + } + + preview.inputs.push({ + amount: witnessUtxo.value, + address, + }); + } + for (let i = 0; i < unsignedPsbt.data.outputs.length; i++) { + const txOutput = unsignedPsbt.txOutputs[i]; + const output = unsignedPsbt.data.outputs[i]; + if (!output.tapBip32Derivation) { + throw new Error("No tapBip32Derivation in output"); + } + const address = btc.p2tr( + output.tapBip32Derivation[0].pubkey, + undefined, + network + ).address; + if (!address) { + throw new Error("No address found in output " + i); + } + + const previewOutput: Address = { + amount: txOutput.value, + address, + }; + preview.outputs.push(previewOutput); + } + return preview; +} diff --git a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts new file mode 100644 index 0000000000..58ecd040db --- /dev/null +++ b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts @@ -0,0 +1,100 @@ +import { hex } from "@scure/base"; +import * as btc from "@scure/btc-signer"; +import { getPsbtPreview } from "~/common/lib/psbt"; +import signPsbt from "~/extension/background-script/actions/webbtc/signPsbt"; +import state from "~/extension/background-script/state"; +import { btcFixture } from "~/fixtures/btc"; +import type { MessageSignPsbt } from "~/types"; + +const passwordMock = jest.fn; + +const mockState = { + password: passwordMock, + currentAccountId: "1e1e8ea6-493e-480b-9855-303d37506e97", + getAccount: () => ({ + mnemonic: btcFixture.mnemnoic, + }), + getConnector: jest.fn(), + settings: { + bitcoinNetwork: "regtest", + }, +}; + +state.getState = jest.fn().mockReturnValue(mockState); + +jest.mock("~/common/lib/crypto", () => { + return { + decryptData: jest.fn((encrypted, _password) => { + return encrypted; + }), + }; +}); + +beforeEach(async () => { + // fill the DB first +}); + +afterEach(() => { + jest.clearAllMocks(); +}); + +async function sendPsbtMessage(psbt: string, derivationPath?: string) { + const message: MessageSignPsbt = { + application: "LBE", + prompt: true, + action: "signPsbt", + origin: { + internal: true, + }, + args: { + psbt, + }, + }; + + return await signPsbt(message); +} + +describe("signPsbt", () => { + test("1 input, taproot, regtest", async () => { + const result = await sendPsbtMessage(btcFixture.regtestTaprootPsbt); + if (!result.data) { + throw new Error("Result should have data"); + } + + expect(result.data).not.toBe(undefined); + expect(result.data?.signed).not.toBe(undefined); + expect(result.error).toBe(undefined); + + const checkTx = btc.Transaction.fromRaw(hex.decode(result.data.signed)); + expect(checkTx.isFinal).toBe(true); + expect(result.data?.signed).toBe(btcFixture.regtestTaprootSignedPsbt); + }); +}); + +describe("signPsbt input validation", () => { + test("invalid psbt", async () => { + const result = await sendPsbtMessage("test"); + expect(result.error).not.toBe(null); + }); +}); + +describe("decode psbt", () => { + test("get taproot transaction preview", async () => { + const preview = getPsbtPreview(btcFixture.regtestTaprootPsbt, "regtest"); + expect(preview.inputs.length).toBe(1); + expect(preview.inputs[0].address).toBe( + "bcrt1p8wpt9v4frpf3tkn0srd97pksgsxc5hs52lafxwru9kgeephvs7rqjeprhg" + ); + expect(preview.inputs[0].amount).toBe(10_000_000); + expect(preview.outputs.length).toBe(2); + + expect(preview.outputs[0].address).toBe( + "bcrt1p6uav7en8k7zsumsqugdmg5j6930zmzy4dg7jcddshsr0fvxlqx7qnc7l22" + ); + expect(preview.outputs[0].amount).toBe(4_999_845); + expect(preview.outputs[1].address).toBe( + "bcrt1p90h6z3p36n9hrzy7580h5l429uwchyg8uc9sz4jwzhdtuhqdl5eqkcyx0f" + ); + expect(preview.outputs[1].amount).toBe(5_000_000); + }); +}); diff --git a/src/extension/background-script/actions/webbtc/getInfo.ts b/src/extension/background-script/actions/webbtc/getInfo.ts index 9043e35101..720a1fef32 100644 --- a/src/extension/background-script/actions/webbtc/getInfo.ts +++ b/src/extension/background-script/actions/webbtc/getInfo.ts @@ -1,7 +1,7 @@ import { MessageGetInfo } from "~/types"; const getInfo = async (message: MessageGetInfo) => { - const supportedMethods = ["getInfo", "getAddress"]; + const supportedMethods = ["getInfo", "signPsbt", "getAddress"]; return { data: { diff --git a/src/extension/background-script/actions/webbtc/index.ts b/src/extension/background-script/actions/webbtc/index.ts index 4f78ae22f0..c03795d9a7 100644 --- a/src/extension/background-script/actions/webbtc/index.ts +++ b/src/extension/background-script/actions/webbtc/index.ts @@ -1,5 +1,14 @@ -import getAddress from "./getAddress"; -import getAddressWithPrompt from "./getAddressWithPrompt"; +import getAddress from "~/extension/background-script/actions/webbtc/getAddress"; +import getAddressWithPrompt from "~/extension/background-script/actions/webbtc/getAddressWithPrompt"; +import signPsbt from "~/extension/background-script/actions/webbtc/signPsbt"; + import getInfo from "./getInfo"; +import signPsbtWithPrompt from "./signPsbtWithPrompt"; -export { getAddress, getAddressWithPrompt, getInfo }; +export { + getAddress, + getAddressWithPrompt, + getInfo, + signPsbt, + signPsbtWithPrompt, +}; diff --git a/src/extension/background-script/actions/webbtc/signPsbt.ts b/src/extension/background-script/actions/webbtc/signPsbt.ts new file mode 100644 index 0000000000..1c9efd6484 --- /dev/null +++ b/src/extension/background-script/actions/webbtc/signPsbt.ts @@ -0,0 +1,126 @@ +import * as secp256k1 from "@noble/secp256k1"; +import * as bitcoin from "bitcoinjs-lib"; +import ECPairFactory, { ECPairAPI } from "ecpair"; +import * as tinysecp from "tiny-secp256k1"; +import { decryptData } from "~/common/lib/crypto"; +import { + BTC_TAPROOT_DERIVATION_PATH, + BTC_TAPROOT_DERIVATION_PATH_REGTEST, + derivePrivateKey, +} from "~/common/lib/mnemonic"; +import state from "~/extension/background-script/state"; +import { MessageSignPsbt } from "~/types"; + +const signPsbt = async (message: MessageSignPsbt) => { + try { + // TODO: is this the correct way to decrypt the mnmenonic? + const password = await state.getState().password(); + if (!password) { + throw new Error("No password set"); + } + const account = await state.getState().getAccount(); + if (!account) { + throw new Error("No account selected"); + } + if (!account.mnemonic) { + throw new Error("No mnemonic set"); + } + const mnemonic = decryptData(account.mnemonic, password); + const settings = state.getState().settings; + + const derivationPath = + settings.bitcoinNetwork === "bitcoin" + ? BTC_TAPROOT_DERIVATION_PATH + : BTC_TAPROOT_DERIVATION_PATH_REGTEST; + + const privateKey = secp256k1.utils.hexToBytes( + derivePrivateKey(mnemonic, derivationPath) + ); + + const taprootPsbt = bitcoin.Psbt.fromHex(message.args.psbt, { + network: bitcoin.networks[settings.bitcoinNetwork], + }); + + // fix usages of window (unavailable in service worker) + globalThis.window ??= globalThis.window || {}; + if (!globalThis.window.crypto) { + globalThis.window.crypto = crypto; + } + + bitcoin.initEccLib(tinysecp); + const ECPair: ECPairAPI = ECPairFactory(tinysecp); + + const keyPair = tweakSigner( + ECPair, + ECPair.fromPrivateKey(Buffer.from(privateKey), { + network: bitcoin.networks[settings.bitcoinNetwork], + }), + { + network: bitcoin.networks[settings.bitcoinNetwork], + } + ); + + // Step 1: Sign the Taproot PSBT inputs + taprootPsbt.data.inputs.forEach((input, index) => { + taprootPsbt.signTaprootInput(index, keyPair); + }); + + // Step 2: Finalize the Taproot PSBT + taprootPsbt.finalizeAllInputs(); + + // Step 3: Get the finalized transaction + const signedTransaction = taprootPsbt.extractTransaction().toHex(); + + return { + data: { + signed: signedTransaction, + }, + }; + } catch (e) { + console.error("signPsbt failed: ", e); + return { + error: "signPsbt failed: " + e, + }; + } +}; + +export default signPsbt; + +// Below code taken from https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.spec.ts#L636 +const toXOnly = (pubKey: Buffer) => + pubKey.length === 32 ? pubKey : pubKey.slice(1, 33); + +function tweakSigner( + ECPair: ECPairAPI, + signer: bitcoin.Signer, + opts: { network: bitcoin.Network; tweakHash?: Buffer | undefined } +): bitcoin.Signer { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + let privateKey: Uint8Array | undefined = signer.privateKey; + if (!privateKey) { + throw new Error("Private key is required for tweaking signer!"); + } + if (signer.publicKey[0] === 3) { + privateKey = tinysecp.privateNegate(privateKey); + } + + const tweakedPrivateKey = tinysecp.privateAdd( + privateKey, + tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash) + ); + if (!tweakedPrivateKey) { + throw new Error("Invalid tweaked private key!"); + } + + return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { + network: opts.network, + }); +} + +function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { + return bitcoin.crypto.taggedHash( + "TapTweak", + Buffer.concat(h ? [pubKey, h] : [pubKey]) + ); +} diff --git a/src/extension/background-script/actions/webbtc/signPsbtWithPrompt.ts b/src/extension/background-script/actions/webbtc/signPsbtWithPrompt.ts new file mode 100644 index 0000000000..0d0ccef65c --- /dev/null +++ b/src/extension/background-script/actions/webbtc/signPsbtWithPrompt.ts @@ -0,0 +1,26 @@ +import utils from "~/common/lib/utils"; +import { Message } from "~/types"; + +const signPsbtWithPrompt = async (message: Message) => { + const psbt = message.args.psbt; + if (typeof psbt !== "string") { + return { + error: "PSBT missing.", + }; + } + + try { + const response = await utils.openPrompt({ + ...message, + action: "confirmSignPsbt", + }); + return response; + } catch (e) { + console.error("signPsbt cancelled", e); + if (e instanceof Error) { + return { error: e.message }; + } + } +}; + +export default signPsbtWithPrompt; diff --git a/src/extension/background-script/router.ts b/src/extension/background-script/router.ts index 3a51420006..812c5b7564 100644 --- a/src/extension/background-script/router.ts +++ b/src/extension/background-script/router.ts @@ -59,6 +59,7 @@ const routes = { lnurl: lnurl, lnurlAuth: auth, getCurrencyRate: cache.getCurrencyRate, + signPsbt: webbtc.signPsbt, getAddress: webbtc.getAddress, setMnemonic: mnemonic.setMnemonic, getMnemonic: mnemonic.getMnemonic, @@ -77,6 +78,7 @@ const routes = { webbtc: { enable: allowances.enable, getInfo: webbtc.getInfo, + signPsbtWithPrompt: webbtc.signPsbtWithPrompt, getAddressWithPrompt: webbtc.getAddressWithPrompt, }, alby: { diff --git a/src/extension/content-script/onendwebbtc.js b/src/extension/content-script/onendwebbtc.js index ffec3824b2..9a60ad35b0 100644 --- a/src/extension/content-script/onendwebbtc.js +++ b/src/extension/content-script/onendwebbtc.js @@ -8,6 +8,7 @@ import shouldInject from "./shouldInject"; const webbtcCalls = [ "webbtc/enable", "webbtc/getInfo", + "webbtc/signPsbtWithPrompt", "webbtc/getAddressWithPrompt", ]; // calls that can be executed when `window.webbtc` is not enabled for the current content page diff --git a/src/extension/providers/webbtc/index.ts b/src/extension/providers/webbtc/index.ts index f3cf99e047..9bfaf376d1 100644 --- a/src/extension/providers/webbtc/index.ts +++ b/src/extension/providers/webbtc/index.ts @@ -37,6 +37,14 @@ export default class WebBTCProvider { return this.execute("getInfo"); } + signPsbt(psbt: string) { + if (!this.enabled) { + throw new Error("Provider must be enabled before calling signPsbt"); + } + + return this.execute("signPsbtWithPrompt", { psbt }); + } + sendTransaction(address: string, amount: string) { if (!this.enabled) { throw new Error( diff --git a/src/fixtures/btc.ts b/src/fixtures/btc.ts index c843532f74..5b6f46ce88 100644 --- a/src/fixtures/btc.ts +++ b/src/fixtures/btc.ts @@ -1,4 +1,15 @@ export const btcFixture = { + // generated in sparrow wallet using mock mnemonic below, + // native taproot derivation: m/86'/1'/0' - 1 input ("m/86'/1'/0'/0/0" - first receive address), 2 outputs, saved as binary PSBT file + // imported using `cat taproot.psbt | xxd -p -c 1000` + regtestTaprootPsbt: + "70736274ff0100890200000001b58806ecf5f7a2677dce4d357cc3ef14c59586db851f253e7d495b6506e7c8210100000000fdffffff02a54a4c0000000000225120d73acf6667b7850e6e00e21bb4525a2c5e2d88956a3d2c35b0bc06f4b0df01bc404b4c00000000002251202befa14431d4cb71889ea1df7a7eaa2f1d8b9107e60b01564e15dabe5c0dfd32340100004f01043587cf03e017d1bb8000000001835c0b51218376c61455428a9f47bfcce1f6ce8397ead7b00387e9d9ea568302cfbd7100311e0e85844c3738728314394eb8302a6b5070d692e41b14ba8180901073c5da0a5600008001000080000000800001007d020000000184d4669ffd8232e83b7bf70fd8425b913f83e8664ab7128f196b70c33afc8d9e0100000000fdffffff02dc556202000000001600147d221583ec7f1023a7188ce4e8d2836ff96aac1380969800000000002251203b82b2b2a9185315da6f80da5f06d0440d8a5e1457fa93387c2d919c86ec87862a01000001012b80969800000000002251203b82b2b2a9185315da6f80da5f06d0440d8a5e1457fa93387c2d919c86ec878601030400000000211655355ca83c973f1d97ce0e3843c85d78905af16b4dc531bc488e57212d230116190073c5da0a560000800100008000000080000000000000000001172055355ca83c973f1d97ce0e3843c85d78905af16b4dc531bc488e57212d230116002107b10ac97f676cf1f3ccdacb0b78171282bbe94a94df143201700dc59bcc15f368190073c5da0a5600008001000080000000800100000000000000010520b10ac97f676cf1f3ccdacb0b78171282bbe94a94df143201700dc59bcc15f3680021073058679f6d60b87ef921d98a2a9a1f1e0779dae27bedbd1cdb2f147a07835ac9190073c5da0a56000080010000800000008000000000010000000105203058679f6d60b87ef921d98a2a9a1f1e0779dae27bedbd1cdb2f147a07835ac900", + + // signed PSBT and verified by importing in sparrow and broadcasting transaction + // echo hex | xxd -r -p > taproot_signed.psbt + regtestTaprootSignedPsbt: + "02000000000101b58806ecf5f7a2677dce4d357cc3ef14c59586db851f253e7d495b6506e7c8210100000000fdffffff02a54a4c0000000000225120d73acf6667b7850e6e00e21bb4525a2c5e2d88956a3d2c35b0bc06f4b0df01bc404b4c00000000002251202befa14431d4cb71889ea1df7a7eaa2f1d8b9107e60b01564e15dabe5c0dfd32014091d48b7c4bb1dc7cb4d0da360dfd0ca35ea1e73ca6f1891c25a6a3bd90a6269eaa2ee97bca15969181981eb1abb1c9ab8574add9453355b00b521069dca7dc1634010000", + mnemnoic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", }; diff --git a/src/types.ts b/src/types.ts index 21c9c9cf12..dd10c2210e 100644 --- a/src/types.ts +++ b/src/types.ts @@ -518,6 +518,13 @@ export interface MessageDecryptGet extends MessageDefault { action: "decrypt"; } +export interface MessageSignPsbt extends MessageDefault { + args: { + psbt: string; + }; + action: "signPsbt"; +} + export interface MessageGetAddress extends MessageDefault { // eslint-disable-next-line @typescript-eslint/ban-types args: {}; diff --git a/webpack.config.js b/webpack.config.js index 6d3681ce90..d02d402f33 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -61,6 +61,11 @@ var options = { }, mode: nodeEnv, + experiments: { + // TODO: remove along with tiny-secp256k1 + asyncWebAssembly: true, + }, + entry: { manifest: "./src/manifest.json", background: "./src/extension/background-script/index.ts", diff --git a/yarn.lock b/yarn.lock index 26f2d6f561..32e97643b1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -2565,6 +2565,11 @@ bip174@^2.0.1: resolved "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz" integrity sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ== +bip174@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.0.tgz#cd3402581feaa5116f0f00a0eaee87a5843a2d30" + integrity sha512-lkc0XyiX9E9KiVAS1ZiOqK1xfiwvf4FXDDdkDq5crcDzOq+xGytY+14qCsqz7kCiy8rpN1CRNfacRhf9G3JNSA== + bitcoinjs-lib@^6.0.0: version "6.0.1" resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.0.1.tgz#4fa9438bb86a0449451ac58607e83d9b5a7732e6" @@ -2578,6 +2583,20 @@ bitcoinjs-lib@^6.0.0: varuint-bitcoin "^1.1.2" wif "^2.0.1" +bitcoinjs-lib@^6.1.0: + version "6.1.0" + resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.0.tgz#2e3123d63eab5e8e752fd7e2f237314f35ed738f" + integrity sha512-eupi1FBTJmPuAZdChnzTXLv2HBqFW2AICpzXZQLniP0V9FWWeeUQSMKES6sP8isy/xO0ijDexbgkdEyFVrsuJw== + dependencies: + bech32 "^2.0.0" + bip174 "^2.1.0" + bs58check "^2.1.2" + create-hash "^1.1.0" + ripemd160 "^2.0.2" + typeforce "^1.11.3" + varuint-bitcoin "^1.1.2" + wif "^2.0.1" + bl@^4.0.3, bl@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/bl/-/bl-4.1.0.tgz#451535264182bec2fbbc83a62ab98cf11d9f7b3a" @@ -3919,6 +3938,15 @@ eastasianwidth@^0.2.0: resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== +ecpair@^2.1.0: + version "2.1.0" + resolved "https://registry.yarnpkg.com/ecpair/-/ecpair-2.1.0.tgz#673f826b1d80d5eb091b8e2010c6b588e8d2cb45" + integrity sha512-cL/mh3MtJutFOvFc27GPZE2pWL3a3k4YvzUWEOvilnfZVlH3Jwgx/7d6tlD7/75tNk8TG2m+7Kgtz0SI1tWcqw== + dependencies: + randombytes "^2.1.0" + typeforce "^1.18.0" + wif "^2.0.6" + ee-first@1.1.1: version "1.1.1" resolved "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz" @@ -8534,7 +8562,7 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -ripemd160@^2.0.1: +ripemd160@^2.0.1, ripemd160@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz" integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== @@ -9421,6 +9449,13 @@ thunky@^1.0.2: resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== +tiny-secp256k1@^2.2.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-2.2.1.tgz#a61d4791b7031aa08a9453178a131349c3e10f9b" + integrity sha512-/U4xfVqnVxJXN4YVsru0E6t5wVncu2uunB8+RVR40fYUxkKYUPS10f+ePQZgFBoE/Jbf9H1NBveupF2VmB58Ng== + dependencies: + uint8array-tools "0.0.7" + tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -9639,7 +9674,7 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typeforce@^1.11.3: +typeforce@^1.11.3, typeforce@^1.18.0: version "1.18.0" resolved "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz" integrity sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g== @@ -9668,6 +9703,11 @@ typeson@^6.0.0, typeson@^6.1.0: resolved "https://registry.yarnpkg.com/typeson/-/typeson-6.1.0.tgz#5b2a53705a5f58ff4d6f82f965917cabd0d7448b" integrity sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA== +uint8array-tools@0.0.7: + version "0.0.7" + resolved "https://registry.yarnpkg.com/uint8array-tools/-/uint8array-tools-0.0.7.tgz#a7a2bb5d8836eae2fade68c771454e6a438b390d" + integrity sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ== + unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz" @@ -10128,7 +10168,7 @@ which@^2.0.1: dependencies: isexe "^2.0.0" -wif@^2.0.1: +wif@^2.0.1, wif@^2.0.6: version "2.0.6" resolved "https://registry.npmjs.org/wif/-/wif-2.0.6.tgz" integrity sha1-CNP1IFbGZnkplyb63g1DKudLRwQ= From ed164b68d1497eaf42fc5834bfce11cd2b467737 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Aaron?= Date: Fri, 7 Jul 2023 13:15:08 +0200 Subject: [PATCH 02/73] fix: update keyboard shortcut to mv3 --- src/manifest.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/manifest.json b/src/manifest.json index cf6226c257..8e9df12426 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -130,7 +130,7 @@ "default_title": "Alby - Bitcoin Lightning Wallet" }, "commands": { - "_execute_browser_action": { + "_execute_action": { "suggested_key": { "default": "Alt+Shift+A" } From 92f6b698048b1dba1ffd78b875f761fb62d5632e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Aaron?= Date: Fri, 7 Jul 2023 13:41:11 +0200 Subject: [PATCH 03/73] fix: mv2 + mv3 --- src/manifest.json | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/src/manifest.json b/src/manifest.json index 8e9df12426..e1afc008bb 100644 --- a/src/manifest.json +++ b/src/manifest.json @@ -130,7 +130,12 @@ "default_title": "Alby - Bitcoin Lightning Wallet" }, "commands": { - "_execute_action": { + "__chrome___execute_action": { + "suggested_key": { + "default": "Alt+Shift+A" + } + }, + "__firefox___execute_browser_action": { "suggested_key": { "default": "Alt+Shift+A" } From 6a959dda9269cef67beb674991f4991674ef1eac Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 5 Sep 2023 15:02:47 +0700 Subject: [PATCH 04/73] feat: move signpsbt to bitcoin object --- package.json | 3 +- .../actions/accounts/__tests__/get.test.ts | 2 +- .../actions/lnurl/__tests__/auth.test.ts | 4 +- .../webbtc/__tests__/getAddress.test.ts | 6 +- .../actions/webbtc/__tests__/signPsbt.test.ts | 10 +- .../actions/webbtc/signPsbt.ts | 108 +------------ .../background-script/bitcoin/index.ts | 143 +++++++++++------- .../background-script/bitcoin/networks.ts | 56 +++++++ .../background-script/liquid/index.ts | 7 +- src/fixtures/btc.ts | 2 +- webpack.config.js | 5 - yarn.lock | 45 +++--- 12 files changed, 187 insertions(+), 204 deletions(-) create mode 100644 src/extension/background-script/bitcoin/networks.ts diff --git a/package.json b/package.json index 0f9930036e..743dc7543e 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ }, "dependencies": { "@bitcoin-design/bitcoin-icons-react": "^0.1.9", + "@bitcoinerlab/secp256k1": "^1.0.5", "@getalby/sdk": "^2.2.3", "@headlessui/react": "^1.7.16", "@lightninglabs/lnc-web": "^0.2.4-alpha", @@ -51,9 +52,9 @@ "bitcoinjs-lib": "^6.1.0", "bolt11": "^1.4.1", "crypto-js": "^4.1.1", - "ecpair": "^2.1.0", "dayjs": "^1.11.9", "dexie": "^3.2.4", + "ecpair": "^2.1.0", "elliptic": "^6.5.4", "events": "^3.3.0", "html5-qrcode": "^2.3.8", diff --git a/src/extension/background-script/actions/accounts/__tests__/get.test.ts b/src/extension/background-script/actions/accounts/__tests__/get.test.ts index bf0ddd4f8b..6022ed3c94 100644 --- a/src/extension/background-script/actions/accounts/__tests__/get.test.ts +++ b/src/extension/background-script/actions/accounts/__tests__/get.test.ts @@ -27,7 +27,7 @@ const mockState = { id: "8b7f1dc6-ab87-4c6c-bca5-19fa8632731e", name: "Alby", nostrPrivateKey: "nostr-123-456", - mnemonic: btcFixture.mnemnoic, + mnemonic: btcFixture.mnemonic, bitcoinNetwork: "regtest", useMnemonicForLnurlAuth: true, }, diff --git a/src/extension/background-script/actions/lnurl/__tests__/auth.test.ts b/src/extension/background-script/actions/lnurl/__tests__/auth.test.ts index 8832b92524..c8db39b2a8 100644 --- a/src/extension/background-script/actions/lnurl/__tests__/auth.test.ts +++ b/src/extension/background-script/actions/lnurl/__tests__/auth.test.ts @@ -39,11 +39,11 @@ describe("auth with mnemonic", () => { password: passwordMock, currentAccountId: "1e1e8ea6-493e-480b-9855-303d37506e97", getAccount: () => ({ - mnemonic: btcFixture.mnemnoic, + mnemonic: btcFixture.mnemonic, bitcoinNetwork: "regtest", useMnemonicForLnurlAuth: true, }), - getMnemonic: () => new Mnemonic(btcFixture.mnemnoic), + getMnemonic: () => new Mnemonic(btcFixture.mnemonic), getConnector: jest.fn(), }; diff --git a/src/extension/background-script/actions/webbtc/__tests__/getAddress.test.ts b/src/extension/background-script/actions/webbtc/__tests__/getAddress.test.ts index 055eded865..58bfdd6f7b 100644 --- a/src/extension/background-script/actions/webbtc/__tests__/getAddress.test.ts +++ b/src/extension/background-script/actions/webbtc/__tests__/getAddress.test.ts @@ -11,11 +11,11 @@ const mockState = { password: passwordMock, currentAccountId: "1e1e8ea6-493e-480b-9855-303d37506e97", getAccount: () => ({ - mnemonic: btcFixture.mnemnoic, + mnemonic: btcFixture.mnemonic, bitcoinNetwork: "regtest", }), - getMnemonic: () => new Mnemonic(btcFixture.mnemnoic), - getBitcoin: () => new Bitcoin(new Mnemonic(btcFixture.mnemnoic), "regtest"), + getMnemonic: () => new Mnemonic(btcFixture.mnemonic), + getBitcoin: () => new Bitcoin(new Mnemonic(btcFixture.mnemonic), "regtest"), getConnector: jest.fn(), }; diff --git a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts index 58ecd040db..d16a28ba73 100644 --- a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts +++ b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts @@ -2,6 +2,8 @@ import { hex } from "@scure/base"; import * as btc from "@scure/btc-signer"; import { getPsbtPreview } from "~/common/lib/psbt"; import signPsbt from "~/extension/background-script/actions/webbtc/signPsbt"; +import Bitcoin from "~/extension/background-script/bitcoin"; +import Mnemonic from "~/extension/background-script/mnemonic"; import state from "~/extension/background-script/state"; import { btcFixture } from "~/fixtures/btc"; import type { MessageSignPsbt } from "~/types"; @@ -12,12 +14,12 @@ const mockState = { password: passwordMock, currentAccountId: "1e1e8ea6-493e-480b-9855-303d37506e97", getAccount: () => ({ - mnemonic: btcFixture.mnemnoic, + mnemonic: btcFixture.mnemonic, + bitcoinNetwork: "regtest", }), + getMnemonic: () => new Mnemonic(btcFixture.mnemonic), + getBitcoin: () => new Bitcoin(new Mnemonic(btcFixture.mnemonic), "regtest"), getConnector: jest.fn(), - settings: { - bitcoinNetwork: "regtest", - }, }; state.getState = jest.fn().mockReturnValue(mockState); diff --git a/src/extension/background-script/actions/webbtc/signPsbt.ts b/src/extension/background-script/actions/webbtc/signPsbt.ts index 1c9efd6484..23823827d6 100644 --- a/src/extension/background-script/actions/webbtc/signPsbt.ts +++ b/src/extension/background-script/actions/webbtc/signPsbt.ts @@ -1,76 +1,11 @@ -import * as secp256k1 from "@noble/secp256k1"; -import * as bitcoin from "bitcoinjs-lib"; -import ECPairFactory, { ECPairAPI } from "ecpair"; -import * as tinysecp from "tiny-secp256k1"; -import { decryptData } from "~/common/lib/crypto"; -import { - BTC_TAPROOT_DERIVATION_PATH, - BTC_TAPROOT_DERIVATION_PATH_REGTEST, - derivePrivateKey, -} from "~/common/lib/mnemonic"; import state from "~/extension/background-script/state"; import { MessageSignPsbt } from "~/types"; const signPsbt = async (message: MessageSignPsbt) => { try { - // TODO: is this the correct way to decrypt the mnmenonic? - const password = await state.getState().password(); - if (!password) { - throw new Error("No password set"); - } - const account = await state.getState().getAccount(); - if (!account) { - throw new Error("No account selected"); - } - if (!account.mnemonic) { - throw new Error("No mnemonic set"); - } - const mnemonic = decryptData(account.mnemonic, password); - const settings = state.getState().settings; - - const derivationPath = - settings.bitcoinNetwork === "bitcoin" - ? BTC_TAPROOT_DERIVATION_PATH - : BTC_TAPROOT_DERIVATION_PATH_REGTEST; - - const privateKey = secp256k1.utils.hexToBytes( - derivePrivateKey(mnemonic, derivationPath) - ); - - const taprootPsbt = bitcoin.Psbt.fromHex(message.args.psbt, { - network: bitcoin.networks[settings.bitcoinNetwork], - }); - - // fix usages of window (unavailable in service worker) - globalThis.window ??= globalThis.window || {}; - if (!globalThis.window.crypto) { - globalThis.window.crypto = crypto; - } - - bitcoin.initEccLib(tinysecp); - const ECPair: ECPairAPI = ECPairFactory(tinysecp); - - const keyPair = tweakSigner( - ECPair, - ECPair.fromPrivateKey(Buffer.from(privateKey), { - network: bitcoin.networks[settings.bitcoinNetwork], - }), - { - network: bitcoin.networks[settings.bitcoinNetwork], - } - ); - - // Step 1: Sign the Taproot PSBT inputs - taprootPsbt.data.inputs.forEach((input, index) => { - taprootPsbt.signTaprootInput(index, keyPair); - }); - - // Step 2: Finalize the Taproot PSBT - taprootPsbt.finalizeAllInputs(); - - // Step 3: Get the finalized transaction - const signedTransaction = taprootPsbt.extractTransaction().toHex(); + const bitcoin = await state.getState().getBitcoin(); + const signedTransaction = await bitcoin.signPsbt(message.args.psbt); return { data: { signed: signedTransaction, @@ -85,42 +20,3 @@ const signPsbt = async (message: MessageSignPsbt) => { }; export default signPsbt; - -// Below code taken from https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.spec.ts#L636 -const toXOnly = (pubKey: Buffer) => - pubKey.length === 32 ? pubKey : pubKey.slice(1, 33); - -function tweakSigner( - ECPair: ECPairAPI, - signer: bitcoin.Signer, - opts: { network: bitcoin.Network; tweakHash?: Buffer | undefined } -): bitcoin.Signer { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - // @ts-ignore - let privateKey: Uint8Array | undefined = signer.privateKey; - if (!privateKey) { - throw new Error("Private key is required for tweaking signer!"); - } - if (signer.publicKey[0] === 3) { - privateKey = tinysecp.privateNegate(privateKey); - } - - const tweakedPrivateKey = tinysecp.privateAdd( - privateKey, - tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash) - ); - if (!tweakedPrivateKey) { - throw new Error("Invalid tweaked private key!"); - } - - return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { - network: opts.network, - }); -} - -function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { - return bitcoin.crypto.taggedHash( - "TapTweak", - Buffer.concat(h ? [pubKey, h] : [pubKey]) - ); -} diff --git a/src/extension/background-script/bitcoin/index.ts b/src/extension/background-script/bitcoin/index.ts index 1a649ec330..43bd337656 100644 --- a/src/extension/background-script/bitcoin/index.ts +++ b/src/extension/background-script/bitcoin/index.ts @@ -1,11 +1,19 @@ import * as secp256k1 from "@noble/secp256k1"; import * as btc from "@scure/btc-signer"; +import * as bitcoin from "bitcoinjs-lib"; +import ECPairFactory, { ECPairAPI } from "ecpair"; +import { + Network, + networks, +} from "~/extension/background-script/bitcoin/networks"; import Mnemonic from "~/extension/background-script/mnemonic"; import { BitcoinAddress, BitcoinNetworkType } from "~/types"; const BTC_TAPROOT_DERIVATION_PATH = "m/86'/0'/0'/0"; const BTC_TAPROOT_DERIVATION_PATH_REGTEST = "m/86'/1'/0'/0"; +import * as ecc from "@bitcoinerlab/secp256k1"; + class Bitcoin { readonly networkType: BitcoinNetworkType; readonly mnemonic: Mnemonic; @@ -16,6 +24,53 @@ class Bitcoin { this.networkType = networkType; this.network = networks[this.networkType]; } + + signPsbt(psbt: string) { + const index = 0; + const derivationPathWithoutIndex = + this.networkType === "bitcoin" + ? BTC_TAPROOT_DERIVATION_PATH + : BTC_TAPROOT_DERIVATION_PATH_REGTEST; + + const derivationPath = `${derivationPathWithoutIndex}/${index}`; + const derivedKey = this.mnemonic.deriveKey(derivationPath); + + const taprootPsbt = bitcoin.Psbt.fromHex(psbt, { + network: this.network, + }); + + // // fix usages of window (unavailable in service worker) + // globalThis.window ??= globalThis.window || {}; + // if (!globalThis.window.crypto) { + // globalThis.window.crypto = crypto; + // } + + bitcoin.initEccLib(ecc); + const ECPair: ECPairAPI = ECPairFactory(ecc); + + const keyPair = tweakSigner( + ECPair, + ECPair.fromPrivateKey(Buffer.from(derivedKey.privateKey as Uint8Array), { + network: this.network, + }), + { + network: this.network, + } + ); + + // Step 1: Sign the Taproot PSBT inputs + taprootPsbt.data.inputs.forEach((input, index) => { + taprootPsbt.signTaprootInput(index, keyPair); + }); + + // Step 2: Finalize the Taproot PSBT + taprootPsbt.finalizeAllInputs(); + + // Step 3: Get the finalized transaction + const signedTransaction = taprootPsbt.extractTransaction().toHex(); + + return signedTransaction; + } getTaprootAddress(): BitcoinAddress { const index = 0; const derivationPathWithoutIndex = @@ -45,59 +100,41 @@ class Bitcoin { export default Bitcoin; -// from https://github1s.com/bitcoinjs/bitcoinjs-lib -interface Network { - messagePrefix: string; - bech32: string; - bip32: Bip32; - pubKeyHash: number; - scriptHash: number; - wif: number; -} +// Below code taken from https://github.com/bitcoinjs/bitcoinjs-lib/blob/master/test/integration/taproot.spec.ts#L636 +const toXOnly = (pubKey: Buffer) => + pubKey.length === 32 ? pubKey : pubKey.slice(1, 33); -interface Bip32 { - public: number; - private: number; +function tweakSigner( + ECPair: ECPairAPI, + signer: bitcoin.Signer, + opts: { network: bitcoin.Network; tweakHash?: Buffer | undefined } +): bitcoin.Signer { + // eslint-disable-next-line @typescript-eslint/ban-ts-comment + // @ts-ignore + let privateKey: Uint8Array | undefined = signer.privateKey; + if (!privateKey) { + throw new Error("Private key is required for tweaking signer!"); + } + if (signer.publicKey[0] === 3) { + privateKey = ecc.privateNegate(privateKey); + } + + const tweakedPrivateKey = ecc.privateAdd( + privateKey, + tapTweakHash(toXOnly(signer.publicKey), opts.tweakHash) + ); + if (!tweakedPrivateKey) { + throw new Error("Invalid tweaked private key!"); + } + + return ECPair.fromPrivateKey(Buffer.from(tweakedPrivateKey), { + network: opts.network, + }); } -const bitcoin: Network = { - messagePrefix: "\x18Bitcoin Signed Message:\n", - bech32: "bc", - bip32: { - public: 0x0488b21e, - private: 0x0488ade4, - }, - pubKeyHash: 0x00, - scriptHash: 0x05, - wif: 0x80, -}; - -export const testnet: Network = { - messagePrefix: "\x18Bitcoin Signed Message:\n", - bech32: "tb", - bip32: { - public: 0x043587cf, - private: 0x04358394, - }, - pubKeyHash: 0x6f, - scriptHash: 0xc4, - wif: 0xef, -}; - -const regtest: Network = { - messagePrefix: "\x18Bitcoin Signed Message:\n", - bech32: "bcrt", - bip32: { - public: 0x043587cf, - private: 0x04358394, - }, - pubKeyHash: 0x6f, - scriptHash: 0xc4, - wif: 0xef, -}; - -export const networks = { - bitcoin, - testnet, - regtest, -}; +function tapTweakHash(pubKey: Buffer, h: Buffer | undefined): Buffer { + return bitcoin.crypto.taggedHash( + "TapTweak", + Buffer.concat(h ? [pubKey, h] : [pubKey]) + ); +} diff --git a/src/extension/background-script/bitcoin/networks.ts b/src/extension/background-script/bitcoin/networks.ts new file mode 100644 index 0000000000..2a06821f78 --- /dev/null +++ b/src/extension/background-script/bitcoin/networks.ts @@ -0,0 +1,56 @@ +// from https://github1s.com/bitcoinjs/bitcoinjs-lib +export interface Network { + messagePrefix: string; + bech32: string; + bip32: Bip32; + pubKeyHash: number; + scriptHash: number; + wif: number; +} + +interface Bip32 { + public: number; + private: number; +} + +const bitcoin: Network = { + messagePrefix: "\x18Bitcoin Signed Message:\n", + bech32: "bc", + bip32: { + public: 0x0488b21e, + private: 0x0488ade4, + }, + pubKeyHash: 0x00, + scriptHash: 0x05, + wif: 0x80, +}; + +export const testnet: Network = { + messagePrefix: "\x18Bitcoin Signed Message:\n", + bech32: "tb", + bip32: { + public: 0x043587cf, + private: 0x04358394, + }, + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef, +}; + +const regtest: Network = { + messagePrefix: "\x18Bitcoin Signed Message:\n", + bech32: "bcrt", + bip32: { + public: 0x043587cf, + private: 0x04358394, + }, + pubKeyHash: 0x6f, + scriptHash: 0xc4, + wif: 0xef, +}; + +export const networks = { + bitcoin, + testnet, + regtest, +}; diff --git a/src/extension/background-script/liquid/index.ts b/src/extension/background-script/liquid/index.ts index c436c5f16f..a0ec6a6fd5 100644 --- a/src/extension/background-script/liquid/index.ts +++ b/src/extension/background-script/liquid/index.ts @@ -20,7 +20,6 @@ import { getPsetPreview } from "~/extension/background-script/liquid/pset"; import Mnemonic from "~/extension/background-script/mnemonic"; import { LiquidNetworkType, PsetPreview } from "~/types"; -import * as tinysecp from "../liquid/secp256k1"; import * as tinysecp256k1Adapter from "./secp256k1"; const LIQUID_DERIVATION_PATH = "m/84'/1776'/0'/0/0"; @@ -174,7 +173,7 @@ class Liquid { signer.addSignature( inIndex, partialSig, - Pset.SchnorrSigValidator(tinysecp) + Pset.SchnorrSigValidator(tinysecp256k1Adapter) ); continue; @@ -221,7 +220,7 @@ class Liquid { signer.addSignature( inIndex, partialSig, - Pset.SchnorrSigValidator(tinysecp) + Pset.SchnorrSigValidator(tinysecp256k1Adapter) ); } } @@ -241,7 +240,7 @@ class Liquid { } private deriveLiquidMasterBlindingKey(): string { - return SLIP77Factory(tinysecp) + return SLIP77Factory(tinysecp256k1Adapter) .fromSeed(Buffer.from(bip39.mnemonicToSeedSync(this.mnemonic.mnemonic))) .masterKey.toString("hex"); } diff --git a/src/fixtures/btc.ts b/src/fixtures/btc.ts index 5b6f46ce88..b1e407a222 100644 --- a/src/fixtures/btc.ts +++ b/src/fixtures/btc.ts @@ -10,6 +10,6 @@ export const btcFixture = { regtestTaprootSignedPsbt: "02000000000101b58806ecf5f7a2677dce4d357cc3ef14c59586db851f253e7d495b6506e7c8210100000000fdffffff02a54a4c0000000000225120d73acf6667b7850e6e00e21bb4525a2c5e2d88956a3d2c35b0bc06f4b0df01bc404b4c00000000002251202befa14431d4cb71889ea1df7a7eaa2f1d8b9107e60b01564e15dabe5c0dfd32014091d48b7c4bb1dc7cb4d0da360dfd0ca35ea1e73ca6f1891c25a6a3bd90a6269eaa2ee97bca15969181981eb1abb1c9ab8574add9453355b00b521069dca7dc1634010000", - mnemnoic: + mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", }; diff --git a/webpack.config.js b/webpack.config.js index e6cedfda0c..b7e4ad3f21 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -151,11 +151,6 @@ var options = { }, mode: nodeEnv, - experiments: { - // TODO: remove along with tiny-secp256k1 - asyncWebAssembly: true, - }, - entry: { manifest: "./src/manifest.json", background: "./src/extension/background-script/index.ts", diff --git a/yarn.lock b/yarn.lock index f5d5a64e34..1736a279a7 100644 --- a/yarn.lock +++ b/yarn.lock @@ -400,6 +400,14 @@ resolved "https://registry.yarnpkg.com/@bitcoin-design/bitcoin-icons-react/-/bitcoin-icons-react-0.1.9.tgz#25c18808f167e242cd15ba27c185d785f2728980" integrity sha512-nJvTD1+zG/ffHdMeGQ39vdsmEFo9WcCIP1RlR7ZpZoP2H+IgKwzwow8VSY6ebroLoCT7WWtUPJQSbgQwgWYrFg== +"@bitcoinerlab/secp256k1@^1.0.5": + version "1.0.5" + resolved "https://registry.yarnpkg.com/@bitcoinerlab/secp256k1/-/secp256k1-1.0.5.tgz#4643ba73619c24c7c455cc63c6338c69c2cf187c" + integrity sha512-8gT+ukTCFN2rTxn4hD9Jq3k+UJwcprgYjfK/SQUSLgznXoIgsBnlPuARMkyyuEjycQK9VvnPiejKdszVTflh+w== + dependencies: + "@noble/hashes" "^1.1.5" + "@noble/secp256k1" "^1.7.1" + "@commitlint/cli@^17.7.1": version "17.7.1" resolved "https://registry.yarnpkg.com/@commitlint/cli/-/cli-17.7.1.tgz#f3ab35bd38d82fcd4ab03ec5a1e9db26d57fe1b0" @@ -1036,6 +1044,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" integrity sha512-EbqwksQwz9xDRGfDST86whPBgM65E0OH/pCgqW0GBVzO22bNE+NuIbeTb714+IfSjU3aRk47EUvXIb5bTsenKA== +"@noble/hashes@^1.1.5": + version "1.3.2" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" + integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== + "@noble/secp256k1@^1.7.1": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -2700,11 +2713,6 @@ bip174-liquid@^1.0.3: resolved "https://registry.yarnpkg.com/bip174-liquid/-/bip174-liquid-1.0.3.tgz#5009444091da80277c45ee90c255d7919646f907" integrity sha512-e69sC0Cq2tBJuhG2+wieXv40DN13YBR/wbIjZp4Mqwpar5vQm8Ldqijdd6N33XG7LtfvQi/zKm5fSzdPY/9mgw== -bip174@^2.0.1: - version "2.0.1" - resolved "https://registry.npmjs.org/bip174/-/bip174-2.0.1.tgz" - integrity sha512-i3X26uKJOkDTAalYAp0Er+qGMDhrbbh2o93/xiPyAN2s25KrClSpe3VXo/7mNJoqA5qfko8rLS2l3RWZgYmjKQ== - bip174@^2.1.0: version "2.1.0" resolved "https://registry.yarnpkg.com/bip174/-/bip174-2.1.0.tgz#cd3402581feaa5116f0f00a0eaee87a5843a2d30" @@ -2717,20 +2725,21 @@ bip66@^1.1.0: dependencies: safe-buffer "^5.0.1" -bitcoinjs-lib@^6.0.0: - version "6.0.1" - resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.0.1.tgz#4fa9438bb86a0449451ac58607e83d9b5a7732e6" - integrity sha512-x/7D4jDj/MMkmO6t3p2CSDXTqpwZ/jRsRiJDmaiXabrR9XRo7jwby8HRn7EyK1h24rKFFI7vI0ay4czl6bDOZQ== +bitcoinjs-lib@^6.0.0, bitcoinjs-lib@^6.0.2: + version "6.1.0" + resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.0.tgz#2e3123d63eab5e8e752fd7e2f237314f35ed738f" + integrity sha512-eupi1FBTJmPuAZdChnzTXLv2HBqFW2AICpzXZQLniP0V9FWWeeUQSMKES6sP8isy/xO0ijDexbgkdEyFVrsuJw== dependencies: bech32 "^2.0.0" - bip174 "^2.0.1" + bip174 "^2.1.0" bs58check "^2.1.2" create-hash "^1.1.0" + ripemd160 "^2.0.2" typeforce "^1.11.3" varuint-bitcoin "^1.1.2" wif "^2.0.1" -bitcoinjs-lib@^6.0.2: +bitcoinjs-lib@^6.1.0: version "6.1.3" resolved "https://registry.yarnpkg.com/bitcoinjs-lib/-/bitcoinjs-lib-6.1.3.tgz#88aed5a8d052e9faa04c6402d3f0865441f928d7" integrity sha512-TYXs/Qf+GNk2nnsB9HrXWqzFuEgCg0Gx+v3UW3B8VuceFHXVvhT+7hRnTSvwkX0i8rz2rtujeU6gFaDcFqYFDw== @@ -9051,7 +9060,7 @@ rimraf@^3.0.2: dependencies: glob "^7.1.3" -ripemd160@^2.0.0, ripemd160@^2.0.1: +ripemd160@^2.0.0, ripemd160@^2.0.1, ripemd160@^2.0.2: version "2.0.2" resolved "https://registry.npmjs.org/ripemd160/-/ripemd160-2.0.2.tgz" integrity sha512-ii4iagi25WusVoiC4B4lq7pbXfAp3D9v5CwfkY33vffw2+pkDjY1D8GaN7spsxvCSx8dkPqOZCEZyfxcmJG2IA== @@ -9967,13 +9976,6 @@ thunky@^1.0.2: resolved "https://registry.npmjs.org/thunky/-/thunky-1.1.0.tgz" integrity sha512-eHY7nBftgThBqOyHGVN+l8gF0BucP09fMo0oO/Lb0w1OF80dJv+lDVpXG60WMQvkcxAkNybKsrEIE3ZtKGmPrA== -tiny-secp256k1@^2.2.1: - version "2.2.1" - resolved "https://registry.yarnpkg.com/tiny-secp256k1/-/tiny-secp256k1-2.2.1.tgz#a61d4791b7031aa08a9453178a131349c3e10f9b" - integrity sha512-/U4xfVqnVxJXN4YVsru0E6t5wVncu2uunB8+RVR40fYUxkKYUPS10f+ePQZgFBoE/Jbf9H1NBveupF2VmB58Ng== - dependencies: - uint8array-tools "0.0.7" - tmp@^0.0.33: version "0.0.33" resolved "https://registry.yarnpkg.com/tmp/-/tmp-0.0.33.tgz#6d34335889768d21b2bcda0aa277ced3b1bfadf9" @@ -10253,11 +10255,6 @@ typeson@^6.0.0, typeson@^6.1.0: resolved "https://registry.yarnpkg.com/typeson/-/typeson-6.1.0.tgz#5b2a53705a5f58ff4d6f82f965917cabd0d7448b" integrity sha512-6FTtyGr8ldU0pfbvW/eOZrEtEkczHRUtduBnA90Jh9kMPCiFNnXIon3vF41N0S4tV1HHQt4Hk1j4srpESziCaA== -uint8array-tools@0.0.7: - version "0.0.7" - resolved "https://registry.yarnpkg.com/uint8array-tools/-/uint8array-tools-0.0.7.tgz#a7a2bb5d8836eae2fade68c771454e6a438b390d" - integrity sha512-vrrNZJiusLWoFWBqz5Y5KMCgP9W9hnjZHzZiZRT8oNAkq3d5Z5Oe76jAvVVSRh4U8GGR90N2X1dWtrhvx6L8UQ== - unbox-primitive@^1.0.1: version "1.0.1" resolved "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.1.tgz" From 59560ff41a712ad56d8fd78c6eee72843ad37158 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 5 Sep 2023 16:12:55 +0700 Subject: [PATCH 05/73] feat: move getPsbtPreview to background script, update signPsbt dialog --- src/app/screens/ConfirmSignPsbt/index.tsx | 40 +++++----- src/common/lib/api.ts | 9 +++ src/common/lib/psbt.ts | 76 ------------------- .../actions/webbtc/getPsbtPreview.ts | 21 +++++ .../background-script/actions/webbtc/index.ts | 7 +- .../background-script/bitcoin/index.ts | 69 ++++++++++++++++- src/extension/background-script/router.ts | 3 + src/i18n/locales/en/translation.json | 11 +++ src/types.ts | 15 ++++ 9 files changed, 149 insertions(+), 102 deletions(-) delete mode 100644 src/common/lib/psbt.ts create mode 100644 src/extension/background-script/actions/webbtc/getPsbtPreview.ts diff --git a/src/app/screens/ConfirmSignPsbt/index.tsx b/src/app/screens/ConfirmSignPsbt/index.tsx index b64222ee76..21a9a145ff 100644 --- a/src/app/screens/ConfirmSignPsbt/index.tsx +++ b/src/app/screens/ConfirmSignPsbt/index.tsx @@ -14,14 +14,13 @@ import { useNavigationState } from "~/app/hooks/useNavigationState"; import { USER_REJECTED_ERROR } from "~/common/constants"; import api from "~/common/lib/api"; import msg from "~/common/lib/msg"; -import { Address, PsbtPreview, getPsbtPreview } from "~/common/lib/psbt"; -import type { OriginData } from "~/types"; +import type { OriginData, PsbtPreview } from "~/types"; function ConfirmSignPsbt() { const navState = useNavigationState(); const { t: tCommon } = useTranslation("common"); const { t } = useTranslation("translation", { - keyPrefix: "confirm_sign_psbt", + keyPrefix: "bitcoin.confirm_sign_psbt", }); const navigate = useNavigate(); @@ -35,8 +34,8 @@ function ConfirmSignPsbt() { useEffect(() => { (async () => { - const settings = await api.getSettings(); - setPreview(getPsbtPreview(psbt, settings.bitcoinNetwork)); + const preview = await api.bitcoin.getPsbtPreview(psbt); + setPreview(preview); })(); }, [origin, psbt]); @@ -90,39 +89,36 @@ function ConfirmSignPsbt() { image={origin.icon} url={origin.host} /> -
- {t("warning")} -

{t("allow_sign", { host: origin.host })}

- {showAddresses ? t("hide_addresses") : t("view_addresses")} - - {"•"} - - {showHex ? t("hide_hex") : t("view_hex")} + {showAddresses ? t("hide_details") : t("view_details")}
{showAddresses && ( -
-

{t("input")}

- -
- )} - - {showAddresses && ( -
+ <> +

{t("inputs")}

+
+ {preview.inputs.map((input) => ( + + ))} +

{t("outputs")}

{preview.outputs.map((output) => ( ))}
-
+ + {showHex + ? t("hide_raw_transaction") + : t("view_raw_transaction")} + + )}
diff --git a/src/common/lib/api.ts b/src/common/lib/api.ts index 1af9c725ef..79c19becd4 100644 --- a/src/common/lib/api.ts +++ b/src/common/lib/api.ts @@ -27,6 +27,7 @@ import type { MessageLnurlAuth, MessageSettingsSet, NodeInfo, + PsbtPreview, PsetPreview, SettingsStorage, ValidateAccountResponse, @@ -235,6 +236,11 @@ const signPset = (pset: string): Promise => pset, }); +const getPsbtPreview = (psbt: string): Promise => + msg.request("webbtc/getPsbtPreview", { + psbt, + }); + export default { getAccount, getAccountInfo, @@ -278,4 +284,7 @@ export default { fetchAssetRegistry: fetchLiquidAssetRegistry, signPset: signPset, }, + bitcoin: { + getPsbtPreview, + }, }; diff --git a/src/common/lib/psbt.ts b/src/common/lib/psbt.ts deleted file mode 100644 index b92366005c..0000000000 --- a/src/common/lib/psbt.ts +++ /dev/null @@ -1,76 +0,0 @@ -import * as btc from "@scure/btc-signer"; -import { Psbt, networks } from "bitcoinjs-lib"; - -export type Address = { amount: number; address: string }; - -export type PsbtPreview = { - inputs: Address[]; - outputs: Address[]; -}; - -export function getPsbtPreview( - psbt: string, - networkType?: keyof typeof networks -): PsbtPreview { - const network = networkType ? networks[networkType] : undefined; - - const unsignedPsbt = Psbt.fromHex(psbt, { - network, - }); - - const preview: PsbtPreview = { - inputs: [], - outputs: [], - }; - - for (let i = 0; i < unsignedPsbt.data.inputs.length; i++) { - if (i > 0) { - throw new Error("Multiple inputs currently unsupported"); - } - - const tapBip32Derivation = unsignedPsbt.data.inputs[i].tapBip32Derivation; - if (!tapBip32Derivation) { - throw new Error("No bip32Derivation in input " + i); - } - const address = btc.p2tr( - tapBip32Derivation[0].pubkey, - undefined, - network - ).address; - - if (!address) { - throw new Error("No address found in input " + i); - } - const witnessUtxo = unsignedPsbt.data.inputs[i].witnessUtxo; - if (!witnessUtxo) { - throw new Error("No witnessUtxo in input " + i); - } - - preview.inputs.push({ - amount: witnessUtxo.value, - address, - }); - } - for (let i = 0; i < unsignedPsbt.data.outputs.length; i++) { - const txOutput = unsignedPsbt.txOutputs[i]; - const output = unsignedPsbt.data.outputs[i]; - if (!output.tapBip32Derivation) { - throw new Error("No tapBip32Derivation in output"); - } - const address = btc.p2tr( - output.tapBip32Derivation[0].pubkey, - undefined, - network - ).address; - if (!address) { - throw new Error("No address found in output " + i); - } - - const previewOutput: Address = { - amount: txOutput.value, - address, - }; - preview.outputs.push(previewOutput); - } - return preview; -} diff --git a/src/extension/background-script/actions/webbtc/getPsbtPreview.ts b/src/extension/background-script/actions/webbtc/getPsbtPreview.ts new file mode 100644 index 0000000000..6ae11784b2 --- /dev/null +++ b/src/extension/background-script/actions/webbtc/getPsbtPreview.ts @@ -0,0 +1,21 @@ +import state from "~/extension/background-script/state"; +import { MessageGetPsbtPreview } from "~/types"; + +const getPsbtPreview = async (message: MessageGetPsbtPreview) => { + try { + const bitcoin = await state.getState().getBitcoin(); + + const data = bitcoin.getPsbtPreview(message.args.psbt); + + return { + data, + }; + } catch (e) { + console.error("getPsbtPreview failed: ", e); + return { + error: "getPsbtPreview failed: " + e, + }; + } +}; + +export default getPsbtPreview; diff --git a/src/extension/background-script/actions/webbtc/index.ts b/src/extension/background-script/actions/webbtc/index.ts index 927b389837..733d0b2053 100644 --- a/src/extension/background-script/actions/webbtc/index.ts +++ b/src/extension/background-script/actions/webbtc/index.ts @@ -1,14 +1,15 @@ import getAddress from "~/extension/background-script/actions/webbtc/getAddress"; import getAddressOrPrompt from "~/extension/background-script/actions/webbtc/getAddressOrPrompt"; +import getInfo from "~/extension/background-script/actions/webbtc/getInfo"; +import getPsbtPreview from "~/extension/background-script/actions/webbtc/getPsbtPreview"; import signPsbt from "~/extension/background-script/actions/webbtc/signPsbt"; - -import getInfo from "./getInfo"; -import signPsbtWithPrompt from "./signPsbtWithPrompt"; +import signPsbtWithPrompt from "~/extension/background-script/actions/webbtc/signPsbtWithPrompt"; export { getAddress, getAddressOrPrompt, getInfo, + getPsbtPreview, signPsbt, signPsbtWithPrompt, }; diff --git a/src/extension/background-script/bitcoin/index.ts b/src/extension/background-script/bitcoin/index.ts index 43bd337656..58fbb80f98 100644 --- a/src/extension/background-script/bitcoin/index.ts +++ b/src/extension/background-script/bitcoin/index.ts @@ -7,7 +7,12 @@ import { networks, } from "~/extension/background-script/bitcoin/networks"; import Mnemonic from "~/extension/background-script/mnemonic"; -import { BitcoinAddress, BitcoinNetworkType } from "~/types"; +import { + Address, + BitcoinAddress, + BitcoinNetworkType, + PsbtPreview, +} from "~/types"; const BTC_TAPROOT_DERIVATION_PATH = "m/86'/0'/0'/0"; const BTC_TAPROOT_DERIVATION_PATH_REGTEST = "m/86'/1'/0'/0"; @@ -96,6 +101,68 @@ class Bitcoin { publicKey: secp256k1.etc.bytesToHex(derivedKey.publicKey as Uint8Array), }; } + + getPsbtPreview(psbt: string): PsbtPreview { + const unsignedPsbt = bitcoin.Psbt.fromHex(psbt, { + network: this.network, + }); + + const preview: PsbtPreview = { + inputs: [], + outputs: [], + }; + + for (let i = 0; i < unsignedPsbt.data.inputs.length; i++) { + if (i > 0) { + throw new Error("Multiple inputs currently unsupported"); + } + + const tapBip32Derivation = unsignedPsbt.data.inputs[i].tapBip32Derivation; + if (!tapBip32Derivation) { + throw new Error("No bip32Derivation in input " + i); + } + const address = btc.p2tr( + tapBip32Derivation[0].pubkey, + undefined, + this.network + ).address; + + if (!address) { + throw new Error("No address found in input " + i); + } + const witnessUtxo = unsignedPsbt.data.inputs[i].witnessUtxo; + if (!witnessUtxo) { + throw new Error("No witnessUtxo in input " + i); + } + + preview.inputs.push({ + amount: witnessUtxo.value, + address, + }); + } + for (let i = 0; i < unsignedPsbt.data.outputs.length; i++) { + const txOutput = unsignedPsbt.txOutputs[i]; + const output = unsignedPsbt.data.outputs[i]; + if (!output.tapBip32Derivation) { + throw new Error("No tapBip32Derivation in output"); + } + const address = btc.p2tr( + output.tapBip32Derivation[0].pubkey, + undefined, + this.network + ).address; + if (!address) { + throw new Error("No address found in output " + i); + } + + const previewOutput: Address = { + amount: txOutput.value, + address, + }; + preview.outputs.push(previewOutput); + } + return preview; + } } export default Bitcoin; diff --git a/src/extension/background-script/router.ts b/src/extension/background-script/router.ts index 648c7b4f2f..0c6421aba3 100644 --- a/src/extension/background-script/router.ts +++ b/src/extension/background-script/router.ts @@ -81,6 +81,9 @@ const routes = { removePrivateKey: nostr.removePrivateKey, setPrivateKey: nostr.setPrivateKey, }, + webbtc: { + getPsbtPreview: webbtc.getPsbtPreview, + }, // Public calls that are accessible from the inpage script (through the content script) public: { diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index dba64dd0af..3cda82bc8e 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -818,6 +818,17 @@ "confirm_get_address": { "title": "Get Address" }, + "confirm_sign_psbt": { + "title": "Sign", + "allow_sign": "This website asks you to sign a Bitcoin Transaction:", + "view_details": "View details", + "hide_details": "Hide details", + "view_raw_transaction": "View raw transaction (Hex)", + "hide_raw_transaction": "Hide raw transaction (Hex)", + "inputs": "Inputs", + "outputs": "Outputs", + "amount": "{{amount}} sats" + }, "allow": "Allow this website to:", "allow_sign": "Allow {{host}} to sign:", "block_and_ignore": "Block and ignore {{host}}", diff --git a/src/types.ts b/src/types.ts index fa274fb0a4..68086bda9a 100644 --- a/src/types.ts +++ b/src/types.ts @@ -169,6 +169,7 @@ export type NavigationState = { sigHash?: string; description?: string; details?: string; + psbt?: string; requestPermission: { method: string; description: string; @@ -560,6 +561,13 @@ export interface MessageSignPsbt extends MessageDefault { action: "signPsbt"; } +export interface MessageGetPsbtPreview extends MessageDefault { + args: { + psbt: string; + }; + action: "getPsbtPreview"; +} + export interface MessageBalanceGet extends MessageDefault { action: "getBalance"; } @@ -943,3 +951,10 @@ export type EsploraAssetInfos = { }; export type EsploraAssetRegistry = Record; + +export type Address = { amount: number; address: string }; + +export type PsbtPreview = { + inputs: Address[]; + outputs: Address[]; +}; From dbd6ed17227a6a4f60287652d58ee8e9c01f3781 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 5 Sep 2023 16:30:44 +0700 Subject: [PATCH 06/73] fix: signPsbt tests --- src/app/screens/ConfirmSignPsbt/index.tsx | 4 +-- .../actions/webbtc/__tests__/signPsbt.test.ts | 31 ++++++++++++++++--- src/fixtures/btc.ts | 2 +- 3 files changed, 30 insertions(+), 7 deletions(-) diff --git a/src/app/screens/ConfirmSignPsbt/index.tsx b/src/app/screens/ConfirmSignPsbt/index.tsx index 21a9a145ff..f6a8738c74 100644 --- a/src/app/screens/ConfirmSignPsbt/index.tsx +++ b/src/app/screens/ConfirmSignPsbt/index.tsx @@ -14,7 +14,7 @@ import { useNavigationState } from "~/app/hooks/useNavigationState"; import { USER_REJECTED_ERROR } from "~/common/constants"; import api from "~/common/lib/api"; import msg from "~/common/lib/msg"; -import type { OriginData, PsbtPreview } from "~/types"; +import type { Address, OriginData, PsbtPreview } from "~/types"; function ConfirmSignPsbt() { const navState = useNavigationState(); @@ -154,7 +154,7 @@ function AddressPreview({ amount, t, }: Address & { - t: TFunction<"translation", "confirm_sign_psbt", "translation">; + t: TFunction<"translation", "bitcoin.confirm_sign_psbt", "translation">; }) { return (
diff --git a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts index d16a28ba73..08842eadc2 100644 --- a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts +++ b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts @@ -1,12 +1,16 @@ import { hex } from "@scure/base"; import * as btc from "@scure/btc-signer"; -import { getPsbtPreview } from "~/common/lib/psbt"; +import getPsbtPreview from "~/extension/background-script/actions/webbtc/getPsbtPreview"; import signPsbt from "~/extension/background-script/actions/webbtc/signPsbt"; import Bitcoin from "~/extension/background-script/bitcoin"; import Mnemonic from "~/extension/background-script/mnemonic"; import state from "~/extension/background-script/state"; import { btcFixture } from "~/fixtures/btc"; -import type { MessageSignPsbt } from "~/types"; +import type { + MessageGetPsbtPreview, + MessageSignPsbt, + PsbtPreview, +} from "~/types"; const passwordMock = jest.fn; @@ -40,7 +44,7 @@ afterEach(() => { jest.clearAllMocks(); }); -async function sendPsbtMessage(psbt: string, derivationPath?: string) { +async function sendPsbtMessage(psbt: string) { const message: MessageSignPsbt = { application: "LBE", prompt: true, @@ -56,6 +60,22 @@ async function sendPsbtMessage(psbt: string, derivationPath?: string) { return await signPsbt(message); } +async function sendGetPsbtPreviewMessage(psbt: string) { + const message: MessageGetPsbtPreview = { + application: "LBE", + prompt: true, + action: "getPsbtPreview", + origin: { + internal: true, + }, + args: { + psbt, + }, + }; + + return await getPsbtPreview(message); +} + describe("signPsbt", () => { test("1 input, taproot, regtest", async () => { const result = await sendPsbtMessage(btcFixture.regtestTaprootPsbt); @@ -82,7 +102,10 @@ describe("signPsbt input validation", () => { describe("decode psbt", () => { test("get taproot transaction preview", async () => { - const preview = getPsbtPreview(btcFixture.regtestTaprootPsbt, "regtest"); + const previewResponse = await sendGetPsbtPreviewMessage( + btcFixture.regtestTaprootPsbt + ); + const preview = previewResponse.data as PsbtPreview; expect(preview.inputs.length).toBe(1); expect(preview.inputs[0].address).toBe( "bcrt1p8wpt9v4frpf3tkn0srd97pksgsxc5hs52lafxwru9kgeephvs7rqjeprhg" diff --git a/src/fixtures/btc.ts b/src/fixtures/btc.ts index b1e407a222..5018bc3797 100644 --- a/src/fixtures/btc.ts +++ b/src/fixtures/btc.ts @@ -8,7 +8,7 @@ export const btcFixture = { // signed PSBT and verified by importing in sparrow and broadcasting transaction // echo hex | xxd -r -p > taproot_signed.psbt regtestTaprootSignedPsbt: - "02000000000101b58806ecf5f7a2677dce4d357cc3ef14c59586db851f253e7d495b6506e7c8210100000000fdffffff02a54a4c0000000000225120d73acf6667b7850e6e00e21bb4525a2c5e2d88956a3d2c35b0bc06f4b0df01bc404b4c00000000002251202befa14431d4cb71889ea1df7a7eaa2f1d8b9107e60b01564e15dabe5c0dfd32014091d48b7c4bb1dc7cb4d0da360dfd0ca35ea1e73ca6f1891c25a6a3bd90a6269eaa2ee97bca15969181981eb1abb1c9ab8574add9453355b00b521069dca7dc1634010000", + "02000000000101b58806ecf5f7a2677dce4d357cc3ef14c59586db851f253e7d495b6506e7c8210100000000fdffffff02a54a4c0000000000225120d73acf6667b7850e6e00e21bb4525a2c5e2d88956a3d2c35b0bc06f4b0df01bc404b4c00000000002251202befa14431d4cb71889ea1df7a7eaa2f1d8b9107e60b01564e15dabe5c0dfd320140dcb575e673464796c4484808517f3e59afd0948cad9eda4cfd137e4e80f2eced24108f6ec116d5e6860424be0132697f53e69a89d54413cdec68dfb51fc7203734010000", mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", From 3fdddcf3d44a6d0616316b871dc4119d3c3343a4 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 5 Sep 2023 16:59:07 +0700 Subject: [PATCH 07/73] chore: move signpsbt and getAddress into webbtc --- src/app/router/Prompt/Prompt.tsx | 7 +++++-- .../screens/{ => Bitcoin}/ConfirmSignPsbt/index.test.tsx | 0 src/app/screens/{ => Bitcoin}/ConfirmSignPsbt/index.tsx | 2 +- src/common/lib/api.ts | 5 +++++ .../background-script/actions/webbtc/signPsbtWithPrompt.ts | 2 +- src/extension/background-script/router.ts | 4 ++-- 6 files changed, 14 insertions(+), 6 deletions(-) rename src/app/screens/{ => Bitcoin}/ConfirmSignPsbt/index.test.tsx (100%) rename src/app/screens/{ => Bitcoin}/ConfirmSignPsbt/index.tsx (98%) diff --git a/src/app/router/Prompt/Prompt.tsx b/src/app/router/Prompt/Prompt.tsx index 507cf289c9..f84421bc94 100644 --- a/src/app/router/Prompt/Prompt.tsx +++ b/src/app/router/Prompt/Prompt.tsx @@ -23,7 +23,7 @@ import AlbyLogo from "~/app/components/AlbyLogo"; import Providers from "~/app/context/Providers"; import RequireAuth from "~/app/router/RequireAuth"; import BitcoinConfirmGetAddress from "~/app/screens/Bitcoin/ConfirmGetAddress"; -import ConfirmSignPsbt from "~/app/screens/ConfirmSignPsbt"; +import ConfirmSignPsbt from "~/app/screens/Bitcoin/ConfirmSignPsbt"; import Onboard from "~/app/screens/Onboard/Prompt"; import type { NavigationState, OriginData } from "~/types"; @@ -97,6 +97,10 @@ function Prompt() { path="public/webbtc/confirmGetAddress" element={} /> + } + /> } @@ -128,7 +132,6 @@ function Prompt() { } /> } /> } /> - } /> } /> } /> } /> diff --git a/src/app/screens/ConfirmSignPsbt/index.test.tsx b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx similarity index 100% rename from src/app/screens/ConfirmSignPsbt/index.test.tsx rename to src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx diff --git a/src/app/screens/ConfirmSignPsbt/index.tsx b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx similarity index 98% rename from src/app/screens/ConfirmSignPsbt/index.tsx rename to src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx index f6a8738c74..dd3043619f 100644 --- a/src/app/screens/ConfirmSignPsbt/index.tsx +++ b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx @@ -42,7 +42,7 @@ function ConfirmSignPsbt() { async function confirm() { try { setLoading(true); - const response = await msg.request("signPsbt", { psbt }, { origin }); + const response = await api.bitcoin.signPsbt(psbt); msg.reply(response); setSuccessMessage(tCommon("success")); } catch (e) { diff --git a/src/common/lib/api.ts b/src/common/lib/api.ts index 79c19becd4..89ec01d926 100644 --- a/src/common/lib/api.ts +++ b/src/common/lib/api.ts @@ -240,6 +240,10 @@ const getPsbtPreview = (psbt: string): Promise => msg.request("webbtc/getPsbtPreview", { psbt, }); +const signPsbt = (psbt: string): Promise => + msg.request("webbtc/signPsbt", { + psbt, + }); export default { getAccount, @@ -286,5 +290,6 @@ export default { }, bitcoin: { getPsbtPreview, + signPsbt, }, }; diff --git a/src/extension/background-script/actions/webbtc/signPsbtWithPrompt.ts b/src/extension/background-script/actions/webbtc/signPsbtWithPrompt.ts index 0d0ccef65c..9886009fc7 100644 --- a/src/extension/background-script/actions/webbtc/signPsbtWithPrompt.ts +++ b/src/extension/background-script/actions/webbtc/signPsbtWithPrompt.ts @@ -12,7 +12,7 @@ const signPsbtWithPrompt = async (message: Message) => { try { const response = await utils.openPrompt({ ...message, - action: "confirmSignPsbt", + action: "webbtc/confirmSignPsbt", }); return response; } catch (e) { diff --git a/src/extension/background-script/router.ts b/src/extension/background-script/router.ts index 0c6421aba3..0abaa3350d 100644 --- a/src/extension/background-script/router.ts +++ b/src/extension/background-script/router.ts @@ -62,8 +62,6 @@ const routes = { lnurl: lnurl, lnurlAuth: auth, getCurrencyRate: cache.getCurrencyRate, - signPsbt: webbtc.signPsbt, - getAddress: webbtc.getAddress, setMnemonic: mnemonic.setMnemonic, getMnemonic: mnemonic.getMnemonic, generateMnemonic: mnemonic.generateMnemonic, @@ -83,6 +81,8 @@ const routes = { }, webbtc: { getPsbtPreview: webbtc.getPsbtPreview, + signPsbt: webbtc.signPsbt, + getAddress: webbtc.getAddress, }, // Public calls that are accessible from the inpage script (through the content script) From 0f2a7d3177176236ab3bacfa084cd90fbc04f5c5 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 5 Sep 2023 17:36:21 +0700 Subject: [PATCH 08/73] fix: broken confirm sign psbt test --- .../Bitcoin/ConfirmSignPsbt/index.test.tsx | 40 +++++++++++-------- .../background-script/bitcoin/index.ts | 6 --- 2 files changed, 24 insertions(+), 22 deletions(-) diff --git a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx index 5613ff9b39..d18b18d3bc 100644 --- a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx +++ b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx @@ -6,6 +6,8 @@ import state from "~/extension/background-script/state"; import { btcFixture } from "~/fixtures/btc"; import type { OriginData } from "~/types"; +import Bitcoin from "~/extension/background-script/bitcoin"; +import Mnemonic from "~/extension/background-script/mnemonic"; import ConfirmSignPsbt from "./index"; const mockOrigin: OriginData = { @@ -44,19 +46,26 @@ const mockState = { password: passwordMock, currentAccountId: "1e1e8ea6-493e-480b-9855-303d37506e97", getAccount: () => ({ + mnemonic: btcFixture.mnemonic, bitcoinNetwork: "regtest", }), + getMnemonic: () => new Mnemonic(btcFixture.mnemonic), + getBitcoin: () => new Bitcoin(new Mnemonic(btcFixture.mnemonic), "regtest"), getConnector: jest.fn(), }; state.getState = jest.fn().mockReturnValue(mockState); // mock get settings -msg.request = jest.fn().mockReturnValue({ - bitcoinNetwork: "regtest", -}); - -describe("ConfirmSignMessage", () => { +msg.request = jest + .fn() + .mockReturnValue( + new Bitcoin(new Mnemonic(btcFixture.mnemonic), "regtest").getPsbtPreview( + btcFixture.regtestTaprootPsbt + ) + ); + +describe("ConfirmSignPsbt", () => { test("render", async () => { await act(async () => { render( @@ -69,25 +78,17 @@ describe("ConfirmSignMessage", () => { const user = userEvent.setup(); await act(async () => { - await user.click(screen.getByText("View addresses")); - }); - await act(async () => { - await user.click(screen.getByText("View PSBT hex")); + await user.click(screen.getByText("View details")); }); - // TODO: update copy expect( await screen.findByText( - "This website asks you to sign a Partially Signed Bitcoin Transaction:" + "This website asks you to sign a Bitcoin Transaction:" ) ).toBeInTheDocument(); - expect( - await screen.findByText(btcFixture.regtestTaprootPsbt) - ).toBeInTheDocument(); - // Check inputs - const inputsContainer = (await screen.getByText("Input") + const inputsContainer = (await screen.getByText("Inputs") .parentElement) as HTMLElement; expect(inputsContainer).toBeInTheDocument(); const inputsRef = within(inputsContainer); @@ -114,5 +115,12 @@ describe("ConfirmSignMessage", () => { "bcrt1p90h6z3p36n9hrzy7580h5l429uwchyg8uc9sz4jwzhdtuhqdl5eqkcyx0f" ) ).toBeInTheDocument(); + + await act(async () => { + await user.click(screen.getByText("View raw transaction (Hex)")); + }); + expect( + await screen.findByText(btcFixture.regtestTaprootPsbt) + ).toBeInTheDocument(); }); }); diff --git a/src/extension/background-script/bitcoin/index.ts b/src/extension/background-script/bitcoin/index.ts index 58fbb80f98..c46ce19a8a 100644 --- a/src/extension/background-script/bitcoin/index.ts +++ b/src/extension/background-script/bitcoin/index.ts @@ -44,12 +44,6 @@ class Bitcoin { network: this.network, }); - // // fix usages of window (unavailable in service worker) - // globalThis.window ??= globalThis.window || {}; - // if (!globalThis.window.crypto) { - // globalThis.window.crypto = crypto; - // } - bitcoin.initEccLib(ecc); const ECPair: ECPairAPI = ECPairFactory(ecc); From cb9758ffd0e645e9558f591442a04b2bf40f675a Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Thu, 7 Sep 2023 14:59:56 +0700 Subject: [PATCH 09/73] chore: improve psbt preview transaction decoding --- .../screens/Bitcoin/ConfirmSignPsbt/index.tsx | 26 +++++++++++++---- .../actions/webbtc/__tests__/signPsbt.test.ts | 16 +++++++++++ .../background-script/bitcoin/index.ts | 28 ++++++++----------- src/fixtures/btc.ts | 4 +++ 4 files changed, 53 insertions(+), 21 deletions(-) diff --git a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx index dd3043619f..be9649c37e 100644 --- a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx +++ b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx @@ -3,7 +3,7 @@ import Container from "@components/Container"; import PublisherCard from "@components/PublisherCard"; import SuccessMessage from "@components/SuccessMessage"; import { TFunction } from "i18next"; -import { useEffect, useState } from "react"; +import React, { useEffect, useState } from "react"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import { toast } from "react-toastify"; @@ -31,13 +31,22 @@ function ConfirmSignPsbt() { const [preview, setPreview] = useState(undefined); const [showAddresses, setShowAddresses] = useState(false); const [showHex, setShowHex] = useState(false); + const [error, setError] = useState(); useEffect(() => { (async () => { - const preview = await api.bitcoin.getPsbtPreview(psbt); - setPreview(preview); + try { + const preview = await api.bitcoin.getPsbtPreview(psbt); + setPreview(preview); + } catch (e) { + console.error(e); + const error = e as { message: string }; + const errorMessage = error.message || "Unknown error"; + setError(errorMessage); + toast.error(`${tCommon("error")}: ${errorMessage}`); + } })(); - }, [origin, psbt]); + }, [origin, psbt, tCommon]); async function confirm() { try { @@ -47,7 +56,10 @@ function ConfirmSignPsbt() { setSuccessMessage(tCommon("success")); } catch (e) { console.error(e); - if (e instanceof Error) toast.error(`${tCommon("error")}: ${e.message}`); + const error = e as { message: string }; + const errorMessage = error.message || "Unknown error"; + setError(errorMessage); + toast.error(`${tCommon("error")}: ${errorMessage}`); } finally { setLoading(false); } @@ -74,6 +86,10 @@ function ConfirmSignPsbt() { setShowHex((current) => !current); } + if (error) { + return

{error}

; + } + if (!preview) { return ; } diff --git a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts index 08842eadc2..510ae57e36 100644 --- a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts +++ b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts @@ -122,4 +122,20 @@ describe("decode psbt", () => { ); expect(preview.outputs[1].amount).toBe(5_000_000); }); + + test("get taproot transaction preview 2", async () => { + const previewResponse = await sendGetPsbtPreviewMessage( + btcFixture.testnetTaprootPsbt + ); + const preview = previewResponse.data as PsbtPreview; + expect(preview.inputs.length).toBe(1); + expect(preview.inputs[0].address).toBe( + "bcrt1p8wpt9v4frpf3tkn0srd97pksgsxc5hs52lafxwru9kgeephvs7rqjeprhg" + ); + expect(preview.inputs[0].amount).toBe(5000); + expect(preview.outputs.length).toBe(1); + + expect(preview.outputs[0].address).toBe("UNKNOWN"); + expect(preview.outputs[0].amount).toBe(100); + }); }); diff --git a/src/extension/background-script/bitcoin/index.ts b/src/extension/background-script/bitcoin/index.ts index c46ce19a8a..e9652151c1 100644 --- a/src/extension/background-script/bitcoin/index.ts +++ b/src/extension/background-script/bitcoin/index.ts @@ -111,15 +111,13 @@ class Bitcoin { throw new Error("Multiple inputs currently unsupported"); } - const tapBip32Derivation = unsignedPsbt.data.inputs[i].tapBip32Derivation; - if (!tapBip32Derivation) { - throw new Error("No bip32Derivation in input " + i); + const pubkey: Buffer | undefined = + unsignedPsbt.data.inputs[i].tapInternalKey || + unsignedPsbt.data.inputs[i].tapBip32Derivation?.[0]?.pubkey; + if (!pubkey) { + throw new Error("No pubkey found in input " + i); } - const address = btc.p2tr( - tapBip32Derivation[0].pubkey, - undefined, - this.network - ).address; + const address = btc.p2tr(pubkey, undefined, this.network).address; if (!address) { throw new Error("No address found in input " + i); @@ -137,14 +135,12 @@ class Bitcoin { for (let i = 0; i < unsignedPsbt.data.outputs.length; i++) { const txOutput = unsignedPsbt.txOutputs[i]; const output = unsignedPsbt.data.outputs[i]; - if (!output.tapBip32Derivation) { - throw new Error("No tapBip32Derivation in output"); - } - const address = btc.p2tr( - output.tapBip32Derivation[0].pubkey, - undefined, - this.network - ).address; + + const pubkey = output.tapBip32Derivation?.[0].pubkey || txOutput.address; + + const address = pubkey + ? btc.p2tr(pubkey, undefined, this.network).address + : "UNKNOWN"; if (!address) { throw new Error("No address found in output " + i); } diff --git a/src/fixtures/btc.ts b/src/fixtures/btc.ts index 5018bc3797..bc7c11a68d 100644 --- a/src/fixtures/btc.ts +++ b/src/fixtures/btc.ts @@ -12,4 +12,8 @@ export const btcFixture = { mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + + // made with bitcoinjs-lib, only has one output + testnetTaprootPsbt: + "70736274ff01005e020000000135e316e6c54d3ef6b56394ef8fdb3addec3098b932820fbaf2818df0a1a6b3ca0100000000ffffffff016400000000000000225120da002fb251b6270e999364e434ed56219562b5a909eab26ae6797ca408f44223000000000001012b88130000000000002251203b82b2b2a9185315da6f80da5f06d0440d8a5e1457fa93387c2d919c86ec878601172055355ca83c973f1d97ce0e3843c85d78905af16b4dc531bc488e57212d2301160000", }; From 45e50038cb6a23bb05f8fd109b8518aaa9b7c0b0 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Fri, 8 Sep 2023 16:36:47 +0700 Subject: [PATCH 10/73] feat: add accountChanged to webbtc --- src/extension/content-script/webbtc.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/extension/content-script/webbtc.js b/src/extension/content-script/webbtc.js index 7152126118..b5d1d98a48 100644 --- a/src/extension/content-script/webbtc.js +++ b/src/extension/content-script/webbtc.js @@ -25,6 +25,16 @@ async function init() { return; } + browser.runtime.onMessage.addListener((request, sender, sendResponse) => { + // forward account changed messaged to inpage script + if (request.action === "accountChanged") { + window.postMessage( + { action: "accountChanged", scope: "webbtc" }, + window.location.origin + ); + } + }); + // message listener to listen to inpage webbtc calls // those calls get passed on to the background script // (the inpage script can not do that directly, but only the inpage script can make webln available to the page) From 0a361e05a96058c9a685b4b95a42613897be319b Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Fri, 8 Sep 2023 16:39:15 +0700 Subject: [PATCH 11/73] fix: support multiple inputs in psbt preview --- .../actions/webbtc/__tests__/signPsbt.test.ts | 25 +++++--- .../background-script/bitcoin/index.ts | 27 ++++++--- .../background-script/bitcoin/networks.ts | 58 +------------------ src/fixtures/btc.ts | 9 ++- 4 files changed, 44 insertions(+), 75 deletions(-) diff --git a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts index 510ae57e36..9a223f3baf 100644 --- a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts +++ b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts @@ -19,10 +19,10 @@ const mockState = { currentAccountId: "1e1e8ea6-493e-480b-9855-303d37506e97", getAccount: () => ({ mnemonic: btcFixture.mnemonic, - bitcoinNetwork: "regtest", + bitcoinNetwork: "testnet", }), getMnemonic: () => new Mnemonic(btcFixture.mnemonic), - getBitcoin: () => new Bitcoin(new Mnemonic(btcFixture.mnemonic), "regtest"), + getBitcoin: () => new Bitcoin(new Mnemonic(btcFixture.mnemonic), "testnet"), getConnector: jest.fn(), }; @@ -125,17 +125,28 @@ describe("decode psbt", () => { test("get taproot transaction preview 2", async () => { const previewResponse = await sendGetPsbtPreviewMessage( - btcFixture.testnetTaprootPsbt + btcFixture.taprootPsbt2 ); const preview = previewResponse.data as PsbtPreview; expect(preview.inputs.length).toBe(1); + // first address from mnemonic 1 expect(preview.inputs[0].address).toBe( - "bcrt1p8wpt9v4frpf3tkn0srd97pksgsxc5hs52lafxwru9kgeephvs7rqjeprhg" + "tb1p8wpt9v4frpf3tkn0srd97pksgsxc5hs52lafxwru9kgeephvs7rqlqt9zj" ); - expect(preview.inputs[0].amount).toBe(5000); - expect(preview.outputs.length).toBe(1); + expect(preview.inputs[0].amount).toBe(2700); + expect(preview.outputs.length).toBe(2); - expect(preview.outputs[0].address).toBe("UNKNOWN"); + // first address from mnemonic 2 + // FIXME: + expect(preview.outputs[0].address).toBe( + "tb1pmgqzlvj3kcnsaxvnvnjrfm2kyx2k9ddfp84ty6hx0972gz85gg3slq3j59" + ); expect(preview.outputs[0].amount).toBe(100); + + // change sent back to original address + expect(preview.outputs[1].address).toBe( + "bcrt1p8wpt9v4frpf3tkn0srd97pksgsxc5hs52lafxwru9kgeephvs7rqjeprhg" + ); + expect(preview.outputs[1].amount).toBe(900); }); }); diff --git a/src/extension/background-script/bitcoin/index.ts b/src/extension/background-script/bitcoin/index.ts index e9652151c1..2f571fd279 100644 --- a/src/extension/background-script/bitcoin/index.ts +++ b/src/extension/background-script/bitcoin/index.ts @@ -107,17 +107,12 @@ class Bitcoin { }; for (let i = 0; i < unsignedPsbt.data.inputs.length; i++) { - if (i > 0) { - throw new Error("Multiple inputs currently unsupported"); - } - const pubkey: Buffer | undefined = unsignedPsbt.data.inputs[i].tapInternalKey || unsignedPsbt.data.inputs[i].tapBip32Derivation?.[0]?.pubkey; - if (!pubkey) { - throw new Error("No pubkey found in input " + i); - } - const address = btc.p2tr(pubkey, undefined, this.network).address; + const address = pubkey + ? btc.p2tr(pubkey, undefined, this.network).address + : "UNKNOWN"; if (!address) { throw new Error("No address found in input " + i); @@ -136,7 +131,21 @@ class Bitcoin { const txOutput = unsignedPsbt.txOutputs[i]; const output = unsignedPsbt.data.outputs[i]; - const pubkey = output.tapBip32Derivation?.[0].pubkey || txOutput.address; + const pubkey = + output.tapBip32Derivation?.[0].pubkey || + txOutput.address || + (txOutput.script && + (() => { + try { + return bitcoin.address.fromOutputScript( + txOutput.script, + this.network + ); + } catch (error) { + console.error(error); + } + return undefined; + })()); const address = pubkey ? btc.p2tr(pubkey, undefined, this.network).address diff --git a/src/extension/background-script/bitcoin/networks.ts b/src/extension/background-script/bitcoin/networks.ts index 2a06821f78..5962ad9154 100644 --- a/src/extension/background-script/bitcoin/networks.ts +++ b/src/extension/background-script/bitcoin/networks.ts @@ -1,56 +1,2 @@ -// from https://github1s.com/bitcoinjs/bitcoinjs-lib -export interface Network { - messagePrefix: string; - bech32: string; - bip32: Bip32; - pubKeyHash: number; - scriptHash: number; - wif: number; -} - -interface Bip32 { - public: number; - private: number; -} - -const bitcoin: Network = { - messagePrefix: "\x18Bitcoin Signed Message:\n", - bech32: "bc", - bip32: { - public: 0x0488b21e, - private: 0x0488ade4, - }, - pubKeyHash: 0x00, - scriptHash: 0x05, - wif: 0x80, -}; - -export const testnet: Network = { - messagePrefix: "\x18Bitcoin Signed Message:\n", - bech32: "tb", - bip32: { - public: 0x043587cf, - private: 0x04358394, - }, - pubKeyHash: 0x6f, - scriptHash: 0xc4, - wif: 0xef, -}; - -const regtest: Network = { - messagePrefix: "\x18Bitcoin Signed Message:\n", - bech32: "bcrt", - bip32: { - public: 0x043587cf, - private: 0x04358394, - }, - pubKeyHash: 0x6f, - scriptHash: 0xc4, - wif: 0xef, -}; - -export const networks = { - bitcoin, - testnet, - regtest, -}; +import { Network, networks } from "bitcoinjs-lib"; +export { Network, networks }; diff --git a/src/fixtures/btc.ts b/src/fixtures/btc.ts index bc7c11a68d..14163996e0 100644 --- a/src/fixtures/btc.ts +++ b/src/fixtures/btc.ts @@ -12,8 +12,11 @@ export const btcFixture = { mnemonic: "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon about", + mnemonic2: + "abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon abandon cactus", - // made with bitcoinjs-lib, only has one output - testnetTaprootPsbt: - "70736274ff01005e020000000135e316e6c54d3ef6b56394ef8fdb3addec3098b932820fbaf2818df0a1a6b3ca0100000000ffffffff016400000000000000225120da002fb251b6270e999364e434ed56219562b5a909eab26ae6797ca408f44223000000000001012b88130000000000002251203b82b2b2a9185315da6f80da5f06d0440d8a5e1457fa93387c2d919c86ec878601172055355ca83c973f1d97ce0e3843c85d78905af16b4dc531bc488e57212d2301160000", + // made with bitcoinjs-lib + // sent from mnemonic to mnemonic2, with some change back to mnemonic (same address) + taprootPsbt2: + "70736274ff010089020000000101d245947b008821fdbf573087c061749fc1a6f74cee947767af917e1be324c10100000000ffffffff026400000000000000225120da002fb251b6270e999364e434ed56219562b5a909eab26ae6797ca408f4422340060000000000002251203b82b2b2a9185315da6f80da5f06d0440d8a5e1457fa93387c2d919c86ec8786000000000001012b8c0a0000000000002251203b82b2b2a9185315da6f80da5f06d0440d8a5e1457fa93387c2d919c86ec878601172055355ca83c973f1d97ce0e3843c85d78905af16b4dc531bc488e57212d230116000000", }; From 12bf0ea21548897086a654292907dfefe690a91c Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Sun, 10 Sep 2023 11:38:48 +0700 Subject: [PATCH 12/73] fix: signpsbt output address decoding, display fee --- .../Bitcoin/ConfirmSignPsbt/index.test.tsx | 7 ++++ .../screens/Bitcoin/ConfirmSignPsbt/index.tsx | 5 +++ .../actions/webbtc/__tests__/signPsbt.test.ts | 40 ++++++++++++------- .../background-script/bitcoin/index.ts | 25 ++++++------ src/i18n/locales/en/translation.json | 1 + src/types.ts | 1 + 6 files changed, 52 insertions(+), 27 deletions(-) diff --git a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx index d18b18d3bc..43418ad286 100644 --- a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx +++ b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx @@ -116,6 +116,13 @@ describe("ConfirmSignPsbt", () => { ) ).toBeInTheDocument(); + // Check fee + const feeContainer = screen.getByText("Fee").parentElement as HTMLElement; + expect(feeContainer).toBeInTheDocument(); + + const feeRef = within(feeContainer); + expect(await feeRef.findByText("155 sats")).toBeInTheDocument(); + await act(async () => { await user.click(screen.getByText("View raw transaction (Hex)")); }); diff --git a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx index edce3f4096..901eae8ace 100644 --- a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx +++ b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx @@ -129,6 +129,11 @@ function ConfirmSignPsbt() { ))}
+

{t("fee")}

+

+ {t("amount", { amount: preview.fee })} +

+ {showHex ? t("hide_raw_transaction") diff --git a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts index 9a223f3baf..8f61293b98 100644 --- a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts +++ b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts @@ -7,6 +7,7 @@ import Mnemonic from "~/extension/background-script/mnemonic"; import state from "~/extension/background-script/state"; import { btcFixture } from "~/fixtures/btc"; import type { + BitcoinNetworkType, MessageGetPsbtPreview, MessageSignPsbt, PsbtPreview, @@ -14,19 +15,21 @@ import type { const passwordMock = jest.fn; -const mockState = { - password: passwordMock, - currentAccountId: "1e1e8ea6-493e-480b-9855-303d37506e97", - getAccount: () => ({ - mnemonic: btcFixture.mnemonic, - bitcoinNetwork: "testnet", - }), - getMnemonic: () => new Mnemonic(btcFixture.mnemonic), - getBitcoin: () => new Bitcoin(new Mnemonic(btcFixture.mnemonic), "testnet"), - getConnector: jest.fn(), -}; +function mockSettings(network: BitcoinNetworkType) { + const mockState = { + password: passwordMock, + currentAccountId: "1e1e8ea6-493e-480b-9855-303d37506e97", + getAccount: () => ({ + mnemonic: btcFixture.mnemonic, + bitcoinNetwork: network, + }), + getMnemonic: () => new Mnemonic(btcFixture.mnemonic), + getBitcoin: () => new Bitcoin(new Mnemonic(btcFixture.mnemonic), network), + getConnector: jest.fn(), + }; -state.getState = jest.fn().mockReturnValue(mockState); + state.getState = jest.fn().mockReturnValue(mockState); +} jest.mock("~/common/lib/crypto", () => { return { @@ -78,6 +81,7 @@ async function sendGetPsbtPreviewMessage(psbt: string) { describe("signPsbt", () => { test("1 input, taproot, regtest", async () => { + mockSettings("regtest"); const result = await sendPsbtMessage(btcFixture.regtestTaprootPsbt); if (!result.data) { throw new Error("Result should have data"); @@ -95,6 +99,7 @@ describe("signPsbt", () => { describe("signPsbt input validation", () => { test("invalid psbt", async () => { + mockSettings("regtest"); const result = await sendPsbtMessage("test"); expect(result.error).not.toBe(null); }); @@ -102,6 +107,7 @@ describe("signPsbt input validation", () => { describe("decode psbt", () => { test("get taproot transaction preview", async () => { + mockSettings("regtest"); const previewResponse = await sendGetPsbtPreviewMessage( btcFixture.regtestTaprootPsbt ); @@ -121,9 +127,12 @@ describe("decode psbt", () => { "bcrt1p90h6z3p36n9hrzy7580h5l429uwchyg8uc9sz4jwzhdtuhqdl5eqkcyx0f" ); expect(preview.outputs[1].amount).toBe(5_000_000); + + expect(preview.fee).toBe(155); }); test("get taproot transaction preview 2", async () => { + mockSettings("testnet"); const previewResponse = await sendGetPsbtPreviewMessage( btcFixture.taprootPsbt2 ); @@ -137,7 +146,6 @@ describe("decode psbt", () => { expect(preview.outputs.length).toBe(2); // first address from mnemonic 2 - // FIXME: expect(preview.outputs[0].address).toBe( "tb1pmgqzlvj3kcnsaxvnvnjrfm2kyx2k9ddfp84ty6hx0972gz85gg3slq3j59" ); @@ -145,8 +153,10 @@ describe("decode psbt", () => { // change sent back to original address expect(preview.outputs[1].address).toBe( - "bcrt1p8wpt9v4frpf3tkn0srd97pksgsxc5hs52lafxwru9kgeephvs7rqjeprhg" + "tb1p8wpt9v4frpf3tkn0srd97pksgsxc5hs52lafxwru9kgeephvs7rqlqt9zj" ); - expect(preview.outputs[1].amount).toBe(900); + expect(preview.outputs[1].amount).toBe(1600); + + expect(preview.fee).toBe(1000); }); }); diff --git a/src/extension/background-script/bitcoin/index.ts b/src/extension/background-script/bitcoin/index.ts index 2f571fd279..149b4301f1 100644 --- a/src/extension/background-script/bitcoin/index.ts +++ b/src/extension/background-script/bitcoin/index.ts @@ -28,6 +28,7 @@ class Bitcoin { this.mnemonic = mnemonic; this.networkType = networkType; this.network = networks[this.networkType]; + bitcoin.initEccLib(ecc); } signPsbt(psbt: string) { @@ -44,7 +45,6 @@ class Bitcoin { network: this.network, }); - bitcoin.initEccLib(ecc); const ECPair: ECPairAPI = ECPairFactory(ecc); const keyPair = tweakSigner( @@ -104,6 +104,7 @@ class Bitcoin { const preview: PsbtPreview = { inputs: [], outputs: [], + fee: 0, }; for (let i = 0; i < unsignedPsbt.data.inputs.length; i++) { @@ -129,10 +130,8 @@ class Bitcoin { } for (let i = 0; i < unsignedPsbt.data.outputs.length; i++) { const txOutput = unsignedPsbt.txOutputs[i]; - const output = unsignedPsbt.data.outputs[i]; - const pubkey = - output.tapBip32Derivation?.[0].pubkey || + const address = txOutput.address || (txOutput.script && (() => { @@ -145,14 +144,8 @@ class Bitcoin { console.error(error); } return undefined; - })()); - - const address = pubkey - ? btc.p2tr(pubkey, undefined, this.network).address - : "UNKNOWN"; - if (!address) { - throw new Error("No address found in output " + i); - } + })()) || + "UNKNOWN"; const previewOutput: Address = { amount: txOutput.value, @@ -160,6 +153,14 @@ class Bitcoin { }; preview.outputs.push(previewOutput); } + + for (const input of preview.inputs) { + preview.fee += input.amount; + } + for (const output of preview.outputs) { + preview.fee -= output.amount; + } + return preview; } } diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index aed2b38c10..d9cf822dcb 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -827,6 +827,7 @@ "hide_raw_transaction": "Hide raw transaction (Hex)", "inputs": "Inputs", "outputs": "Outputs", + "fee": "Fee", "amount": "{{amount}} sats" }, "allow": "Allow this website to:", diff --git a/src/types.ts b/src/types.ts index 68086bda9a..7bb3e73436 100644 --- a/src/types.ts +++ b/src/types.ts @@ -957,4 +957,5 @@ export type Address = { amount: number; address: string }; export type PsbtPreview = { inputs: Address[]; outputs: Address[]; + fee: number; }; From 778c7389764c8e3f841c38b8a2ea2f6377eb9a0e Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 11 Sep 2023 15:00:41 +0700 Subject: [PATCH 13/73] chore: remove @scure/btc-signer --- package.json | 1 - .../actions/webbtc/__tests__/signPsbt.test.ts | 4 --- .../background-script/bitcoin/index.ts | 26 +++++++------- yarn.lock | 36 ++++--------------- 4 files changed, 20 insertions(+), 47 deletions(-) diff --git a/package.json b/package.json index 05179ddfc0..8e762a7cd2 100644 --- a/package.json +++ b/package.json @@ -44,7 +44,6 @@ "@noble/secp256k1": "^2.0.0", "@scure/bip32": "^1.3.1", "@scure/bip39": "^1.2.1", - "@scure/btc-signer": "^0.5.1", "@tailwindcss/forms": "^0.5.4", "@vespaiach/axios-fetch-adapter": "^0.3.0", "axios": "^0.27.2", diff --git a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts index 8f61293b98..7a243ffa9c 100644 --- a/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts +++ b/src/extension/background-script/actions/webbtc/__tests__/signPsbt.test.ts @@ -1,5 +1,3 @@ -import { hex } from "@scure/base"; -import * as btc from "@scure/btc-signer"; import getPsbtPreview from "~/extension/background-script/actions/webbtc/getPsbtPreview"; import signPsbt from "~/extension/background-script/actions/webbtc/signPsbt"; import Bitcoin from "~/extension/background-script/bitcoin"; @@ -91,8 +89,6 @@ describe("signPsbt", () => { expect(result.data?.signed).not.toBe(undefined); expect(result.error).toBe(undefined); - const checkTx = btc.Transaction.fromRaw(hex.decode(result.data.signed)); - expect(checkTx.isFinal).toBe(true); expect(result.data?.signed).toBe(btcFixture.regtestTaprootSignedPsbt); }); }); diff --git a/src/extension/background-script/bitcoin/index.ts b/src/extension/background-script/bitcoin/index.ts index 149b4301f1..89e2a95a59 100644 --- a/src/extension/background-script/bitcoin/index.ts +++ b/src/extension/background-script/bitcoin/index.ts @@ -1,5 +1,4 @@ import * as secp256k1 from "@noble/secp256k1"; -import * as btc from "@scure/btc-signer"; import * as bitcoin from "bitcoinjs-lib"; import ECPairFactory, { ECPairAPI } from "ecpair"; import { @@ -80,11 +79,10 @@ class Bitcoin { const derivationPath = `${derivationPathWithoutIndex}/${index}`; const derivedKey = this.mnemonic.deriveKey(derivationPath); - const address = btc.getAddress( - "tr", - derivedKey.privateKey as Uint8Array, - this.network - ); + const { address } = bitcoin.payments.p2tr({ + internalPubkey: toXOnly(Buffer.from(derivedKey.publicKey as Uint8Array)), + network: this.network, + }); if (!address) { throw new Error("No taproot address found from private key"); } @@ -111,12 +109,16 @@ class Bitcoin { const pubkey: Buffer | undefined = unsignedPsbt.data.inputs[i].tapInternalKey || unsignedPsbt.data.inputs[i].tapBip32Derivation?.[0]?.pubkey; - const address = pubkey - ? btc.p2tr(pubkey, undefined, this.network).address - : "UNKNOWN"; - if (!address) { - throw new Error("No address found in input " + i); + let address = "UNKNOWN"; + if (pubkey) { + const pubkeyAddress = bitcoin.payments.p2tr({ + internalPubkey: pubkey, + network: this.network, + }).address; + if (pubkeyAddress) { + address = pubkeyAddress; + } } const witnessUtxo = unsignedPsbt.data.inputs[i].witnessUtxo; if (!witnessUtxo) { @@ -124,7 +126,7 @@ class Bitcoin { } preview.inputs.push({ - amount: witnessUtxo.value, + amount: unsignedPsbt.data.inputs[i].witnessUtxo?.value || 0, address, }); } diff --git a/yarn.lock b/yarn.lock index 346410a685..469ca2c4c4 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1027,18 +1027,6 @@ dependencies: "@noble/hashes" "1.3.1" -"@noble/curves@~0.8.3": - version "0.8.3" - resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-0.8.3.tgz#ad6d48baf2599cf1d58dcb734c14d5225c8996e0" - integrity sha512-OqaOf4RWDaCRuBKJLDURrgVxjLmneGsiCXGuzYB5y95YithZMA6w4uk34DHSm0rKMrrYiaeZj48/81EvaAScLQ== - dependencies: - "@noble/hashes" "1.3.0" - -"@noble/hashes@1.3.0", "@noble/hashes@^1.2.0": - version "1.3.0" - resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" - integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== - "@noble/hashes@1.3.1", "@noble/hashes@~1.3.0", "@noble/hashes@~1.3.1": version "1.3.1" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.1.tgz#8831ef002114670c603c458ab8b11328406953a9" @@ -1049,6 +1037,11 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.2.tgz#6f26dbc8fbc7205873ce3cee2f690eba0d421b39" integrity sha512-MVC8EAQp7MvEcm30KWENFjgR+Mkmf+D189XJTkFIlwohU5hcBbn1ZkKq7KVTi2Hme3PMGF390DaL52beVrIihQ== +"@noble/hashes@^1.2.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.3.0.tgz#085fd70f6d7d9d109671090ccae1d3bec62554a1" + integrity sha512-ilHEACi9DwqJB0pw7kv+Apvh50jiiSyR/cQ3y4W7lOR5mhvn/50FLUfsnfJz0BDZtl/RR16kXvptiv6q1msYZg== + "@noble/secp256k1@^1.7.1": version "1.7.1" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.7.1.tgz#b251c70f824ce3ca7f8dc3df08d58f005cc0507c" @@ -1118,7 +1111,7 @@ resolved "https://registry.yarnpkg.com/@remix-run/router/-/router-1.7.2.tgz#cba1cf0a04bc04cb66027c51fa600e9cbc388bc8" integrity sha512-7Lcn7IqGMV+vizMPoEl5F0XDshcdDYtMI6uJLQdQz5CfZAwy3vvGKYSUk789qndt5dEC4HfSjviSYlSoHGL2+A== -"@scure/base@1.1.1", "@scure/base@~1.1.0", "@scure/base@~1.1.1": +"@scure/base@1.1.1", "@scure/base@~1.1.0": version "1.1.1" resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.1.tgz#ebb651ee52ff84f420097055f4bf46cfba403938" integrity sha512-ZxOhsSyxYwLJj3pLZCefNitxsj093tb2vq90mp2txoYeBqbcjDjqFhyM8eUjq/uFm6zJ+mUuqxlS2FkuSY1MTA== @@ -1140,16 +1133,6 @@ "@noble/hashes" "~1.3.0" "@scure/base" "~1.1.0" -"@scure/btc-signer@^0.5.1": - version "0.5.1" - resolved "https://registry.yarnpkg.com/@scure/btc-signer/-/btc-signer-0.5.1.tgz#79e2e89a359c5ba9aa944ba5bd487a6ce1b2ad36" - integrity sha512-T8ViYQEwAz79UNdfrdpxUeGuriYlvgxH2EouL7gTJZJ3jAqK/0ft3gL0VsOkrmYx8XfIX+p89tJFxuy/MXhgoA== - dependencies: - "@noble/curves" "~0.8.3" - "@noble/hashes" "~1.3.0" - "@scure/base" "~1.1.0" - micro-packed "~0.3.2" - "@sinclair/typebox@^0.25.16": version "0.25.21" resolved "https://registry.yarnpkg.com/@sinclair/typebox/-/typebox-0.25.21.tgz#763b05a4b472c93a8db29b2c3e359d55b29ce272" @@ -7267,13 +7250,6 @@ methods@~1.1.2: resolved "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz" integrity sha1-VSmk1nZUE07cxSZmVoNbD4Ua/O4= -micro-packed@~0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/micro-packed/-/micro-packed-0.3.2.tgz#3679188366c2283cb60a78366ed0416e5472b7cf" - integrity sha512-D1Bq0/lVOzdxhnX5vylCxZpdw5LylH7Vd81py0DfRsKUP36XYpwvy8ZIsECVo3UfnoROn8pdKqkOzL7Cd82sGA== - dependencies: - "@scure/base" "~1.1.1" - micromatch@4.0.5, micromatch@^4.0.2, micromatch@^4.0.4, micromatch@^4.0.5: version "4.0.5" resolved "https://registry.yarnpkg.com/micromatch/-/micromatch-4.0.5.tgz#bc8999a7cbbf77cdc89f132f6e467051b49090c6" From 6a621b42dedb06909f3a7856bac0fdc123ef716d Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Fri, 15 Sep 2023 09:24:28 +0530 Subject: [PATCH 14/73] feat: persist-enable --- src/extension/content-script/webln.js | 7 ++++++- src/extension/providers/providerBase.ts | 12 +++++++++++- 2 files changed, 17 insertions(+), 2 deletions(-) diff --git a/src/extension/content-script/webln.js b/src/extension/content-script/webln.js index fa5822ff03..439782e48b 100644 --- a/src/extension/content-script/webln.js +++ b/src/extension/content-script/webln.js @@ -24,7 +24,12 @@ const weblnCalls = [ // calls that can be executed when webln is not enabled for the current content page const disabledCalls = ["webln/enable"]; -let isEnabled = false; // store if webln is enabled for this content page +function loadEnabledState() { + const storedEnabled = localStorage.getItem("enabled"); + return storedEnabled === "true"; +} + +let isEnabled = loadEnabledState(); // store if webln is enabled for this content page let isRejected = false; // store if the webln enable call failed. if so we do not prompt again let account; diff --git a/src/extension/providers/providerBase.ts b/src/extension/providers/providerBase.ts index 58d9087266..0a19913382 100644 --- a/src/extension/providers/providerBase.ts +++ b/src/extension/providers/providerBase.ts @@ -8,11 +8,20 @@ export default class ProviderBase { private _scope: string; constructor(scope: string) { - this.enabled = false; + this.enabled = this.loadEnabledState(); this._eventEmitter = new EventEmitter(); this._scope = scope; } + private loadEnabledState(): boolean { + const storedEnabled = localStorage.getItem("enabled"); + return storedEnabled === "true"; + } + + private saveEnabledState(enabled: boolean): void { + localStorage.setItem("enabled", enabled.toString()); + } + protected _checkEnabled(methodName: string): void { if (!this.enabled) { throw new Error(`Provider must be enabled before calling ${methodName}`); @@ -26,6 +35,7 @@ export default class ProviderBase { const result = await this.execute("enable"); if (typeof result.enabled === "boolean") { this.enabled = result.enabled; + this.saveEnabledState(this.enabled); } } From b9185e7b4f581c106b2d3e82e7e134bd0d43cb85 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Fri, 15 Sep 2023 10:51:51 +0530 Subject: [PATCH 15/73] feat: lnurl-auth onboarding --- src/app/components/LNURLAuth/index.tsx | 140 ++++++++++++++++ src/app/screens/Enable/LiquidEnable.tsx | 2 +- src/app/screens/Enable/NostrEnable.tsx | 2 +- src/app/screens/Enable/WebbtcEnable.tsx | 2 +- src/app/screens/LNURLAuth/index.tsx | 150 +++--------------- .../actions/lnurl/authOrPrompt.ts | 9 +- .../actions/onboard/index.ts | 3 - .../actions/onboard/prompt.ts | 19 --- src/extension/background-script/router.ts | 2 - src/extension/content-script/webln.js | 12 -- 10 files changed, 171 insertions(+), 170 deletions(-) create mode 100644 src/app/components/LNURLAuth/index.tsx delete mode 100644 src/extension/background-script/actions/onboard/index.ts delete mode 100644 src/extension/background-script/actions/onboard/prompt.ts diff --git a/src/app/components/LNURLAuth/index.tsx b/src/app/components/LNURLAuth/index.tsx new file mode 100644 index 0000000000..cb11ef5eca --- /dev/null +++ b/src/app/components/LNURLAuth/index.tsx @@ -0,0 +1,140 @@ +import Button from "@components/Button"; +import ConfirmOrCancel from "@components/ConfirmOrCancel"; +import Container from "@components/Container"; +import ContentMessage from "@components/ContentMessage"; +import PublisherCard from "@components/PublisherCard"; +import ResultCard from "@components/ResultCard"; +import React, { useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import ScreenHeader from "~/app/components/ScreenHeader"; +import { useNavigationState } from "~/app/hooks/useNavigationState"; +import { USER_REJECTED_ERROR } from "~/common/constants"; +import api from "~/common/lib/api"; +import msg from "~/common/lib/msg"; +import type { LNURLAuthServiceResponse } from "~/types"; + +function LNURLAuthComponent() { + const { t } = useTranslation("translation", { keyPrefix: "lnurlauth" }); + const { t: tCommon } = useTranslation("common"); + + const navigate = useNavigate(); + const navState = useNavigationState(); + + const details = navState.args?.lnurlDetails as LNURLAuthServiceResponse; + const origin = navState.origin; + + const [successMessage, setSuccessMessage] = useState(""); + const [errorMessage, setErrorMessage] = useState(""); + const [loading, setLoading] = useState(false); + + async function confirm() { + try { + setLoading(true); + const response = await api.lnurlAuth({ + origin, + lnurlDetails: details, + }); + + if (navState.isPrompt && origin?.host) { + const allowance = await api.getAllowance(origin.host); + + if (allowance.lnurlAuth === false) { + await msg.request("updateAllowance", { + id: allowance.id, + lnurlAuth: true, + }); + } + } + + if (response.success) { + setSuccessMessage( + t("success", { name: origin ? origin.name : details.domain }) + ); + // ATTENTION: if this LNURL is called through `webln.lnurl` then we immediately return and return the response. This closes the window which means the user will NOT see the above successAction. + // We assume this is OK when it is called through webln. + if (navState.isPrompt) { + msg.reply(response); + } + } else { + setErrorMessage(t("errors.status")); + } + } catch (e) { + console.error(e); + if (e instanceof Error) { + setErrorMessage(`Error: ${e.message}`); + } + } finally { + setLoading(false); + } + } + + function reject(e: React.MouseEvent) { + e.preventDefault(); + if (navState.isPrompt) { + msg.error(USER_REJECTED_ERROR); + } else { + navigate(-1); + } + } + + function close(e: React.MouseEvent) { + // will never be reached via prompt + e.preventDefault(); + navigate(-1); + } + + return ( +
+ + {!successMessage ? ( + <> + +
+ {origin ? ( + + ) : ( + + )} + + + + {errorMessage && ( +

{errorMessage}

+ )} +
+ +
+ + ) : ( + + +
+
+
+ )} +
+ ); +} + +export default LNURLAuthComponent; diff --git a/src/app/screens/Enable/LiquidEnable.tsx b/src/app/screens/Enable/LiquidEnable.tsx index 813acd07de..ce13948978 100644 --- a/src/app/screens/Enable/LiquidEnable.tsx +++ b/src/app/screens/Enable/LiquidEnable.tsx @@ -29,7 +29,7 @@ export default function LiquidEnable(props: Props) { } fetchAccountAndSetComponent(); - }, [props.origin, account]); + }, [account]); return (
diff --git a/src/app/screens/Enable/NostrEnable.tsx b/src/app/screens/Enable/NostrEnable.tsx index e52c7bf783..fe6da28650 100644 --- a/src/app/screens/Enable/NostrEnable.tsx +++ b/src/app/screens/Enable/NostrEnable.tsx @@ -29,7 +29,7 @@ export default function NostrEnable(props: Props) { } fetchAccountAndSetComponent(); - }, [props.origin, account]); + }, [account]); return (
diff --git a/src/app/screens/Enable/WebbtcEnable.tsx b/src/app/screens/Enable/WebbtcEnable.tsx index a7e1267266..63830f8b5d 100644 --- a/src/app/screens/Enable/WebbtcEnable.tsx +++ b/src/app/screens/Enable/WebbtcEnable.tsx @@ -29,7 +29,7 @@ export default function WebbtcEnable(props: Props) { } fetchAccountAndSetComponent(); - }, [props.origin, account]); + }, [account]); return (
diff --git a/src/app/screens/LNURLAuth/index.tsx b/src/app/screens/LNURLAuth/index.tsx index 6d9e71b406..bf647fa4e0 100644 --- a/src/app/screens/LNURLAuth/index.tsx +++ b/src/app/screens/LNURLAuth/index.tsx @@ -1,140 +1,30 @@ -import Button from "@components/Button"; -import ConfirmOrCancel from "@components/ConfirmOrCancel"; -import Container from "@components/Container"; -import ContentMessage from "@components/ContentMessage"; -import PublisherCard from "@components/PublisherCard"; -import ResultCard from "@components/ResultCard"; -import React, { useState } from "react"; -import { useTranslation } from "react-i18next"; -import { useNavigate } from "react-router-dom"; -import ScreenHeader from "~/app/components/ScreenHeader"; -import { useNavigationState } from "~/app/hooks/useNavigationState"; -import { USER_REJECTED_ERROR } from "~/common/constants"; +import { useEffect, useState } from "react"; +import LNURLAuthComponent from "~/app/components/LNURLAuth"; +import Onboard from "~/app/components/onboard"; +import { useAccount } from "~/app/context/AccountContext"; import api from "~/common/lib/api"; -import msg from "~/common/lib/msg"; -import type { LNURLAuthServiceResponse } from "~/types"; -function LNURLAuth() { - const { t } = useTranslation("translation", { keyPrefix: "lnurlauth" }); - const { t: tCommon } = useTranslation("common"); +export default function LNURLAuth() { + const { account } = useAccount(); + const [hasMnemonic, setHasMnemonic] = useState(false); - const navigate = useNavigate(); - const navState = useNavigationState(); + useEffect(() => { + async function fetchAccountAndSetComponent() { + try { + const fetchedAccount = await api.getAccount(); - const details = navState.args?.lnurlDetails as LNURLAuthServiceResponse; - const origin = navState.origin; - - const [successMessage, setSuccessMessage] = useState(""); - const [errorMessage, setErrorMessage] = useState(""); - const [loading, setLoading] = useState(false); - - async function confirm() { - try { - setLoading(true); - const response = await api.lnurlAuth({ - origin, - lnurlDetails: details, - }); - - if (navState.isPrompt && origin?.host) { - const allowance = await api.getAllowance(origin.host); - - if (allowance.lnurlAuth === false) { - await msg.request("updateAllowance", { - id: allowance.id, - lnurlAuth: true, - }); - } - } - - if (response.success) { - setSuccessMessage( - t("success", { name: origin ? origin.name : details.domain }) - ); - // ATTENTION: if this LNURL is called through `webln.lnurl` then we immediately return and return the response. This closes the window which means the user will NOT see the above successAction. - // We assume this is OK when it is called through webln. - if (navState.isPrompt) { - msg.reply(response); + if (fetchedAccount.nostrEnabled) { + setHasMnemonic(true); + } else { + setHasMnemonic(false); } - } else { - setErrorMessage(t("errors.status")); + } catch (e) { + console.error(e); } - } catch (e) { - console.error(e); - if (e instanceof Error) { - setErrorMessage(`Error: ${e.message}`); - } - } finally { - setLoading(false); - } - } - - function reject(e: React.MouseEvent) { - e.preventDefault(); - if (navState.isPrompt) { - msg.error(USER_REJECTED_ERROR); - } else { - navigate(-1); } - } - function close(e: React.MouseEvent) { - // will never be reached via prompt - e.preventDefault(); - navigate(-1); - } + fetchAccountAndSetComponent(); + }, [account]); - return ( -
- - {!successMessage ? ( - <> - -
- {origin ? ( - - ) : ( - - )} - - - - {errorMessage && ( -

{errorMessage}

- )} -
- -
- - ) : ( - - -
-
-
- )} -
- ); + return
{hasMnemonic ? : }
; } - -export default LNURLAuth; diff --git a/src/extension/background-script/actions/lnurl/authOrPrompt.ts b/src/extension/background-script/actions/lnurl/authOrPrompt.ts index 4f2fb2d6a0..4d0451dc0f 100644 --- a/src/extension/background-script/actions/lnurl/authOrPrompt.ts +++ b/src/extension/background-script/actions/lnurl/authOrPrompt.ts @@ -33,10 +33,17 @@ async function authOrPrompt( // we have the check the unlock status manually. The account can still be locked // If it is locked we must show a prompt to unlock const isUnlocked = await state.getState().isUnlocked(); + const account = await state.getState().getAccount(); // check if there is a publisher and lnurlAuth is enabled, // otherwise we we prompt the user - if (isUnlocked && allowance && allowance.enabled && allowance.lnurlAuth) { + if ( + isUnlocked && + allowance && + allowance.enabled && + allowance.lnurlAuth && + account?.mnemonic + ) { return await authFunction({ lnurlDetails, origin: message.origin }); } else { try { diff --git a/src/extension/background-script/actions/onboard/index.ts b/src/extension/background-script/actions/onboard/index.ts deleted file mode 100644 index fc53c5af82..0000000000 --- a/src/extension/background-script/actions/onboard/index.ts +++ /dev/null @@ -1,3 +0,0 @@ -import prompt from "./prompt"; - -export { prompt }; diff --git a/src/extension/background-script/actions/onboard/prompt.ts b/src/extension/background-script/actions/onboard/prompt.ts deleted file mode 100644 index 90d41f95ef..0000000000 --- a/src/extension/background-script/actions/onboard/prompt.ts +++ /dev/null @@ -1,19 +0,0 @@ -import utils from "~/common/lib/utils"; -import { getHostFromSender } from "~/common/utils/helpers"; -import type { MessageAllowanceEnable, Sender } from "~/types"; - -const prompt = async (message: MessageAllowanceEnable, sender: Sender) => { - const host = getHostFromSender(sender); - if (!host) return; - - try { - await utils.openPrompt(message); - } catch (e) { - console.error(e); - if (e instanceof Error) { - return { error: e.message }; - } - } -}; - -export default prompt; diff --git a/src/extension/background-script/router.ts b/src/extension/background-script/router.ts index 1cd1b50b4e..5088f4761d 100644 --- a/src/extension/background-script/router.ts +++ b/src/extension/background-script/router.ts @@ -8,7 +8,6 @@ import * as ln from "./actions/ln"; import lnurl, { auth } from "./actions/lnurl"; import * as mnemonic from "./actions/mnemonic"; import * as nostr from "./actions/nostr"; -import * as onboard from "./actions/onboard"; import * as payments from "./actions/payments"; import * as permissions from "./actions/permissions"; import * as settings from "./actions/settings"; @@ -94,7 +93,6 @@ const routes = { addAccount: accounts.promptAdd, }, webln: { - onboard: onboard.prompt, enable: webln.enable, getInfo: ln.getInfo, sendPaymentOrPrompt: webln.sendPaymentOrPrompt, diff --git a/src/extension/content-script/webln.js b/src/extension/content-script/webln.js index fa5822ff03..21b51e9f99 100644 --- a/src/extension/content-script/webln.js +++ b/src/extension/content-script/webln.js @@ -1,6 +1,5 @@ import browser from "webextension-polyfill"; -import api from "~/common/lib/api"; import extractLightningData from "./batteries"; import getOriginData from "./originData"; import shouldInject from "./shouldInject"; @@ -26,7 +25,6 @@ const disabledCalls = ["webln/enable"]; let isEnabled = false; // store if webln is enabled for this content page let isRejected = false; // store if the webln enable call failed. if so we do not prompt again -let account; async function init() { const inject = await shouldInject(); @@ -91,16 +89,6 @@ async function init() { origin: getOriginData(), }; - // Overrides the enable action so the user can go through onboarding to setup their keys - - // Overrides the enable action so the user can go through onboarding to setup their keys - if (!account || !account.hasMnemonic) { - account = await api.getAccount(); - if (!account.hasMnemonic) { - messageWithOrigin.action = ev.data.action = `public/webln/onboard`; - } - } - const replyFunction = (response) => { // if it is the enable call we store if webln is enabled for this content script if (ev.data.action === "webln/enable") { From b92cd7064f7dddc6a19532b65fb200af17401b1a Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Mon, 25 Sep 2023 15:55:53 +0530 Subject: [PATCH 16/73] feat: set allowance per provider --- .../background-script/actions/alby/enable.ts | 25 ++++++++++++++----- .../allowances/__tests__/delete.test.ts | 1 + .../actions/liquid/enable.ts | 25 ++++++++++++++----- .../background-script/actions/nostr/enable.ts | 20 ++++++++++----- .../actions/webbtc/enable.ts | 25 ++++++++++++++----- .../background-script/actions/webln/enable.ts | 24 +++++++++++++----- src/extension/background-script/db.ts | 5 ++++ src/fixtures/allowances.ts | 1 + src/types.ts | 1 + 9 files changed, 97 insertions(+), 30 deletions(-) diff --git a/src/extension/background-script/actions/alby/enable.ts b/src/extension/background-script/actions/alby/enable.ts index ceb6665d07..483a51eb16 100644 --- a/src/extension/background-script/actions/alby/enable.ts +++ b/src/extension/background-script/actions/alby/enable.ts @@ -16,7 +16,12 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .equalsIgnoreCase(host) .first(); - if (isUnlocked && allowance && allowance.enabled) { + if ( + isUnlocked && + allowance && + allowance.enabled && + allowance.enabledFor?.includes("alby") + ) { return { data: { enabled: true }, }; @@ -38,17 +43,25 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { if (!allowance.id) { return { data: { error: "id is missing" } }; } - await db.allowances.update(allowance.id, { - enabled: true, - name: message.origin.name, - imageURL: message.origin.icon, - }); + if (allowance.enabledFor) { + const updatedEnabledFor = allowance.enabledFor.includes("alby") + ? [...allowance.enabledFor] + : [...allowance.enabledFor, "alby"]; + + await db.allowances.update(allowance.id, { + enabled: true, + enabledFor: updatedEnabledFor, + name: message.origin.name, + imageURL: message.origin.icon, + }); + } } else { await db.allowances.add({ host: host, name: message.origin.name, imageURL: message.origin.icon, enabled: true, + enabledFor: ["alby"], lastPaymentAt: 0, totalBudget: 0, remainingBudget: 0, diff --git a/src/extension/background-script/actions/allowances/__tests__/delete.test.ts b/src/extension/background-script/actions/allowances/__tests__/delete.test.ts index 11c0a924a7..e7804e1884 100644 --- a/src/extension/background-script/actions/allowances/__tests__/delete.test.ts +++ b/src/extension/background-script/actions/allowances/__tests__/delete.test.ts @@ -78,6 +78,7 @@ describe("delete allowance", () => { id: 1, imageURL: "https://pro.kollider.xyz/favicon.ico", lastPaymentAt: 0, + enabledFor: ["webln"], lnurlAuth: true, name: "pro kollider", remainingBudget: 500, diff --git a/src/extension/background-script/actions/liquid/enable.ts b/src/extension/background-script/actions/liquid/enable.ts index b560416bbf..8b5696aaac 100644 --- a/src/extension/background-script/actions/liquid/enable.ts +++ b/src/extension/background-script/actions/liquid/enable.ts @@ -17,7 +17,13 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .equalsIgnoreCase(host) .first(); - if (isUnlocked && allowance && allowance.enabled && account?.mnemonic) { + if ( + isUnlocked && + allowance && + allowance.enabled && + account?.mnemonic && + allowance.enabledFor?.includes("liquid") + ) { return { data: { enabled: true }, }; @@ -39,17 +45,24 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { if (!allowance.id) { return { data: { error: "id is missing" } }; } - await db.allowances.update(allowance.id, { - enabled: true, - name: message.origin.name, - imageURL: message.origin.icon, - }); + if (allowance.enabledFor) { + const updatedEnabledFor = allowance.enabledFor.includes("liquid") + ? [...allowance.enabledFor] + : [...allowance.enabledFor, "liquid"]; + await db.allowances.update(allowance.id, { + enabled: true, + enabledFor: updatedEnabledFor, + name: message.origin.name, + imageURL: message.origin.icon, + }); + } } else { await db.allowances.add({ host: host, name: message.origin.name, imageURL: message.origin.icon, enabled: true, + enabledFor: ["liquid"], lastPaymentAt: 0, totalBudget: 0, remainingBudget: 0, diff --git a/src/extension/background-script/actions/nostr/enable.ts b/src/extension/background-script/actions/nostr/enable.ts index d8c9fa8d98..6ad17bca63 100644 --- a/src/extension/background-script/actions/nostr/enable.ts +++ b/src/extension/background-script/actions/nostr/enable.ts @@ -22,7 +22,8 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { isUnlocked && allowance && allowance.enabled && - account?.nostrPrivateKey + account?.nostrPrivateKey && + allowance.enabledFor?.includes("nostr") ) { return { data: { enabled: true }, @@ -45,16 +46,23 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { if (!allowance.id) { return { data: { error: "id is missing" } }; } - await db.allowances.update(allowance.id, { - enabled: true, - name: message.origin.name, - imageURL: message.origin.icon, - }); + if (allowance.enabledFor) { + const updatedEnabledFor = allowance.enabledFor.includes("nostr") + ? [...allowance.enabledFor] + : [...allowance.enabledFor, "nostr"]; + await db.allowances.update(allowance.id, { + enabled: true, + enabledFor: updatedEnabledFor, + name: message.origin.name, + imageURL: message.origin.icon, + }); + } } else { await db.allowances.add({ host: host, name: message.origin.name, imageURL: message.origin.icon, + enabledFor: ["nostr"], enabled: true, lastPaymentAt: 0, totalBudget: 0, diff --git a/src/extension/background-script/actions/webbtc/enable.ts b/src/extension/background-script/actions/webbtc/enable.ts index b560416bbf..ca998940cc 100644 --- a/src/extension/background-script/actions/webbtc/enable.ts +++ b/src/extension/background-script/actions/webbtc/enable.ts @@ -17,7 +17,13 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .equalsIgnoreCase(host) .first(); - if (isUnlocked && allowance && allowance.enabled && account?.mnemonic) { + if ( + isUnlocked && + allowance && + allowance.enabled && + account?.mnemonic && + allowance.enabledFor?.includes("webbtc") + ) { return { data: { enabled: true }, }; @@ -39,17 +45,24 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { if (!allowance.id) { return { data: { error: "id is missing" } }; } - await db.allowances.update(allowance.id, { - enabled: true, - name: message.origin.name, - imageURL: message.origin.icon, - }); + if (allowance.enabledFor) { + const updatedEnabledFor = allowance.enabledFor.includes("webbtc") + ? [...allowance.enabledFor] + : [...allowance.enabledFor, "webbtc"]; + await db.allowances.update(allowance.id, { + enabled: true, + enabledFor: updatedEnabledFor, + name: message.origin.name, + imageURL: message.origin.icon, + }); + } } else { await db.allowances.add({ host: host, name: message.origin.name, imageURL: message.origin.icon, enabled: true, + enabledFor: ["webbtc"], lastPaymentAt: 0, totalBudget: 0, remainingBudget: 0, diff --git a/src/extension/background-script/actions/webln/enable.ts b/src/extension/background-script/actions/webln/enable.ts index ceb6665d07..f5e312c504 100644 --- a/src/extension/background-script/actions/webln/enable.ts +++ b/src/extension/background-script/actions/webln/enable.ts @@ -16,7 +16,12 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .equalsIgnoreCase(host) .first(); - if (isUnlocked && allowance && allowance.enabled) { + if ( + isUnlocked && + allowance && + allowance.enabled && + allowance.enabledFor?.includes("webln") + ) { return { data: { enabled: true }, }; @@ -38,17 +43,24 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { if (!allowance.id) { return { data: { error: "id is missing" } }; } - await db.allowances.update(allowance.id, { - enabled: true, - name: message.origin.name, - imageURL: message.origin.icon, - }); + if (allowance.enabledFor) { + const updatedEnabledFor = allowance.enabledFor.includes("webln") + ? [...allowance.enabledFor] + : [...allowance.enabledFor, "webln"]; + await db.allowances.update(allowance.id, { + enabled: true, + enabledFor: updatedEnabledFor, + name: message.origin.name, + imageURL: message.origin.icon, + }); + } } else { await db.allowances.add({ host: host, name: message.origin.name, imageURL: message.origin.icon, enabled: true, + enabledFor: ["webln"], lastPaymentAt: 0, totalBudget: 0, remainingBudget: 0, diff --git a/src/extension/background-script/db.ts b/src/extension/background-script/db.ts index 28c841d490..34bf43929b 100644 --- a/src/extension/background-script/db.ts +++ b/src/extension/background-script/db.ts @@ -51,6 +51,11 @@ export class DB extends Dexie { payments: "++id,accountId,allowanceId,host,location,name,description,totalAmount,totalFees,preimage,paymentRequest,paymentHash,destination,createdAt", }); + this.version(6).stores({ + allowances: + "++id,&host,name,imageURL,tag,enabled,&enabledFor,totalBudget,remainingBudget,lastPaymentAt,lnurlAuth,createdAt", + }); + this.on("ready", this.loadFromStorage.bind(this)); this.allowances = this.table("allowances"); this.payments = this.table("payments"); diff --git a/src/fixtures/allowances.ts b/src/fixtures/allowances.ts index 61fef45e2e..4574e0b978 100644 --- a/src/fixtures/allowances.ts +++ b/src/fixtures/allowances.ts @@ -7,6 +7,7 @@ export const allowanceFixture: DbAllowance[] = [ id: 1, imageURL: "https://pro.kollider.xyz/favicon.ico", lastPaymentAt: 0, + enabledFor: ["webln"], lnurlAuth: true, name: "pro kollider", remainingBudget: 500, diff --git a/src/types.ts b/src/types.ts index 8e1d5e8da5..8a3616c844 100644 --- a/src/types.ts +++ b/src/types.ts @@ -806,6 +806,7 @@ export interface Blocklist extends DbBlocklist {} export interface DbAllowance { createdAt: string; + enabledFor?: string[]; enabled: boolean; host: string; id?: number; From 5693a46aa6130bd1aabe4b5c2f35487a45f8dbc3 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Wed, 27 Sep 2023 15:31:18 +0530 Subject: [PATCH 17/73] feat: setup array properly --- src/extension/background-script/db.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/extension/background-script/db.ts b/src/extension/background-script/db.ts index 34bf43929b..96358d3178 100644 --- a/src/extension/background-script/db.ts +++ b/src/extension/background-script/db.ts @@ -53,7 +53,7 @@ export class DB extends Dexie { }); this.version(6).stores({ allowances: - "++id,&host,name,imageURL,tag,enabled,&enabledFor,totalBudget,remainingBudget,lastPaymentAt,lnurlAuth,createdAt", + "++id,&host,name,imageURL,tag,enabled,*enabledFor,totalBudget,remainingBudget,lastPaymentAt,lnurlAuth,createdAt", }); this.on("ready", this.loadFromStorage.bind(this)); From 6c019b8f686fcf0c32978ae55264ee5e5a495930 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Wed, 27 Sep 2023 16:07:39 +0530 Subject: [PATCH 18/73] feat: resolve test errors cleanup prompt logic to set EnabledFor use set to do logical operations and convert back to array to avoid tertiary operator --- .../background-script/actions/alby/enable.ts | 28 ++++++++----------- .../actions/liquid/enable.ts | 23 +++++++-------- .../background-script/actions/nostr/enable.ts | 23 +++++++-------- .../actions/webbtc/enable.ts | 23 +++++++-------- .../background-script/actions/webln/enable.ts | 28 ++++++++----------- 5 files changed, 60 insertions(+), 65 deletions(-) diff --git a/src/extension/background-script/actions/alby/enable.ts b/src/extension/background-script/actions/alby/enable.ts index 483a51eb16..de2c7c3518 100644 --- a/src/extension/background-script/actions/alby/enable.ts +++ b/src/extension/background-script/actions/alby/enable.ts @@ -16,12 +16,9 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .equalsIgnoreCase(host) .first(); - if ( - isUnlocked && - allowance && - allowance.enabled && - allowance.enabledFor?.includes("alby") - ) { + const enabledFor = new Set(allowance?.enabledFor); + + if (isUnlocked && allowance && allowance.enabled && enabledFor.has("alby")) { return { data: { enabled: true }, }; @@ -43,18 +40,17 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { if (!allowance.id) { return { data: { error: "id is missing" } }; } - if (allowance.enabledFor) { - const updatedEnabledFor = allowance.enabledFor.includes("alby") - ? [...allowance.enabledFor] - : [...allowance.enabledFor, "alby"]; - await db.allowances.update(allowance.id, { - enabled: true, - enabledFor: updatedEnabledFor, - name: message.origin.name, - imageURL: message.origin.icon, - }); + if (!enabledFor.has("alby")) { + enabledFor.add("alby"); } + + await db.allowances.update(allowance.id, { + enabled: true, + enabledFor: [...enabledFor], + name: message.origin.name, + imageURL: message.origin.icon, + }); } else { await db.allowances.add({ host: host, diff --git a/src/extension/background-script/actions/liquid/enable.ts b/src/extension/background-script/actions/liquid/enable.ts index 8b5696aaac..551ec90bdd 100644 --- a/src/extension/background-script/actions/liquid/enable.ts +++ b/src/extension/background-script/actions/liquid/enable.ts @@ -17,12 +17,14 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .equalsIgnoreCase(host) .first(); + const enabledFor = new Set(allowance?.enabledFor); + if ( isUnlocked && allowance && allowance.enabled && account?.mnemonic && - allowance.enabledFor?.includes("liquid") + enabledFor.has("liquid") ) { return { data: { enabled: true }, @@ -45,17 +47,16 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { if (!allowance.id) { return { data: { error: "id is missing" } }; } - if (allowance.enabledFor) { - const updatedEnabledFor = allowance.enabledFor.includes("liquid") - ? [...allowance.enabledFor] - : [...allowance.enabledFor, "liquid"]; - await db.allowances.update(allowance.id, { - enabled: true, - enabledFor: updatedEnabledFor, - name: message.origin.name, - imageURL: message.origin.icon, - }); + + if (!enabledFor.has("liquid")) { + enabledFor.add("liquid"); } + await db.allowances.update(allowance.id, { + enabled: true, + enabledFor: [...enabledFor], + name: message.origin.name, + imageURL: message.origin.icon, + }); } else { await db.allowances.add({ host: host, diff --git a/src/extension/background-script/actions/nostr/enable.ts b/src/extension/background-script/actions/nostr/enable.ts index 6ad17bca63..a52f4b3e6b 100644 --- a/src/extension/background-script/actions/nostr/enable.ts +++ b/src/extension/background-script/actions/nostr/enable.ts @@ -18,12 +18,14 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .equalsIgnoreCase(host) .first(); + const enabledFor = new Set(allowance?.enabledFor); + if ( isUnlocked && allowance && allowance.enabled && account?.nostrPrivateKey && - allowance.enabledFor?.includes("nostr") + enabledFor.has("nostr") ) { return { data: { enabled: true }, @@ -46,17 +48,16 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { if (!allowance.id) { return { data: { error: "id is missing" } }; } - if (allowance.enabledFor) { - const updatedEnabledFor = allowance.enabledFor.includes("nostr") - ? [...allowance.enabledFor] - : [...allowance.enabledFor, "nostr"]; - await db.allowances.update(allowance.id, { - enabled: true, - enabledFor: updatedEnabledFor, - name: message.origin.name, - imageURL: message.origin.icon, - }); + + if (!enabledFor.has("nostr")) { + enabledFor.add("nostr"); } + await db.allowances.update(allowance.id, { + enabled: true, + enabledFor: [...enabledFor], + name: message.origin.name, + imageURL: message.origin.icon, + }); } else { await db.allowances.add({ host: host, diff --git a/src/extension/background-script/actions/webbtc/enable.ts b/src/extension/background-script/actions/webbtc/enable.ts index ca998940cc..81ea4e8455 100644 --- a/src/extension/background-script/actions/webbtc/enable.ts +++ b/src/extension/background-script/actions/webbtc/enable.ts @@ -17,12 +17,14 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .equalsIgnoreCase(host) .first(); + const enabledFor = new Set(allowance?.enabledFor); + if ( isUnlocked && allowance && allowance.enabled && account?.mnemonic && - allowance.enabledFor?.includes("webbtc") + enabledFor.has("webbtc") ) { return { data: { enabled: true }, @@ -45,17 +47,16 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { if (!allowance.id) { return { data: { error: "id is missing" } }; } - if (allowance.enabledFor) { - const updatedEnabledFor = allowance.enabledFor.includes("webbtc") - ? [...allowance.enabledFor] - : [...allowance.enabledFor, "webbtc"]; - await db.allowances.update(allowance.id, { - enabled: true, - enabledFor: updatedEnabledFor, - name: message.origin.name, - imageURL: message.origin.icon, - }); + + if (!enabledFor.has("webbtc")) { + enabledFor.add("webbtc"); } + await db.allowances.update(allowance.id, { + enabled: true, + enabledFor: [...enabledFor], + name: message.origin.name, + imageURL: message.origin.icon, + }); } else { await db.allowances.add({ host: host, diff --git a/src/extension/background-script/actions/webln/enable.ts b/src/extension/background-script/actions/webln/enable.ts index f5e312c504..d92683f162 100644 --- a/src/extension/background-script/actions/webln/enable.ts +++ b/src/extension/background-script/actions/webln/enable.ts @@ -16,12 +16,9 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { .equalsIgnoreCase(host) .first(); - if ( - isUnlocked && - allowance && - allowance.enabled && - allowance.enabledFor?.includes("webln") - ) { + const enabledFor = new Set(allowance?.enabledFor); + + if (isUnlocked && allowance && allowance.enabled && enabledFor.has("webln")) { return { data: { enabled: true }, }; @@ -43,17 +40,16 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { if (!allowance.id) { return { data: { error: "id is missing" } }; } - if (allowance.enabledFor) { - const updatedEnabledFor = allowance.enabledFor.includes("webln") - ? [...allowance.enabledFor] - : [...allowance.enabledFor, "webln"]; - await db.allowances.update(allowance.id, { - enabled: true, - enabledFor: updatedEnabledFor, - name: message.origin.name, - imageURL: message.origin.icon, - }); + + if (!enabledFor.has("webln")) { + enabledFor.add("webln"); } + await db.allowances.update(allowance.id, { + enabled: true, + enabledFor: [...enabledFor], + name: message.origin.name, + imageURL: message.origin.icon, + }); } else { await db.allowances.add({ host: host, From 001f1a7858d2219ec65b3623207680abcaa44349 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Wed, 27 Sep 2023 04:59:42 +0200 Subject: [PATCH 19/73] Translated using Weblate (Portuguese (Brazil)) Currently translated at 83.4% (591 of 708 strings) Co-authored-by: Leonardo Translate-URL: https://hosted.weblate.org/projects/getalby-lightning-browser-extension/getalby-lightning-browser-extension/pt_BR/ Translation: getAlby - lightning-browser-extension/getAlby - lightning-browser-extension --- src/i18n/locales/pt_BR/translation.json | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/i18n/locales/pt_BR/translation.json b/src/i18n/locales/pt_BR/translation.json index c1e2958b4b..32bca410fd 100644 --- a/src/i18n/locales/pt_BR/translation.json +++ b/src/i18n/locales/pt_BR/translation.json @@ -465,7 +465,7 @@ }, "subtitle": "Escolha a rede para gerar endereços e decodificar transações bitcoin e liquid" }, - "no_mnemonic_hint": "💡 Você ainda não tem uma chave mestra. <0>Clique aqui
para criar sua chave mestra e desbloquear essas configurações." + "no_mnemonic_hint": "💡 Você ainda não tem uma chave mestra. <0>Clique aqui
para gerar sua chave mestra e desbloquear essas configurações." } }, "unlock": { @@ -609,7 +609,7 @@ "create_invoice": "Gerar fatura", "redeem": { "description": "Receba bitcoin usando um código LNURL", - "title": "Resgatar" + "title": "Vale-satoshi" }, "invoice": { "title": "Fatura relâmpago", @@ -843,7 +843,7 @@ "instructions2": "Seu endereço bitcoin está na página <0>Receber." }, "lnurlredeem": { - "title": "Resgatar bitcoin", + "title": "Resgatar vale-satoshi", "input": { "placeholder": "LNURL...", "label": "Código LNURL" From 0b17f69dc1dfc032ff9a5c2157ac44eaf5fdeb3c Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Fri, 29 Sep 2023 09:32:33 +0530 Subject: [PATCH 20/73] feat: just assign set value directly to the enabledFor object --- src/extension/background-script/actions/alby/enable.ts | 2 +- src/extension/background-script/actions/liquid/enable.ts | 2 +- src/extension/background-script/actions/nostr/enable.ts | 2 +- src/extension/background-script/actions/webbtc/enable.ts | 2 +- src/extension/background-script/actions/webln/enable.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/extension/background-script/actions/alby/enable.ts b/src/extension/background-script/actions/alby/enable.ts index de2c7c3518..ae3b84e071 100644 --- a/src/extension/background-script/actions/alby/enable.ts +++ b/src/extension/background-script/actions/alby/enable.ts @@ -47,7 +47,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { await db.allowances.update(allowance.id, { enabled: true, - enabledFor: [...enabledFor], + enabledFor: enabledFor, name: message.origin.name, imageURL: message.origin.icon, }); diff --git a/src/extension/background-script/actions/liquid/enable.ts b/src/extension/background-script/actions/liquid/enable.ts index 551ec90bdd..1d0ae2087b 100644 --- a/src/extension/background-script/actions/liquid/enable.ts +++ b/src/extension/background-script/actions/liquid/enable.ts @@ -53,7 +53,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { } await db.allowances.update(allowance.id, { enabled: true, - enabledFor: [...enabledFor], + enabledFor: enabledFor, name: message.origin.name, imageURL: message.origin.icon, }); diff --git a/src/extension/background-script/actions/nostr/enable.ts b/src/extension/background-script/actions/nostr/enable.ts index a52f4b3e6b..ea6420a115 100644 --- a/src/extension/background-script/actions/nostr/enable.ts +++ b/src/extension/background-script/actions/nostr/enable.ts @@ -54,7 +54,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { } await db.allowances.update(allowance.id, { enabled: true, - enabledFor: [...enabledFor], + enabledFor: enabledFor, name: message.origin.name, imageURL: message.origin.icon, }); diff --git a/src/extension/background-script/actions/webbtc/enable.ts b/src/extension/background-script/actions/webbtc/enable.ts index 81ea4e8455..bc73d4e755 100644 --- a/src/extension/background-script/actions/webbtc/enable.ts +++ b/src/extension/background-script/actions/webbtc/enable.ts @@ -53,7 +53,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { } await db.allowances.update(allowance.id, { enabled: true, - enabledFor: [...enabledFor], + enabledFor: enabledFor, name: message.origin.name, imageURL: message.origin.icon, }); diff --git a/src/extension/background-script/actions/webln/enable.ts b/src/extension/background-script/actions/webln/enable.ts index d92683f162..bc0ca396e5 100644 --- a/src/extension/background-script/actions/webln/enable.ts +++ b/src/extension/background-script/actions/webln/enable.ts @@ -46,7 +46,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { } await db.allowances.update(allowance.id, { enabled: true, - enabledFor: [...enabledFor], + enabledFor: enabledFor, name: message.origin.name, imageURL: message.origin.icon, }); From d2f81e6c5968a8fadfca0d6981865a31f6e4963a Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Fri, 29 Sep 2023 10:27:56 +0530 Subject: [PATCH 21/73] chore: use shorthand syntax --- src/extension/background-script/actions/alby/enable.ts | 2 +- src/extension/background-script/actions/liquid/enable.ts | 2 +- src/extension/background-script/actions/nostr/enable.ts | 2 +- src/extension/background-script/actions/webbtc/enable.ts | 2 +- src/extension/background-script/actions/webln/enable.ts | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/src/extension/background-script/actions/alby/enable.ts b/src/extension/background-script/actions/alby/enable.ts index ae3b84e071..a9fde2c743 100644 --- a/src/extension/background-script/actions/alby/enable.ts +++ b/src/extension/background-script/actions/alby/enable.ts @@ -47,7 +47,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { await db.allowances.update(allowance.id, { enabled: true, - enabledFor: enabledFor, + enabledFor, name: message.origin.name, imageURL: message.origin.icon, }); diff --git a/src/extension/background-script/actions/liquid/enable.ts b/src/extension/background-script/actions/liquid/enable.ts index 1d0ae2087b..cea14c541c 100644 --- a/src/extension/background-script/actions/liquid/enable.ts +++ b/src/extension/background-script/actions/liquid/enable.ts @@ -53,7 +53,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { } await db.allowances.update(allowance.id, { enabled: true, - enabledFor: enabledFor, + enabledFor, name: message.origin.name, imageURL: message.origin.icon, }); diff --git a/src/extension/background-script/actions/nostr/enable.ts b/src/extension/background-script/actions/nostr/enable.ts index ea6420a115..2ebbf7b73a 100644 --- a/src/extension/background-script/actions/nostr/enable.ts +++ b/src/extension/background-script/actions/nostr/enable.ts @@ -54,7 +54,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { } await db.allowances.update(allowance.id, { enabled: true, - enabledFor: enabledFor, + enabledFor, name: message.origin.name, imageURL: message.origin.icon, }); diff --git a/src/extension/background-script/actions/webbtc/enable.ts b/src/extension/background-script/actions/webbtc/enable.ts index bc73d4e755..0625498aa5 100644 --- a/src/extension/background-script/actions/webbtc/enable.ts +++ b/src/extension/background-script/actions/webbtc/enable.ts @@ -53,7 +53,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { } await db.allowances.update(allowance.id, { enabled: true, - enabledFor: enabledFor, + enabledFor, name: message.origin.name, imageURL: message.origin.icon, }); diff --git a/src/extension/background-script/actions/webln/enable.ts b/src/extension/background-script/actions/webln/enable.ts index bc0ca396e5..e70e50f60b 100644 --- a/src/extension/background-script/actions/webln/enable.ts +++ b/src/extension/background-script/actions/webln/enable.ts @@ -46,7 +46,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { } await db.allowances.update(allowance.id, { enabled: true, - enabledFor: enabledFor, + enabledFor, name: message.origin.name, imageURL: message.origin.icon, }); From 2b04e4b11f706a66a2014efe2f5c77ea6e96aed1 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Fri, 29 Sep 2023 10:40:18 +0530 Subject: [PATCH 22/73] fix: use webbtc enable screen for webbtc provider --- src/app/screens/Enable/WebbtcEnable.tsx | 4 ++-- src/i18n/locales/en/translation.json | 3 +-- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/app/screens/Enable/WebbtcEnable.tsx b/src/app/screens/Enable/WebbtcEnable.tsx index 99fc52d15e..fe325dcf3c 100644 --- a/src/app/screens/Enable/WebbtcEnable.tsx +++ b/src/app/screens/Enable/WebbtcEnable.tsx @@ -1,5 +1,5 @@ import { useEffect, useState } from "react"; -import LiquidEnableComponent from "~/app/components/Enable/LiquidEnable"; +import WebbtcEnableComponent from "~/app/components/Enable/WebbtcEnable"; import Onboard from "~/app/components/onboard"; import { useAccount } from "~/app/context/AccountContext"; import api from "~/common/lib/api"; @@ -34,7 +34,7 @@ export default function WebbtcEnable(props: Props) { return ( <> {hasMnemonic ? ( - + ) : ( )} diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 0a0f178cd5..a651d2b7ac 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -530,7 +530,6 @@ }, "webln_enable": { "title": "Connect to WebLN", - "request2": "Request invoices and lightning information" }, "alby_enable": { @@ -548,7 +547,7 @@ }, "webbtc_enable": { "title": "Connect to WebBTC", - "request2": "Request invoices and liquid information" + "request2": "Request invoices and Webbtc information" }, "unlock": { "unlock_to_continue": "Unlock to continue", From 8ef511b9655a6de0520101dd3e6fa95003db6450 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Fri, 29 Sep 2023 15:37:42 +0530 Subject: [PATCH 23/73] feat: add support for legacy lnurl-auth users, to allow login without setup keys --- src/app/screens/LNURLAuth/index.tsx | 16 +++++++++---- .../actions/lnurl/authOrPrompt.ts | 23 ++++++++++++------- 2 files changed, 27 insertions(+), 12 deletions(-) diff --git a/src/app/screens/LNURLAuth/index.tsx b/src/app/screens/LNURLAuth/index.tsx index bf647fa4e0..83ca0ce8c9 100644 --- a/src/app/screens/LNURLAuth/index.tsx +++ b/src/app/screens/LNURLAuth/index.tsx @@ -2,18 +2,22 @@ import { useEffect, useState } from "react"; import LNURLAuthComponent from "~/app/components/LNURLAuth"; import Onboard from "~/app/components/onboard"; import { useAccount } from "~/app/context/AccountContext"; +import { isAlbyOAuthAccount } from "~/app/utils"; import api from "~/common/lib/api"; export default function LNURLAuth() { const { account } = useAccount(); const [hasMnemonic, setHasMnemonic] = useState(false); + const [albyOAuthAccount, setAlbyOAuthAccount] = useState(false); useEffect(() => { - async function fetchAccountAndSetComponent() { + async function fetchAccountInfo() { try { const fetchedAccount = await api.getAccount(); + const connectorType = isAlbyOAuthAccount(fetchedAccount.connectorType); + setAlbyOAuthAccount(connectorType); - if (fetchedAccount.nostrEnabled) { + if (fetchedAccount.hasMnemonic) { setHasMnemonic(true); } else { setHasMnemonic(false); @@ -23,8 +27,12 @@ export default function LNURLAuth() { } } - fetchAccountAndSetComponent(); + fetchAccountInfo(); }, [account]); - return
{hasMnemonic ? : }
; + return ( + <> + {albyOAuthAccount && !hasMnemonic ? : } + + ); } diff --git a/src/extension/background-script/actions/lnurl/authOrPrompt.ts b/src/extension/background-script/actions/lnurl/authOrPrompt.ts index 4d0451dc0f..1d41e7769f 100644 --- a/src/extension/background-script/actions/lnurl/authOrPrompt.ts +++ b/src/extension/background-script/actions/lnurl/authOrPrompt.ts @@ -10,6 +10,7 @@ import type { Sender, } from "~/types"; +import { isAlbyOAuthAccount } from "~/app/utils"; import { authFunction } from "./auth"; async function authOrPrompt( @@ -34,18 +35,24 @@ async function authOrPrompt( // If it is locked we must show a prompt to unlock const isUnlocked = await state.getState().isUnlocked(); const account = await state.getState().getAccount(); + const isAlbyOAuthConnector = isAlbyOAuthAccount(account?.connector); // check if there is a publisher and lnurlAuth is enabled, // otherwise we we prompt the user - if ( - isUnlocked && - allowance && - allowance.enabled && - allowance.lnurlAuth && - account?.mnemonic - ) { - return await authFunction({ lnurlDetails, origin: message.origin }); + + if (isUnlocked && allowance && allowance.enabled && allowance.lnurlAuth) { + if (!isAlbyOAuthConnector) { + return await authFunction({ lnurlDetails, origin: message.origin }); + } else if (account?.mnemonic) { + return await authFunction({ lnurlDetails, origin: message.origin }); + } else { + authPrompt(); + } } else { + authPrompt(); + } + + async function authPrompt() { try { const promptMessage = { ...message, From 4f5ced0e74a3126f20d08424708edbd7c3927be4 Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Fri, 29 Sep 2023 17:26:13 +0400 Subject: [PATCH 24/73] chore: prompt for oauth credentials during build This prompts the user for the OAuth credentials if they are not provided during the yarn package script This makes it easier for new users and makes sure that OAuth credentials are provided. --- create-packages.sh | 34 +++++++++++++++++++++++++++++++++- package.json | 2 +- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/create-packages.sh b/create-packages.sh index b3d48057a2..c9ccb8d10f 100755 --- a/create-packages.sh +++ b/create-packages.sh @@ -3,6 +3,38 @@ # Extract version from package.json VERSION=$(node -pe "require('./package.json').version") +if [ -z ${ALBY_OAUTH_CLIENT_ID_CHROME+x} ]; +then + echo "OAuth client id for Chrome:" + read ALBY_OAUTH_CLIENT_ID_CHROME +fi + +if [ -z ${ALBY_OAUTH_CLIENT_SECRET_CHROME+x} ]; +then + echo "OAuth client secret for Chrome:" + read ALBY_OAUTH_CLIENT_SECRET_CHROME +fi + +if [ -z ${ALBY_OAUTH_CLIENT_ID_FIREFOX+x} ]; +then + echo "OAuth client id for Firefox:" + read ALBY_OAUTH_CLIENT_ID_FIREFOX +fi + +if [ -z ${ALBY_OAUTH_CLIENT_SECRET_FIREFOX+x} ]; +then + echo "OAuth client secret for Firefox:" + read ALBY_OAUTH_CLIENT_SECRET_FIREFOX +fi + +if [ -z ${ALBY_API_URL+x} ]; +then + ALBY_API_URL="https://api.getalby.com" +fi + +echo "Creating the build for v$VERSION" +yarn build + echo "Creating zip packages for v$VERSION" cd dist/production @@ -38,4 +70,4 @@ echo "Created alby-opera-v$VERSION.crx (SHA512: $SHA)" echo "done!" -cd ../../../ \ No newline at end of file +cd ../../../ diff --git a/package.json b/package.json index 9eaafc3d75..32b7e6be3b 100644 --- a/package.json +++ b/package.json @@ -21,7 +21,7 @@ "build:firefox": "NODE_ENV=production TARGET_BROWSER=firefox webpack", "build:opera": "NODE_ENV=production TARGET_BROWSER=opera webpack", "build": "yarn build:chrome && yarn build:firefox && yarn build:opera", - "package": "yarn build && ./create-packages.sh", + "package": "./create-packages.sh", "lint": "yarn lint:js && yarn tsc:compile && yarn format:fix", "lint:js": "eslint src --ext .js,.jsx,.ts,.tsx --max-warnings 0", "lint:js:fix": "eslint src --ext .js,.jsx,.ts,.tsx --fix", From 50bcb5d5803f04bf5dd86f0f3ddf38d8d8cd433d Mon Sep 17 00:00:00 2001 From: Michael Bumann Date: Fri, 29 Sep 2023 18:27:04 +0400 Subject: [PATCH 25/73] chore: set node env to production for builds --- create-packages.sh | 1 + 1 file changed, 1 insertion(+) diff --git a/create-packages.sh b/create-packages.sh index c9ccb8d10f..82a8da6bc2 100755 --- a/create-packages.sh +++ b/create-packages.sh @@ -2,6 +2,7 @@ # Extract version from package.json VERSION=$(node -pe "require('./package.json').version") +NODE_ENV=production if [ -z ${ALBY_OAUTH_CLIENT_ID_CHROME+x} ]; then From d526f5610648adf481869209a2d6c23322bf7198 Mon Sep 17 00:00:00 2001 From: Rithvik-padma Date: Sat, 30 Sep 2023 09:55:58 +0530 Subject: [PATCH 26/73] fix: increase the qr eclevel to make it work for short address --- src/app/components/QRCode/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/QRCode/index.tsx b/src/app/components/QRCode/index.tsx index faf5719506..c8803afcac 100644 --- a/src/app/components/QRCode/index.tsx +++ b/src/app/components/QRCode/index.tsx @@ -19,7 +19,7 @@ export default function QRCode({ value, size, className }: Props) { className={classNames("rounded-md", className ?? "")} fgColor={fgColor} bgColor={bgColor} - level="M" + level="Q" /> ); } From 8e16277bbb82a216a45e26a8b73057dc40643d63 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 2 Oct 2023 07:18:44 +0200 Subject: [PATCH 27/73] Translated using Weblate (Portuguese (Brazil)) Currently translated at 84.7% (600 of 708 strings) Co-authored-by: Leonardo Translate-URL: https://hosted.weblate.org/projects/getalby-lightning-browser-extension/getalby-lightning-browser-extension/pt_BR/ Translation: getAlby - lightning-browser-extension/getAlby - lightning-browser-extension --- src/i18n/locales/pt_BR/translation.json | 31 ++++++++++++++++++++----- 1 file changed, 25 insertions(+), 6 deletions(-) diff --git a/src/i18n/locales/pt_BR/translation.json b/src/i18n/locales/pt_BR/translation.json index 32bca410fd..77e489b4bf 100644 --- a/src/i18n/locales/pt_BR/translation.json +++ b/src/i18n/locales/pt_BR/translation.json @@ -766,7 +766,7 @@ "title": "Sua carteira Alby está pronta", "description": "Algumas dicas para você começar a usar a Alby 🐝", "mnemonic": { - "description": "Planejando usar apps Nostr?\nClique aqui para gerar suas chaves.", + "description": "Planejando usar apps Nostr?\nClique aqui para configurar suas chaves.", "title": "Nostr" } } @@ -907,19 +907,37 @@ "provider": { "label": "Serviço provedor de trocas" }, - "time_estimate": "ℹ️ As transações geralmente são recebidas dentro de 10 a 30 minutos." + "time_estimate": "ℹ️ As transações geralmente são recebidas dentro de 10 a 30 minutos.", + "title": "Enviar para um endereço bitcoin", + "recipient": { + "label": "Endereço" + } }, "onboard": { "request2": "Você ainda não configurou suas chaves", "request3": "A configuração é rápida e fácil", "title": "Configure suas chaves", "actions": { - "start_setup": "Iniciar configuração" - } + "start_setup": "Configurar" + }, + "request1": "Interagir com este site exige configurar uma chave" }, "nostr_enable": { "request2": "Assinar eventos usando sua chave privada Nostr", - "request1": "Solicitar a leitura de sua chave pública Nostr" + "request1": "Solicitar a leitura de sua chave pública Nostr", + "title": "Conectar no Nostr" + }, + "webln_enable": { + "title": "Conectar no WebLN" + }, + "webbtc_enable": { + "title": "Conectar no WebBTC" + }, + "liquid_enable": { + "title": "Conectar na Liquid" + }, + "alby_enable": { + "title": "Conectar na Alby" } }, "common": { @@ -966,7 +984,8 @@ "download": "Baixar", "copy_clipboard": "Copiar para área de transferência", "disconnect": "Desconectar", - "copied_to_clipboard": "Copiado para a área de transferência" + "copied_to_clipboard": "Copiado para a área de transferência", + "review": "Revisar" }, "errors": { "connection_failed": "Falha na conexão", From 81b81addb8c37252ae723a8c2b0a60f8f9194e1b Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 2 Oct 2023 07:18:44 +0200 Subject: [PATCH 28/73] Translated using Weblate (German) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (708 of 708 strings) Co-authored-by: BSN ∞/21M ₿ Co-authored-by: Hosted Weblate Translate-URL: https://hosted.weblate.org/projects/getalby-lightning-browser-extension/getalby-lightning-browser-extension/de/ Translation: getAlby - lightning-browser-extension/getAlby - lightning-browser-extension --- src/i18n/locales/de/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/locales/de/translation.json b/src/i18n/locales/de/translation.json index 4b8ca2d244..66aa0f0fab 100644 --- a/src/i18n/locales/de/translation.json +++ b/src/i18n/locales/de/translation.json @@ -959,7 +959,7 @@ "request1": "Anfrage zum Lesen deines öffentlichen Nostr-Schlüssels" }, "webbtc_enable": { - "request2": "Rechnungen und Liquidinformationen anfordern", + "request2": "Rechnungen und Webbtc-Informationen anfordern", "title": "Mit WebBTC verbinden" }, "alby_enable": { From 8d0868f03582ad47abfc0209746d46ef6802ff8c Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 2 Oct 2023 07:18:44 +0200 Subject: [PATCH 29/73] Translated using Weblate (Czech) Currently translated at 65.5% (464 of 708 strings) Co-authored-by: Japas Tapas Translate-URL: https://hosted.weblate.org/projects/getalby-lightning-browser-extension/getalby-lightning-browser-extension/cs/ Translation: getAlby - lightning-browser-extension/getAlby - lightning-browser-extension --- src/i18n/locales/cs/translation.json | 97 ++++++++++++++++++++++++++-- 1 file changed, 92 insertions(+), 5 deletions(-) diff --git a/src/i18n/locales/cs/translation.json b/src/i18n/locales/cs/translation.json index 205d360753..7486600627 100644 --- a/src/i18n/locales/cs/translation.json +++ b/src/i18n/locales/cs/translation.json @@ -252,7 +252,7 @@ }, "title": "Připojte Lightning peněženku", "lnc": { - "title": "LND s LNC", + "title": "Lightning terminál (LNC)", "page": { "title": "Připojit k vašemu LND uzlu", "description": "Vytvořte nové sezení v terminálu (litd) pro získání nové párovací fráze a vložte ji zde" @@ -266,14 +266,28 @@ "description": "Přihlášení k vašemu účtu Kollider", "title": "Kollider", "errors": { - "connection_failed": "Připojení se nezdařilo. Jste si jisti, že jste zadali údaje k účtu správně?" + "connection_failed": "Připojení se nezdařilo. Jste si jisti, že jste zadali údaje k účtu správně?", + "user_already_exists": "Uživatelské jméno již existuje", + "registration_limit_exceeded": "Limit registrací byl překročen, zkuste to znovu později." }, "username": { "label": "Vložte vaše uživatelské jméno ke Kollider" }, "currency": { "label": "Zvolte měnu vašeho účtu" + }, + "choose_path": { + "create_new": "Přihlásit se", + "title": "Připojení k peněžence Kollider", + "description": "Přihlašte se nebo si vytvořte nový Kollider účet a propojte ho s Alby." + }, + "warning": "⚠️ Prosím ujistěte se, že máte své přihlašovací údaje uložené bezpečně ve svém správci hesel. Bez těchto údajů nepůjde váš účet obnovit.", + "create": { + "title": "Vytvořte si svůj Kollider účet a propojte ho s Alby" } + }, + "umbrel_lightning_node": { + "title": "Lightning uzel" } }, "home": { @@ -284,12 +298,19 @@ "recent_transactions": "Nedávné transakce", "allowance_view": { "recent_transactions": "Nedávné transakce", - "no_transactions": "Prozatím žádné transakce na <0>{{name}}." + "no_transactions": "Prozatím žádné transakce na <0>{{name}}.", + "sats": "satů", + "total_spent": "Cekově utraceno", + "total_payments": "Platby celkem", + "permissions": "Oprávnění" }, "default_view": { "recent_transactions": "Nedávné transakce", "is_blocked_hint": "Alby je v současnosti zakázaná na {host}}", - "block_removed": "Povolen {{host}}. Prosím znovu načtěte webovou stránku." + "block_removed": "Povolen {{host}}. Prosím znovu načtěte webovou stránku.", + "no_outgoing_transactions": "Zatím z tohoto účtu neproběhla žádná odchozí transakce.", + "all_transactions_link": "Zobrazit všechny transakce", + "no_incoming_transactions": "Zatím na tento účet neproběhla žádná příchozí transakce." } }, "accounts": { @@ -330,6 +351,11 @@ "title": "Nostr", "public_key": { "label": "Veřejný klíč" + }, + "settings": { + "label": "Nastavení Nostru", + "remove": "Odstranit současné klíče", + "title": "Nastavení Nostru" } }, "actions": { @@ -349,6 +375,45 @@ "name": { "title": "Jméno", "placeholder": "Jméno účtu" + }, + "mnemonic": { + "new": { + "title": "Co je to Master klíč?" + }, + "generate": { + "button": "Vygenerovat Master klíč", + "confirm": "Zazálohoval jsem svou záložní (recovery) frázi k mému Master klíči na soukromém bezpečném místě.", + "title": "Vygenerování nového Master klíče", + "error_confirm": "Prosím potvrďte, že jste zazálohovali svou záložní frázi." + }, + "inputs": { + "title": "Záložní fráze" + }, + "saved": "Master klíč byl zašifrován a úspěšně uložen.", + "lnurl": { + "use_mnemonic": "Používat Master klíč pro přihlašování do aplikací podporujících autentizaci přes Lightning (LNURL Auth)", + "title": "Přihlášení přes Lightning" + }, + "backup": { + "protocols": { + "nostr": "Nostr" + }, + "save": "Uložit Master klíč", + "button": "Zobrazit záložní frázi" + }, + "title": "🔑 Správa klíčů", + "import": { + "button": "Importovat Master klíč", + "title": "Import Master klíče" + } + }, + "network": { + "options": { + "testnet": "Testnet", + "bitcoin": "Mainnet", + "regtest": "Regtest" + }, + "title": "Síť" } } }, @@ -608,7 +673,8 @@ }, "alby": { "title": "Alby", - "description": "Zaregistrujte se, nebo se přihlašte ke svému existujícímu Alby účtu a začněte využívat lightning platby." + "description": "Zaregistrujte se, nebo se přihlašte ke svému existujícímu Alby účtu a začněte využívat lightning platby.", + "connect": "Připojit se s Alby" } }, "alby": { @@ -652,6 +718,27 @@ "title": "🕹️ Vyzkoušejte demo Alby" } } + }, + "distributions": { + "title": "Připojit k {{name}}", + "raspiblitz": { + "name": "Raspiblitz" + }, + "mynode": { + "name": "myNode" + }, + "btcpay": { + "name": "BTCPay" + }, + "start9": { + "name": "Start9" + }, + "umbrel": { + "name": "Umbrel" + }, + "citadel": { + "name": "Citadel" + } } }, "common": { From ae53accdc2fed0e438b3f3b6e2d52b43d60623b0 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Mon, 2 Oct 2023 07:18:44 +0200 Subject: [PATCH 30/73] Translated using Weblate (Swedish) Currently translated at 95.1% (674 of 708 strings) Co-authored-by: Hosted Weblate Co-authored-by: Pextar Translate-URL: https://hosted.weblate.org/projects/getalby-lightning-browser-extension/getalby-lightning-browser-extension/sv/ Translation: getAlby - lightning-browser-extension/getAlby - lightning-browser-extension --- src/i18n/locales/sv/translation.json | 26 +++++++++++++++++++++----- 1 file changed, 21 insertions(+), 5 deletions(-) diff --git a/src/i18n/locales/sv/translation.json b/src/i18n/locales/sv/translation.json index ad9e2ab144..a48c8e6531 100644 --- a/src/i18n/locales/sv/translation.json +++ b/src/i18n/locales/sv/translation.json @@ -303,7 +303,12 @@ "recent_transactions": "Senaste Transaktioner", "allowance_view": { "recent_transactions": "Senaste Transaktioner", - "no_transactions": "Inga transaktioner för <0>{{name}} ännu." + "no_transactions": "Inga transaktioner för <0>{{name}} ännu.", + "sats": "sats", + "budget_spent": "Budget förbrukad", + "total_spent": "Totalt spenderat", + "total_payments": "Totala betalningar", + "permissions": "Behörigheter" }, "default_view": { "recent_transactions": "Senaste Transaktioner", @@ -339,7 +344,8 @@ "label": "Nostr Privat Nyckel", "subtitle": "Klistra in din nostr privata nyckel eller skapa en ny. <0>Läs mer »", "success": "Nostr privat nyckel krypterad och sparad.", - "failed_to_remove": "Det angivna kontonamnet matchade inte, din gamla Nostr privata nyckel har återställts." + "failed_to_remove": "Det angivna kontonamnet matchade inte, din gamla Nostr privata nyckel har återställts.", + "warning": "Vänligen ange namnet på kontot för att bekräfta raderingen av din nostr privata nyckel:\n\n{{ name }}" }, "public_key": { "label": "Nostr Publik Nyckel" @@ -386,7 +392,8 @@ "remove": { "title": "Ta bort detta konto", "subtitle": "Tar bort alla traktamenten, betalningsdata och nycklar som är kopplade till detta konto.", - "error": "Det angivna kontonamnet matchade inte." + "error": "Det angivna kontonamnet matchade inte.", + "confirm": "Ange namnet på kontot för att bekräfta raderingen av ditt konto.\n\n{{ name }}\n\n ⚠️ Alla associerade nycklar (Master Key, Nostr, etc) kommer att raderas om du fortsätter. Se till att du har säkerhetskopierat dem, det finns inget annat sätt att återställa dem." }, "title1": "Konto inställningar", "title2": "Konto", @@ -430,7 +437,8 @@ "items": { "recovery_phrase": "Du kan alltid komma åt din huvudnyckel genom att använda dess återställningsfras.", "words": "Återställningsfrasen består av 12 ord som fungerar som ett lösenord, men det kan inte ändras eller återställas om det tappas bort.", - "storage": "Se till att skriva ner det någonstans säkert och privat!" + "storage": "Se till att skriva ner det någonstans säkert och privat!", + "keys": "Master Key låter dig interagera med olika protokoll som: Nostr, Liquid eller baslager av Bitcoin." } }, "import": { @@ -445,12 +453,16 @@ "lnurl": { "title": "Logga in med Lightning", "use_mnemonic": "Använd Master Key för att logga in på lightning appar (LNURL Auth)" + }, + "new": { + "title": "Vad är en Master Key?" } }, "remove_secretkey": { "title": "Ta bort huvudnyckeln", "subtitle": "Tar bort huvudnyckeln från det här kontot.", - "success": "Masternyckeln har tagits bort." + "success": "Masternyckeln har tagits bort.", + "confirm": "Ange namnet på kontot för att bekräfta raderingen av din huvudnyckel.\n{{ name }}" }, "no_mnemonic_hint": "💡 Du har ingen huvudnyckel än. <0>Klicka här
för att skapa din huvudnyckel och låsa upp dessa inställningar.", "network": { @@ -913,6 +925,10 @@ }, "view_on_explorer": "Visa på mempool explorer", "time_estimate": "ℹ️ Transaktioner kommer vanligtvis inom 10-30 minuter." + }, + "webln_enable": { + "title": "Anslut till WebLN", + "request2": "Begär fakturor och lightning information" } }, "common": { From d11d190ee4e23a163c33f43a1e5e429c2fc99f92 Mon Sep 17 00:00:00 2001 From: Rithvik-padma Date: Mon, 2 Oct 2023 20:23:25 +0530 Subject: [PATCH 31/73] fix: add required attribute to the account name field --- src/app/screens/Accounts/Detail/index.tsx | 1 + 1 file changed, 1 insertion(+) diff --git a/src/app/screens/Accounts/Detail/index.tsx b/src/app/screens/Accounts/Detail/index.tsx index 78ded234f9..dc3fc7b193 100644 --- a/src/app/screens/Accounts/Detail/index.tsx +++ b/src/app/screens/Accounts/Detail/index.tsx @@ -201,6 +201,7 @@ function AccountDetail() { onChange={(event) => { setAccountName(event.target.value); }} + required />
From 3a89af936462abf4c09626130e8a71f1ccf256b9 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 3 Oct 2023 15:37:40 +0700 Subject: [PATCH 32/73] chore: remove unnecessary checks --- src/extension/background-script/actions/alby/enable.ts | 4 +--- src/extension/background-script/actions/liquid/enable.ts | 5 ++--- src/extension/background-script/actions/nostr/enable.ts | 5 ++--- src/extension/background-script/actions/webbtc/enable.ts | 5 ++--- src/extension/background-script/actions/webln/enable.ts | 5 ++--- 5 files changed, 9 insertions(+), 15 deletions(-) diff --git a/src/extension/background-script/actions/alby/enable.ts b/src/extension/background-script/actions/alby/enable.ts index a9fde2c743..b47c7b26e1 100644 --- a/src/extension/background-script/actions/alby/enable.ts +++ b/src/extension/background-script/actions/alby/enable.ts @@ -41,9 +41,7 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { return { data: { error: "id is missing" } }; } - if (!enabledFor.has("alby")) { - enabledFor.add("alby"); - } + enabledFor.add("alby"); await db.allowances.update(allowance.id, { enabled: true, diff --git a/src/extension/background-script/actions/liquid/enable.ts b/src/extension/background-script/actions/liquid/enable.ts index cea14c541c..331ccc3319 100644 --- a/src/extension/background-script/actions/liquid/enable.ts +++ b/src/extension/background-script/actions/liquid/enable.ts @@ -48,9 +48,8 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { return { data: { error: "id is missing" } }; } - if (!enabledFor.has("liquid")) { - enabledFor.add("liquid"); - } + enabledFor.add("liquid"); + await db.allowances.update(allowance.id, { enabled: true, enabledFor, diff --git a/src/extension/background-script/actions/nostr/enable.ts b/src/extension/background-script/actions/nostr/enable.ts index 2ebbf7b73a..4879b154f5 100644 --- a/src/extension/background-script/actions/nostr/enable.ts +++ b/src/extension/background-script/actions/nostr/enable.ts @@ -49,9 +49,8 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { return { data: { error: "id is missing" } }; } - if (!enabledFor.has("nostr")) { - enabledFor.add("nostr"); - } + enabledFor.add("nostr"); + await db.allowances.update(allowance.id, { enabled: true, enabledFor, diff --git a/src/extension/background-script/actions/webbtc/enable.ts b/src/extension/background-script/actions/webbtc/enable.ts index 0625498aa5..26f0d008be 100644 --- a/src/extension/background-script/actions/webbtc/enable.ts +++ b/src/extension/background-script/actions/webbtc/enable.ts @@ -48,9 +48,8 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { return { data: { error: "id is missing" } }; } - if (!enabledFor.has("webbtc")) { - enabledFor.add("webbtc"); - } + enabledFor.add("webbtc"); + await db.allowances.update(allowance.id, { enabled: true, enabledFor, diff --git a/src/extension/background-script/actions/webln/enable.ts b/src/extension/background-script/actions/webln/enable.ts index e70e50f60b..f1be38ee93 100644 --- a/src/extension/background-script/actions/webln/enable.ts +++ b/src/extension/background-script/actions/webln/enable.ts @@ -41,9 +41,8 @@ const enable = async (message: MessageAllowanceEnable, sender: Sender) => { return { data: { error: "id is missing" } }; } - if (!enabledFor.has("webln")) { - enabledFor.add("webln"); - } + enabledFor.add("webln"); + await db.allowances.update(allowance.id, { enabled: true, enabledFor, From 12ce1fb4e1bcf619bc5dd467ae003ec4ce72d286 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 3 Oct 2023 22:14:04 +0700 Subject: [PATCH 33/73] feat: add webln sendPaymentAsync --- src/app/router/Prompt/Prompt.tsx | 5 + src/app/screens/ConfirmPayment/index.tsx | 16 +-- .../ConfirmPaymentAsync/index.test.tsx | 80 +++++++++++ src/app/screens/ConfirmPaymentAsync/index.tsx | 125 ++++++++++++++++++ src/common/lib/api.ts | 28 ++++ .../background-script/actions/ln/index.ts | 4 +- .../actions/ln/sendPaymentAsync.ts | 32 +++++ .../background-script/actions/webln/index.ts | 2 + .../webln/sendPaymentAsyncWithPrompt.ts | 21 +++ .../actions/webln/sendPaymentOrPrompt.ts | 7 +- .../background-script/connectors/alby.ts | 9 +- .../background-script/connectors/citadel.ts | 1 + .../background-script/connectors/commando.ts | 1 + .../connectors/connector.interface.ts | 5 + .../background-script/connectors/eclair.ts | 1 + .../background-script/connectors/galoy.ts | 1 + .../background-script/connectors/lnbits.ts | 1 + .../background-script/connectors/lnc.ts | 1 + .../background-script/connectors/lnd.ts | 1 + .../background-script/connectors/lndhub.ts | 1 + src/extension/background-script/router.ts | 2 + src/extension/content-script/webln.js | 1 + src/extension/providers/webln/index.ts | 4 + src/i18n/locales/en/translation.json | 7 + 24 files changed, 337 insertions(+), 19 deletions(-) create mode 100644 src/app/screens/ConfirmPaymentAsync/index.test.tsx create mode 100644 src/app/screens/ConfirmPaymentAsync/index.tsx create mode 100644 src/extension/background-script/actions/ln/sendPaymentAsync.ts create mode 100644 src/extension/background-script/actions/webln/sendPaymentAsyncWithPrompt.ts diff --git a/src/app/router/Prompt/Prompt.tsx b/src/app/router/Prompt/Prompt.tsx index 36ffae360c..0c123d055c 100644 --- a/src/app/router/Prompt/Prompt.tsx +++ b/src/app/router/Prompt/Prompt.tsx @@ -22,6 +22,7 @@ import Toaster from "~/app/components/Toast/Toaster"; import Providers from "~/app/context/Providers"; import RequireAuth from "~/app/router/RequireAuth"; import BitcoinConfirmGetAddress from "~/app/screens/Bitcoin/ConfirmGetAddress"; +import ConfirmPaymentAsync from "~/app/screens/ConfirmPaymentAsync"; import AlbyEnable from "~/app/screens/Enable/AlbyEnable"; import LiquidEnable from "~/app/screens/Enable/LiquidEnable"; import NostrEnable from "~/app/screens/Enable/NostrEnable"; @@ -137,6 +138,10 @@ function Prompt() { } /> } /> } /> + } + /> } /> } /> } /> diff --git a/src/app/screens/ConfirmPayment/index.tsx b/src/app/screens/ConfirmPayment/index.tsx index 4138f41c76..90321990ad 100644 --- a/src/app/screens/ConfirmPayment/index.tsx +++ b/src/app/screens/ConfirmPayment/index.tsx @@ -15,6 +15,7 @@ import { useAccount } from "~/app/context/AccountContext"; import { useSettings } from "~/app/context/SettingsContext"; import { useNavigationState } from "~/app/hooks/useNavigationState"; import { USER_REJECTED_ERROR } from "~/common/constants"; +import api from "~/common/lib/api"; import msg from "~/common/lib/msg"; function ConfirmPayment() { @@ -76,16 +77,9 @@ function ConfirmPayment() { try { setLoading(true); - // TODO: move to api - const response = await msg.request( - "sendPayment", - { paymentRequest: paymentRequest }, - { - origin: navState.origin, - } - ); - if (response.error) { - throw new Error(response.error as string); + const response = await api.sendPayment(paymentRequest, navState.origin); + if ("error" in response) { + throw new Error(response.error); } auth.fetchAccountInfo(); // Update balance. @@ -156,7 +150,7 @@ function ConfirmPayment() {
diff --git a/src/app/screens/ConfirmPaymentAsync/index.test.tsx b/src/app/screens/ConfirmPaymentAsync/index.test.tsx new file mode 100644 index 0000000000..dd525f0fed --- /dev/null +++ b/src/app/screens/ConfirmPaymentAsync/index.test.tsx @@ -0,0 +1,80 @@ +import { act, render, screen } from "@testing-library/react"; +import { MemoryRouter } from "react-router-dom"; +import { settingsFixture as mockSettings } from "~/../tests/fixtures/settings"; +import type { OriginData } from "~/types"; + +import ConfirmPaymentAsync from "./index"; + +const mockOrigin: OriginData = { + location: "https://getalby.com/demo", + domain: "https://getalby.com", + host: "getalby.com", + pathname: "/demo", + name: "Alby", + description: "", + icon: "https://getalby.com/assets/alby-503261fa1b83c396b7ba8d927db7072d15fea5a84d387a654c5d0a2cefd44604.svg", + metaData: { + title: "Alby Demo", + url: "https://getalby.com/demo", + provider: "Alby", + image: + "https://getalby.com/assets/alby-503261fa1b83c396b7ba8d927db7072d15fea5a84d387a654c5d0a2cefd44604.svg", + icon: "https://getalby.com/favicon.ico", + }, + external: true, +}; + +const paymentRequest = + "lnbc250n1p3qzycupp58uc2wa29470f98wrxmy4xwuqt8cywjygf5t2cp0s376y7nwdyq3sdqhf35kw6r5de5kueeqg3jk6mccqzpgxqyz5vqsp5wfdmwtv5rmru00ajsnn3f8lzpxa4snug2tmqvc8zj8semr4kjjts9qyyssq83h74pte8nrkqs8sr2hscv5zcdmhwunwnd6xr3mskeayh96pu7ksswa6p7trknlpp6t3js4k6uytxutv5ecgcwaxz7fj4zfy5khjcjcpf66muy"; + +let parameters = {}; + +jest.mock("~/app/hooks/useNavigationState", () => { + return { + useNavigationState: jest.fn(() => parameters), + }; +}); + +let mockGetFiatValue = jest.fn(); +let mockSettingsTmp = { ...mockSettings }; + +jest.mock("~/app/context/SettingsContext", () => ({ + useSettings: () => ({ + settings: mockSettingsTmp, + isLoading: false, + updateSetting: jest.fn(), + getFormattedFiat: mockGetFiatValue, + getFormattedNumber: jest.fn(), + getFormattedSats: jest.fn(() => "25 sats"), + }), +})); + +describe("ConfirmPaymentAsync", () => { + afterEach(() => { + jest.clearAllMocks(); + }); + + test("prompt: renders with fiat", async () => { + parameters = { + origin: mockOrigin, + args: { + paymentRequest, + }, + }; + + mockSettingsTmp = { ...mockSettings }; + mockGetFiatValue = jest.fn(() => Promise.resolve("$0.01")); + + await act(async () => { + render( + + + + ); + }); + + expect(await screen.findByText("Amount")).toBeInTheDocument(); + expect(await screen.findByText("Description")).toBeInTheDocument(); + expect(await screen.findByText("(~$0.01)")).toBeInTheDocument(); + }); +}); diff --git a/src/app/screens/ConfirmPaymentAsync/index.tsx b/src/app/screens/ConfirmPaymentAsync/index.tsx new file mode 100644 index 0000000000..43f431028c --- /dev/null +++ b/src/app/screens/ConfirmPaymentAsync/index.tsx @@ -0,0 +1,125 @@ +import ConfirmOrCancel from "@components/ConfirmOrCancel"; +import Container from "@components/Container"; +import PaymentSummary from "@components/PaymentSummary"; +import PublisherCard from "@components/PublisherCard"; +import lightningPayReq from "bolt11"; +import { useEffect, useState } from "react"; +import { useTranslation } from "react-i18next"; +import { useNavigate } from "react-router-dom"; +import Alert from "~/app/components/Alert"; +import ScreenHeader from "~/app/components/ScreenHeader"; +import toast from "~/app/components/Toast"; +import { useSettings } from "~/app/context/SettingsContext"; +import { useNavigationState } from "~/app/hooks/useNavigationState"; +import { USER_REJECTED_ERROR } from "~/common/constants"; +import api from "~/common/lib/api"; +import msg from "~/common/lib/msg"; + +function ConfirmPaymentAsync() { + const { + isLoading: isLoadingSettings, + settings, + getFormattedFiat, + } = useSettings(); + + const showFiat = !isLoadingSettings && settings.showFiat; + + const { t } = useTranslation("translation", { + keyPrefix: "confirm_payment_async", + }); + + const navState = useNavigationState(); + const paymentRequest = navState.args?.paymentRequest as string; + const invoice = lightningPayReq.decode(paymentRequest); + + const navigate = useNavigate(); + + const [fiatAmount, setFiatAmount] = useState(""); + + useEffect(() => { + (async () => { + if (showFiat && invoice.satoshis) { + const res = await getFormattedFiat(invoice.satoshis); + setFiatAmount(res); + } + })(); + }, [invoice.satoshis, showFiat, getFormattedFiat]); + + const [loading, setLoading] = useState(false); + + async function confirm() { + try { + setLoading(true); + const response = await api.sendPaymentAsync( + paymentRequest, + navState.origin + ); + + if ("error" in response) { + throw new Error(response.error); + } + + msg.reply(response); + } catch (e) { + console.error(e); + if (e instanceof Error) toast.error(`Error: ${e.message}`); + } finally { + setLoading(false); + } + } + + function reject(e: React.MouseEvent) { + e.preventDefault(); + if (navState.isPrompt) { + msg.error(USER_REJECTED_ERROR); + } else { + navigate(-1); + } + } + + function handleSubmit(event: React.FormEvent) { + event.preventDefault(); + confirm(); + } + + return ( +
+ +
+ +
+ {navState.origin && ( + + )} +
+
+ +
+
+
+ {t("description")} +
+
+
+ +
+
+
+
+ ); +} + +export default ConfirmPaymentAsync; diff --git a/src/common/lib/api.ts b/src/common/lib/api.ts index 3211403022..00d8e4666b 100644 --- a/src/common/lib/api.ts +++ b/src/common/lib/api.ts @@ -9,6 +9,8 @@ import { ConnectPeerResponse, MakeInvoiceArgs, MakeInvoiceResponse, + SendPaymentAsyncResponse, + SendPaymentResponse, } from "~/extension/background-script/connectors/connector.interface"; import type { Account, @@ -27,6 +29,7 @@ import type { MessageLnurlAuth, MessageSettingsSet, NodeInfo, + OriginData, PsetPreview, SettingsStorage, ValidateAccountResponse, @@ -173,6 +176,29 @@ export const lnurlAuth = ( ): Promise => msg.request("lnurlAuth", options); +export const sendPayment = ( + paymentRequest: string, + origin: OriginData | undefined +) => + msg.request( + "sendPayment", + { paymentRequest }, + { + origin, + } + ); +export const sendPaymentAsync = ( + paymentRequest: string, + origin: OriginData | undefined +) => + msg.request( + "sendPaymentAsync", + { paymentRequest }, + { + origin, + } + ); + export const getCurrencyRate = async () => msg.request<{ rate: number }>("getCurrencyRate"); @@ -262,6 +288,8 @@ export default { getInvoices, lnurlAuth, getCurrencyRate, + sendPayment, + sendPaymentAsync, nostr: { getPrivateKey: getNostrPrivateKey, getPublicKey: getNostrPublicKey, diff --git a/src/extension/background-script/actions/ln/index.ts b/src/extension/background-script/actions/ln/index.ts index 5f26c3fe3e..ad3b3f7eb1 100644 --- a/src/extension/background-script/actions/ln/index.ts +++ b/src/extension/background-script/actions/ln/index.ts @@ -6,6 +6,7 @@ import keysend from "./keysend"; import makeInvoice from "./makeInvoice"; import request from "./request"; import sendPayment from "./sendPayment"; +import sendPaymentAsync from "./sendPaymentAsync"; import signMessage from "./signMessage"; export { @@ -15,7 +16,8 @@ export { invoices, keysend, makeInvoice, + request, sendPayment, + sendPaymentAsync, signMessage, - request, }; diff --git a/src/extension/background-script/actions/ln/sendPaymentAsync.ts b/src/extension/background-script/actions/ln/sendPaymentAsync.ts new file mode 100644 index 0000000000..4cba2e89a9 --- /dev/null +++ b/src/extension/background-script/actions/ln/sendPaymentAsync.ts @@ -0,0 +1,32 @@ +import state from "~/extension/background-script/state"; +import { MessageSendPayment } from "~/types"; + +export default async function sendPayment(message: MessageSendPayment) { + const accountId = await state.getState().currentAccountId; + if (!accountId) { + return { + error: "Select an account.", + }; + } + + const { paymentRequest } = message.args; + if (typeof paymentRequest !== "string") { + return { + error: "Payment request missing.", + }; + } + + const connector = await state.getState().getConnector(); + + // NOTE: currently there is no way to know if the initial payment + // succeeds or not. The payment might not work at all or the http request might time out + // before the HODL invoice is paid or times out itself. + // any errors thrown by sendPayment will not be caught. + connector.sendPayment({ + paymentRequest, + }); + + return { + data: {}, + }; +} diff --git a/src/extension/background-script/actions/webln/index.ts b/src/extension/background-script/actions/webln/index.ts index faa8513fec..949848e232 100644 --- a/src/extension/background-script/actions/webln/index.ts +++ b/src/extension/background-script/actions/webln/index.ts @@ -3,6 +3,7 @@ import getBalanceOrPrompt from "./getBalanceOrPrompt"; import keysendOrPrompt from "./keysendOrPrompt"; import lnurl from "./lnurl"; import makeInvoiceOrPrompt from "./makeInvoiceOrPrompt"; +import { sendPaymentAsyncWithPrompt } from "./sendPaymentAsyncWithPrompt"; import { sendPaymentOrPrompt } from "./sendPaymentOrPrompt"; import signMessageOrPrompt from "./signMessageOrPrompt"; @@ -12,6 +13,7 @@ export { keysendOrPrompt, lnurl, makeInvoiceOrPrompt, + sendPaymentAsyncWithPrompt, sendPaymentOrPrompt, signMessageOrPrompt, }; diff --git a/src/extension/background-script/actions/webln/sendPaymentAsyncWithPrompt.ts b/src/extension/background-script/actions/webln/sendPaymentAsyncWithPrompt.ts new file mode 100644 index 0000000000..830422888a --- /dev/null +++ b/src/extension/background-script/actions/webln/sendPaymentAsyncWithPrompt.ts @@ -0,0 +1,21 @@ +import utils from "~/common/lib/utils"; +import { MessageSendPayment } from "~/types"; + +// Async payments cannot be budgeted for (they do not get saved to the extension DB) +// so always require a prompt. +async function sendPaymentAsyncWithPrompt(message: MessageSendPayment) { + try { + const response = await utils.openPrompt({ + ...message, + action: "confirmPaymentAsync", + }); + return response; + } catch (e) { + console.error("Payment cancelled", e); + if (e instanceof Error) { + return { error: e.message }; + } + } +} + +export { sendPaymentAsyncWithPrompt }; diff --git a/src/extension/background-script/actions/webln/sendPaymentOrPrompt.ts b/src/extension/background-script/actions/webln/sendPaymentOrPrompt.ts index 2333db4474..48a22752e6 100644 --- a/src/extension/background-script/actions/webln/sendPaymentOrPrompt.ts +++ b/src/extension/background-script/actions/webln/sendPaymentOrPrompt.ts @@ -61,9 +61,4 @@ async function payWithPrompt(message: Message) { } } -export { - sendPaymentOrPrompt, - payWithPrompt, - checkAllowance, - sendPaymentWithAllowance, -}; +export { sendPaymentOrPrompt, payWithPrompt, checkAllowance }; diff --git a/src/extension/background-script/connectors/alby.ts b/src/extension/background-script/connectors/alby.ts index 260feebfcb..3df2a38fd7 100644 --- a/src/extension/background-script/connectors/alby.ts +++ b/src/extension/background-script/connectors/alby.ts @@ -69,7 +69,14 @@ export default class Alby implements Connector { } get supportedMethods() { - return ["getInfo", "keysend", "makeInvoice", "sendPayment", "getBalance"]; + return [ + "getInfo", + "keysend", + "makeInvoice", + "sendPayment", + "sendPaymentAsync", + "getBalance", + ]; } // not yet implemented diff --git a/src/extension/background-script/connectors/citadel.ts b/src/extension/background-script/connectors/citadel.ts index 24e1a90234..1cde1135d9 100644 --- a/src/extension/background-script/connectors/citadel.ts +++ b/src/extension/background-script/connectors/citadel.ts @@ -56,6 +56,7 @@ class CitadelConnector implements Connector { return [ "makeInvoice", "sendPayment", + "sendPaymentAsync", "signMessage", "getInfo", "getBalance", diff --git a/src/extension/background-script/connectors/commando.ts b/src/extension/background-script/connectors/commando.ts index 6ea545af53..9d7d71d6e0 100644 --- a/src/extension/background-script/connectors/commando.ts +++ b/src/extension/background-script/connectors/commando.ts @@ -148,6 +148,7 @@ export default class Commando implements Connector { "keysend", "makeInvoice", "sendPayment", + "sendPaymentAsync", "signMessage", "getBalance", ...flattenRequestMethods(supportedMethods), diff --git a/src/extension/background-script/connectors/connector.interface.ts b/src/extension/background-script/connectors/connector.interface.ts index 03700cef9b..cd08e97ef7 100644 --- a/src/extension/background-script/connectors/connector.interface.ts +++ b/src/extension/background-script/connectors/connector.interface.ts @@ -69,6 +69,11 @@ export type SendPaymentResponse = { }; }; +export type SendPaymentAsyncResponse = { + // eslint-disable-next-line @typescript-eslint/ban-types + data: {}; +}; + export interface SendPaymentArgs { paymentRequest: string; } diff --git a/src/extension/background-script/connectors/eclair.ts b/src/extension/background-script/connectors/eclair.ts index 4f73c267ff..73a569e34d 100644 --- a/src/extension/background-script/connectors/eclair.ts +++ b/src/extension/background-script/connectors/eclair.ts @@ -47,6 +47,7 @@ class Eclair implements Connector { "keysend", "makeInvoice", "sendPayment", + "sendPaymentAsync", "signMessage", "getBalance", ]; diff --git a/src/extension/background-script/connectors/galoy.ts b/src/extension/background-script/connectors/galoy.ts index 9690cfdd80..cd68376f04 100644 --- a/src/extension/background-script/connectors/galoy.ts +++ b/src/extension/background-script/connectors/galoy.ts @@ -47,6 +47,7 @@ class Galoy implements Connector { "getInfo", "makeInvoice", "sendPayment", + "sendPaymentAsync", "signMessage", "getBalance", ]; diff --git a/src/extension/background-script/connectors/lnbits.ts b/src/extension/background-script/connectors/lnbits.ts index 31939584c4..4d2de70e9f 100644 --- a/src/extension/background-script/connectors/lnbits.ts +++ b/src/extension/background-script/connectors/lnbits.ts @@ -50,6 +50,7 @@ class LnBits implements Connector { "getInfo", "makeInvoice", "sendPayment", + "sendPaymentAsync", "signMessage", "getBalance", ]; diff --git a/src/extension/background-script/connectors/lnc.ts b/src/extension/background-script/connectors/lnc.ts index 69789de0f6..062aef2f5b 100644 --- a/src/extension/background-script/connectors/lnc.ts +++ b/src/extension/background-script/connectors/lnc.ts @@ -194,6 +194,7 @@ class Lnc implements Connector { "keysend", "makeInvoice", "sendPayment", + "sendPaymentAsync", "signMessage", "getBalance", ...flattenRequestMethods(Object.keys(methods)), diff --git a/src/extension/background-script/connectors/lnd.ts b/src/extension/background-script/connectors/lnd.ts index a488a12951..7bf6968151 100644 --- a/src/extension/background-script/connectors/lnd.ts +++ b/src/extension/background-script/connectors/lnd.ts @@ -167,6 +167,7 @@ class Lnd implements Connector { "keysend", "makeInvoice", "sendPayment", + "sendPaymentAsync", "signMessage", "getBalance", ...flattenRequestMethods(Object.keys(methods)), diff --git a/src/extension/background-script/connectors/lndhub.ts b/src/extension/background-script/connectors/lndhub.ts index 0d0e7473bd..871b899f8b 100644 --- a/src/extension/background-script/connectors/lndhub.ts +++ b/src/extension/background-script/connectors/lndhub.ts @@ -71,6 +71,7 @@ export default class LndHub implements Connector { "keysend", "makeInvoice", "sendPayment", + "sendPaymentAsync", "signMessage", "getBalance", ]; diff --git a/src/extension/background-script/router.ts b/src/extension/background-script/router.ts index 1cd1b50b4e..89c81268f1 100644 --- a/src/extension/background-script/router.ts +++ b/src/extension/background-script/router.ts @@ -33,6 +33,7 @@ const routes = { getInfo: ln.getInfo, getInvoices: ln.invoices, sendPayment: ln.sendPayment, + sendPaymentAsync: ln.sendPaymentAsync, keysend: ln.keysend, checkPayment: ln.checkPayment, signMessage: ln.signMessage, @@ -98,6 +99,7 @@ const routes = { enable: webln.enable, getInfo: ln.getInfo, sendPaymentOrPrompt: webln.sendPaymentOrPrompt, + sendPaymentAsyncWithPrompt: webln.sendPaymentAsyncWithPrompt, keysendOrPrompt: webln.keysendOrPrompt, signMessageOrPrompt: webln.signMessageOrPrompt, lnurl: webln.lnurl, diff --git a/src/extension/content-script/webln.js b/src/extension/content-script/webln.js index 21b51e9f99..ac54fb19aa 100644 --- a/src/extension/content-script/webln.js +++ b/src/extension/content-script/webln.js @@ -11,6 +11,7 @@ const weblnCalls = [ "webln/getInfo", "webln/lnurl", "webln/sendPaymentOrPrompt", + "webln/sendPaymentAsyncWithPrompt", "webln/keysendOrPrompt", "webln/makeInvoice", "webln/signMessageOrPrompt", diff --git a/src/extension/providers/webln/index.ts b/src/extension/providers/webln/index.ts index c0b9b8dd73..2ed7815354 100644 --- a/src/extension/providers/webln/index.ts +++ b/src/extension/providers/webln/index.ts @@ -39,6 +39,10 @@ export default class WebLNProvider extends ProviderBase { this._checkEnabled("sendPayment"); return this.execute("sendPaymentOrPrompt", { paymentRequest }); } + sendPaymentAsync(paymentRequest: string) { + this._checkEnabled("sendPaymentAsync"); + return this.execute("sendPaymentAsyncWithPrompt", { paymentRequest }); + } keysend(args: KeysendArgs) { this._checkEnabled("keysend"); diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 0a0f178cd5..93013a254a 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -890,6 +890,13 @@ "time_estimate": "ℹ️ Transactions usually arrive within 10-30 minutes.", "service_unavailable": "Service temporary not available, please try again in a few minutes." }, + "confirm_payment_async": { + "title": "Approve Delayed / Conditional Payment", + "description": "This payment may stay unsettled for a day or longer. The receiver can choose to settle or cancel the payment by leaving it to expire.", + "actions": { + "pay_now": "Pay now" + } + }, "confirm_payment": { "title": "Approve Payment", "success": "Payment of {{amount}} successful!", From c26229ae1b05fced9174d3f528797684eef05452 Mon Sep 17 00:00:00 2001 From: "depfu[bot]" <23717796+depfu[bot]@users.noreply.github.com> Date: Tue, 3 Oct 2023 16:14:05 +0000 Subject: [PATCH 34/73] Update postcss to version 8.4.31 --- package.json | 2 +- yarn.lock | 11 ++++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index 32b7e6be3b..811d918424 100644 --- a/package.json +++ b/package.json @@ -126,7 +126,7 @@ "lint-staged": "^13.3.0", "mini-css-extract-plugin": "^2.7.6", "msw": "^1.3.1", - "postcss": "^8.4.28", + "postcss": "^8.4.31", "postcss-cli": "^10.1.0", "postcss-loader": "^7.3.3", "pptr-testing-library": "^0.7.0", diff --git a/yarn.lock b/yarn.lock index 873ba7dc08..be06cfa74e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7649,7 +7649,7 @@ postcss-value-parser@^4.0.0, postcss-value-parser@^4.1.0, postcss-value-parser@^ resolved "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz" integrity sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ== -postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.24, postcss@^8.4.28: +postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.24: version "8.4.29" resolved "https://registry.npmjs.org/postcss/-/postcss-8.4.29.tgz" integrity sha512-cbI+jaqIeu/VGqXEarWkRCCffhjgXc0qjBtXpqJhTBohMUjUQnbBr0xqX3vEKudc4iviTewcJo5ajcec5+wdJw== @@ -7658,6 +7658,15 @@ postcss@^8.4.21, postcss@^8.4.23, postcss@^8.4.24, postcss@^8.4.28: picocolors "^1.0.0" source-map-js "^1.0.2" +postcss@^8.4.31: + version "8.4.31" + resolved "https://registry.yarnpkg.com/postcss/-/postcss-8.4.31.tgz#92b451050a9f914da6755af352bdc0192508656d" + integrity sha512-PS08Iboia9mts/2ygV3eLpY5ghnUcfLV/EXTOW1E2qYxJKGGBUtNjN76FYHnMs36RmARn41bC0AZmn+rR0OVpQ== + dependencies: + nanoid "^3.3.6" + picocolors "^1.0.0" + source-map-js "^1.0.2" + pptr-testing-library@^0.7.0: version "0.7.0" resolved "https://registry.npmjs.org/pptr-testing-library/-/pptr-testing-library-0.7.0.tgz" From 11ba2b99b5bee40329f219e5bc92a2c27cd1c6f2 Mon Sep 17 00:00:00 2001 From: Rithvik Padma Date: Wed, 4 Oct 2023 01:02:47 +0530 Subject: [PATCH 35/73] fix: align the create invoice button to the bottom (#2796) --- src/app/screens/ReceiveInvoice/index.tsx | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/app/screens/ReceiveInvoice/index.tsx b/src/app/screens/ReceiveInvoice/index.tsx index e0dfe387eb..4c883b6b20 100644 --- a/src/app/screens/ReceiveInvoice/index.tsx +++ b/src/app/screens/ReceiveInvoice/index.tsx @@ -241,9 +241,9 @@ function ReceiveInvoice() { {invoice ? ( {renderInvoice()} ) : ( -
-
-
+
+ +
From 31e928a01b79e40b8dd907798a48c7a93653379a Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Wed, 4 Oct 2023 10:45:54 +0530 Subject: [PATCH 36/73] fix: return proper response back to the content script --- .../background-script/actions/lnurl/authOrPrompt.ts | 9 ++++----- src/extension/background-script/actions/lnurl/index.ts | 2 +- 2 files changed, 5 insertions(+), 6 deletions(-) diff --git a/src/extension/background-script/actions/lnurl/authOrPrompt.ts b/src/extension/background-script/actions/lnurl/authOrPrompt.ts index 1d41e7769f..20a2c7e674 100644 --- a/src/extension/background-script/actions/lnurl/authOrPrompt.ts +++ b/src/extension/background-script/actions/lnurl/authOrPrompt.ts @@ -43,13 +43,11 @@ async function authOrPrompt( if (isUnlocked && allowance && allowance.enabled && allowance.lnurlAuth) { if (!isAlbyOAuthConnector) { return await authFunction({ lnurlDetails, origin: message.origin }); - } else if (account?.mnemonic) { - return await authFunction({ lnurlDetails, origin: message.origin }); } else { - authPrompt(); + return await authPrompt(); } } else { - authPrompt(); + return await authPrompt(); } async function authPrompt() { @@ -63,7 +61,8 @@ async function authOrPrompt( }, }; - return await utils.openPrompt(promptMessage); + const response = await utils.openPrompt(promptMessage); + return response; } catch (e) { // user rejected return { error: e instanceof Error ? e.message : e }; diff --git a/src/extension/background-script/actions/lnurl/index.ts b/src/extension/background-script/actions/lnurl/index.ts index f199e035b9..1c0588b2be 100644 --- a/src/extension/background-script/actions/lnurl/index.ts +++ b/src/extension/background-script/actions/lnurl/index.ts @@ -39,4 +39,4 @@ async function lnurl(message: MessageWebLnLnurl, sender: Sender) { } export default lnurl; -export { authOrPrompt, payWithPrompt, withdrawWithPrompt, auth }; +export { auth, authOrPrompt, payWithPrompt, withdrawWithPrompt }; From 162c635efd244122ac2a1e680b24ea26796d9225 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Wed, 4 Oct 2023 11:56:45 +0530 Subject: [PATCH 37/73] chore: auth prompt conditions --- src/app/screens/LNURLAuth/index.tsx | 4 ++-- src/extension/background-script/actions/lnurl/authOrPrompt.ts | 2 ++ 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/src/app/screens/LNURLAuth/index.tsx b/src/app/screens/LNURLAuth/index.tsx index 83ca0ce8c9..c09def68a3 100644 --- a/src/app/screens/LNURLAuth/index.tsx +++ b/src/app/screens/LNURLAuth/index.tsx @@ -14,8 +14,8 @@ export default function LNURLAuth() { async function fetchAccountInfo() { try { const fetchedAccount = await api.getAccount(); - const connectorType = isAlbyOAuthAccount(fetchedAccount.connectorType); - setAlbyOAuthAccount(connectorType); + const isOAuthAccount = isAlbyOAuthAccount(fetchedAccount.connectorType); + setAlbyOAuthAccount(isOAuthAccount); if (fetchedAccount.hasMnemonic) { setHasMnemonic(true); diff --git a/src/extension/background-script/actions/lnurl/authOrPrompt.ts b/src/extension/background-script/actions/lnurl/authOrPrompt.ts index 20a2c7e674..174879ca09 100644 --- a/src/extension/background-script/actions/lnurl/authOrPrompt.ts +++ b/src/extension/background-script/actions/lnurl/authOrPrompt.ts @@ -43,6 +43,8 @@ async function authOrPrompt( if (isUnlocked && allowance && allowance.enabled && allowance.lnurlAuth) { if (!isAlbyOAuthConnector) { return await authFunction({ lnurlDetails, origin: message.origin }); + } else if (account?.mnemonic) { + return await authFunction({ lnurlDetails, origin: message.origin }); } else { return await authPrompt(); } From 50948213cfea20450649875662662b675f8c49ac Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Wed, 4 Oct 2023 12:59:44 +0530 Subject: [PATCH 38/73] chore: improve conditionals --- src/extension/background-script/actions/lnurl/authOrPrompt.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/extension/background-script/actions/lnurl/authOrPrompt.ts b/src/extension/background-script/actions/lnurl/authOrPrompt.ts index 174879ca09..f124dca117 100644 --- a/src/extension/background-script/actions/lnurl/authOrPrompt.ts +++ b/src/extension/background-script/actions/lnurl/authOrPrompt.ts @@ -41,9 +41,7 @@ async function authOrPrompt( // otherwise we we prompt the user if (isUnlocked && allowance && allowance.enabled && allowance.lnurlAuth) { - if (!isAlbyOAuthConnector) { - return await authFunction({ lnurlDetails, origin: message.origin }); - } else if (account?.mnemonic) { + if (!isAlbyOAuthConnector || account?.mnemonic) { return await authFunction({ lnurlDetails, origin: message.origin }); } else { return await authPrompt(); From 1cb0e3fadb77a2867b92debd62301a964daec88e Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Wed, 4 Oct 2023 15:54:17 +0700 Subject: [PATCH 39/73] chore: simplify conditional --- .../actions/lnurl/authOrPrompt.ts | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/src/extension/background-script/actions/lnurl/authOrPrompt.ts b/src/extension/background-script/actions/lnurl/authOrPrompt.ts index f124dca117..085faa558c 100644 --- a/src/extension/background-script/actions/lnurl/authOrPrompt.ts +++ b/src/extension/background-script/actions/lnurl/authOrPrompt.ts @@ -40,12 +40,14 @@ async function authOrPrompt( // check if there is a publisher and lnurlAuth is enabled, // otherwise we we prompt the user - if (isUnlocked && allowance && allowance.enabled && allowance.lnurlAuth) { - if (!isAlbyOAuthConnector || account?.mnemonic) { - return await authFunction({ lnurlDetails, origin: message.origin }); - } else { - return await authPrompt(); - } + if ( + isUnlocked && + allowance && + allowance.enabled && + allowance.lnurlAuth && + (!isAlbyOAuthConnector || account?.mnemonic) + ) { + return await authFunction({ lnurlDetails, origin: message.origin }); } else { return await authPrompt(); } From 636bc0470c33360250ac1c2ae67eab9f829ee252 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Wed, 4 Oct 2023 16:01:40 +0700 Subject: [PATCH 40/73] chore: remove unnecessary else, move authPrompt above return statements --- .../actions/lnurl/authOrPrompt.ts | 28 +++++++++---------- 1 file changed, 13 insertions(+), 15 deletions(-) diff --git a/src/extension/background-script/actions/lnurl/authOrPrompt.ts b/src/extension/background-script/actions/lnurl/authOrPrompt.ts index 085faa558c..b94982945d 100644 --- a/src/extension/background-script/actions/lnurl/authOrPrompt.ts +++ b/src/extension/background-script/actions/lnurl/authOrPrompt.ts @@ -37,21 +37,6 @@ async function authOrPrompt( const account = await state.getState().getAccount(); const isAlbyOAuthConnector = isAlbyOAuthAccount(account?.connector); - // check if there is a publisher and lnurlAuth is enabled, - // otherwise we we prompt the user - - if ( - isUnlocked && - allowance && - allowance.enabled && - allowance.lnurlAuth && - (!isAlbyOAuthConnector || account?.mnemonic) - ) { - return await authFunction({ lnurlDetails, origin: message.origin }); - } else { - return await authPrompt(); - } - async function authPrompt() { try { const promptMessage = { @@ -70,6 +55,19 @@ async function authOrPrompt( return { error: e instanceof Error ? e.message : e }; } } + + // check if there is a publisher and lnurlAuth is enabled, + // otherwise we we prompt the user + if ( + isUnlocked && + allowance && + allowance.enabled && + allowance.lnurlAuth && + (!isAlbyOAuthConnector || account?.mnemonic) + ) { + return await authFunction({ lnurlDetails, origin: message.origin }); + } + return await authPrompt(); } export default authOrPrompt; From 95597c76ff001ed6931906825de2c8dc80d2e2f3 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Wed, 4 Oct 2023 14:32:55 +0530 Subject: [PATCH 41/73] chore: move authprompt above return statements --- .../actions/lnurl/authOrPrompt.ts | 24 +++++++++---------- 1 file changed, 12 insertions(+), 12 deletions(-) diff --git a/src/extension/background-script/actions/lnurl/authOrPrompt.ts b/src/extension/background-script/actions/lnurl/authOrPrompt.ts index 085faa558c..2fd73f233a 100644 --- a/src/extension/background-script/actions/lnurl/authOrPrompt.ts +++ b/src/extension/background-script/actions/lnurl/authOrPrompt.ts @@ -40,18 +40,6 @@ async function authOrPrompt( // check if there is a publisher and lnurlAuth is enabled, // otherwise we we prompt the user - if ( - isUnlocked && - allowance && - allowance.enabled && - allowance.lnurlAuth && - (!isAlbyOAuthConnector || account?.mnemonic) - ) { - return await authFunction({ lnurlDetails, origin: message.origin }); - } else { - return await authPrompt(); - } - async function authPrompt() { try { const promptMessage = { @@ -70,6 +58,18 @@ async function authOrPrompt( return { error: e instanceof Error ? e.message : e }; } } + + if ( + isUnlocked && + allowance && + allowance.enabled && + allowance.lnurlAuth && + (!isAlbyOAuthConnector || account?.mnemonic) + ) { + return await authFunction({ lnurlDetails, origin: message.origin }); + } else { + return await authPrompt(); + } } export default authOrPrompt; From 08b7aae56b2e7e7d56e8de95b75dbbbdbcd5287f Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Thu, 5 Oct 2023 11:03:50 +0530 Subject: [PATCH 42/73] chore: make setthing toggle work --- .../background-script/actions/lnurl/authOrPrompt.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/extension/background-script/actions/lnurl/authOrPrompt.ts b/src/extension/background-script/actions/lnurl/authOrPrompt.ts index b94982945d..1a897f9165 100644 --- a/src/extension/background-script/actions/lnurl/authOrPrompt.ts +++ b/src/extension/background-script/actions/lnurl/authOrPrompt.ts @@ -10,7 +10,6 @@ import type { Sender, } from "~/types"; -import { isAlbyOAuthAccount } from "~/app/utils"; import { authFunction } from "./auth"; async function authOrPrompt( @@ -35,7 +34,6 @@ async function authOrPrompt( // If it is locked we must show a prompt to unlock const isUnlocked = await state.getState().isUnlocked(); const account = await state.getState().getAccount(); - const isAlbyOAuthConnector = isAlbyOAuthAccount(account?.connector); async function authPrompt() { try { @@ -58,15 +56,17 @@ async function authOrPrompt( // check if there is a publisher and lnurlAuth is enabled, // otherwise we we prompt the user + if ( isUnlocked && allowance && allowance.enabled && allowance.lnurlAuth && - (!isAlbyOAuthConnector || account?.mnemonic) + (!account?.useMnemonicForLnurlAuth || account?.mnemonic) ) { return await authFunction({ lnurlDetails, origin: message.origin }); } + return await authPrompt(); } From 52cfc4993252ac8de1c113f4a4f49d3c0db86577 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Thu, 5 Oct 2023 15:14:11 +0530 Subject: [PATCH 43/73] feat: use db storage instead of local storage --- .../background-script/actions/webln/index.ts | 2 ++ .../actions/webln/isEnabled.ts | 30 +++++++++++++++++++ src/extension/background-script/router.ts | 1 + src/extension/content-script/webln.js | 14 ++++----- src/extension/providers/providerBase.ts | 24 ++++++++------- 5 files changed, 53 insertions(+), 18 deletions(-) create mode 100644 src/extension/background-script/actions/webln/isEnabled.ts diff --git a/src/extension/background-script/actions/webln/index.ts b/src/extension/background-script/actions/webln/index.ts index faa8513fec..cfdf4db129 100644 --- a/src/extension/background-script/actions/webln/index.ts +++ b/src/extension/background-script/actions/webln/index.ts @@ -1,5 +1,6 @@ import enable from "./enable"; import getBalanceOrPrompt from "./getBalanceOrPrompt"; +import isEnabled from "./isEnabled"; import keysendOrPrompt from "./keysendOrPrompt"; import lnurl from "./lnurl"; import makeInvoiceOrPrompt from "./makeInvoiceOrPrompt"; @@ -9,6 +10,7 @@ import signMessageOrPrompt from "./signMessageOrPrompt"; export { enable, getBalanceOrPrompt, + isEnabled, keysendOrPrompt, lnurl, makeInvoiceOrPrompt, diff --git a/src/extension/background-script/actions/webln/isEnabled.ts b/src/extension/background-script/actions/webln/isEnabled.ts new file mode 100644 index 0000000000..d72319a43c --- /dev/null +++ b/src/extension/background-script/actions/webln/isEnabled.ts @@ -0,0 +1,30 @@ +import { getHostFromSender } from "~/common/utils/helpers"; +import db from "~/extension/background-script/db"; +import type { MessageAllowanceEnable, Sender } from "~/types"; + +import state from "../../state"; + +const isEnabled = async (message: MessageAllowanceEnable, sender: Sender) => { + const host = getHostFromSender(sender); + if (!host) return; + + const isUnlocked = await state.getState().isUnlocked(); + const allowance = await db.allowances + .where("host") + .equalsIgnoreCase(host) + .first(); + + const enabledFor = new Set(allowance?.enabledFor); + + if (isUnlocked && allowance && allowance.enabled && enabledFor.has("webln")) { + return { + data: { isEnabled: true }, + }; + } else { + return { + data: { isEnabled: false }, + }; + } +}; + +export default isEnabled; diff --git a/src/extension/background-script/router.ts b/src/extension/background-script/router.ts index 1cd1b50b4e..2e0aaaecaa 100644 --- a/src/extension/background-script/router.ts +++ b/src/extension/background-script/router.ts @@ -96,6 +96,7 @@ const routes = { webln: { onboard: onboard.prompt, enable: webln.enable, + isEnabled: webln.isEnabled, getInfo: ln.getInfo, sendPaymentOrPrompt: webln.sendPaymentOrPrompt, keysendOrPrompt: webln.keysendOrPrompt, diff --git a/src/extension/content-script/webln.js b/src/extension/content-script/webln.js index f8b9798eed..0434d1cd0b 100644 --- a/src/extension/content-script/webln.js +++ b/src/extension/content-script/webln.js @@ -19,16 +19,12 @@ const weblnCalls = [ "webln/on", "webln/emit", "webln/off", + "webln/isEnabled", ]; // calls that can be executed when webln is not enabled for the current content page -const disabledCalls = ["webln/enable"]; +const disabledCalls = ["webln/enable", "webln/isEnabled"]; -function loadEnabledState() { - const storedEnabled = localStorage.getItem("enabled"); - return storedEnabled === "true"; -} - -let isEnabled = loadEnabledState(); // store if webln is enabled for this content page +let isEnabled = false; // store if webln is enabled for this content page let isRejected = false; // store if the webln enable call failed. if so we do not prompt again async function init() { @@ -106,6 +102,10 @@ async function init() { isRejected = true; } } + + if (ev.data.action === "webln/isEnabled") { + isEnabled = response.data?.isEnabled; + } postMessage(ev, response); }; return browser.runtime diff --git a/src/extension/providers/providerBase.ts b/src/extension/providers/providerBase.ts index 0a19913382..a0de62bbfa 100644 --- a/src/extension/providers/providerBase.ts +++ b/src/extension/providers/providerBase.ts @@ -8,20 +8,12 @@ export default class ProviderBase { private _scope: string; constructor(scope: string) { - this.enabled = this.loadEnabledState(); + this._scope = scope; + this.enabled = false; this._eventEmitter = new EventEmitter(); this._scope = scope; } - private loadEnabledState(): boolean { - const storedEnabled = localStorage.getItem("enabled"); - return storedEnabled === "true"; - } - - private saveEnabledState(enabled: boolean): void { - localStorage.setItem("enabled", enabled.toString()); - } - protected _checkEnabled(methodName: string): void { if (!this.enabled) { throw new Error(`Provider must be enabled before calling ${methodName}`); @@ -35,7 +27,6 @@ export default class ProviderBase { const result = await this.execute("enable"); if (typeof result.enabled === "boolean") { this.enabled = result.enabled; - this.saveEnabledState(this.enabled); } } @@ -53,6 +44,17 @@ export default class ProviderBase { this._eventEmitter.emit(...args); } + async isProviderEnabled(): Promise { + if (this.enabled) { + return true; + } + const result = await this.execute("isEnabled"); + if (typeof result.isEnabled === "boolean") { + this.enabled = result.isEnabled; + } + return this.enabled; + } + // NOTE: new call `action`s must be specified also in the content script execute( action: string, From b140b6f271455e2682b3db7bae214937c697e8e8 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Fri, 6 Oct 2023 12:03:28 +0530 Subject: [PATCH 44/73] feat: add isEnabled provider method --- .../background-script/actions/alby/index.ts | 3 +- .../actions/alby/isEnabled.ts | 30 ++++++++++++++++ .../background-script/actions/liquid/index.ts | 2 ++ .../actions/liquid/isEnabled.ts | 35 +++++++++++++++++++ .../background-script/actions/nostr/index.ts | 2 ++ .../actions/nostr/isEnabled.ts | 30 ++++++++++++++++ .../background-script/actions/webbtc/index.ts | 3 +- .../actions/webbtc/isEnabled.ts | 35 +++++++++++++++++++ src/extension/background-script/router.ts | 4 +++ src/extension/content-script/alby.js | 7 ++-- src/extension/content-script/liquid.js | 7 +++- src/extension/content-script/nostr.js | 6 +++- src/extension/content-script/webbtc.js | 7 +++- src/extension/providers/providerBase.ts | 2 +- src/extension/providers/webbtc/index.ts | 4 +-- 15 files changed, 167 insertions(+), 10 deletions(-) create mode 100644 src/extension/background-script/actions/alby/isEnabled.ts create mode 100644 src/extension/background-script/actions/liquid/isEnabled.ts create mode 100644 src/extension/background-script/actions/nostr/isEnabled.ts create mode 100644 src/extension/background-script/actions/webbtc/isEnabled.ts diff --git a/src/extension/background-script/actions/alby/index.ts b/src/extension/background-script/actions/alby/index.ts index e9478e1569..ecef3392b1 100644 --- a/src/extension/background-script/actions/alby/index.ts +++ b/src/extension/background-script/actions/alby/index.ts @@ -1,2 +1,3 @@ import enable from "./enable"; -export { enable }; +import isEnabled from "./isEnabled"; +export { enable, isEnabled }; diff --git a/src/extension/background-script/actions/alby/isEnabled.ts b/src/extension/background-script/actions/alby/isEnabled.ts new file mode 100644 index 0000000000..ed3aae85d7 --- /dev/null +++ b/src/extension/background-script/actions/alby/isEnabled.ts @@ -0,0 +1,30 @@ +import { getHostFromSender } from "~/common/utils/helpers"; +import db from "~/extension/background-script/db"; +import type { MessageAllowanceEnable, Sender } from "~/types"; + +import state from "../../state"; + +const isEnabled = async (message: MessageAllowanceEnable, sender: Sender) => { + const host = getHostFromSender(sender); + if (!host) return; + + const isUnlocked = await state.getState().isUnlocked(); + const allowance = await db.allowances + .where("host") + .equalsIgnoreCase(host) + .first(); + + const enabledFor = new Set(allowance?.enabledFor); + + if (isUnlocked && allowance && allowance.enabled && enabledFor.has("alby")) { + return { + data: { isEnabled: true }, + }; + } else { + return { + data: { isEnabled: false }, + }; + } +}; + +export default isEnabled; diff --git a/src/extension/background-script/actions/liquid/index.ts b/src/extension/background-script/actions/liquid/index.ts index e1e48d8bd0..71a2f937a6 100644 --- a/src/extension/background-script/actions/liquid/index.ts +++ b/src/extension/background-script/actions/liquid/index.ts @@ -2,6 +2,7 @@ import enable from "./enable"; import fetchAssetRegistry from "./fetchAssetRegistry"; import getAddressOrPrompt from "./getAddressOrPrompt"; import getPsetPreview from "./getPsetPreview"; +import isEnabled from "./isEnabled"; import signPset from "./signPset"; import signPsetWithPrompt from "./signPsetWithPrompt"; @@ -10,6 +11,7 @@ export { fetchAssetRegistry, getAddressOrPrompt, getPsetPreview, + isEnabled, signPset, signPsetWithPrompt, }; diff --git a/src/extension/background-script/actions/liquid/isEnabled.ts b/src/extension/background-script/actions/liquid/isEnabled.ts new file mode 100644 index 0000000000..bfb351f5a6 --- /dev/null +++ b/src/extension/background-script/actions/liquid/isEnabled.ts @@ -0,0 +1,35 @@ +import { getHostFromSender } from "~/common/utils/helpers"; +import db from "~/extension/background-script/db"; +import type { MessageAllowanceEnable, Sender } from "~/types"; + +import state from "../../state"; + +const isEnabled = async (message: MessageAllowanceEnable, sender: Sender) => { + const host = getHostFromSender(sender); + if (!host) return; + + const isUnlocked = await state.getState().isUnlocked(); + const allowance = await db.allowances + .where("host") + .equalsIgnoreCase(host) + .first(); + + const enabledFor = new Set(allowance?.enabledFor); + + if ( + isUnlocked && + allowance && + allowance.enabled && + enabledFor.has("liquid") + ) { + return { + data: { isEnabled: true }, + }; + } else { + return { + data: { isEnabled: false }, + }; + } +}; + +export default isEnabled; diff --git a/src/extension/background-script/actions/nostr/index.ts b/src/extension/background-script/actions/nostr/index.ts index 54ae6b8c47..32fb85eee4 100644 --- a/src/extension/background-script/actions/nostr/index.ts +++ b/src/extension/background-script/actions/nostr/index.ts @@ -7,6 +7,7 @@ import generatePrivateKey from "./generatePrivateKey"; import getPrivateKey from "./getPrivateKey"; import getPublicKeyOrPrompt from "./getPublicKeyOrPrompt"; import getRelays from "./getRelays"; +import isEnabled from "./isEnabled"; import removePrivateKey from "./removePrivateKey"; import setPrivateKey from "./setPrivateKey"; import signEventOrPrompt from "./signEventOrPrompt"; @@ -21,6 +22,7 @@ export { getPublicKey, getPublicKeyOrPrompt, getRelays, + isEnabled, removePrivateKey, setPrivateKey, signEventOrPrompt, diff --git a/src/extension/background-script/actions/nostr/isEnabled.ts b/src/extension/background-script/actions/nostr/isEnabled.ts new file mode 100644 index 0000000000..0d9986a19a --- /dev/null +++ b/src/extension/background-script/actions/nostr/isEnabled.ts @@ -0,0 +1,30 @@ +import { getHostFromSender } from "~/common/utils/helpers"; +import db from "~/extension/background-script/db"; +import type { MessageAllowanceEnable, Sender } from "~/types"; + +import state from "../../state"; + +const isEnabled = async (message: MessageAllowanceEnable, sender: Sender) => { + const host = getHostFromSender(sender); + if (!host) return; + + const isUnlocked = await state.getState().isUnlocked(); + const allowance = await db.allowances + .where("host") + .equalsIgnoreCase(host) + .first(); + + const enabledFor = new Set(allowance?.enabledFor); + + if (isUnlocked && allowance && allowance.enabled && enabledFor.has("nostr")) { + return { + data: { isEnabled: true }, + }; + } else { + return { + data: { isEnabled: false }, + }; + } +}; + +export default isEnabled; diff --git a/src/extension/background-script/actions/webbtc/index.ts b/src/extension/background-script/actions/webbtc/index.ts index 18779f9280..1833c0be03 100644 --- a/src/extension/background-script/actions/webbtc/index.ts +++ b/src/extension/background-script/actions/webbtc/index.ts @@ -2,5 +2,6 @@ import enable from "./enable"; import getAddress from "./getAddress"; import getAddressOrPrompt from "./getAddressOrPrompt"; import getInfo from "./getInfo"; +import isEnabled from "./isEnabled"; -export { enable, getAddress, getAddressOrPrompt, getInfo }; +export { enable, getAddress, getAddressOrPrompt, getInfo, isEnabled }; diff --git a/src/extension/background-script/actions/webbtc/isEnabled.ts b/src/extension/background-script/actions/webbtc/isEnabled.ts new file mode 100644 index 0000000000..d19355ca66 --- /dev/null +++ b/src/extension/background-script/actions/webbtc/isEnabled.ts @@ -0,0 +1,35 @@ +import { getHostFromSender } from "~/common/utils/helpers"; +import db from "~/extension/background-script/db"; +import type { MessageAllowanceEnable, Sender } from "~/types"; + +import state from "../../state"; + +const isEnabled = async (message: MessageAllowanceEnable, sender: Sender) => { + const host = getHostFromSender(sender); + if (!host) return; + + const isUnlocked = await state.getState().isUnlocked(); + const allowance = await db.allowances + .where("host") + .equalsIgnoreCase(host) + .first(); + + const enabledFor = new Set(allowance?.enabledFor); + + if ( + isUnlocked && + allowance && + allowance.enabled && + enabledFor.has("webbtc") + ) { + return { + data: { isEnabled: true }, + }; + } else { + return { + data: { isEnabled: false }, + }; + } +}; + +export default isEnabled; diff --git a/src/extension/background-script/router.ts b/src/extension/background-script/router.ts index 2e0aaaecaa..6ae6fcdb07 100644 --- a/src/extension/background-script/router.ts +++ b/src/extension/background-script/router.ts @@ -85,11 +85,13 @@ const routes = { // Public calls that are accessible from the inpage script (through the content script) public: { webbtc: { + isEnabled: webbtc.isEnabled, enable: webbtc.enable, getInfo: webbtc.getInfo, getAddressOrPrompt: webbtc.getAddressOrPrompt, }, alby: { + isEnabled: alby.isEnabled, enable: alby.enable, addAccount: accounts.promptAdd, }, @@ -107,11 +109,13 @@ const routes = { request: ln.request, }, liquid: { + isEnabled: liquid.isEnabled, enable: liquid.enable, getAddressOrPrompt: liquid.getAddressOrPrompt, signPsetWithPrompt: liquid.signPsetWithPrompt, }, nostr: { + isEnabled: nostr.isEnabled, enable: nostr.enable, getPublicKeyOrPrompt: nostr.getPublicKeyOrPrompt, signEventOrPrompt: nostr.signEventOrPrompt, diff --git a/src/extension/content-script/alby.js b/src/extension/content-script/alby.js index 09302ab21d..d8e8e2ea44 100644 --- a/src/extension/content-script/alby.js +++ b/src/extension/content-script/alby.js @@ -5,9 +5,9 @@ import shouldInject from "./shouldInject"; // Alby calls that can be executed from the AlbyProvider. // Update when new calls are added -const albyCalls = ["alby/enable", "alby/addAccount"]; +const albyCalls = ["alby/enable", "alby/addAccount", "alby/isEnabled"]; // calls that can be executed when alby is not enabled for the current content page -const disabledCalls = ["alby/enable"]; +const disabledCalls = ["alby/enable", "alby/isEnabled"]; let isEnabled = false; // store if alby is enabled for this content page let isRejected = false; // store if the alby enable call failed. if so we do not prompt again @@ -70,6 +70,9 @@ async function init() { isRejected = true; } } + if (ev.data.action === "alby/isEnabled") { + isEnabled = response.data?.isEnabled; + } postMessage(ev, response); }; return browser.runtime diff --git a/src/extension/content-script/liquid.js b/src/extension/content-script/liquid.js index 0d1f5e147a..6bd452ddce 100644 --- a/src/extension/content-script/liquid.js +++ b/src/extension/content-script/liquid.js @@ -9,9 +9,10 @@ const liquidCalls = [ "liquid/getAddressOrPrompt", "liquid/signPsetWithPrompt", "liquid/enable", + "liquid/isEnabled", ]; // calls that can be executed when liquid is not enabled for the current content page -const disabledCalls = ["liquid/enable"]; +const disabledCalls = ["liquid/enable", "liquid/isEnabled"]; let isEnabled = false; // store if liquid is enabled for this content page let isRejected = false; // store if the liquid enable call failed. if so we do not prompt again @@ -76,6 +77,10 @@ async function init() { } } + if (ev.data.action === `${SCOPE}/isEnabled`) { + isEnabled = response.data?.isEnabled; + } + postMessage(ev, response); }; diff --git a/src/extension/content-script/nostr.js b/src/extension/content-script/nostr.js index 92ff55266c..36b5d42f99 100644 --- a/src/extension/content-script/nostr.js +++ b/src/extension/content-script/nostr.js @@ -16,9 +16,10 @@ const nostrCalls = [ "nostr/on", "nostr/off", "nostr/emit", + "nostr/isEnabled", ]; // calls that can be executed when nostr is not enabled for the current content page -const disabledCalls = ["nostr/enable"]; +const disabledCalls = ["nostr/enable", "nostr/isEnabled"]; let isEnabled = false; // store if nostr is enabled for this content page let isRejected = false; // store if the nostr enable call failed. if so we do not prompt again @@ -92,6 +93,9 @@ async function init() { isRejected = true; } } + if (ev.data.action === "nostr/isEnabled") { + isEnabled = response.data?.isEnabled; + } postMessage(ev, response); }; diff --git a/src/extension/content-script/webbtc.js b/src/extension/content-script/webbtc.js index db2ff09d87..7ee4ca5f32 100644 --- a/src/extension/content-script/webbtc.js +++ b/src/extension/content-script/webbtc.js @@ -8,9 +8,10 @@ const webbtcCalls = [ "webbtc/enable", "webbtc/getInfo", "webbtc/getAddressOrPrompt", + "webbtc/isEnabled", ]; // calls that can be executed when `window.webbtc` is not enabled for the current content page -const disabledCalls = ["webbtc/enable"]; +const disabledCalls = ["webbtc/enable", "webbtc/isEnabled"]; let isEnabled = false; // store if webbtc is enabled for this content page let isRejected = false; // store if the webbtc enable call failed. if so we do not prompt again @@ -75,6 +76,10 @@ async function init() { } } + if (ev.data.action === `${SCOPE}/isEnabled`) { + isEnabled = response.data?.isEnabled; + } + postMessage(ev, response); }; diff --git a/src/extension/providers/providerBase.ts b/src/extension/providers/providerBase.ts index a0de62bbfa..ac6a75b476 100644 --- a/src/extension/providers/providerBase.ts +++ b/src/extension/providers/providerBase.ts @@ -44,7 +44,7 @@ export default class ProviderBase { this._eventEmitter.emit(...args); } - async isProviderEnabled(): Promise { + async isEnabled(): Promise { if (this.enabled) { return true; } diff --git a/src/extension/providers/webbtc/index.ts b/src/extension/providers/webbtc/index.ts index 3bd8e38a7f..896f9a97bf 100644 --- a/src/extension/providers/webbtc/index.ts +++ b/src/extension/providers/webbtc/index.ts @@ -7,12 +7,12 @@ declare global { } export default class WebBTCProvider extends ProviderBase { - isEnabled: boolean; + isWebBTCEnabled: boolean; executing: boolean; constructor() { super("webbtc"); - this.isEnabled = false; + this.isWebBTCEnabled = false; this.executing = false; } From 082b26919551ed87548b17da87bad654e8c61165 Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Fri, 6 Oct 2023 15:36:33 +0530 Subject: [PATCH 45/73] feat: make enabled field private --- src/extension/providers/providerBase.ts | 16 ++++++++-------- src/extension/providers/webbtc/index.ts | 5 ----- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/src/extension/providers/providerBase.ts b/src/extension/providers/providerBase.ts index ac6a75b476..0b3188ad45 100644 --- a/src/extension/providers/providerBase.ts +++ b/src/extension/providers/providerBase.ts @@ -2,31 +2,31 @@ import { EventEmitter } from "events"; import { postMessage } from "~/extension/providers/postMessage"; export default class ProviderBase { - enabled: boolean; + private _isEnabled: boolean; private _eventEmitter: EventEmitter; private _scope: string; constructor(scope: string) { this._scope = scope; - this.enabled = false; + this._isEnabled = false; this._eventEmitter = new EventEmitter(); this._scope = scope; } protected _checkEnabled(methodName: string): void { - if (!this.enabled) { + if (!this._isEnabled) { throw new Error(`Provider must be enabled before calling ${methodName}`); } } async enable(): Promise { - if (this.enabled) { + if (this._isEnabled) { return; } const result = await this.execute("enable"); if (typeof result.enabled === "boolean") { - this.enabled = result.enabled; + this._isEnabled = result.enabled; } } @@ -45,14 +45,14 @@ export default class ProviderBase { } async isEnabled(): Promise { - if (this.enabled) { + if (this._isEnabled) { return true; } const result = await this.execute("isEnabled"); if (typeof result.isEnabled === "boolean") { - this.enabled = result.isEnabled; + this._isEnabled = result.isEnabled; } - return this.enabled; + return this._isEnabled; } // NOTE: new call `action`s must be specified also in the content script diff --git a/src/extension/providers/webbtc/index.ts b/src/extension/providers/webbtc/index.ts index 896f9a97bf..de5d49a0ed 100644 --- a/src/extension/providers/webbtc/index.ts +++ b/src/extension/providers/webbtc/index.ts @@ -7,13 +7,8 @@ declare global { } export default class WebBTCProvider extends ProviderBase { - isWebBTCEnabled: boolean; - executing: boolean; - constructor() { super("webbtc"); - this.isWebBTCEnabled = false; - this.executing = false; } getInfo() { From d00aba564cd3ce247cda84efefb9d2b0e2914b8e Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 9 Oct 2023 11:48:56 +0700 Subject: [PATCH 46/73] fix: only fire accountChanged event if provider is enabled --- src/extension/content-script/nostr.js | 2 +- src/extension/content-script/webbtc.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/src/extension/content-script/nostr.js b/src/extension/content-script/nostr.js index 76fc586729..bb321c64eb 100644 --- a/src/extension/content-script/nostr.js +++ b/src/extension/content-script/nostr.js @@ -33,7 +33,7 @@ async function init() { browser.runtime.onMessage.addListener((request, sender, sendResponse) => { // forward account changed messaged to inpage script - if (request.action === "accountChanged") { + if (request.action === "accountChanged" && isEnabled) { window.postMessage( { action: "accountChanged", scope: "nostr" }, window.location.origin diff --git a/src/extension/content-script/webbtc.js b/src/extension/content-script/webbtc.js index 370435c22f..bf16afa5d8 100644 --- a/src/extension/content-script/webbtc.js +++ b/src/extension/content-script/webbtc.js @@ -27,7 +27,7 @@ async function init() { browser.runtime.onMessage.addListener((request, sender, sendResponse) => { // forward account changed messaged to inpage script - if (request.action === "accountChanged") { + if (request.action === "accountChanged" && isEnabled) { window.postMessage( { action: "accountChanged", scope: "webbtc" }, window.location.origin From e59dcf9f9bb6bc9291298355b12b21eb4df52215 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 9 Oct 2023 11:54:29 +0700 Subject: [PATCH 47/73] feat: add accountChanged event for window.liquid --- src/extension/content-script/liquid.js | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/src/extension/content-script/liquid.js b/src/extension/content-script/liquid.js index 281431dd9d..aedb69094e 100644 --- a/src/extension/content-script/liquid.js +++ b/src/extension/content-script/liquid.js @@ -26,6 +26,16 @@ async function init() { return; } + browser.runtime.onMessage.addListener((request, sender, sendResponse) => { + // forward account changed messaged to inpage script + if (request.action === "accountChanged" && isEnabled) { + window.postMessage( + { action: "accountChanged", scope: "liquid" }, + window.location.origin + ); + } + }); + // message listener to listen to inpage liquid calls // those calls get passed on to the background script // (the inpage script can not do that directly, but only the inpage script can make liquid available to the page) From a1ad9b46bb4eb733096374047e372c7ac956eded Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 9 Oct 2023 12:18:07 +0700 Subject: [PATCH 48/73] fix: only error show toast when signPSBT fails --- src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx | 1 - 1 file changed, 1 deletion(-) diff --git a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx index 901eae8ace..3657cad47b 100644 --- a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx +++ b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx @@ -58,7 +58,6 @@ function ConfirmSignPsbt() { console.error(e); const error = e as { message: string }; const errorMessage = error.message || "Unknown error"; - setError(errorMessage); toast.error(`${tCommon("error")}: ${errorMessage}`); } finally { setLoading(false); From 78ae4fcf0ec2fc2e84abefa5e7738230255d3041 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 9 Oct 2023 12:33:31 +0700 Subject: [PATCH 49/73] fix: remove signPsbt error state and always use toasts --- src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx | 15 +++++++-------- 1 file changed, 7 insertions(+), 8 deletions(-) diff --git a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx index 3657cad47b..0f0b697371 100644 --- a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx +++ b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx @@ -26,23 +26,22 @@ function ConfirmSignPsbt() { const psbt = navState.args?.psbt as string; const origin = navState.origin as OriginData; - const [loading, setLoading] = useState(false); + const [loading, setLoading] = useState(true); const [successMessage, setSuccessMessage] = useState(""); const [preview, setPreview] = useState(undefined); const [showAddresses, setShowAddresses] = useState(false); const [showHex, setShowHex] = useState(false); - const [error, setError] = useState(); useEffect(() => { (async () => { try { const preview = await api.bitcoin.getPsbtPreview(psbt); setPreview(preview); + setLoading(false); } catch (e) { console.error(e); const error = e as { message: string }; const errorMessage = error.message || "Unknown error"; - setError(errorMessage); toast.error(`${tCommon("error")}: ${errorMessage}`); } })(); @@ -85,12 +84,12 @@ function ConfirmSignPsbt() { setShowHex((current) => !current); } - if (error) { - return

{error}

; - } - if (!preview) { - return ; + return ( +
+ +
+ ); } return ( From ed421bde101651d87b082ea13a87aa2d00e54347 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 9 Oct 2023 13:35:37 +0700 Subject: [PATCH 50/73] chore: remove unneeded dependency from package.json --- package.json | 1 - 1 file changed, 1 deletion(-) diff --git a/package.json b/package.json index 18ba9e68fb..b6a9dcd822 100644 --- a/package.json +++ b/package.json @@ -53,7 +53,6 @@ "crypto-js": "^4.1.1", "dayjs": "^1.11.9", "dexie": "^3.2.4", - "ecpair": "^2.1.0", "elliptic": "^6.5.4", "events": "^3.3.0", "html5-qrcode": "^2.3.8", From 2631656f94128271ed4909b15fc5caf37fd72d47 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 9 Oct 2023 13:39:52 +0700 Subject: [PATCH 51/73] chore: use getFormattedSats instead of new translation --- src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx | 7 +++++-- src/i18n/locales/en/translation.json | 3 +-- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx index 0f0b697371..46879b6ef1 100644 --- a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx +++ b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx @@ -10,6 +10,7 @@ import Hyperlink from "~/app/components/Hyperlink"; import Loading from "~/app/components/Loading"; import ScreenHeader from "~/app/components/ScreenHeader"; import toast from "~/app/components/Toast"; +import { useSettings } from "~/app/context/SettingsContext"; import { useNavigationState } from "~/app/hooks/useNavigationState"; import { USER_REJECTED_ERROR } from "~/common/constants"; import api from "~/common/lib/api"; @@ -23,6 +24,7 @@ function ConfirmSignPsbt() { keyPrefix: "bitcoin.confirm_sign_psbt", }); const navigate = useNavigate(); + const { getFormattedSats } = useSettings(); const psbt = navState.args?.psbt as string; const origin = navState.origin as OriginData; @@ -129,7 +131,7 @@ function ConfirmSignPsbt() {

{t("fee")}

- {t("amount", { amount: preview.fee })} + {getFormattedSats(preview.fee)}

@@ -175,11 +177,12 @@ function AddressPreview({ }: Address & { t: TFunction<"translation", "bitcoin.confirm_sign_psbt", "translation">; }) { + const { getFormattedSats } = useSettings(); return (

{address}

- {t("amount", { amount })} + {getFormattedSats(amount)}

); diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index 0d5b187a12..fe3709a507 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -849,8 +849,7 @@ "hide_raw_transaction": "Hide raw transaction (Hex)", "inputs": "Inputs", "outputs": "Outputs", - "fee": "Fee", - "amount": "{{amount}} sats" + "fee": "Fee" }, "allow": "Allow this website to:", "allow_sign": "Allow {{host}} to sign:", From d61060b5d4bb0238e2f2b18a04b175b7200f5253 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 9 Oct 2023 13:43:05 +0700 Subject: [PATCH 52/73] fix: signpsbt allow sign translation --- src/i18n/locales/en/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index fe3709a507..aba5626df8 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -842,7 +842,7 @@ }, "confirm_sign_psbt": { "title": "Sign", - "allow_sign": "This website asks you to sign a Bitcoin Transaction:", + "allow_sign": "This website asks you to sign a bitcoin transaction", "view_details": "View details", "hide_details": "Hide details", "view_raw_transaction": "View raw transaction (Hex)", From 649795e1b1078fca6956152f0ed15f28cefff11e Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 9 Oct 2023 13:51:15 +0700 Subject: [PATCH 53/73] chore: make psbt and pset signing screens consistent with nostr --- .../screens/Bitcoin/ConfirmSignPsbt/index.tsx | 25 +++++++++++-------- src/app/screens/Liquid/ConfirmSignPset.tsx | 24 ++++++++++-------- 2 files changed, 28 insertions(+), 21 deletions(-) diff --git a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx index 46879b6ef1..03355a37f8 100644 --- a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx +++ b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.tsx @@ -109,14 +109,16 @@ function ConfirmSignPsbt() {

{t("allow_sign", { host: origin.host })}

-
- - {showAddresses ? t("hide_details") : t("view_details")} - -
+
+
+ + {showAddresses ? t("hide_details") : t("view_details")} + +
- {showAddresses && ( - <> + {showAddresses && ( + <> +

{t("inputs")}

{preview.inputs.map((input) => ( @@ -133,15 +135,16 @@ function ConfirmSignPsbt() {

{getFormattedSats(preview.fee)}

- +
+
{showHex ? t("hide_raw_transaction") : t("view_raw_transaction")} - - )} -
+
+ + )} {showHex && (
diff --git a/src/app/screens/Liquid/ConfirmSignPset.tsx b/src/app/screens/Liquid/ConfirmSignPset.tsx index 119458a732..862ed58f94 100644 --- a/src/app/screens/Liquid/ConfirmSignPset.tsx +++ b/src/app/screens/Liquid/ConfirmSignPset.tsx @@ -117,14 +117,16 @@ function ConfirmSignPset() {
-
- - {showDetails ? t("hide_details") : t("view_details")} - -
+
+
+ + {showDetails ? t("hide_details") : t("view_details")} + +
- {showDetails && ( - <> + {showDetails && ( + <> +

{t("inputs")}

@@ -154,15 +156,17 @@ function ConfirmSignPset() { ))}
+
+
{showRawTransaction ? t("hide_raw_transaction") : t("view_raw_transaction")} - - )} -
+
+ + )} {showRawTransaction && (
From de7aeb9567b5a9a1477735478f6c12c3dab50870 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 9 Oct 2023 13:52:13 +0700 Subject: [PATCH 54/73] fix: casing in liquid transaction translation --- src/i18n/locales/en/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index aba5626df8..e069783546 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -827,7 +827,7 @@ }, "confirm_sign_pset": { "title": "Sign", - "allow_sign": "This website asks you to sign a Liquid Transaction:", + "allow_sign": "This website asks you to sign a liquid transaction", "view_details": "View details", "hide_details": "Hide details", "view_raw_transaction": "View raw transaction (Base64)", From 11360a8b5eb134341dd20c480cfb5c07355c8c75 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Mon, 9 Oct 2023 14:15:30 +0700 Subject: [PATCH 55/73] fix: update old comment --- .../Bitcoin/ConfirmSignPsbt/index.test.tsx | 15 +++++++++++++-- 1 file changed, 13 insertions(+), 2 deletions(-) diff --git a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx index 43418ad286..8afcf54d4a 100644 --- a/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx +++ b/src/app/screens/Bitcoin/ConfirmSignPsbt/index.test.tsx @@ -6,6 +6,7 @@ import state from "~/extension/background-script/state"; import { btcFixture } from "~/fixtures/btc"; import type { OriginData } from "~/types"; +import { getFormattedSats } from "~/common/utils/currencyConvert"; import Bitcoin from "~/extension/background-script/bitcoin"; import Mnemonic from "~/extension/background-script/mnemonic"; import ConfirmSignPsbt from "./index"; @@ -56,7 +57,17 @@ const mockState = { state.getState = jest.fn().mockReturnValue(mockState); -// mock get settings +jest.mock("~/app/context/SettingsContext", () => ({ + useSettings: () => ({ + getFormattedSats: (amount: number) => + getFormattedSats({ + amount, + locale: "en", + }), + }), +})); + +// mock getPsbtPreview request msg.request = jest .fn() .mockReturnValue( @@ -83,7 +94,7 @@ describe("ConfirmSignPsbt", () => { expect( await screen.findByText( - "This website asks you to sign a Bitcoin Transaction:" + "This website asks you to sign a bitcoin transaction" ) ).toBeInTheDocument(); From af378222b218923a18b2ac8628f791d847c378af Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Aaron?= Date: Mon, 9 Oct 2023 12:04:37 +0200 Subject: [PATCH 56/73] fix: improve QR code readability for different wallets --- src/app/components/QRCode/index.tsx | 9 ++- src/app/screens/Receive/index.tsx | 8 +-- src/app/screens/ReceiveInvoice/index.tsx | 83 ++++++++++-------------- 3 files changed, 44 insertions(+), 56 deletions(-) diff --git a/src/app/components/QRCode/index.tsx b/src/app/components/QRCode/index.tsx index c8803afcac..e34f18559c 100644 --- a/src/app/components/QRCode/index.tsx +++ b/src/app/components/QRCode/index.tsx @@ -5,9 +5,12 @@ export type Props = { value: string; size?: number; className?: string; + + // set the level to Q if there are overlays + level?: string; }; -export default function QRCode({ value, size, className }: Props) { +export default function QRCode({ value, size, level, className }: Props) { const theme = useTheme(); const fgColor = theme === "dark" ? "#FFFFFF" : "#000000"; const bgColor = theme === "dark" ? "#000000" : "#FFFFFF"; @@ -16,10 +19,10 @@ export default function QRCode({ value, size, className }: Props) { ); } diff --git a/src/app/screens/Receive/index.tsx b/src/app/screens/Receive/index.tsx index a2966598d4..f0fe6a32e1 100644 --- a/src/app/screens/Receive/index.tsx +++ b/src/app/screens/Receive/index.tsx @@ -76,14 +76,10 @@ function Receive() { ) : ( <> - )} diff --git a/src/app/screens/ReceiveInvoice/index.tsx b/src/app/screens/ReceiveInvoice/index.tsx index 4c883b6b20..aefab6c61c 100644 --- a/src/app/screens/ReceiveInvoice/index.tsx +++ b/src/app/screens/ReceiveInvoice/index.tsx @@ -1,6 +1,5 @@ import { CaretLeftIcon, - CheckIcon, CopyIcon, } from "@bitcoin-design/bitcoin-icons-react/outline"; import Button from "@components/Button"; @@ -15,6 +14,7 @@ import Confetti from "react-confetti"; import { useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import QRCode from "~/app/components/QRCode"; +import ResultCard from "~/app/components/ResultCard"; import toast from "~/app/components/Toast"; import { useAccount } from "~/app/context/AccountContext"; import { useSettings } from "~/app/context/SettingsContext"; @@ -46,9 +46,6 @@ function ReceiveInvoice() { paymentRequest: string; rHash: string; } | null>(); - const [copyInvoiceLabel, setCopyInvoiceLabel] = useState( - tCommon("actions.copy_invoice") as string - ); const [paid, setPaid] = useState(false); const [pollingForPayment, setPollingForPayment] = useState(false); @@ -142,44 +139,22 @@ function ReceiveInvoice() { if (!invoice) return null; return ( <> -
- - {paid && ( -
-
-
- -
-

{t("success")}

-
-
- )} -
- {paid && ( -
-
- )} {!paid && ( <> -
+
+
+ +
+
+
-
{pollingForPayment && (
@@ -210,15 +184,30 @@ function ReceiveInvoice() { )} {paid && ( - { - confetti && confetti.reset(); - }} - style={{ pointerEvents: "none" }} - /> + <> + +
+
+ { + confetti && confetti.reset(); + }} + style={{ pointerEvents: "none" }} + /> + )} ); From 4d3d27661a89b1bd22b327e19d2555a99957b2ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Aaron?= Date: Mon, 9 Oct 2023 16:19:17 +0200 Subject: [PATCH 57/73] fix: remove lightning prefix --- src/app/screens/Receive/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/screens/Receive/index.tsx b/src/app/screens/Receive/index.tsx index f0fe6a32e1..9558692df9 100644 --- a/src/app/screens/Receive/index.tsx +++ b/src/app/screens/Receive/index.tsx @@ -77,7 +77,7 @@ function Receive() { <> From 48372099b91a51bc3968b2f7938d28835d9a735c Mon Sep 17 00:00:00 2001 From: Roland <33993199+rolznz@users.noreply.github.com> Date: Mon, 9 Oct 2023 21:58:46 +0700 Subject: [PATCH 58/73] fix: liquid casing in sign transaction translation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: René Aaron <100827540+reneaaron@users.noreply.github.com> --- src/i18n/locales/en/translation.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index e069783546..7516cb5523 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -827,7 +827,7 @@ }, "confirm_sign_pset": { "title": "Sign", - "allow_sign": "This website asks you to sign a liquid transaction", + "allow_sign": "This website asks you to sign a Liquid transaction", "view_details": "View details", "hide_details": "Hide details", "view_raw_transaction": "View raw transaction (Base64)", From 570c979afb0726fd4d387ea89be7ee0369ebc475 Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 10 Oct 2023 11:52:13 +0700 Subject: [PATCH 59/73] chore: update sendPaymentAsync UI and translations --- src/app/components/PaymentSummary/index.tsx | 2 +- src/app/screens/ConfirmPaymentAsync/index.tsx | 19 +++++++++++++++++-- src/i18n/locales/en/translation.json | 4 ++-- 3 files changed, 20 insertions(+), 5 deletions(-) diff --git a/src/app/components/PaymentSummary/index.tsx b/src/app/components/PaymentSummary/index.tsx index 641816c6ea..15ae87942b 100644 --- a/src/app/components/PaymentSummary/index.tsx +++ b/src/app/components/PaymentSummary/index.tsx @@ -41,7 +41,7 @@ const PaymentSummary: FC = ({
{tCommon("description")}
-
+
{description}
diff --git a/src/app/screens/ConfirmPaymentAsync/index.tsx b/src/app/screens/ConfirmPaymentAsync/index.tsx index 43f431028c..26a12cd2f7 100644 --- a/src/app/screens/ConfirmPaymentAsync/index.tsx +++ b/src/app/screens/ConfirmPaymentAsync/index.tsx @@ -4,9 +4,10 @@ import PaymentSummary from "@components/PaymentSummary"; import PublisherCard from "@components/PublisherCard"; import lightningPayReq from "bolt11"; import { useEffect, useState } from "react"; -import { useTranslation } from "react-i18next"; +import { Trans, useTranslation } from "react-i18next"; import { useNavigate } from "react-router-dom"; import Alert from "~/app/components/Alert"; +import Hyperlink from "~/app/components/Hyperlink"; import ScreenHeader from "~/app/components/ScreenHeader"; import toast from "~/app/components/Toast"; import { useSettings } from "~/app/context/SettingsContext"; @@ -105,7 +106,21 @@ function ConfirmPaymentAsync() {
- {t("description")} + + , + ]} + /> +
diff --git a/src/i18n/locales/en/translation.json b/src/i18n/locales/en/translation.json index f3aae4ae8c..fb1b1da9b9 100644 --- a/src/i18n/locales/en/translation.json +++ b/src/i18n/locales/en/translation.json @@ -901,8 +901,8 @@ "service_unavailable": "Service temporary not available, please try again in a few minutes." }, "confirm_payment_async": { - "title": "Approve Delayed / Conditional Payment", - "description": "This payment may stay unsettled for a day or longer. The receiver can choose to settle or cancel the payment by leaving it to expire.", + "title": "Approve Payment", + "description": "This is a HOLD invoice. <0>Learn more »", "actions": { "pay_now": "Pay now" } From 4e4a2fbaa4b98d66091eefe41aca476fbeb6380f Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 10 Oct 2023 11:53:59 +0700 Subject: [PATCH 60/73] chore: update comment to add more info about sendPaymentAsync --- .../background-script/actions/ln/sendPaymentAsync.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/src/extension/background-script/actions/ln/sendPaymentAsync.ts b/src/extension/background-script/actions/ln/sendPaymentAsync.ts index 4cba2e89a9..77592c5cbb 100644 --- a/src/extension/background-script/actions/ln/sendPaymentAsync.ts +++ b/src/extension/background-script/actions/ln/sendPaymentAsync.ts @@ -19,9 +19,11 @@ export default async function sendPayment(message: MessageSendPayment) { const connector = await state.getState().getConnector(); // NOTE: currently there is no way to know if the initial payment - // succeeds or not. The payment might not work at all or the http request might time out - // before the HODL invoice is paid or times out itself. - // any errors thrown by sendPayment will not be caught. + // succeeds or not. The payment might not work at all or the http request might time out + // before the HODL invoice is paid or times out itself. + // any errors thrown by sendPayment will not be caught. + // NOTE: it is the receiver's responsibility to check if they have received the payment or not + // and update the UI or re-prompt the user if they haven't received a payment. connector.sendPayment({ paymentRequest, }); From b3341bcb8d9352d1dbe3c2f28aafbad27737449a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Aaron?= Date: Tue, 10 Oct 2023 14:59:02 +0200 Subject: [PATCH 61/73] fix: rounded QR codes --- src/app/components/QRCode/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/QRCode/index.tsx b/src/app/components/QRCode/index.tsx index e34f18559c..607a94f3c0 100644 --- a/src/app/components/QRCode/index.tsx +++ b/src/app/components/QRCode/index.tsx @@ -21,7 +21,7 @@ export default function QRCode({ value, size, level, className }: Props) { size={size} fgColor={fgColor} bgColor={bgColor} - className={classNames("w-full h-auto", className ?? "")} + className={classNames("w-full h-auto rounded-md", className ?? "")} level={level} /> ); From 8899895af1e0a0418930d60ebbcb9032efd073d0 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 10 Oct 2023 15:21:42 +0200 Subject: [PATCH 62/73] Translated using Weblate (Portuguese (Brazil)) Currently translated at 85.9% (619 of 720 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 85.9% (616 of 717 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 85.7% (607 of 708 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 85.7% (607 of 708 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 85.7% (607 of 708 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 85.5% (606 of 708 strings) Translated using Weblate (Portuguese (Brazil)) Currently translated at 85.0% (602 of 708 strings) Co-authored-by: Hosted Weblate Co-authored-by: Leonardo Translate-URL: https://hosted.weblate.org/projects/getalby-lightning-browser-extension/getalby-lightning-browser-extension/pt_BR/ Translation: getAlby - lightning-browser-extension/getAlby - lightning-browser-extension --- src/i18n/locales/pt_BR/translation.json | 50 ++++++++++++++++++------- 1 file changed, 36 insertions(+), 14 deletions(-) diff --git a/src/i18n/locales/pt_BR/translation.json b/src/i18n/locales/pt_BR/translation.json index 77e489b4bf..d64ea8b21a 100644 --- a/src/i18n/locales/pt_BR/translation.json +++ b/src/i18n/locales/pt_BR/translation.json @@ -608,12 +608,12 @@ "actions": { "create_invoice": "Gerar fatura", "redeem": { - "description": "Receba bitcoin usando um código LNURL", + "description": "Receba bitcoin de forma instantânea usando um código LNURL", "title": "Vale-satoshi" }, "invoice": { "title": "Fatura relâmpago", - "description": "Solicite pagamentos instantâneos e de valores específicos" + "description": "Receba pagamentos bitcoin de valores específicos e de forma instantânea" }, "bitcoin_address": { "description": "Receba via endereço bitcoin usando um serviço de trocas", @@ -740,7 +740,7 @@ "confirm_request_permission": { "title": "Aprovar solicitação", "allow": "Permitir este site executar:", - "always_allow": "Lembrar minha escolha e não perguntar novamente" + "always_allow": "Lembrar e não perguntar novamente" }, "discover": { "title": "Explore o ecossistema ⚡️", @@ -867,7 +867,7 @@ "outputs": "Saídas", "amount": "{{amount}} {{ticker}}", "title": "Assinar", - "allow_sign": "Este site solicita que você assine uma transação Liquid:", + "allow_sign": "Este site solicita que você assine uma transação liquid", "inputs": "Entradas" }, "bitcoin": { @@ -877,7 +877,18 @@ "allow": "Permitir este site:", "allow_sign": "Permitir {{host}} assinar:", "block_and_ignore": "Bloquear e ignorar {{host}}", - "block_added": "{{host}} adicionado na lista de bloqueios, recarregue o site." + "block_added": "{{host}} adicionado na lista de bloqueios, recarregue o site.", + "confirm_sign_psbt": { + "title": "Assinar", + "hide_raw_transaction": "Ocultar transação bruta (Hex)", + "view_raw_transaction": "Ver transação bruta (Hex)", + "inputs": "Entradas", + "fee": "Taxa", + "hide_details": "Ocultar detalhes", + "allow_sign": "Este site solicita que você assine uma transação de bitcoin", + "view_details": "Ver detalhes", + "outputs": "Saídas" + } }, "liquid": { "title": "Liquid", @@ -907,7 +918,7 @@ "provider": { "label": "Serviço provedor de trocas" }, - "time_estimate": "ℹ️ As transações geralmente são recebidas dentro de 10 a 30 minutos.", + "time_estimate": "ℹ️ As transações geralmente são recebidas no prazo de 10 a 30 minutos.", "title": "Enviar para um endereço bitcoin", "recipient": { "label": "Endereço" @@ -928,16 +939,27 @@ "title": "Conectar no Nostr" }, "webln_enable": { - "title": "Conectar no WebLN" + "title": "Conectar no WebLN", + "request2": "Solicitar faturas e informações da rede relâmpago" }, "webbtc_enable": { - "title": "Conectar no WebBTC" + "title": "Conectar no WebBTC", + "request2": "Solicitar faturas e informações Webbtc" }, "liquid_enable": { - "title": "Conectar na Liquid" + "title": "Conectar na Liquid", + "request2": "Solicitar faturas e informações da liquid" }, "alby_enable": { - "title": "Conectar na Alby" + "title": "Conectar na Alby", + "request2": "Solicitar faturas e informações da alby" + }, + "confirm_payment_async": { + "title": "Aprovar pagamento", + "description": "Esta é uma fatura do tipo HOLD. <0>Saiba mais »", + "actions": { + "pay_now": "Pagar agora" + } } }, "common": { @@ -974,7 +996,7 @@ "copy_invoice": "Copiar código", "back": "Voltar", "log_in": "Fazer login", - "remember": "Lembrar minha escolha e não perguntar novamente", + "remember": "Lembrar e não perguntar novamente", "receive_again": "Receber outro pagamento", "transactions": "Transações", "more": "Mais", @@ -996,7 +1018,7 @@ "message": "Mensagem", "help": "Ajuda", "response": "Resposta", - "success_message": "{{amount}}{{fiatAmount}} estão a caminho de {{destination}}", + "success_message": "{{amount}}{{fiatAmount}} enviados para {{destination}}", "advanced": "Avançado", "discover": "Explorar", "confirm_password": "Confirmar senha", @@ -1043,7 +1065,7 @@ "screen_reader": "Opções limite de gastos" }, "enable_login": { - "title": "Login relâmpago", + "title": "Login", "subtitle": "O login é realizado de forma automática, sem necessidade de confirmação." }, "edit_permissions": "Permissões" @@ -1112,7 +1134,7 @@ }, "badge": { "label": { - "auth": "LOGIN RELÂMPAGO", + "auth": "LOGIN", "imported": "IMPORTADA", "budget": "LIMITE DE GASTOS" } From e851ef393072dc0b3371641972be3d1f9a4df1e5 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 10 Oct 2023 15:21:42 +0200 Subject: [PATCH 63/73] Translated using Weblate (Greek) Currently translated at 6.3% (45 of 708 strings) Co-authored-by: Hosted Weblate Co-authored-by: Nikos Charonitakis Translate-URL: https://hosted.weblate.org/projects/getalby-lightning-browser-extension/getalby-lightning-browser-extension/el/ Translation: getAlby - lightning-browser-extension/getAlby - lightning-browser-extension --- src/i18n/locales/el/translation.json | 42 ++++++++++++++++++++++++++-- 1 file changed, 39 insertions(+), 3 deletions(-) diff --git a/src/i18n/locales/el/translation.json b/src/i18n/locales/el/translation.json index 7fb27bf4db..e409546aaf 100644 --- a/src/i18n/locales/el/translation.json +++ b/src/i18n/locales/el/translation.json @@ -37,6 +37,9 @@ "other": { "title": "Άλλα πορτοφόλια", "connect": "Σύνδεση" + }, + "alby": { + "title": "Λογαριασμός Alby" } }, "alby": { @@ -44,13 +47,24 @@ "set_password": { "choose_password": { "label": "Κωδικός" + }, + "errors": { + "mismatched_password": "Οι κωδικοί δεν ταιριάζουν.", + "enter_password": "Παρακαλώ εισάγετε έναν κωδικό.", + "confirm_password": "Παρακαλώ επιβεβαιώστε τον κωδικός σας." + }, + "confirm_password": { + "label": "Επιβεβαίωση κωδικού πρόσβασης" } } } }, "choose_connector": { "lndhub_bluewallet": { - "title": "Bluewallet" + "title": "Bluewallet", + "page": { + "title": "Σύνδεση σε BlueWallet" + } }, "lnbits": { "title": "LNbits" @@ -59,7 +73,17 @@ "title": "LNDHub" }, "citadel": { - "title": "Citadel" + "title": "Citadel", + "url": { + "label": "Citadel URL", + "placeholder": "http://citadel.local" + }, + "page": { + "title": "Σύνδεση σε κόμβο <0>Citadel" + }, + "password": { + "label": "Κωδικός Citadel" + } }, "start9": { "title": "Start9" @@ -73,7 +97,11 @@ "eclair": { "title": "Eclair", "url": { - "placeholder": "http://localhost:8080" + "placeholder": "http://localhost:8080", + "label": "Eclair URL" + }, + "password": { + "label": "Κωδικός Eclair" } }, "kollider": { @@ -85,6 +113,14 @@ "port": { "label": "Θύρα" } + }, + "title": "Σύνδεση με πορτοφόλι Lightning", + "description": "Σύνδεση σε εξωτερικό πορτοφόλι lightning ή σε κόμβο", + "umbrel": { + "page": { + "title": "Σύνδεση σε κόμβο <0>Umbrel" + }, + "title": "Umbrel" } }, "distributions": { From e7e09c0cf10fa8ec9ac23c7fb3c63e853cbca24d Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 10 Oct 2023 15:21:42 +0200 Subject: [PATCH 64/73] Translated using Weblate (Swedish) Currently translated at 100.0% (720 of 720 strings) Translated using Weblate (Swedish) Currently translated at 100.0% (708 of 708 strings) Co-authored-by: Hosted Weblate Co-authored-by: Pextar Translate-URL: https://hosted.weblate.org/projects/getalby-lightning-browser-extension/getalby-lightning-browser-extension/sv/ Translation: getAlby - lightning-browser-extension/getAlby - lightning-browser-extension --- src/i18n/locales/sv/translation.json | 90 ++++++++++++++++++++++++---- 1 file changed, 79 insertions(+), 11 deletions(-) diff --git a/src/i18n/locales/sv/translation.json b/src/i18n/locales/sv/translation.json index a48c8e6531..ae6214b1a5 100644 --- a/src/i18n/locales/sv/translation.json +++ b/src/i18n/locales/sv/translation.json @@ -462,7 +462,7 @@ "title": "Ta bort huvudnyckeln", "subtitle": "Tar bort huvudnyckeln från det här kontot.", "success": "Masternyckeln har tagits bort.", - "confirm": "Ange namnet på kontot för att bekräfta raderingen av din huvudnyckel.\n{{ name }}" + "confirm": "Ange namnet på kontot för att bekräfta raderingen av din huvudnyckel.\n\n{{ name }}" }, "no_mnemonic_hint": "💡 Du har ingen huvudnyckel än. <0>Klicka här
för att skapa din huvudnyckel och låsa upp dessa inställningar.", "network": { @@ -642,7 +642,19 @@ "receive": { "title": "Tag emot", "actions": { - "create_invoice": "Skapa Faktura" + "create_invoice": "Skapa Faktura", + "redeem": { + "description": "Ta ut en bitcoin-kupong direkt via en LNURL-kod", + "title": "Lösa in" + }, + "invoice": { + "title": "Lightning faktura", + "description": "Begär omedelbara och specifika belopp bitcoinbetalningar" + }, + "bitcoin_address": { + "description": "Ta emot via bitcoin-adress med en bytestjänst", + "title": "Bitcoin adress" + } }, "amount": { "label": "Belopp", @@ -890,7 +902,18 @@ "allow": "Tillåt den här webbplatsen att:", "allow_sign": "Tillåt {{host}} att signera:", "block_and_ignore": "Blockera och ignorera {{host}}", - "block_added": "Lade till {{host}} till blockeringslistan, ladda om webbplatsen." + "block_added": "Lade till {{host}} till blockeringslistan, ladda om webbplatsen.", + "confirm_sign_psbt": { + "title": "Signera", + "hide_raw_transaction": "Göm råtransaktion (hexadecimal)", + "view_raw_transaction": "Visa råtransaktion (hexadecimal)", + "inputs": "Ingångar", + "fee": "Avgift", + "hide_details": "Göm detaljer", + "allow_sign": "Den här webbplatsen ber dig att underteckna en bitcoin-transaktion", + "view_details": "Visa detaljer", + "outputs": "Utgångar" + } }, "liquid": { "title": "Liquid", @@ -929,6 +952,39 @@ "webln_enable": { "title": "Anslut till WebLN", "request2": "Begär fakturor och lightning information" + }, + "onboard": { + "request2": "Du har inte konfigurerat dina nycklar än", + "request1": "Denna webbplats kräver nyckelinteraktion", + "request3": "Installationen är snabb och enkel", + "title": "Ställ in dina nycklar", + "actions": { + "start_setup": "Starta installationen" + } + }, + "liquid_enable": { + "request2": "Begär fakturor och likvida uppgifter", + "title": "Anslut till Liquid" + }, + "nostr_enable": { + "title": "Anslut till Nostr", + "request2": "Signera händelser med din privata Nostr-nyckel", + "request1": "Begär att läsa din publika Nostr-nyckel" + }, + "webbtc_enable": { + "request2": "Begär fakturor och Webbtc-information", + "title": "Anslut till WebBTC" + }, + "alby_enable": { + "title": "Anslut till Alby", + "request2": "Begär fakturor och albyinformation" + }, + "confirm_payment_async": { + "title": "Godkänn betalning", + "description": "Detta är en HOLD-faktura. <0>Läs mer »", + "actions": { + "pay_now": "Betala nu" + } } }, "common": { @@ -976,7 +1032,9 @@ "copy_clipboard": "Kopiera till urklipp", "paste": "Klistra in", "paste_clipboard": "Klistra in från urklipp", - "review": "Recension" + "review": "Recension", + "copied_to_clipboard": "Kopierade till urklipp", + "disconnect": "Koppla ifrån" }, "errors": { "connection_failed": "Anslutning misslyckades", @@ -1011,21 +1069,28 @@ "citadel": "Citadel" }, "or": "eller", - "pasted": "Klistrat in!" + "pasted": "Klistrat in!", + "enable": { + "block_added": "Lade till {{host}} till blockeringslistan, ladda om webbplatsen.", + "request1": "Begär godkännande för transaktioner", + "allow": "Tillåt den här webbplatsen att:", + "block_and_ignore": "Blockera och ignorera {{host}}" + } }, "components": { "allowance_menu": { - "confirm_delete": "Är du säker på att du vill radera webbplatsen?", - "hint": "Detta nollställer nuvarande budget", + "confirm_delete": "Är du säker på att du vill koppla bort den här webbplatsen?", + "hint": "Om du ändrar detta återställs den nuvarande budgeten", "new_budget": { - "label": "Ny budget" + "label": "Betalningsbudget med ett klick", + "link_label": "Ställ in en budget för betalningar med ett klick" }, "enable_login": { "title": "Aktivera webbplatsinloggning", "subtitle": "Logga in automatiskt utan bekräftelse när webbplatsen begär det." }, "edit_allowance": { - "title": "Redigera Inställningar", + "title": "Webbplatsinställningar", "screen_reader": "Alternativ för Beloppsgräns" }, "edit_permissions": "Redigera behörigheter" @@ -1089,12 +1154,15 @@ } }, "publishers_table": { - "payments": "betalningar" + "payments": "betalningar", + "total": "totalt", + "budget": "budget" }, "badge": { "label": { "auth": "LOGGA IN", - "imported": "IMPORTERAD" + "imported": "IMPORTERAD", + "budget": "BUDGET" } }, "companion_download_info": { From 13e73117c52830271ceb95c7c0e2f69146566054 Mon Sep 17 00:00:00 2001 From: Hosted Weblate Date: Tue, 10 Oct 2023 15:21:42 +0200 Subject: [PATCH 65/73] Translated using Weblate (Slovenian) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 48.4% (349 of 720 strings) Co-authored-by: Matjaž Lipuš Translate-URL: https://hosted.weblate.org/projects/getalby-lightning-browser-extension/getalby-lightning-browser-extension/sl/ Translation: getAlby - lightning-browser-extension/getAlby - lightning-browser-extension --- src/i18n/locales/sl/translation.json | 210 +++++++++++++++++++++++++-- 1 file changed, 196 insertions(+), 14 deletions(-) diff --git a/src/i18n/locales/sl/translation.json b/src/i18n/locales/sl/translation.json index e1c13197df..b170e75d94 100644 --- a/src/i18n/locales/sl/translation.json +++ b/src/i18n/locales/sl/translation.json @@ -165,6 +165,9 @@ }, "privKey": { "label": "Lokalni zasebni ključ (samodejno generiran)" + }, + "errors": { + "connection_failed": "Neuspešna povezava. Je tvoje Core Lightning vozlišče povezano in uporablja commando vtičnik?" } }, "lnc": { @@ -240,7 +243,8 @@ "umbrel": { "title": "Umbrel", "page": { - "title": "Poveži <0>Umbrel vozlišče" + "title": "Poveži <0>Umbrel vozlišče", + "instructions": "1. V tvoji nadzorni plošči Lightning vozliška pojdi na <0>Poveži denarnico<1/>2. Izberi <0>REST (Tor) ali <0>REST (Local Network) način<1/>3. Skopiraj <0>lndconnect URL in ga prilepi spodaj" }, "rest_url": { "label": "lndconnect REST URL", @@ -260,6 +264,12 @@ "invalid_uri": "Nepravilni LNDHub URI", "connection_failed": "Povezava ni bila uspešna. Je tvoj LNDHub URI pravilen?" } + }, + "blink": { + "page": { + "title": "Poveži z <0>Blink denarnico" + }, + "title": "Blink denarnica" } }, "welcome": { @@ -340,7 +350,12 @@ "recent_transactions": "Pretekle transakcije", "allowance_view": { "recent_transactions": "Pretekle transakcije", - "no_transactions": "NA <0>{{name}} še ni transakcij." + "no_transactions": "NA <0>{{name}} še ni transakcij.", + "sats": "sats", + "budget_spent": "Porabljen proračun", + "total_spent": "Porabljeno skupaj", + "total_payments": "Skupaj plačila", + "permissions": "Pravice" }, "default_view": { "recent_transactions": "Pretekle transakcije", @@ -360,7 +375,8 @@ "screen_reader": "Izvozi podrobnosti računa", "your_ln_address": "Tvoj Lightning naslov:", "waiting": "čakanje na LndHub podatke...", - "export_uri": "LNDHub URI poverilnice" + "export_uri": "LNDHub URI poverilnice", + "scan_qr": "Uvozi to denarnico v Zeus ali BlueWallet s skeniranjem QR kode." }, "name": { "title": "Prikazno ime", @@ -371,7 +387,10 @@ "saved": "Glavni ključ kriptiran in uspešno shranjen.", "generate": { "title": "Generiraj nov Glavni ključ", - "button": "Generiraj Glavni ključ" + "button": "Generiraj Glavni ključ", + "confirm": "Naredil sem varnostno kopijo fraze in shranil na varno mesto.", + "description": "Glavni ključ ti omogoča interakcijo z različnimi protokoli, kot so: Nostr, Bitcoin omrežje ali LNURL-Auth.", + "error_confirm": "Prosim potrdi, da si naredil varnostno kopijo obnovitvene fraze." }, "backup": { "title": "Varnostno kopiraj obnovitveno frazo", @@ -395,7 +414,11 @@ "title": "Obnovitvena fraza" }, "lnurl": { - "title": "Prijava z Lightning" + "title": "Prijava z Lightning", + "use_mnemonic": "Uporabi Glavni ključ za prijavo v Lightning omogočene aplikacije (LNURL Auth)" + }, + "new": { + "title": "Kaj je Glavni ključ?" } }, "network": { @@ -410,7 +433,9 @@ "settings": { "label": "Nostr nastavitve", "remove": "Odstrani trenutne ključe", - "title": "Nostr nastavitve" + "title": "Nostr nastavitve", + "can_restore": "✅ Nostr ključ, izpeljan iz vašega glavnega ključa", + "derive": "Izpelji iz glavnega ključa" }, "setup": { "title": "Konfiguriraj Nostr ključe", @@ -421,12 +446,17 @@ "label": "Uporabi Nostr zasebni ključ" }, "recovery_phrase": { - "label": "Uporabi obnovitveno frazo" - } + "label": "Uporabi obnovitveno frazo", + "description": "Za uvoz ključev Nostr uporabite frazo za obnovitev glavnega ključa" + }, + "description": "Uporabite obstoječi zasebni ključ Nostr ali ga izpeljite iz svojega glavnega ključa", + "title": "Kako želite uvoziti svoj račun Nostr?" }, "new": { - "label": "Ustvari nov Nostr račun" - } + "label": "Ustvari nov Nostr račun", + "description": "Ustvarite glavni ključ s parom novih ključev Nostr." + }, + "new_to_nostr": "Ste novi pri Nostru? <0>Preverite več" }, "title": "Nostr", "public_key": { @@ -436,20 +466,26 @@ "screen_reader": "Generiraj nov Nostr ključ za svoj račun", "title": "Generiraj nov Nostr ključ", "actions": { - "random_keys": "Generiraj naključni ključ" + "random_keys": "Generiraj naključni ključ", + "derived_keys": "Izpelji ključ iz računa" } }, "private_key": { - "label": "Zasebni ključ Nostr" + "label": "Zasebni ključ Nostr", + "title": "Upravljajte svoj zasebni ključ Nostr" }, "actions": { "generate": "Generiraj nov ključ" + }, + "errors": { + "failed_to_load": "Nalaganje ključa Nostr ni uspelo. Je to veljaven ključ Nostr?" } }, "title1": "Nastavitve računa", "remove": { "title": "Odstrani ta račun", - "error": "Vnešeno ime računa se ne ujema." + "error": "Vnešeno ime računa se ne ujema.", + "subtitle": "Odstrani vse odobritve, podatke o plačilih in ključe, povezane s tem računom." }, "remove_secretkey": { "subtitle": "Odstrani Glavni ključ iz tega računa.", @@ -484,7 +520,153 @@ "browser_notifications": { "title": "Obvestila brskalnika" }, - "title": "Nastavitve" + "title": "Nastavitve", + "personal_data": { + "title": "Osebni podatki" + }, + "camera_access": { + "allow": "Omogoči dostop do kamere", + "subtitle": "Za skeniranje QR kod", + "title": "Dostop do kamere", + "granted": "Dovoljenje omogočeno" + }, + "lnurl_auth": { + "legacy_lnurl_auth": { + "title": "Starerjša verzija podpisovanja za LNDhub in LNbits" + }, + "title": "LNURL-Auth", + "legacy_lnurl_auth_202207": { + "title": "Stara verzija LNURL-Auth" + } + }, + "currency": { + "title": "Valuta", + "subtitle": "Zneske dodatno prikaži v tej valuti" + }, + "exchange": { + "subtitle": "Vir menjalnih tečajev bitcoinov", + "title": "Vir tečaja" + }, + "show_fiat": { + "title": "Sats v Fiat", + "subtitle": "Vedno pretvori v izbrano valuto iz izbrane menjalnice" + }, + "language": { + "title": "Jezik", + "subtitle": "Prevodi še niso 100% dokončani. <0>Pomagajte nam prevesti Alby v vaš jezik!" + }, + "theme": { + "title": "Tema", + "options": { + "system": "Sistem", + "dark": "Temna", + "light": "Svetla" + }, + "subtitle": "Uporabi Alby v temnem ali svetlem načinu" + }, + "website_enhancements": { + "subtitle": "Izboljšave napitnin za Twitter, YouTube itd.", + "title": "Izboljšave spletne strani" + } + }, + "webln_enable": { + "title": "Poveži se z WebLN", + "request2": "Zahtevaj račune in Lightning informacije" + }, + "make_invoice": { + "title": "Ustvari račun", + "amount": { + "label": "Znesek (Satoshi)" + }, + "errors": { + "amount_too_small": "Znesek je manjši on minimalne vrednosti", + "amount_too_big": "Znesek je višji od maksimalne vrednosti" + }, + "memo": { + "label": "Beležka" + } + }, + "on_chain": { + "go": "Pojdi na svoj račun Alby →", + "title": "Prejmi preko bitcoin naslova", + "instructions1": "Če želiš prejemati bitcoin prek naslova bitcoin, se prijavi v svoj <0>Alby račun na getalby.com", + "instructions2": "Svoj bitcoin naslov najdete na strani <0>Prejmi." + }, + "confirm_sign_pset": { + "allow_sign": "To spletno mesto zahteva, da podpišeš Liquid transakcijo", + "hide_details": "Skrij podrobnosti", + "view_details": "Poglej podrobnosti", + "title": "Podpiši" + }, + "publishers": { + "description": "Spletna mesta, kjer si že uporabljal Alby", + "discover": "Okdrij spletna mesta", + "publisher": { + "allowance": { + "used_budget": "porabljeni satoshiji", + "title": "Odobritev" + } + }, + "no_info": "Videti je, da Albyja še nisi uporabljal na nobenem spletnem mestu.", + "title": "Tvoja ⚡ spletna stran" + }, + "discover": { + "tips": { + "demo": { + "title": "Alby demo", + "description": "Odkrijte Albyjeve lastnosti\nna naši predstavitveni spletni strani" + }, + "mnemonic": { + "title": "Nostr" + }, + "top_up_wallet": { + "description": "Potrebuješ polnitev?\nSkupaj zmoremo!", + "title": "Kupi Bitcoin" + }, + "description": "Nekaj nasvetov, da začneš brneti🐝", + "title": "Tvoja denarnica Alby je pripravljena" + }, + "list": { + "gaming": "Igre", + "trading": "Trgovanje", + "nostr": "Nostr", + "shopping": "Nakupovanje", + "nodeManagement": "Upravljanje vozlišča", + "showcases": "Primeri", + "miscellaneous": "Razno", + "entertainment": "Zabava" + }, + "description": "Spletna mesta in spletne aplikacije, kjer lahko uporabljaš Alby", + "title": "Razišči ekosistem Lightning ⚡️" + }, + "receive": { + "payment": { + "status": "Preveri status plačila" + } + }, + "confirm_sign_message": { + "title": "Podpiši", + "content": "To spletno mesto te prosi, da podpišeš:" + }, + "liquid_enable": { + "request2": "Pridobi račune in podatke o liquid", + "title": "Poveži se z Liquid" + }, + "nostr_enable": { + "title": "Povežite se z Nostr", + "request2": "Podpiše dogodke s tvojim zasebnim ključem Nostr", + "request1": "Zahteva dostop do javnega ključa Nostr" + }, + "webbtc_enable": { + "request2": "Pridobi račune in podatke o Webbtc", + "title": "Poveži se z WebBTC" + }, + "alby_enable": { + "title": "Poveži se z Albyjem", + "request2": "Zahtevajte račune in podatke o albi" + }, + "scan_qrcode": { + "title": "Čakam na skeniranje" } }, "permissions": { From 76b49c10fee83bbc3129f984b01dfc4b2739939e Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 10 Oct 2023 20:51:54 +0700 Subject: [PATCH 66/73] chore: add extra info about qr Q level error correction --- src/app/components/QRCode/index.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/app/components/QRCode/index.tsx b/src/app/components/QRCode/index.tsx index 607a94f3c0..ff0447617e 100644 --- a/src/app/components/QRCode/index.tsx +++ b/src/app/components/QRCode/index.tsx @@ -7,7 +7,14 @@ export type Props = { className?: string; // set the level to Q if there are overlays - level?: string; + // Q will improve error correction (so we can add overlays covering up to 25% of the QR) + // at the price of decreased information density (meaning the QR codes "pixels" have to be + // smaller to encode the same information). + // While that isn't that much of a problem for lightning addresses (because they are usually quite short), + // for invoices that contain larger amount of data those QR codes can get "harder" to read. + // (meaning you have to aim your phone very precisely and have to wait longer for the reader + // to recognize the QR code) + level?: "Q" | undefined; }; export default function QRCode({ value, size, level, className }: Props) { From 050b44a4c77de7e218f8e358acfcbb2e817bc99e Mon Sep 17 00:00:00 2001 From: Roland Bewick Date: Tue, 10 Oct 2023 21:44:11 +0700 Subject: [PATCH 67/73] chore: bump version to 3.3.0 --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index b6a9dcd822..e9c283f850 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "lightning-browser-extension", - "version": "3.2.1", + "version": "3.3.0", "description": "Lightning browser extension", "private": true, "repository": "https://github.com/bumi/lightning-browser-extension.git", From 4aefd75a1877bbb132c9eee50576482fd1be22bf Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Thu, 12 Oct 2023 16:15:19 +0530 Subject: [PATCH 68/73] feat: add on and off calls in webbtc and liquid provider --- src/extension/content-script/liquid.js | 2 ++ src/extension/content-script/webbtc.js | 2 ++ 2 files changed, 4 insertions(+) diff --git a/src/extension/content-script/liquid.js b/src/extension/content-script/liquid.js index 9bd3c481cb..5fa6ca70da 100644 --- a/src/extension/content-script/liquid.js +++ b/src/extension/content-script/liquid.js @@ -10,6 +10,8 @@ const liquidCalls = [ "liquid/signPsetWithPrompt", "liquid/enable", "liquid/isEnabled", + "liquid/on", + "liquid/off", ]; // calls that can be executed when liquid is not enabled for the current content page const disabledCalls = ["liquid/enable", "liquid/isEnabled"]; diff --git a/src/extension/content-script/webbtc.js b/src/extension/content-script/webbtc.js index 4c507c8015..3646c10287 100644 --- a/src/extension/content-script/webbtc.js +++ b/src/extension/content-script/webbtc.js @@ -10,6 +10,8 @@ const webbtcCalls = [ "webbtc/signPsbtWithPrompt", "webbtc/getAddressOrPrompt", "webbtc/isEnabled", + "webbtc/on", + "webbtc/off", ]; // calls that can be executed when `window.webbtc` is not enabled for the current content page const disabledCalls = ["webbtc/enable", "webbtc/isEnabled"]; From 62ae77c52a65ff1c3b67701d95422c8a14096bf7 Mon Sep 17 00:00:00 2001 From: Lukas Jakob Date: Mon, 16 Oct 2023 13:48:49 +0200 Subject: [PATCH 69/73] fix: receive another payment button styles (#2820) --- src/app/screens/ReceiveInvoice/index.tsx | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/src/app/screens/ReceiveInvoice/index.tsx b/src/app/screens/ReceiveInvoice/index.tsx index aefab6c61c..02242e8e2c 100644 --- a/src/app/screens/ReceiveInvoice/index.tsx +++ b/src/app/screens/ReceiveInvoice/index.tsx @@ -228,7 +228,9 @@ function ReceiveInvoice() { {t("title")} {invoice ? ( - {renderInvoice()} + + {renderInvoice()} + ) : (
From ffc16ccd5f625cbca5cb8d2439d73eba015ff34e Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Fri, 20 Oct 2023 12:37:37 +0530 Subject: [PATCH 70/73] feat: use new info circle icon from bitcoin design icons --- package.json | 2 +- src/app/components/onboard/index.tsx | 2 +- src/app/icons/InfoCircleIcon.tsx | 21 --------------------- yarn.lock | 8 ++++---- 4 files changed, 6 insertions(+), 27 deletions(-) delete mode 100644 src/app/icons/InfoCircleIcon.tsx diff --git a/package.json b/package.json index e9c283f850..b02c3d7ab0 100644 --- a/package.json +++ b/package.json @@ -35,7 +35,7 @@ "prepare": "husky install" }, "dependencies": { - "@bitcoin-design/bitcoin-icons-react": "^0.1.9", + "@bitcoin-design/bitcoin-icons-react": "^0.1.10", "@bitcoinerlab/secp256k1": "^1.0.5", "@getalby/sdk": "^2.2.3", "@headlessui/react": "^1.7.16", diff --git a/src/app/components/onboard/index.tsx b/src/app/components/onboard/index.tsx index 152b26f410..d5dfb14dc3 100644 --- a/src/app/components/onboard/index.tsx +++ b/src/app/components/onboard/index.tsx @@ -1,5 +1,6 @@ import { ClockIcon, + InfoCircleIcon, TwoKeysIcon, } from "@bitcoin-design/bitcoin-icons-react/outline"; import { useTranslation } from "react-i18next"; @@ -9,7 +10,6 @@ import PublisherCard from "~/app/components/PublisherCard"; import ScreenHeader from "~/app/components/ScreenHeader"; import { useAccount } from "~/app/context/AccountContext"; import { useNavigationState } from "~/app/hooks/useNavigationState"; -import InfoCircleIcon from "~/app/icons/InfoCircleIcon"; import { NO_KEYS_ERROR, USER_REJECTED_ERROR } from "~/common/constants"; import api from "~/common/lib/api"; import msg from "~/common/lib/msg"; diff --git a/src/app/icons/InfoCircleIcon.tsx b/src/app/icons/InfoCircleIcon.tsx deleted file mode 100644 index 2c99d4b941..0000000000 --- a/src/app/icons/InfoCircleIcon.tsx +++ /dev/null @@ -1,21 +0,0 @@ -import { SVGProps } from "react"; - -export default function InfoCircleIcon(props: SVGProps) { - return ( - - - - - - ); -} diff --git a/yarn.lock b/yarn.lock index 300c99a960..65d8d8878e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -354,10 +354,10 @@ resolved "https://registry.npmjs.org/@bcoe/v8-coverage/-/v8-coverage-0.2.3.tgz" integrity sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw== -"@bitcoin-design/bitcoin-icons-react@^0.1.9": - version "0.1.9" - resolved "https://registry.npmjs.org/@bitcoin-design/bitcoin-icons-react/-/bitcoin-icons-react-0.1.9.tgz" - integrity sha512-nJvTD1+zG/ffHdMeGQ39vdsmEFo9WcCIP1RlR7ZpZoP2H+IgKwzwow8VSY6ebroLoCT7WWtUPJQSbgQwgWYrFg== +"@bitcoin-design/bitcoin-icons-react@^0.1.10": + version "0.1.10" + resolved "https://registry.yarnpkg.com/@bitcoin-design/bitcoin-icons-react/-/bitcoin-icons-react-0.1.10.tgz#11acf1d386c1094d3eff673ca236345860762f64" + integrity sha512-f7GSutKHa4EK4LWI/phnGCJsN8fzFbVAVQ4F1MYxiza34LVmXmbgHUmdP/BR8ZeQSIbZLt19inpJZDBtQvYe4Q== "@bitcoinerlab/secp256k1@^1.0.5": version "1.0.5" From 81887a2b4ea65a7103e36c54e05af8537a84a7e0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ren=C3=A9=20Aaron?= Date: Thu, 12 Oct 2023 16:08:59 +0200 Subject: [PATCH 71/73] fix: remove dynamic classes from badges --- src/app/components/Badge/index.tsx | 13 +++++++------ src/app/components/BadgesList/index.tsx | 15 +++------------ src/app/components/TransactionsTable/index.tsx | 14 -------------- src/app/screens/Accounts/Detail/index.tsx | 3 +-- src/types.ts | 4 +--- 5 files changed, 12 insertions(+), 37 deletions(-) diff --git a/src/app/components/Badge/index.tsx b/src/app/components/Badge/index.tsx index be8125b1cd..e9e977db66 100644 --- a/src/app/components/Badge/index.tsx +++ b/src/app/components/Badge/index.tsx @@ -1,22 +1,23 @@ import { useTranslation } from "react-i18next"; +import { classNames } from "~/app/utils"; type Props = { label: "budget" | "auth" | "imported"; - color: string; - textColor: string; + className: string; small?: boolean; }; -export default function Badge({ label, color, textColor, small }: Props) { +export default function Badge({ label, className, small }: Props) { const { t: tComponents } = useTranslation("components", { keyPrefix: "badge", }); return ( {tComponents(`label.${label}`)} diff --git a/src/app/components/BadgesList/index.tsx b/src/app/components/BadgesList/index.tsx index f91d696bb2..c489894a89 100644 --- a/src/app/components/BadgesList/index.tsx +++ b/src/app/components/BadgesList/index.tsx @@ -10,29 +10,20 @@ export default function BadgesList({ allowance }: Props) { if (allowance.remainingBudget > 0) { badges.push({ label: "budget", - color: "green-bitcoin", - textColor: "white", + className: "bg-green-bitcoin text-white", }); } if (allowance.lnurlAuth) { badges.push({ label: "auth", - color: "green-bitcoin", - textColor: "white", + className: "bg-green-bitcoin text-white", }); } return ( <> {badges?.map((b) => { - return ( - - ); + return ; })} ); diff --git a/src/app/components/TransactionsTable/index.tsx b/src/app/components/TransactionsTable/index.tsx index ab155106d3..af143e6a92 100644 --- a/src/app/components/TransactionsTable/index.tsx +++ b/src/app/components/TransactionsTable/index.tsx @@ -6,8 +6,6 @@ import Button from "~/app/components/Button"; import { useSettings } from "~/app/context/SettingsContext"; import { Transaction } from "~/types"; -import Badge from "../Badge"; - export type Props = { transactions: Transaction[] | null | undefined; loading?: boolean; @@ -60,18 +58,6 @@ export default function TransactionsTable({ {tx.date}

- {tx.badges && ( -
- {tx.badges.map((badge) => ( - - ))} -
- )}

diff --git a/src/app/screens/Accounts/Detail/index.tsx b/src/app/screens/Accounts/Detail/index.tsx index dc3fc7b193..54ad2b3a40 100644 --- a/src/app/screens/Accounts/Detail/index.tsx +++ b/src/app/screens/Accounts/Detail/index.tsx @@ -404,8 +404,7 @@ function AccountDetail() {

)} diff --git a/src/types.ts b/src/types.ts index 189c7d271c..1bdb51b022 100644 --- a/src/types.ts +++ b/src/types.ts @@ -709,7 +709,6 @@ export interface RequestInvoiceArgs { export type Transaction = { amount?: string; boostagram?: Invoice["boostagram"]; - badges?: Badge[]; createdAt?: string; currency?: string; date: string; @@ -860,8 +859,7 @@ export interface SettingsStorage { export interface Badge { label: "budget" | "auth" | "imported"; - color: string; - textColor: string; + className: string; } export interface Publisher From 70210cfd7f45796d3820e037ff18aefa8220fedf Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Mon, 23 Oct 2023 17:44:45 +0530 Subject: [PATCH 72/73] chore: change color of budget badge --- src/app/components/BadgesList/index.tsx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/app/components/BadgesList/index.tsx b/src/app/components/BadgesList/index.tsx index c489894a89..524121449d 100644 --- a/src/app/components/BadgesList/index.tsx +++ b/src/app/components/BadgesList/index.tsx @@ -10,7 +10,7 @@ export default function BadgesList({ allowance }: Props) { if (allowance.remainingBudget > 0) { badges.push({ label: "budget", - className: "bg-green-bitcoin text-white", + className: "bg-blue-500 text-white", }); } if (allowance.lnurlAuth) { From 47edecf91ffadd710eb236dab58183c0149e85df Mon Sep 17 00:00:00 2001 From: pavanjoshi914 Date: Mon, 23 Oct 2023 17:52:28 +0530 Subject: [PATCH 73/73] fix: we don't use small prop anymore to set text size --- src/app/components/Badge/index.tsx | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/app/components/Badge/index.tsx b/src/app/components/Badge/index.tsx index e9e977db66..2fe880828a 100644 --- a/src/app/components/Badge/index.tsx +++ b/src/app/components/Badge/index.tsx @@ -4,10 +4,9 @@ import { classNames } from "~/app/utils"; type Props = { label: "budget" | "auth" | "imported"; className: string; - small?: boolean; }; -export default function Badge({ label, className, small }: Props) { +export default function Badge({ label, className }: Props) { const { t: tComponents } = useTranslation("components", { keyPrefix: "badge", });