-
Notifications
You must be signed in to change notification settings - Fork 195
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: commando connector #1577
feat: commando connector #1577
Changes from 4 commits
0ac3c0c
3423c23
45d3f40
a357be4
b570402
be103cf
95f6c41
1463471
7948844
8649592
1b68578
f096ac3
b955f77
4342c28
9b30c4e
05c38d9
ff74995
62f7ae0
588c248
34c29aa
3651846
d2e0e63
67353f7
45efa02
884c5f3
af591da
bb971f1
6aaab06
d07982a
d3e4a94
7876df6
149c26e
15d1a74
96568dd
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,131 @@ | ||
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 "react-toastify"; | ||
import utils from "~/common/lib/utils"; | ||
|
||
export default function ConnectCommando() { | ||
const navigate = useNavigate(); | ||
const { t } = useTranslation("translation", { | ||
keyPrefix: `choose_connector.commando`, | ||
}); | ||
const [formData, setFormData] = useState({ | ||
host: "", | ||
pubkey: "", | ||
rune: "", | ||
}); | ||
const [loading, setLoading] = useState(false); | ||
|
||
function handleChange(event: React.ChangeEvent<HTMLInputElement>) { | ||
setFormData({ | ||
...formData, | ||
[event.target.name]: event.target.value.trim(), | ||
}); | ||
} | ||
|
||
function getConnectorType() { | ||
return "commando"; | ||
} | ||
|
||
async function handleSubmit(event: React.FormEvent<HTMLFormElement>) { | ||
event.preventDefault(); | ||
setLoading(true); | ||
const host = formData.host; | ||
const pubkey = formData.pubkey; | ||
const rune = formData.rune; | ||
const account = { | ||
name: "commando", | ||
config: { | ||
host, | ||
pubkey, | ||
rune, | ||
}, | ||
connector: getConnectorType(), | ||
}; | ||
|
||
try { | ||
let validation; | ||
// TODO: for native connectors we currently skip the validation because it is too slow (booting up Tor etc.) | ||
if (account.connector === "nativelndhub") { | ||
kiwiidb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
validation = { valid: true, error: "" }; | ||
} else { | ||
validation = await utils.call("validateAccount", account); | ||
} | ||
|
||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. please review this. this contains c&p code. |
||
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.description")} | ||
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="host" | ||
title="host" | ||
value={formData.host} | ||
onChange={handleChange} | ||
/> | ||
</div> | ||
<div className="mb-6"> | ||
<TextField | ||
id="pubkey" | ||
label={t("pubkey.label")} | ||
type="text" | ||
required | ||
placeholder="pubkey" | ||
title="pubkey" | ||
value={formData.pubkey} | ||
onChange={handleChange} | ||
/> | ||
</div> | ||
<div className="mb-6"> | ||
<TextField | ||
id="rune" | ||
label={t("rune.label")} | ||
type="text" | ||
required | ||
placeholder="rune" | ||
title="rune" | ||
value={formData.rune} | ||
onChange={handleChange} | ||
/> | ||
</div> | ||
</ConnectorForm> | ||
); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,151 @@ | ||
import LnMessage from "lnmessage"; | ||
import { v4 as uuidv4 } from "uuid"; | ||
|
||
import Connector, { | ||
CheckPaymentArgs, | ||
CheckPaymentResponse, | ||
GetBalanceResponse, | ||
GetInfoResponse, | ||
GetInvoicesResponse, | ||
KeysendArgs, | ||
MakeInvoiceArgs, | ||
MakeInvoiceResponse, | ||
SendPaymentArgs, | ||
SendPaymentResponse, | ||
SignMessageArgs, | ||
SignMessageResponse, | ||
} from "./connector.interface"; | ||
|
||
interface Config { | ||
host: string; | ||
port: number; | ||
rune: string; | ||
pubkey: string; | ||
wsProxy: string; | ||
privateKey: string; | ||
} | ||
|
||
type CommandoGetInfoResponse = { | ||
alias: string; | ||
id: string; | ||
color: string; | ||
}; | ||
type CommandoMakeInvoiceResponse = { | ||
bolt11: string; | ||
payment_hash: string; | ||
payment_secret: string; | ||
}; | ||
type CommandoChannel = { | ||
peer_id: string; | ||
channel_sat: number; | ||
amount_msat: number; | ||
funding_txid: string; | ||
funding_output: number; | ||
connected: boolean; | ||
state: string; | ||
}; | ||
type CommandoListFundsResponse = { | ||
channels: CommandoChannel[]; | ||
}; | ||
|
||
export default class Commando implements Connector { | ||
config: Config; | ||
ln: LnMessage; | ||
|
||
constructor(config: Config) { | ||
this.config = config; | ||
this.ln = new LnMessage({ | ||
remoteNodePublicKey: this.config.pubkey, | ||
wsProxy: this.config.wsProxy || "wss://lnwsproxy.regtest.getalby.com", | ||
kiwiidb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
ip: this.config.host, | ||
port: this.config.port || 9735, | ||
privateKey: | ||
this.config.privateKey || | ||
"d6a2eba36168cc31e97396a781a4dd46dd3648c001d3f4fde221d256e41715ea", | ||
}); | ||
} | ||
|
||
async init() { | ||
// initiate the connection to the remote node | ||
await this.ln.connect(); | ||
bumi marked this conversation as resolved.
Show resolved
Hide resolved
|
||
} | ||
|
||
unload() { | ||
return Promise.resolve(); | ||
} | ||
|
||
// not yet implemented | ||
connectPeer() { | ||
console.error( | ||
`${this.constructor.name} does not implement the getInvoices call` | ||
); | ||
return new Error("Not yet supported with the currently used account."); | ||
} | ||
|
||
async getInvoices(): Promise<GetInvoicesResponse> { | ||
throw new Error("Not yet supported with the currently used account."); | ||
} | ||
|
||
async getInfo(): Promise<GetInfoResponse> { | ||
const response = (await this.ln.commando({ | ||
method: "getinfo", | ||
params: [], | ||
rune: this.config.rune, | ||
})) as CommandoGetInfoResponse; | ||
return { | ||
data: { | ||
alias: response.alias, | ||
pubkey: response.id, | ||
color: response.color, | ||
}, | ||
}; | ||
} | ||
|
||
async getBalance(): Promise<GetBalanceResponse> { | ||
const response = (await this.ln.commando({ | ||
method: "listfunds", | ||
params: [], | ||
rune: this.config.rune, | ||
})) as CommandoListFundsResponse; | ||
let lnBalance = 0; | ||
for (let i = 0; i < response.channels.length; i++) { | ||
kiwiidb marked this conversation as resolved.
Show resolved
Hide resolved
|
||
lnBalance = lnBalance + response.channels[i].channel_sat; | ||
} | ||
return { | ||
data: { | ||
balance: lnBalance, | ||
}, | ||
}; | ||
} | ||
|
||
async sendPayment(args: SendPaymentArgs): Promise<SendPaymentResponse> { | ||
throw new Error("Not yet supported with the currently used account."); | ||
} | ||
|
||
async keysend(args: KeysendArgs): Promise<SendPaymentResponse> { | ||
throw new Error("Not yet supported with the currently used account."); | ||
} | ||
|
||
async checkPayment(args: CheckPaymentArgs): Promise<CheckPaymentResponse> { | ||
throw new Error("Not yet supported with the currently used account."); | ||
} | ||
|
||
signMessage(args: SignMessageArgs): Promise<SignMessageResponse> { | ||
throw new Error("Not yet supported with the currently used account."); | ||
} | ||
|
||
async makeInvoice(args: MakeInvoiceArgs): Promise<MakeInvoiceResponse> { | ||
const label = uuidv4(); | ||
const response = (await this.ln.commando({ | ||
method: "invoice", | ||
params: [(args.amount as number) * 1000, label, args.memo], | ||
rune: this.config.rune, | ||
})) as CommandoMakeInvoiceResponse; | ||
return { | ||
data: { | ||
paymentRequest: response.bolt11, | ||
rHash: response.payment_hash, | ||
}, | ||
}; | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -127,6 +127,12 @@ var options = { | |
test: /\.(woff|woff2|eot|ttf|otf)$/i, | ||
type: "asset/resource", | ||
}, | ||
{ | ||
test: /\.m?js/, | ||
resolve: { | ||
fullySpecified: false | ||
} | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @escapedcat what's the status here? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Unfortunately not: lnbc1QWFyb24/lnmessage#3 (comment) |
||
}, | ||
], | ||
}, | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What is that for?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To solve this:
I don't understand the details, maybe @escapedcat remembers.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
My understanding is that
lnmessage
is being distributed asmodule
:But our build system can't handle this per default if it's not called a
mjs
file so we need to adjust the webpack config, which also effects our storybook config.tbh I'm not sure if we still need storybook.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
hmm... in alby-tools this worked fine: https://github.com/getAlby/alby-tools/blob/master/package.json#L5
also is storybook even using our build system? if the build system can not handle it then we would need some webpack config update I guess?
@kiwiidb where do you see that error that you posted? Can try to remove this and run it again?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh I missed it's in the webpack config also.
but somewhat I would like to get rid of that and the npm package should be in a way that it also works for us - because I think we don't have such a strange build system.
maybe we can make a PR there?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Background: https://webpack.js.org/configuration/module/#resolvefullyspecified
Removing
"type": "module",
from the "lnmessage"package.json
already makes it work without this special webpack option."type": "module",
is letting node know that this is an esm package but this shouldn't be needed for a package that should run in the browser (only).I can create a PR for this and ask for feedback.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
what's with the following package.json keys:
source
,main
,unpkg
,exports
?packages that can run in different environments should publish the package valid for the different environments.
Does the build step need to be improved? https://github.com/aaronbarnardsound/lnmessage/blob/master/package.json#L14
tsc
is not enough?I don't know enough about the build setups in 2022, but I used
microbundle
recently in this package: https://github.com/getAlby/alby-tools/blob/master/package.json#L28There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We started a discussion with Aaron on this and will solve this later on.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think this can be removed no.
Actually if not 1000% needed I would like to resolve this now. because otherwise we will forget it and not take care of it and it will stay in there.