Skip to content

Commit

Permalink
feat: commando connector (#1577)
Browse files Browse the repository at this point in the history
* feat: start commando connector

* chore: storybook - apply webpack mjs config

* feat: implement get balance

* feat: add make invoice call

* chore: bump lnmessage dep

* feat: add connectpeer call

* feat: implement getinvoice response

* feat: implement payinvoice response

* feat: implement checkinvoice response

* feat: implement missing methods

* chore: consistent params

* fix: disconnect on unload

* feat: commando: add proxy and peer options

* feat: improve commando connector page

* fix: update lnmessage version

* feat: add private key

* fix: translation + keysend

* chore: remove package lock file

* fix: bumi PR remarks

* fix: use number field, no need to parse strings, my mistake

* chore: remove unnecessary code

* chore: 3 ='s

* feat: add advanced settings

* chore: update yarn lock

* chore: use math.floor

* fix: msatoshi_sent

* feat: translation advanced

* feat: disable getinvoices

* chore: add test

* feat: listinvoices

* fix: milliseconds

Co-authored-by: escapedcat <github@htmlcss.de>
Co-authored-by: ext.kwinten.de.backer <ext.kwinten.de.backer@sofico.be>
Co-authored-by: Michael Bumann <hello@michaelbumann.com>
  • Loading branch information
4 people authored Nov 9, 2022
1 parent f73196e commit 9d213ce
Show file tree
Hide file tree
Showing 10 changed files with 656 additions and 4 deletions.
1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@
"html5-qrcode": "^2.2.3",
"i18next": "^21.10.0",
"i18next-browser-languagedetector": "^7.0.1",
"lnmessage": "^0.0.11",
"lodash.merge": "^4.6.2",
"lodash.pick": "^4.4.0",
"pubsub-js": "^1.9.4",
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 @@ -13,9 +13,11 @@ import NewWallet from "@screens/connectors/NewWallet";
import i18n from "~/i18n/i18nConfig";
import { translationI18nNamespace } from "~/i18n/namespaces";

import ConnectCommando from "../screens/connectors/ConnectCommando";
import alby from "/static/assets/icons/alby.png";
import btcpay from "/static/assets/icons/btcpay.svg";
import citadel from "/static/assets/icons/citadel.png";
import core_ln from "/static/assets/icons/core_ln.svg";
import eclair from "/static/assets/icons/eclair.jpg";
import galoyBitcoinBeach from "/static/assets/icons/galoy_bitcoin_beach.png";
import galoyBitcoinJungle from "/static/assets/icons/galoy_bitcoin_jungle.png";
Expand Down Expand Up @@ -55,6 +57,19 @@ function getConnectorRoutes() {
),
logo: lnd,
},
{
path: "commando",
element: <ConnectCommando />,
title: i18n.t(
"choose_connector.commando.title",
translationI18nNamespace
),
description: i18n.t(
"choose_connector.commando.description",
translationI18nNamespace
),
logo: core_ln,
},
{
path: "lnbits",
element: <ConnectLnbits />,
Expand Down
205 changes: 205 additions & 0 deletions src/app/screens/connectors/ConnectCommando/index.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,205 @@
import {
HiddenIcon,
VisibleIcon,
} from "@bitcoin-design/bitcoin-icons-react/outline";
import Button from "@components/Button";
import ConnectorForm from "@components/ConnectorForm";
import TextField from "@components/form/TextField";
import ConnectionErrorToast from "@components/toasts/ConnectionErrorToast";
import * as secp256k1 from "@noble/secp256k1";
import { useState } from "react";
import { useTranslation } from "react-i18next";
import { useNavigate } from "react-router-dom";
import { toast } from "react-toastify";
import utils from "~/common/lib/utils";

export default function ConnectCommando() {
const navigate = useNavigate();
const { t } = useTranslation("translation", {
keyPrefix: `choose_connector.commando`,
});
const { t: tCommon } = useTranslation("common");
const [formData, setFormData] = useState({
host: "",
pubkey: "",
rune: "",
port: 9735,
privateKey: generateCommandoPrivateKey(),
proxy: "wss://lnproxy.getalby.com",
});
const [loading, setLoading] = useState(false);
const [showAdvanced, setShowAdvanced] = useState(false);
const [commandoPrivateKeyVisible, setCommandoPrivateKeyVisible] =
useState(false);

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

function getConnectorType() {
return "commando";
}

function generateCommandoPrivateKey(): string {
const privKey = secp256k1.utils.randomPrivateKey();
return secp256k1.utils.bytesToHex(privKey);
}

async function handleSubmit(event: React.FormEvent<HTMLFormElement>) {
event.preventDefault();
setLoading(true);
const host = formData.host;
const pubkey = formData.pubkey;
const rune = formData.rune;
const port = formData.port;
const wsProxy = formData.proxy;
const privateKey = formData.privateKey;
const account = {
name: "commando",
config: {
host,
pubkey,
rune,
port,
wsProxy,
privateKey,
},
connector: getConnectorType(),
};

try {
const validation = await utils.call("validateAccount", account);
if (validation.valid) {
const addResult = await utils.call("addAccount", account);
if (addResult.accountId) {
await utils.call("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("errors.connection_failed");
if (e instanceof Error) {
message += `\n\n${e.message}`;
}
toast.error(message);
}
setLoading(false);
}

return (
<ConnectorForm
title={t("page.title")}
description={t("page.instructions")}
submitLoading={loading}
submitDisabled={
formData.host === "" && formData.pubkey === "" && formData.rune === ""
}
onSubmit={handleSubmit}
>
<div className="mb-6">
<TextField
id="host"
label={t("host.label")}
type="text"
required
placeholder="0.0.0.0"
title="host"
value={formData.host}
onChange={handleChange}
/>
</div>
<div className="mb-6">
<TextField
id="pubkey"
label={t("pubkey.label")}
type="text"
required
placeholder="02...."
title="pubkey"
value={formData.pubkey}
onChange={handleChange}
/>
</div>
<div className="mb-6">
<TextField
id="rune"
label={t("rune.label")}
type="text"
required
placeholder=""
title="rune"
value={formData.rune}
onChange={handleChange}
/>
</div>
<div className="mb-6">
<TextField
id="port"
label={t("port.label")}
type="number"
required
title="port"
value={formData.port}
onChange={handleChange}
/>
</div>
<Button
onClick={() => {
setShowAdvanced(!showAdvanced);
}}
label={tCommon("advanced")}
/>
{showAdvanced && (
<div className="mt-6">
<div className="mb-6">
<TextField
id="proxy"
label={t("proxy.label")}
type="text"
placeholder="proxy"
required
title="proxy"
value={formData.proxy}
onChange={handleChange}
/>
</div>
<div className="mb-6">
<TextField
id="commandoPrivateKey"
label={t("privKey.label")}
type={commandoPrivateKeyVisible ? "text" : "password"}
value={formData.privateKey}
endAdornment={
<button
type="button"
tabIndex={-1}
className="flex justify-center items-center w-10 h-8"
onClick={() => {
setCommandoPrivateKeyVisible(!commandoPrivateKeyVisible);
}}
>
{commandoPrivateKeyVisible ? (
<HiddenIcon className="h-6 w-6" />
) : (
<VisibleIcon className="h-6 w-6" />
)}
</button>
}
/>
</div>
</div>
)}
</ConnectorForm>
);
}
Loading

0 comments on commit 9d213ce

Please sign in to comment.