Skip to content

Commit

Permalink
feat: add lawallet extension support (#2934)
Browse files Browse the repository at this point in the history
* Adding LaWallet.io extension.
* Full support with NIP-05 and LUD-16.
* Added nostr-tools as lawallet stack requires nostr websocket connection.

---------

Co-authored-by: Jonathan Llamas <[email protected]>
  • Loading branch information
agustinkassis and unllamas authored May 15, 2024
1 parent 92bb90b commit a87ce1c
Show file tree
Hide file tree
Showing 10 changed files with 849 additions and 0 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
"lodash.merge": "^4.6.2",
"lodash.pick": "^4.4.0",
"lodash.snakecase": "^4.1.1",
"nostr-tools": "^1.17.0",
"pubsub-js": "^1.9.4",
"react": "^18.2.0",
"react-confetti": "^6.1.0",
Expand Down
15 changes: 15 additions & 0 deletions src/app/router/connectorRoutes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ import ConnectBtcpay from "@screens/connectors/ConnectBtcpay";
import ConnectCitadel from "@screens/connectors/ConnectCitadel";
import ConnectEclair from "@screens/connectors/ConnectEclair";
import ConnectGaloy, { galoyUrls } from "@screens/connectors/ConnectGaloy";
import ConnectLaWallet from "@screens/connectors/ConnectLaWallet";
import ConnectLnbits from "@screens/connectors/ConnectLnbits";
import ConnectLnc from "@screens/connectors/ConnectLnc";
import ConnectLnd from "@screens/connectors/ConnectLnd";
Expand All @@ -23,6 +24,7 @@ import core_ln from "/static/assets/icons/core_ln.svg";
import eclair from "/static/assets/icons/eclair.jpg";
import galoyBitcoinJungle from "/static/assets/icons/galoy_bitcoin_jungle.png";
import galoyBlink from "/static/assets/icons/galoy_blink.png";
import lawallet from "/static/assets/icons/lawallet.png";
import lightning_node from "/static/assets/icons/lightning_node.png";
import lightning_terminal from "/static/assets/icons/lightning_terminal.png";
import lnbits from "/static/assets/icons/lnbits.png";
Expand Down Expand Up @@ -165,6 +167,12 @@ const connectorMap: { [key: string]: ConnectorRoute } = {
title: i18n.t("translation:choose_connector.nwc.title"),
logo: nwc,
},
lawallet: {
path: "lawallet",
element: <ConnectLaWallet />,
title: i18n.t("translation:choose_connector.lawallet.title"),
logo: lawallet,
},
};

function getDistribution(key: string): ConnectorRoute {
Expand Down Expand Up @@ -252,6 +260,13 @@ function getConnectorRoutes(): ConnectorRoute[] {
connectorMap["voltage"],
connectorMap[galoyPaths.blink],
connectorMap[galoyPaths.bitcoinJungle],
getDistribution("citadel"),
getDistribution("umbrel"),
getDistribution("mynode"),
getDistribution("startos"),
getDistribution("raspiblitz"),
connectorMap["nwc"],
connectorMap["lawallet"],
connectorMap["lnd-hub-go"],
connectorMap["eclair"],
];
Expand Down
44 changes: 44 additions & 0 deletions src/app/screens/connectors/ConnectLaWallet/LaWalletToast.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Trans, useTranslation } from "react-i18next";

export default function LaWalletToast({ domain }: { domain: string }) {
const { t } = useTranslation("translation", {
keyPrefix: "choose_connector.lawallet.errors.toast",
});

return (
<>
<p className="text-md font-medium">
{t("title")} (
<span className="text-sm">{t("message", { domain })}</span>)
</p>

<p className="my-3 text-md">{t("verify")}</p>
<ul className="list-disc text-sm list-inside">
<li>{t("match")}</li>
<li>
<Trans
i18nKey={"walias"}
t={t}
values={{ domain }}
components={[<b key="walias-strong"></b>]}
/>
</li>
<li>
<Trans
i18nKey={"endpoint"}
t={t}
values={{ domain }}
components={[<b key="endpoint-strong"></b>]}
/>
</li>
<li>
<Trans
i18nKey={"http"}
t={t}
components={[<b key="http-strong"></b>]}
/>
</li>
</ul>
</>
);
}
206 changes: 206 additions & 0 deletions src/app/screens/connectors/ConnectLaWallet/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,206 @@
import ConnectorForm from "@components/ConnectorForm";
import TextField from "@components/form/TextField";
import ConnectionErrorToast from "@components/toasts/ConnectionErrorToast";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import toast from "~/app/components/Toast";
import msg from "~/common/lib/msg";

import Button from "~/app/components/Button";
import PasswordViewAdornment from "~/app/components/PasswordViewAdornment";
import LaWalletToast from "~/app/screens/connectors/ConnectLaWallet/LaWalletToast";
import LaWallet, {
HttpError,
} from "~/extension/background-script/connectors/lawallet";
import Nostr from "~/extension/background-script/nostr";
import { ConnectorType } from "~/types";
import logo from "/static/assets/icons/lawallet.png";

const initialFormData = {
private_key: "",
api_endpoint: "https://api.lawallet.ar",
identity_endpoint: "https://lawallet.ar",
ledger_public_key:
"bd9b0b60d5cd2a9df282fc504e88334995e6fac8b148fa89e0f8c09e2a570a84",
urlx_public_key:
"e17feb5f2cf83546bcf7fd9c8237b05275be958bd521543c2285ffc6c2d654b3",
relay_url: "wss://relay.lawallet.ar",
};

export default function ConnectLaWallet() {
const navigate = useNavigate();
const { t } = useTranslation("translation", {
keyPrefix: "choose_connector.lawallet",
});
const [passwordViewVisible, setPasswordViewVisible] = useState(false);
const { t: tCommon } = useTranslation("common");
const [formData, setFormData] = useState(initialFormData);
const [loading, setLoading] = useState(false);
const [showAdvanced, setShowAdvanced] = useState(false);

function getConnectorType(): ConnectorType {
return "lawallet";
}

function handleChange(event: React.ChangeEvent<HTMLInputElement>) {
setFormData({
...formData,
[event.target.name]: event.target.value.trim(),
});
}

async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
setLoading(true);
const {
private_key,
api_endpoint,
identity_endpoint,
ledger_public_key,
urlx_public_key,
relay_url,
} = formData;

const publicKey = new Nostr(private_key).getPublicKey();
const domain = identity_endpoint.replace(/https?:\/\//, "");

let username;
try {
const response = await LaWallet.request<{ username: string }>(
identity_endpoint,
"GET",
`/api/pubkey/${publicKey}`,
undefined
);
username = response.username;
} catch (e) {
if (e instanceof HttpError && e.status === 404) {
toast.error(<LaWalletToast domain={domain} />, {
position: "top-center",
});
} else {
toast.error(<ConnectionErrorToast message={(e as Error).message} />);
}

setLoading(false);
return;
}

const account = {
name: `${username}@${domain}`,
config: {
privateKey: private_key,
apiEndpoint: api_endpoint,
identityEndpoint: identity_endpoint,
ledgerPublicKey: ledger_public_key,
urlxPublicKey: urlx_public_key,
relayUrl: relay_url,
},
connector: getConnectorType(),
};

await msg.request("validateAccount", account);

try {
const addResult = await msg.request("addAccount", account);
if (addResult.accountId) {
await msg.request("selectAccount", {
id: addResult.accountId,
});
navigate("/test-connection");
}
} catch (e) {
console.error(e);
let message = "";
if (e instanceof Error) {
message += `${e.message}`;
}
toast.error(<ConnectionErrorToast message={message} />);
}
setLoading(false);
}

return (
<ConnectorForm
title={t("page.title")}
description={t("page.instructions")}
logo={logo}
submitLoading={loading}
submitDisabled={formData.private_key === ""}
onSubmit={handleSubmit}
>
<div className="flex flex-col space-y-6">
<TextField
id="private_key"
label={t("private_key")}
placeholder={t("private_key")}
onChange={handleChange}
type={passwordViewVisible ? "text" : "password"}
required
autoFocus={true}
endAdornment={
<PasswordViewAdornment
onChange={(passwordView) => {
setPasswordViewVisible(passwordView);
}}
/>
}
/>

<TextField
id="identity_endpoint"
label={t("identity_endpoint")}
value={formData.identity_endpoint}
required
title={t("identity_endpoint")}
onChange={handleChange}
/>

<Button
onClick={() => {
setShowAdvanced(!showAdvanced);
}}
label={tCommon("advanced")}
/>
</div>
{showAdvanced && (
<div className="mt-6 flex flex-col space-y-6">
<TextField
id="api_endpoint"
label={t("api_endpoint")}
value={formData.api_endpoint}
required
title={t("api_endpoint")}
onChange={handleChange}
/>

<TextField
id="ledger_public_key"
label={t("ledger_public_key")}
value={formData.ledger_public_key}
required
title={t("ledger_public_key")}
onChange={handleChange}
/>
<TextField
id="urlx_public_key"
label={t("urlx_public_key")}
value={formData.urlx_public_key}
required
title={t("urlx_public_key")}
onChange={handleChange}
/>
<TextField
id="relay_url"
label={t("relay_url")}
value={formData.relay_url}
required
title={t("relay_url")}
onChange={handleChange}
/>
</div>
)}
</ConnectorForm>
);
}
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 @@ -4,6 +4,7 @@ import Commando from "./commando";
import Eclair from "./eclair";
import Galoy from "./galoy";
import Kollider from "./kollider";
import LaWallet from "./lawallet";
import LnBits from "./lnbits";
import Lnc from "./lnc";
import Lnd from "./lnd";
Expand Down Expand Up @@ -38,6 +39,7 @@ const connectors = {
commando: Commando,
alby: Alby,
nwc: NWC,
lawallet: LaWallet,
};

export default connectors;
Loading

0 comments on commit a87ce1c

Please sign in to comment.