Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
},
"dependencies": {
"@bitcoinerlab/secp256k1": "^1.1.1",
"@buildonspark/spark-sdk": "^0.3.3",
"@getalby/sdk": "^6.0.0",
"@headlessui/react": "^1.7.18",
"@lightninglabs/lnc-web": "^0.3.1-alpha",
Expand All @@ -53,13 +54,13 @@
"bitcoinjs-lib": "^6.1.7",
"bolt11-signet": "1.4.1",
"crypto-js": "^4.2.0",
"dexie": "^3.2.7",
"dayjs": "^1.11.13",
"dexie": "^3.2.7",
"elliptic": "^6.6.1",
"events": "^3.3.0",
"html5-qrcode": "^2.3.8",
"i18next-browser-languagedetector": "^8.1.0",
"i18next": "^23.11.5",
"i18next-browser-languagedetector": "^8.1.0",
"liquidjs-lib": "^6.0.2-liquid.29",
"lnmessage": "^0.2.7",
"lodash.merge": "^4.6.2",
Expand Down
4 changes: 2 additions & 2 deletions src/app/components/ConnectorForm/index.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ type Props = {
submitLoading?: boolean;
submitDisabled?: boolean;
onSubmit: FormEventHandler;
children: React.ReactNode;
children?: React.ReactNode;
video?: string;
image?: string;
logo?: string;
Expand Down Expand Up @@ -80,7 +80,7 @@ function ConnectorForm({
</div>
)}
{video || (image && media)}
<div>{children}</div>
{children && <div>{children}</div>}
<div className="mt-4 flex justify-center">
<Button
type="submit"
Expand Down
11 changes: 10 additions & 1 deletion src/app/router/connectorRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,8 +15,9 @@ import ConnectUmbrel from "@screens/connectors/ConnectUmbrel";
import { Route } from "react-router-dom";
import i18n from "~/i18n/i18nConfig";

import ConnectAlbyHub from "~/app/screens/connectors/ConnectAlbyHub";
import { default as ConnectAlbyHub } from "~/app/screens/connectors/ConnectAlbyHub";
import ConnectNWC from "~/app/screens/connectors/ConnectNWC";
import ConnectSpark from "~/app/screens/connectors/ConnectSpark";
import ConnectVoltage from "~/app/screens/connectors/ConnectVoltage";
import ConnectCommando from "../screens/connectors/ConnectCommando";
import albyhub from "/static/assets/icons/albyhub.png";
Expand All @@ -36,6 +37,7 @@ import mynode from "/static/assets/icons/mynode.png";
import nirvati from "/static/assets/icons/nirvati.svg";
import nwc from "/static/assets/icons/nwc.svg";
import raspiblitz from "/static/assets/icons/raspiblitz.png";
import spark from "/static/assets/icons/spark.png";
import startos from "/static/assets/icons/startos.png";
import umbrel from "/static/assets/icons/umbrel.png";
import voltage from "/static/assets/icons/voltage.png";
Expand Down Expand Up @@ -176,6 +178,12 @@ const connectorMap: { [key: string]: ConnectorRoute } = {
title: i18n.t("translation:choose_connector.albyhub.title"),
logo: albyhub,
},
spark: {
path: "spark",
element: <ConnectSpark />,
title: i18n.t("translation:choose_connector.spark.title"),
logo: spark,
},
lawallet: {
path: "lawallet",
element: <ConnectLaWallet />,
Expand Down Expand Up @@ -256,6 +264,7 @@ const distributionMap: { [key: string]: { logo: string; children: Route[] } } =
function getConnectorRoutes(): ConnectorRoute[] {
return [
connectorMap["albyhub"],
connectorMap["spark"],
connectorMap["lnd"],
connectorMap["lnc"],
connectorMap["commando"],
Expand Down
117 changes: 117 additions & 0 deletions src/app/screens/connectors/ConnectSpark/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import ConnectorForm from "@components/ConnectorForm";
import ConnectionErrorToast from "@components/toasts/ConnectionErrorToast";
import { useState } from "react";
import { Trans, useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import toast from "~/app/components/Toast";
import msg from "~/common/lib/msg";

import Alert from "~/app/components/Alert";
import api from "~/common/lib/api";
import logo from "/static/assets/icons/spark.png";

export default function ConnectSpark() {
const navigate = useNavigate();
const { t } = useTranslation("translation", {
keyPrefix: "choose_connector.spark",
});
const [loading, setLoading] = useState(false);

function getConnectorType() {
return "spark";
}

async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
setLoading(true);

const mnemonic = await api.generateMnemonic();
const encryptedMnemonic = await api.encryptValue(mnemonic);

const nostrPrivateKey = await api.nostr.generatePrivateKey(
undefined,
mnemonic
);

const encryptedNostrPrivateKey = await api.encryptValue(nostrPrivateKey);
const account = {
name: "Spark",
config: {},
connector: getConnectorType(),
mnemonic: encryptedMnemonic,
useMnemonicForLnurlAuth: true,
nostrPrivateKey: encryptedNostrPrivateKey,
hasImportedNostrKey: false,
};

try {
const validation = await msg.request("validateAccount", account);
if (validation.valid) {
const addResult = await msg.request("addAccount", account);
if (addResult.accountId) {
await msg.request("selectAccount", {
id: addResult.accountId,
});
navigate("/test-connection");
}
} else {
console.error(validation);
toast.error(
<ConnectionErrorToast message={validation.error as string} />
);
}
} catch (e) {
console.error(e);
let message = t("page.errors.connection_failed");
if (e instanceof Error) {
message += `\n\n${e.message}`;
}
toast.error(message);
}
setLoading(false);
}

return (
<ConnectorForm
title={
<h1 className="text-2xl font-bold dark:text-white">
<Trans i18nKey={"title"} t={t} />
</h1>
}
description={
<Trans
i18nKey={"page.instructions"}
t={t}
components={[
// eslint-disable-next-line react/jsx-key
<a
target="_blank"
rel="noreferrer"
className="underline"
href="https://www.spark.money/"
></a>,
]}
/>
}
logo={logo}
submitLoading={loading}
onSubmit={handleSubmit}
>
<Alert type="warn">
<Trans
i18nKey={"page.warning"}
t={t}
components={[
// eslint-disable-next-line react/jsx-key
<a
target="_blank"
rel="noreferrer"
className="underline"
href="https://docs.spark.money/wallet/developer-guide/unilateral-exit"
></a>,
]}
/>
</Alert>
</ConnectorForm>
);
}
12 changes: 11 additions & 1 deletion src/common/lib/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -235,9 +235,13 @@ const getNostrPublicKey = (id: string): Promise<string> =>
id,
});

const generateNostrPrivateKey = (id: string): Promise<string> =>
const generateNostrPrivateKey = (
id?: string,
mnemonic?: string
): Promise<string> =>
msg.request("nostr/generatePrivateKey", {
id,
mnemonic,
});

const removeNostrPrivateKey = (id: string): Promise<void> =>
Expand Down Expand Up @@ -265,6 +269,11 @@ const setMnemonic = (id: string, mnemonic: string | null): Promise<void> =>
mnemonic,
});

const encryptValue = (value: string): Promise<string> =>
msg.request("encryptValue", {
value,
});

const getSwapInfo = (): Promise<SwapInfoResponse> => msg.request("getSwapInfo");
const createSwap = (params: CreateSwapParams): Promise<CreateSwapResponse> =>
msg.request("createSwap", params);
Expand Down Expand Up @@ -332,6 +341,7 @@ export default {
getMnemonic,
setMnemonic,
generateMnemonic,
encryptValue,
getSwapInfo,
createSwap,
liquid: {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import state from "../../state";
const generatePrivateKey = async (message: MessageNostrPrivateKeyGenerate) => {
const id = message.args?.id || state.getState().currentAccountId;
const password = await state.getState().password();
let nostrPrivateKey = "";
if (!password) {
return {
error: "Password is missing.",
Expand All @@ -22,16 +23,19 @@ const generatePrivateKey = async (message: MessageNostrPrivateKeyGenerate) => {
};
}
const mnemonic = new Mnemonic(decryptData(account.mnemonic, password));
const nostrPrivateKey = mnemonic.deriveNostrPrivateKeyHex();

return {
data: nostrPrivateKey,
};
nostrPrivateKey = mnemonic.deriveNostrPrivateKeyHex();
} else if (message.args?.mnemonic) {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm just wondering if this is the best approach since we add more complexity to these functions to both handle accounts and non-accounts.

But I can't see any other way except actually creating the account before we validate it 🤔

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

creating account before validating will go totally opposite to the existing flows that we have for other connectors. instead its better to handle this at script level

it just generates key in two way. either getting mnemonic from account id or if its passed explicitly. i guess this is more better to do somehow

const mnemonic = new Mnemonic(message.args.mnemonic);
nostrPrivateKey = mnemonic.deriveNostrPrivateKeyHex();
} else {
return {
error: "Error generating private key.",
};
}

return {
data: nostrPrivateKey,
};
};

export default generatePrivateKey;
20 changes: 20 additions & 0 deletions src/extension/background-script/actions/setup/encryptValue.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { encryptData } from "~/common/lib/crypto";

import { MessageValueEncrypt } from "~/types";
import state from "../../state";

const encryptValue = async (message: MessageValueEncrypt) => {
const password = await state.getState().password();
if (!password) {
return {
error: "Password is missing.",
};
}
let value = message.args.value;
value = encryptData(value, password);
return {
data: value,
};
};

export default encryptValue;
8 changes: 5 additions & 3 deletions src/extension/background-script/actions/setup/index.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,16 @@
import encryptValue from "./encryptValue";
import reset from "./reset";
import { setIcon, setIconMessageHandler } from "./setIcon";
import setPassword from "./setPassword";
import status from "./status";
import validateAccount from "./validateAccount";

export {
setPassword,
status,
encryptValue,
reset,
validateAccount,
setIcon,
setIconMessageHandler,
setPassword,
status,
validateAccount,
};
2 changes: 2 additions & 0 deletions src/extension/background-script/connectors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import NativeLnBits from "./nativelnbits";
import NativeLnd from "./nativelnd";
import NativeLndHub from "./nativelndhub";
import NWC from "./nwc";
import Spark from "./spark";

/*
const initialize = (account, password) => {
Expand All @@ -38,6 +39,7 @@ const connectors = {
nativecitadel: NativeCitadel,
commando: Commando,
alby: Alby,
spark: Spark,
nwc: NWC,
lawallet: LaWallet,
};
Expand Down
Loading