From 6a1305779482157f49048d21feb30850dcf93ca8 Mon Sep 17 00:00:00 2001 From: jackstar12 Date: Sun, 3 Nov 2024 14:26:58 +0100 Subject: [PATCH] wip --- src/components/CreateButton.tsx | 4 +- src/components/HardwareDerivationPaths.tsx | 4 +- src/components/InvoiceInput.tsx | 6 +- src/components/LockupEvm.tsx | 4 +- src/consts/Types.ts | 3 +- src/context/Web3.tsx | 25 +++--- src/lazy/Loader.ts | 4 +- src/lazy/bolt12.ts | 4 +- src/lazy/ledger.ts | 9 ++- src/lazy/trezor.ts | 9 ++- src/utils/boltzClient.ts | 5 +- src/utils/hardware/LedgerSigner.ts | 62 ++++++++------- src/utils/hardware/TrezorSigner.ts | 32 ++++---- src/utils/invoice.ts | 93 +++++++++++----------- 14 files changed, 141 insertions(+), 123 deletions(-) diff --git a/src/components/CreateButton.tsx b/src/components/CreateButton.tsx index 9195a9c8..6b53757e 100644 --- a/src/components/CreateButton.tsx +++ b/src/components/CreateButton.tsx @@ -330,8 +330,8 @@ export const CreateButton = () => { signer() !== undefined && customDerivationPathRdns.includes(signer().rdns) ? ( - providers()[signer().rdns] - .provider as unknown as HardwareSigner + await providers()[signer().rdns] + .provider.get() as unknown as HardwareSigner ).getDerivationPath() : undefined, }); diff --git a/src/components/HardwareDerivationPaths.tsx b/src/components/HardwareDerivationPaths.tsx index 16140994..f5a20aaa 100644 --- a/src/components/HardwareDerivationPaths.tsx +++ b/src/components/HardwareDerivationPaths.tsx @@ -34,8 +34,8 @@ export const connect = async ( ) => { try { if (derivationPath !== undefined) { - const prov = providers()[provider.rdns] - .provider as unknown as HardwareSigner; + const prov = await providers()[provider.rdns] + .provider.get() as unknown as HardwareSigner; prov.setDerivationPath(derivationPath); } diff --git a/src/components/InvoiceInput.tsx b/src/components/InvoiceInput.tsx index ff2023b4..35fe0d88 100644 --- a/src/components/InvoiceInput.tsx +++ b/src/components/InvoiceInput.tsx @@ -11,7 +11,7 @@ import { decodeInvoice, extractAddress, extractInvoice, - isBolt12Offer, + Bolt12, isLnurl, } from "../utils/invoice"; import { validateInvoice } from "../utils/validation"; @@ -58,13 +58,15 @@ const InvoiceInput = () => { const inputValue = extractInvoice(val); + const bolt12 = await Bolt12.get(); + try { input.setCustomValidity(""); setInvoiceError(undefined); input.classList.remove("invalid"); if (isLnurl(inputValue)) { setLnurl(inputValue); - } else if (await isBolt12Offer(inputValue)) { + } else if (bolt12.isOffer(inputValue)) { setBolt12Offer(inputValue); } else { const sats = await validateInvoice(inputValue); diff --git a/src/components/LockupEvm.tsx b/src/components/LockupEvm.tsx index 90c71cb3..bfba1758 100644 --- a/src/components/LockupEvm.tsx +++ b/src/components/LockupEvm.tsx @@ -70,8 +70,8 @@ const LockupEvm = (props: { if (customDerivationPathRdns.includes(signer().rdns)) { currentSwap.derivationPath = ( - providers()[signer().rdns] - .provider as unknown as HardwareSigner + await providers()[signer().rdns] + .provider.get() as unknown as HardwareSigner ).getDerivationPath(); } diff --git a/src/consts/Types.ts b/src/consts/Types.ts index 47e979b3..d9eaa2c3 100644 --- a/src/consts/Types.ts +++ b/src/consts/Types.ts @@ -1,4 +1,5 @@ import type { DictKey } from "../i18n/i18n"; +import Loader from "../lazy/Loader"; export type ButtonLabelParams = { key: DictKey; @@ -36,5 +37,5 @@ export type EIP1193Provider = { export type EIP6963ProviderDetail = { info: EIP6963ProviderInfo; - provider: EIP1193Provider; + provider: Loader; }; diff --git a/src/context/Web3.tsx b/src/context/Web3.tsx index 11a4341f..02373234 100644 --- a/src/context/Web3.tsx +++ b/src/context/Web3.tsx @@ -23,6 +23,7 @@ import TrezorSigner from "../utils/hardware/TrezorSigner"; import { useGlobalContext } from "./Global"; import LedgerIcon from "/ledger.svg"; import TrezorIcon from "/trezor.svg"; +import Loader from "../lazy/Loader"; declare global { interface WindowEventMap { @@ -86,7 +87,7 @@ const Web3SignerProvider = (props: { Record >({ [HardwareRdns.Ledger]: { - provider: new LedgerSigner(t), + provider: LedgerSigner.loader(t), info: { name: "Ledger", uuid: "ledger", @@ -97,7 +98,7 @@ const Web3SignerProvider = (props: { }, }, [HardwareRdns.Trezor]: { - provider: new TrezorSigner(), + provider: TrezorSigner.loader, info: { name: "Trezor", uuid: "trezor", @@ -117,7 +118,7 @@ const Web3SignerProvider = (props: { setProviders({ ...providers(), [browserRdns]: { - provider: window.ethereum, + provider: new Loader("Ethereum", async () => window.ethereum), info: { name: "Browser native", uuid: browserRdns, @@ -141,7 +142,10 @@ const Web3SignerProvider = (props: { setProviders({ ...existingProviders, - [event.detail.info.rdns]: event.detail, + [event.detail.info.rdns]: { + provider: new Loader("EIP6963", async () => event.detail.provider), + info: event.detail.info, + }, }); }, ); @@ -175,8 +179,8 @@ const Web3SignerProvider = (props: { `Setting derivation path (${derivationPath}) for signer:`, rdns, ); - const prov = providers()[rdns] - .provider as unknown as HardwareSigner; + const prov = await providers()[rdns] + .provider.get() as unknown as HardwareSigner; prov.setDerivationPath(derivationPath); } @@ -188,9 +192,10 @@ const Web3SignerProvider = (props: { if (wallet == undefined) { throw "wallet not found"; } + const provider = await wallet.provider.get() log.debug(`Using wallet ${wallet.info.rdns}: ${wallet.info.name}`); - const addresses = (await wallet.provider.request({ + const addresses = (await provider.request({ method: "eth_requestAccounts", })) as string[]; if (addresses.length === 0) { @@ -199,13 +204,13 @@ const Web3SignerProvider = (props: { log.info(`Connected address from ${wallet.info.rdns}: ${addresses[0]}`); - wallet.provider.on("chainChanged", () => { + provider.on("chainChanged", () => { window.location.reload(); }); - setRawProvider(wallet.provider); + setRawProvider(provider); const signer = new JsonRpcSigner( - new BrowserProvider(wallet.provider), + new BrowserProvider(provider), addresses[0], ) as unknown as Signer; signer.rdns = wallet.info.rdns; diff --git a/src/lazy/Loader.ts b/src/lazy/Loader.ts index 6fc5d87a..079df70b 100644 --- a/src/lazy/Loader.ts +++ b/src/lazy/Loader.ts @@ -1,5 +1,7 @@ import log from "loglevel"; +export type LoadType = Awaited> + class Loader { private modules?: T; @@ -10,7 +12,7 @@ class Loader { public get = async (): Promise => { if (this.modules === undefined) { - log.info(`Loading ${this.name} modules`); + log.info(`Loading ${this.name}`); this.modules = await this.initializer(); } diff --git a/src/lazy/bolt12.ts b/src/lazy/bolt12.ts index a57ca28a..1bb2b72e 100644 --- a/src/lazy/bolt12.ts +++ b/src/lazy/bolt12.ts @@ -1,3 +1 @@ -import Loader from "./Loader"; - -export default new Loader("BOLT12", async () => await import("boltz-bolt12")); +export const load = () => import("boltz-bolt12"); diff --git a/src/lazy/ledger.ts b/src/lazy/ledger.ts index 4a0b2316..f3679c13 100644 --- a/src/lazy/ledger.ts +++ b/src/lazy/ledger.ts @@ -1,8 +1,7 @@ import type Transport from "@ledgerhq/hw-transport"; +import { LoadType } from "./Loader"; -import Loader from "./Loader"; - -export default new Loader("Ledger", async () => { +export const load = async () => { const [eth, webhid] = await Promise.all([ import("@ledgerhq/hw-app-eth"), import("@ledgerhq/hw-transport-webhid"), @@ -12,6 +11,8 @@ export default new Loader("Ledger", async () => { eth: eth.default, webhid: webhid.default, }; -}); +} + +export type LedgerModules = LoadType; export { Transport }; diff --git a/src/lazy/trezor.ts b/src/lazy/trezor.ts index 31c51674..0ad83514 100644 --- a/src/lazy/trezor.ts +++ b/src/lazy/trezor.ts @@ -3,11 +3,12 @@ import type { Response, SuccessWithDevice, } from "@trezor/connect/lib/types/params"; +import { LoadType } from "./Loader"; -import Loader from "./Loader"; - -export default new Loader("Trezor", async () => { +export const load = async () => { return (await import("@trezor/connect-web")).default; -}); +} + +export type TrezorConnect = LoadType; export { Address, Unsuccessful, Response, SuccessWithDevice }; diff --git a/src/utils/boltzClient.ts b/src/utils/boltzClient.ts index 1b0d1b8e..9d67ce0c 100644 --- a/src/utils/boltzClient.ts +++ b/src/utils/boltzClient.ts @@ -6,7 +6,7 @@ import { Transaction as LiquidTransaction } from "liquidjs-lib"; import { config } from "../config"; import { SwapType } from "../consts/Enums"; import { fetcher } from "./helper"; -import { validateInvoiceForOffer } from "./invoice"; +import { Bolt12 } from "./invoice"; const cooperativeErrorMessage = "cooperative signatures for swaps are disabled"; const checkCooperative = () => { @@ -190,7 +190,8 @@ export const fetchBolt12Invoice = async ( amount: amountSat, }, ); - await validateInvoiceForOffer(offer, res.invoice); + const bolt12 = await Bolt12.get(); + bolt12.validateInvoiceForOffer(offer, res.invoice); return res; }; diff --git a/src/utils/hardware/LedgerSigner.ts b/src/utils/hardware/LedgerSigner.ts index 107caff5..69ee72ec 100644 --- a/src/utils/hardware/LedgerSigner.ts +++ b/src/utils/hardware/LedgerSigner.ts @@ -3,36 +3,40 @@ import { Signature, Transaction, TransactionLike, - TypedDataEncoder, + TypedDataEncoder } from "ethers"; import log from "loglevel"; import { config } from "../../config"; import { EIP1193Provider } from "../../consts/Types"; import type { DictKey } from "../../i18n/i18n"; -import ledgerLoader, { Transport } from "../../lazy/ledger"; +import { LedgerModules, load, Transport } from "../../lazy/ledger"; import { HardwareSigner, derivationPaths } from "./HadwareSigner"; +import Loader from "../../lazy/Loader"; class LedgerSigner implements EIP1193Provider, HardwareSigner { private static readonly supportedApps = ["Ethereum", "RSK", "RSK Test"]; private readonly provider: JsonRpcProvider; - private readonly loader: typeof ledgerLoader; private transport?: Transport; private derivationPath = derivationPaths.Ethereum; - constructor( + public static loader = (t: (key: DictKey, values?: Record) => string) => + new Loader("LedgerSigner", async () => + new LedgerSigner(await load(), t) + ); + + public constructor( + private readonly modules: LedgerModules, private readonly t: ( key: DictKey, - values?: Record, - ) => string, + values?: Record + ) => string ) { this.provider = new JsonRpcProvider( - config.assets["RBTC"]?.network?.rpcUrls[0], + config.assets["RBTC"]?.network?.rpcUrls[0] ); - - this.loader = ledgerLoader; } public getDerivationPath = () => { @@ -51,24 +55,22 @@ class LedgerSigner implements EIP1193Provider, HardwareSigner { case "eth_requestAccounts": { log.debug("Getting Ledger accounts"); - const modules = await this.loader.get(); - if (this.transport === undefined) { - this.transport = await modules.webhid.create(); + this.transport = this.modules.webhid.create(); } const openApp = (await this.getApp()).name; log.debug(`Ledger has app open: ${openApp}`); if (!LedgerSigner.supportedApps.includes(openApp)) { log.warn( - `Open Ledger app ${openApp} not in supported: ${LedgerSigner.supportedApps.join(", ")}`, + `Open Ledger app ${openApp} not in supported: ${LedgerSigner.supportedApps.join(", ")}` ); await this.transport.close(); this.transport = undefined; throw this.t("ledger_open_app_prompt"); } - const eth = new modules.eth(this.transport); + const eth = new this.modules.eth(this.transport); const { address } = await eth.getAddress(this.derivationPath); return [address.toLowerCase()]; @@ -82,7 +84,7 @@ class LedgerSigner implements EIP1193Provider, HardwareSigner { const [nonce, network, feeData] = await Promise.all([ this.provider.getTransactionCount(txParams.from), this.provider.getNetwork(), - this.provider.getFeeData(), + this.provider.getFeeData() ]); const tx = Transaction.from({ @@ -92,20 +94,19 @@ class LedgerSigner implements EIP1193Provider, HardwareSigner { from: undefined, chainId: network.chainId, gasPrice: feeData.gasPrice, - gasLimit: (txParams as unknown as { gas: number }).gas, + gasLimit: (txParams as unknown as { gas: number }).gas }); - const modules = await this.loader.get(); - const eth = new modules.eth(this.transport); + const eth = new this.modules.eth(this.transport); const signature = await eth.clearSignTransaction( this.derivationPath, tx.unsignedSerialized.substring(2), - {}, + {} ); tx.signature = this.serializeSignature(signature); await this.provider.send("eth_sendRawTransaction", [ - tx.serialized, + tx.serialized ]); return tx.hash; @@ -114,14 +115,13 @@ class LedgerSigner implements EIP1193Provider, HardwareSigner { case "eth_signTypedData_v4": { log.debug("Signing EIP-712 message with Ledger"); - const modules = await this.loader.get(); - const eth = new modules.eth(this.transport); + const eth = new this.modules.eth(this.transport); const message = JSON.parse(request.params[1] as string); try { const signature = await eth.signEIP712Message( this.derivationPath, - message, + message ); return this.serializeSignature(signature); } catch (e) { @@ -137,8 +137,8 @@ class LedgerSigner implements EIP1193Provider, HardwareSigner { TypedDataEncoder.hashStruct( message.primaryType, types, - message.message, - ), + message.message + ) ); return this.serializeSignature(signature); } @@ -147,13 +147,15 @@ class LedgerSigner implements EIP1193Provider, HardwareSigner { return (await this.provider.send( request.method, - request.params, + request.params )) as never; }; - public on = () => {}; + public on = () => { + }; - public removeAllListeners = () => {}; + public removeAllListeners = () => { + }; private getApp = async (): Promise<{ name: string; @@ -177,7 +179,7 @@ class LedgerSigner implements EIP1193Provider, HardwareSigner { return { name, version, - flags, + flags }; }; @@ -189,7 +191,7 @@ class LedgerSigner implements EIP1193Provider, HardwareSigner { Signature.from({ v: signature.v, r: BigInt(`0x${signature.r}`).toString(), - s: BigInt(`0x${signature.s}`).toString(), + s: BigInt(`0x${signature.s}`).toString() }).serialized; } diff --git a/src/utils/hardware/TrezorSigner.ts b/src/utils/hardware/TrezorSigner.ts index 45fcad78..f3747f9e 100644 --- a/src/utils/hardware/TrezorSigner.ts +++ b/src/utils/hardware/TrezorSigner.ts @@ -9,27 +9,33 @@ import log from "loglevel"; import { config } from "../../config"; import { EIP1193Provider } from "../../consts/Types"; -import trezorLoader, { +import type { Address, Response, - SuccessWithDevice, - Unsuccessful, + SuccessWithDevice, TrezorConnect, + Unsuccessful } from "../../lazy/trezor"; import { HardwareSigner, derivationPaths } from "./HadwareSigner"; +import Loader from "../../lazy/Loader"; +import { load } from "../../lazy/trezor"; class TrezorSigner implements EIP1193Provider, HardwareSigner { private readonly provider: JsonRpcProvider; - private readonly loader: typeof trezorLoader; private initialized = false; private derivationPath!: string; - constructor() { + public static loader = new Loader("TrezorSigner", async () => { + return new TrezorSigner(await load()); + }) + + constructor( + private readonly modules: TrezorConnect, + ) { this.provider = new JsonRpcProvider( config.assets["RBTC"]?.network?.rpcUrls[0], ); this.setDerivationPath(derivationPaths.Ethereum); - this.loader = trezorLoader; } public getDerivationPath = () => { @@ -50,9 +56,8 @@ class TrezorSigner implements EIP1193Provider, HardwareSigner { await this.initialize(); - const connect = await this.loader.get(); const addresses = this.handleError
( - await connect.ethereumGetAddress({ + await this.modules.ethereumGetAddress({ showOnTrezor: false, path: this.derivationPath, } as never), @@ -68,8 +73,7 @@ class TrezorSigner implements EIP1193Provider, HardwareSigner { const txParams = request.params[0] as TransactionLike; - const [connect, nonce, network, feeData] = await Promise.all([ - this.loader.get(), + const [nonce, network, feeData] = await Promise.all([ this.provider.getTransactionCount(txParams.from), this.provider.getNetwork(), this.provider.getFeeData(), @@ -86,7 +90,7 @@ class TrezorSigner implements EIP1193Provider, HardwareSigner { }; const signature = this.handleError( - await connect.ethereumSignTransaction({ + await this.modules.ethereumSignTransaction({ transaction: trezorTx, path: this.derivationPath, } as unknown as never), @@ -119,9 +123,8 @@ class TrezorSigner implements EIP1193Provider, HardwareSigner { }; delete types["EIP712Domain"]; - const connect = await this.loader.get(); const signature = this.handleError( - await connect.ethereumSignTypedData({ + await this.modules.ethereumSignTypedData({ data: message, metamask_v4_compat: true, path: this.derivationPath, @@ -155,8 +158,7 @@ class TrezorSigner implements EIP1193Provider, HardwareSigner { } try { - const connect = await this.loader.get(); - await connect.init({ + await this.modules.init({ lazyLoad: true, manifest: { email: "hi@bol.tz", diff --git a/src/utils/invoice.ts b/src/utils/invoice.ts index 96419068..9f231b77 100644 --- a/src/utils/invoice.ts +++ b/src/utils/invoice.ts @@ -4,10 +4,11 @@ import bolt11 from "bolt11"; import log from "loglevel"; import { config } from "../config"; -import Bolt12 from "../lazy/bolt12"; +import Bolt12, { load } from "../lazy/bolt12"; import { fetchBolt12Invoice } from "./boltzClient"; import { lookup } from "./dnssec/dohLookup"; import { checkResponse } from "./http"; +import Loader from "../lazy/Loader"; type LnurlResponse = { minSendable: number; @@ -246,59 +247,61 @@ export const isLnurl = (data: string) => { ); }; -export const isBolt12Offer = async (offer: string) => { - try { - const { Offer } = await Bolt12.get(); - new Offer(offer); - return true; - - // eslint-disable-next-line @typescript-eslint/no-unused-vars - } catch (e) { - return false; - } -}; +export const Bolt12 = new Loader("Bolt12", async () => { + const { Offer, Invoice } = await load(); + return { + isOffer: (offer: string) => { + try { + new Offer(offer); + return true; -export const validateInvoiceForOffer = async ( - offer: string, - invoice: string, -) => { - const { Offer, Invoice } = await Bolt12.get(); - const of = new Offer(offer); - const possibleSigners: Uint8Array[] = []; + // eslint-disable-next-line @typescript-eslint/no-unused-vars + } catch (e) { + return false; + } + }, + validateInvoiceForOffer: ( + offer: string, + invoice: string, + ) => { + const of = new Offer(offer); + const possibleSigners: Uint8Array[] = []; + + if (of.signing_pubkey !== undefined) { + possibleSigners.push(of.signing_pubkey); + } - if (of.signing_pubkey !== undefined) { - possibleSigners.push(of.signing_pubkey); - } + for (const path of of.paths) { + const hops = path.hops; + if (hops.length > 0) { + possibleSigners.push(hops[hops.length - 1].pubkey); + } - for (const path of of.paths) { - const hops = path.hops; - if (hops.length > 0) { - possibleSigners.push(hops[hops.length - 1].pubkey); - } + hops.forEach((hop) => hop.free()); + path.free(); + } - hops.forEach((hop) => hop.free()); - path.free(); - } + of.free(); - of.free(); + const inv = new Invoice(invoice); - const inv = new Invoice(invoice); + try { + const invoiceSigner = inv.signing_pubkey; - try { - const invoiceSigner = inv.signing_pubkey; + for (const signer of possibleSigners) { + if (signer.length !== invoiceSigner.length) { + continue; + } - for (const signer of possibleSigners) { - if (signer.length !== invoiceSigner.length) { - continue; + if (signer.every((b, i) => b === invoiceSigner[i])) { + return true; + } + } + } finally { + inv.free(); } - if (signer.every((b, i) => b === invoiceSigner[i])) { - return true; - } + throw "invoice does not belong to offer"; } - } finally { - inv.free(); } - - throw "invoice does not belong to offer"; -}; +})