aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAdam Soltys <[email protected]>2020-08-03 21:38:29 -0700
committerAdam Soltys <[email protected]>2020-08-03 21:38:29 -0700
commit2bcd5e9cd2233934b6d78724a07172b187ceb0b0 (patch)
treedba0c6451c1fa962dda1c3aa848abaa36d7012ab
parent1f44dadfb24913d9a6835f3b18340ede580e886a (diff)
downloadcoinos-server-2bcd5e9cd2233934b6d78724a07172b187ceb0b0.tar.gz
coinos-server-2bcd5e9cd2233934b6d78724a07172b187ceb0b0.zip
user urls
-rw-r--r--config/rpc.proto170
-rw-r--r--index.js3
-rw-r--r--lib/send.js2
-rw-r--r--lib/sockets.js26
-rw-r--r--routes/invoices.js23
-rw-r--r--routes/lightning/invoice.js2
-rw-r--r--routes/lightning/receive.js139
-rw-r--r--routes/lnurl.js381
-rw-r--r--routes/payments.js2
-rw-r--r--routes/users.js12
10 files changed, 474 insertions, 286 deletions
diff --git a/config/rpc.proto b/config/rpc.proto
index f69b768f..ee5504d7 100644
--- a/config/rpc.proto
+++ b/config/rpc.proto
@@ -140,6 +140,14 @@ service Lightning {
*/
rpc GetInfo (GetInfoRequest) returns (GetInfoResponse);
+ /** lncli: `getrecoveryinfo`
+ GetRecoveryInfo returns information concerning the recovery mode including
+ whether it's in a recovery mode, whether the recovery is finished, and the
+ progress made so far.
+ */
+ rpc GetRecoveryInfo (GetRecoveryInfoRequest)
+ returns (GetRecoveryInfoResponse);
+
// TODO(roasbeef): merge with below with bool?
/* lncli: `pendingchannels`
PendingChannels returns a list of all the channels that are currently
@@ -985,6 +993,30 @@ enum CommitmentType {
UNKNOWN_COMMITMENT_TYPE = 999;
}
+message ChannelConstraints {
+ /*
+ The CSV delay expressed in relative blocks. If the channel is force closed,
+ we will need to wait for this many blocks before we can regain our funds.
+ */
+ uint32 csv_delay = 1;
+
+ // The minimum satoshis this node is required to reserve in its balance.
+ uint64 chan_reserve_sat = 2;
+
+ // The dust limit (in satoshis) of the initiator's commitment tx.
+ uint64 dust_limit_sat = 3;
+
+ // The maximum amount of coins in millisatoshis that can be pending in this
+ // channel.
+ uint64 max_pending_amt_msat = 4;
+
+ // The smallest HTLC in millisatoshis that the initiator will accept.
+ uint64 min_htlc_msat = 5;
+
+ // The total number of incoming HTLC's that the initiator will accept.
+ uint32 max_accepted_htlcs = 6;
+}
+
message Channel {
// Whether this channel is active or not
bool active = 1;
@@ -1057,10 +1089,11 @@ message Channel {
repeated HTLC pending_htlcs = 15;
/*
- The CSV delay expressed in relative blocks. If the channel is force closed,
- we will need to wait for this many blocks before we can regain our funds.
+ Deprecated. The CSV delay expressed in relative blocks. If the channel is
+ force closed, we will need to wait for this many blocks before we can regain
+ our funds.
*/
- uint32 csv_delay = 16;
+ uint32 csv_delay = 16 [deprecated = true];
// Whether this channel is advertised to the network or not.
bool private = 17;
@@ -1071,13 +1104,15 @@ message Channel {
// A set of flags showing the current state of the channel.
string chan_status_flags = 19;
- // The minimum satoshis this node is required to reserve in its balance.
- int64 local_chan_reserve_sat = 20;
+ // Deprecated. The minimum satoshis this node is required to reserve in its
+ // balance.
+ int64 local_chan_reserve_sat = 20 [deprecated = true];
/*
- The minimum satoshis the other node is required to reserve in its balance.
+ Deprecated. The minimum satoshis the other node is required to reserve in
+ its balance.
*/
- int64 remote_chan_reserve_sat = 21;
+ int64 remote_chan_reserve_sat = 21 [deprecated = true];
// Deprecated. Use commitment_type.
bool static_remote_key = 22 [deprecated = true];
@@ -1122,9 +1157,17 @@ message Channel {
frozen channel doest not allow a cooperative channel close by the
initiator. The thaw_height is the height that this restriction stops
applying to the channel. This field is optional, not setting it or using a
- value of zero will mean the channel has no additional restrictions.
+ value of zero will mean the channel has no additional restrictions. The
+ height can be interpreted in two ways: as a relative height if the value is
+ less than 500,000, or as an absolute height otherwise.
*/
uint32 thaw_height = 28;
+
+ // List constraints for the local node.
+ ChannelConstraints local_constraints = 29;
+
+ // List constraints for the remote node.
+ ChannelConstraints remote_constraints = 30;
}
message ListChannelsRequest {
@@ -1206,6 +1249,79 @@ message ChannelCloseSummary {
force closes, although only one party's close will be confirmed on chain.
*/
Initiator close_initiator = 12;
+
+ repeated Resolution resolutions = 13;
+}
+
+enum ResolutionType {
+ TYPE_UNKNOWN = 0;
+
+ // We resolved an anchor output.
+ ANCHOR = 1;
+
+ /*
+ We are resolving an incoming htlc on chain. This if this htlc is
+ claimed, we swept the incoming htlc with the preimage. If it is timed
+ out, our peer swept the timeout path.
+ */
+ INCOMING_HTLC = 2;
+
+ /*
+ We are resolving an outgoing htlc on chain. If this htlc is claimed,
+ the remote party swept the htlc with the preimage. If it is timed out,
+ we swept it with the timeout path.
+ */
+ OUTGOING_HTLC = 3;
+
+ // We force closed and need to sweep our time locked commitment output.
+ COMMIT = 4;
+}
+
+enum ResolutionOutcome {
+ // Outcome unknown.
+ OUTCOME_UNKNOWN = 0;
+
+ // An output was claimed on chain.
+ CLAIMED = 1;
+
+ // An output was left unclaimed on chain.
+ UNCLAIMED = 2;
+
+ /*
+ ResolverOutcomeAbandoned indicates that an output that we did not
+ claim on chain, for example an anchor that we did not sweep and a
+ third party claimed on chain, or a htlc that we could not decode
+ so left unclaimed.
+ */
+ ABANDONED = 3;
+
+ /*
+ If we force closed our channel, our htlcs need to be claimed in two
+ stages. This outcome represents the broadcast of a timeout or success
+ transaction for this two stage htlc claim.
+ */
+ FIRST_STAGE = 4;
+
+ // A htlc was timed out on chain.
+ TIMEOUT = 5;
+}
+
+message Resolution {
+ // The type of output we are resolving.
+ ResolutionType resolution_type = 1;
+
+ // The outcome of our on chain action that resolved the outpoint.
+ ResolutionOutcome outcome = 2;
+
+ // The outpoint that was spent by the resolution.
+ OutPoint outpoint = 3;
+
+ // The amount that was claimed by the resolution.
+ uint64 amount_sat = 4;
+
+ // The hex-encoded transaction ID of the sweep transaction that spent the
+ // output.
+ string sweep_txid = 5;
}
message ClosedChannelsRequest {
@@ -1381,6 +1497,19 @@ message GetInfoResponse {
map<uint32, Feature> features = 19;
}
+message GetRecoveryInfoRequest {
+}
+message GetRecoveryInfoResponse {
+ // Whether the wallet is in recovery mode
+ bool recovery_mode = 1;
+
+ // Whether the wallet recovery progress is finished
+ bool recovery_finished = 2;
+
+ // The recovery progress, ranging from 0 to 1.
+ double progress = 3;
+}
+
message Chain {
// The blockchain the node is on (eg bitcoin, litecoin)
string chain = 1;
@@ -1537,6 +1666,12 @@ message OpenChannelRequest {
carried out in an interactive manner (PSBT based).
*/
FundingShim funding_shim = 14;
+
+ /*
+ The maximum amount of coins in millisatoshi that can be pending within
+ the channel. It only applies to the remote party.
+ */
+ uint64 remote_max_value_in_flight_msat = 15;
}
message OpenStatusUpdate {
oneof update {
@@ -1611,10 +1746,11 @@ message ChanPointShim {
bytes pending_chan_id = 5;
/*
- This uint32 indicates if this channel is to be considered 'frozen'. A
- frozen channel does not allow a cooperative channel close by the
- initiator. The thaw_height is the height that this restriction stops
- applying to the channel.
+ This uint32 indicates if this channel is to be considered 'frozen'. A frozen
+ channel does not allow a cooperative channel close by the initiator. The
+ thaw_height is the height that this restriction stops applying to the
+ channel. The height can be interpreted in two ways: as a relative height if
+ the value is less than 500,000, or as an absolute height otherwise.
*/
uint32 thaw_height = 6;
}
@@ -1632,6 +1768,16 @@ message PsbtShim {
non-empty, it must be a binary serialized PSBT.
*/
bytes base_psbt = 2;
+
+ /*
+ If a channel should be part of a batch (multiple channel openings in one
+ transaction), it can be dangerous if the whole batch transaction is
+ published too early before all channel opening negotiations are completed.
+ This flag prevents this particular channel from broadcasting the transaction
+ after the negotiation with the remote peer. In a batch of channel openings
+ this flag should be set to true for every channel but the very last.
+ */
+ bool no_publish = 3;
}
message FundingShim {
diff --git a/index.js b/index.js
index 4ea48737..c03936f2 100644
--- a/index.js
+++ b/index.js
@@ -65,7 +65,8 @@ app.use((err, req, res, next) => {
if (req.user) details.username = req.user.username;
- l.error("uncaught error", details, err.message);
+ l.error("Error: ", err.message);
+ throw err;
res.status(500);
res.send(err.message);
return res.end();
diff --git a/lib/send.js b/lib/send.js
index 2e4f1f12..0fd76cc5 100644
--- a/lib/send.js
+++ b/lib/send.js
@@ -5,7 +5,7 @@ send = async (amount, memo, hash, user) => {
if (!amount) amount = payreq.satoshis;
- l.info("attempting lightning payment", user.username, amount);
+ l.info("attempting lightning payment", hash, user.username, amount);
if (seen.includes(hash)) {
l.warn("attempted to pay a paid invoice", user.username);
diff --git a/lib/sockets.js b/lib/sockets.js
index 514d701a..117a8d67 100644
--- a/lib/sockets.js
+++ b/lib/sockets.js
@@ -6,10 +6,21 @@ wss = new WebSocket.Server({ clientTracking: false, noServer: true });
/* eslint-disable-next-line */
const pick = (O, ...K) => K.reduce((o, k) => ((o[k] = O[k]), o), {});
+const subscriptions = [];
emit = (username, type, data) => {
if (data.username !== undefined) data = pick(data, ...whitelist);
+ if (type === "payment" && data.amount > 0) {
+ for (let i = subscriptions.length - 1; i >= 0; i--) {
+ let s = subscriptions[i];
+ if (s.invoice.amount === data.amount) {
+ s.ws.send(JSON.stringify({ type, data }));
+ subscriptions.splice(i, 1);
+ }
+ }
+ }
+
if (!sockets[username]) return;
for (id in sockets[username]) {
const ws = sockets[username][id];
@@ -46,18 +57,17 @@ wss.on("connection", function (ws, req) {
l.error("coudn't parse socket message");
}
- let token;
switch (type) {
case "login":
- token = data;
+ let token = data;
if (!token || token === "null") return;
try {
const username = jwt.decode(token).username;
if (!sockets[username]) sockets[username] = {};
const existing = Object.keys(sockets[username]);
if (existing.length > 5) {
- l.warn("too many sockets, logging out", existing[5]);
- sockets[username][existing[5]].send(
+ l.warn("too many sockets, logging out", existing[3]);
+ sockets[username][existing[3]].send(
JSON.stringify({ type: "login", data: false })
);
}
@@ -88,7 +98,6 @@ wss.on("connection", function (ws, req) {
ws.send(
JSON.stringify({ type: "version", data: config.clientVersion })
);
- l.info("socket open", username, id);
} catch (e) {
ws.close();
l.error(
@@ -101,9 +110,8 @@ wss.on("connection", function (ws, req) {
}
break;
case "token":
- token = data;
if (!ws.user) return;
- ws.token = token;
+ ws.token = data;
break;
case "lnurl":
sessions[data] = ws;
@@ -125,6 +133,9 @@ wss.on("connection", function (ws, req) {
emit(ws.user.username, "payment", payment);
}
break;
+ case "subscribe":
+ subscriptions.push({ invoice: data, ws });
+ break;
default:
l.warn("received socket message of unknown type", type, data);
}
@@ -135,7 +146,6 @@ wss.on("connection", function (ws, req) {
const username = names[id];
if (sockets[username] && sockets[username][id])
delete sockets[username][id];
- l.info("socket closed", username, id);
} catch (e) {
l.error("problem closing socket", e.message);
}
diff --git a/routes/invoices.js b/routes/invoices.js
index fdfb39d8..f344e9f9 100644
--- a/routes/invoices.js
+++ b/routes/invoices.js
@@ -14,14 +14,23 @@ app.get("/invoice", auth, ah(async (req, res, next) => {
}
}));
-app.post("/invoice", auth, ah(async (req, res, next) => {
- const { invoice } = req.body;
- invoice.user_id = req.user.id;
+app.post("/invoice", ah(async (req, res, next) => {
+ let { invoice, user } = req.body;
+ if (!user) ({ user } = req);
+ else {
+ user = await db.User.findOne({
+ where: {
+ username: user.username,
+ }
+ });
+ }
+ if (!user) throw new Error("user not provided");
+ invoice.user_id = user.id;
l.info(
"creating invoice",
invoice.text,
- req.user.username,
+ user.username,
invoice.network,
invoice.amount,
invoice.tip,
@@ -44,7 +53,7 @@ app.post("/invoice", auth, ah(async (req, res, next) => {
},
});
- res.send(
- exists ? await exists.update(invoice) : await db.Invoice.create(invoice)
- );
+ invoice = exists ? await exists.update(invoice) : await db.Invoice.create(invoice);
+ addresses[invoice.address] = user.username;
+ res.send(invoice);
}));
diff --git a/routes/lightning/invoice.js b/routes/lightning/invoice.js
index 689c696a..51eff332 100644
--- a/routes/lightning/invoice.js
+++ b/routes/lightning/invoice.js
@@ -1,8 +1,6 @@
module.exports = ah(async (req, res) => {
const { amount, memo, tip } = req.body;
- l.info("adding lightning invoice", req.user.username, amount, tip);
-
try {
if (config.lna.clightning) {
const invoice = await lnb.invoice(`${amount + tip}sat` || "any", new Date(), memo, 360);
diff --git a/routes/lightning/receive.js b/routes/lightning/receive.js
index 34ac1fc3..e8246ab9 100644
--- a/routes/lightning/receive.js
+++ b/routes/lightning/receive.js
@@ -1,90 +1,97 @@
const asset = "LNBTC";
-const handlePayment = async msg => {
+const handlePayment = async (msg) => {
try {
- await db.transaction(async transaction => {
- if (!msg.settled) return;
+ await db.transaction(async (transaction) => {
+ if (!msg.settled) return;
- const invoice = await db.Invoice.findOne({
- where: {
- text: msg.payment_request
- }
- });
+ const invoice = await db.Invoice.findOne({
+ where: {
+ text: msg.payment_request,
+ },
+ });
+
+ if (!invoice)
+ return l.warn(
+ "received lightning with no invoice",
+ msg.payment_request
+ );
- if (!invoice) return l.warn("received lightning with no invoice", msg.payment_request);
+ const { text: hash, currency, memo, rate, tip, user_id } = invoice;
+ const amount = parseInt(msg.amt_paid_sat) - tip;
- const { text: hash, currency, memo, rate, tip, user_id } = invoice;
- const amount = parseInt(msg.amt_paid_sat) - tip;
+ const account = await db.Account.findOne({
+ where: {
+ user_id,
+ asset: config.liquid.btcasset,
+ },
+ include: {
+ model: db.User,
+ as: "user",
+ },
+ lock: transaction.LOCK.UPDATE,
+ transaction,
+ });
- const account = await db.Account.findOne({
- where: {
- user_id,
- asset: config.liquid.btcasset
- },
- include: {
- model: db.User,
- as: 'user',
- },
- lock: transaction.LOCK.UPDATE,
- transaction
- });
+ const { user } = account;
- const { user } = account;
+ let preimage = msg.r_preimage.toString("hex");
- let preimage = msg.r_preimage.toString("hex");
+ let payment = await db.Payment.create(
+ {
+ account_id: account.id,
+ user_id,
+ hash,
+ memo,
+ amount,
+ currency,
+ preimage,
+ rate,
+ received: true,
+ confirmed: true,
+ network: "LNBTC",
+ tip,
+ },
+ { transaction }
+ );
- let payment = await db.Payment.create({
- account_id: account.id,
- user_id,
- hash,
- memo,
- amount,
- currency,
- preimage,
- rate,
- received: true,
- confirmed: true,
- network: "LNBTC",
- tip
- },
- { transaction });
+ let total = amount + tip;
+ invoice.received += total;
+ account.balance += total;
- let total = amount + tip;
- invoice.received += total;
- account.balance += total;
+ await account.save({ transaction });
+ await invoice.save({ transaction });
+ await payment.save({ transaction });
+ payments.push(msg.payment_request);
- await account.save({ transaction });
- await invoice.save({ transaction });
- await payment.save({ transaction });
- payments.push(msg.payment_request);
+ payment = payment.get({ plain: true });
+ payment.account = account.get({ plain: true });
- payment = payment.get({ plain: true });
- payment.account = account.get({ plain: true });
- emit(user.username, "payment", payment);
- emit(user.username, "account", payment.account);
- notify(user, `Received ${total} SAT`);
+ emit(user.username, "payment", payment);
+ emit(user.username, "account", payment.account);
+ notify(user, `Received ${total} SAT`);
- l.info(
- "lightning payment received",
- user.username,
- payment.amount,
- payment.tip
- );
- });
- } catch(e) {
+ l.info(
+ "lightning payment received",
+ user.username,
+ payment.amount,
+ payment.tip
+ );
+ });
+ } catch (e) {
l.error("problem receiving lightning payment", e.message);
- }
+ }
};
if (config.lna.clightning) {
- const poll = async ln => {
- const wait = async i => {
+ const poll = async (ln) => {
+ const wait = async (i) => {
const {
bolt11: payment_request,
pay_index,
status,
msatoshi_received,
- payment_preimage: r_preimage
+ payment_preimage: r_preimage,
} = await ln.waitanyinvoice(i);
let settled = status === "paid";
@@ -94,13 +101,13 @@ if (config.lna.clightning) {
payment_request,
settled,
amt_paid_sat,
- r_preimage
+ r_preimage,
});
wait(pay_index);
};
const { invoices } = await ln.listinvoices();
- wait(Math.max(...invoices.map(i => i.pay_index).filter(n => n)));
+ wait(Math.max(...invoices.map((i) => i.pay_index).filter((n) => n)));
};
poll(lna);
diff --git a/routes/lnurl.js b/routes/lnurl.js
index d8336709..44eb0fb9 100644
--- a/routes/lnurl.js
+++ b/routes/lnurl.js
@@ -17,186 +17,217 @@ var optionalAuth = function (req, res, next) {
})(req, res, next);
};
-app.get("/url", ah(async (req, res, next) => {
- try {
- const code = await db.Code.findOne({
- where: {
- code: req.query.code,
- },
- });
+app.get(
+ "/url",
+ ah(async (req, res, next) => {
+ try {
+ const code = await db.Code.findOne({
+ where: {
+ code: req.query.code,
+ },
+ });
- if (code) {
- res.send(code.text);
- } else {
- throw new Error("code not found");
+ if (code) {
+ res.send(code.text);
+ } else {
+ throw new Error("code not found");
+ }
+ } catch (e) {
+ l.error("couldn't find url", e.message);
}
- } catch (e) {
- l.error("couldn't find url", e.message);
- }
-}));
-
-app.get("/withdraw", auth, ah(async (req, res, next) => {
- const { min, max } = req.query;
- try {
- const result = await lnurlServer.generateNewUrl("withdrawRequest", {
- minWithdrawable: min * 1000,
- maxWithdrawable: max * 1000,
- defaultDescription: "coinos voucher",
- });
+ })
+);
- withdrawals[result.secret] = req.user;
+app.get(
+ "/withdraw",
+ auth,
+ ah(async (req, res, next) => {
+ const { min, max } = req.query;
+ try {
+ const result = await lnurlServer.generateNewUrl("withdrawRequest", {
+ minWithdrawable: min * 1000,
+ maxWithdrawable: max * 1000,
+ defaultDescription: "coinos voucher",
+ });
- res.send(result);
- } catch (e) {
- l.error("problem generating withdrawl url", e.message);
- res.status(500).send(e.message);
- }
-}));
+ withdrawals[result.secret] = req.user;
-app.post("/code", ah(async (req, res, next) => {
- try {
- const { encoded } = req.body.lnurl;
- const code = await db.Code.create({
- code: "lnurl:" + encoded.substr(-32),
- text: encoded,
- });
+ res.send(result);
+ } catch (e) {
+ l.error("problem generating withdrawl url", e.message);
+ res.status(500).send(e.message);
+ }
+ })
+);
- res.send(code);
- } catch (e) {
- res.status(500).send(e.message);
- }
-}));
+app.post(
+ "/code",
+ ah(async (req, res, next) => {
+ try {
+ const { encoded } = req.body.lnurl;
+ const code = await db.Code.create({
+ code: "lnurl:" + encoded.substr(-32),
+ text: encoded,
+ });
-app.post("/withdraw", auth, ah(async (req, res, next) => {
- const { user } = req;
- const {
- amount: value,
- params: { callback, k1 },
- } = req.body;
- const invoice = await lna.addInvoice({ value });
- const { payment_request: pr } = invoice;
- const url = `${callback}?k1=${k1}&pr=${pr}`;
+ res.send(code);
+ } catch (e) {
+ res.status(500).send(e.message);
+ }
+ })
+);
- await db.Invoice.create({
- user_id: user.id,
- text: pr,
- rate: app.get("rates")[user.currency],
- currency: user.currency,
- amount: value,
- tip: 0,
- network: "BTC",
- });
+app.post(
+ "/withdraw",
+ auth,
+ ah(async (req, res, next) => {
+ const { user } = req;
+ const {
+ amount: value,
+ params: { callback, k1 },
+ } = req.body;
+ const invoice = await lna.addInvoice({ value });
+ const { payment_request: pr } = invoice;
+ const url = `${callback}?k1=${k1}&pr=${pr}`;
- try {
- result = (await axios.get(url)).data;
- res.send(result);
- } catch (e) {
- l.error("failed to withdraw", e.message);
- res.status(500).send(e.message);
- }
-}));
+ await db.Invoice.create({
+ user_id: user.id,
+ text: pr,
+ rate: app.get("rates")[user.currency],
+ currency: user.currency,
+ amount: value,
+ tip: 0,
+ network: "BTC",
+ });
-const pay = async ({ amount, minSendable, maxSendable }, res, user) => {
- minSendable = minSendable || 1000;
- maxSendable = maxSendable || 1000000000;
- if (parseInt(amount)) minSendable = maxSendable = amount * 1000;
+ try {
+ result = (await axios.get(url)).data;
+ res.send(result);
+ } catch (e) {
+ l.error("failed to withdraw", e.message);
+ res.status(500).send(e.message);
+ }
+ })
+);
- try {
- const result = await lnurlServer.generateNewUrl("payRequest", {
- minSendable,
- maxSendable,
- metadata: JSON.stringify([["text/plain", `paying ${user.username}`]]),
+app.get(
+ "/pay/:username",
+ ah(async (req, res, next) => {
+ const { username } = req.params;
+ let user = await db.User.findOne({
+ where: {
+ username,
+ },
});
- recipients[result.secret] = user;
- l.info("recipient", user.username, result.secret);
- res.send(result);
- } catch (e) {
- l.error("problem generating payment url", e.message);
- res.status(500).send(e.message);
- }
-};
+ let { amount, minSendable, maxSendable } = req.query;
+ minSendable = minSendable || 1000;
+ maxSendable = maxSendable || 1000000000;
+ if (parseInt(amount)) minSendable = maxSendable = amount * 1000;
-app.get("/pay/:username", ah(async (req, res, next) => {
- const { username } = req.params;
- let user = await db.User.findOne({
- where: {
- username,
- },
- });
- pay(req.query, res, user);
-}));
+ let invoices = await db.Invoice.findAll({
+ where: {
+ amount,
+ },
+ order: [["id", "DESC"]],
+ limit: 1,
+ });
-app.get("/pay", auth, ah(async (req, res, next) => {
- const { user } = req;
- pay(req.query, res, user);
-}));
+ if (invoices.length) await invoices[0].destroy();
-app.post("/pay", auth, ah(async (req, res, next) => {
- const { user } = req;
- const {
- amount,
- params: { callback, k1 },
- } = req.body;
+ try {
+ const result = await lnurlServer.generateNewUrl("payRequest", {
+ minSendable,
+ maxSendable,
+ metadata: JSON.stringify([["text/plain", `paying ${user.username}`]]),
+ });
- const url = `${callback}?amount=${amount * 1000}`;
- l.info("url", url);
+ recipients[result.secret] = user;
+ l.info("recipient", user.username, result.secret);
+ res.send(result);
+ } catch (e) {
+ l.error("problem generating payment url", e.message);
+ res.status(500).send(e.message);
+ }
+ })
+);
- try {
- const parts = callback.split("/");
- const secret = parts[parts.length - 1];
- payments[secret] = user;
+app.post(
+ "/pay",
+ auth,
+ ah(async (req, res, next) => {
+ const { user } = req;
+ const {
+ amount,
+ params: { callback, k1 },
+ } = req.body;
- const result = (await axios.get(url)).data;
- res.send(await send(amount, '', result.pr, user));
- } catch (e) {
- l.error("failed to send payment", e.message);
- res.status(500).send(e.message);
- }
-}));
+ const url = `${callback}?amount=${amount * 1000}`;
-app.get("/login", optionalAuth, ah(async (req, res, next) => {
- try {
- const result = await lnurlServer.generateNewUrl("login");
+ try {
+ const parts = callback.split("/");
+ const secret = parts[parts.length - 1];
+ payments[secret] = user;
- if (req.user) {
- logins[result.secret] = req.user.username;
+ const result = (await axios.get(url)).data;
+ res.send(await send(amount, "", result.pr, user));
+ } catch (e) {
+ l.error("failed to send payment", e.message);
+ throw e;
+ res.status(500).send(e.message);
}
+ })
+);
- res.send(result);
- } catch (e) {
- l.error("problem generating login url", e.message);
- res.status(500).send(e.message);
- }
-}));
+app.get(
+ "/login",
+ optionalAuth,
+ ah(async (req, res, next) => {
+ try {
+ const result = await lnurlServer.generateNewUrl("login");
-app.get("/decode", ah(async (req, res, next) => {
- const { text } = req.query;
+ if (req.user) {
+ logins[result.secret] = req.user.username;
+ }
- try {
- const url = lnurl.decode(text);
- let spl = url.split("?");
- if (spl.length > 1 && qs.parse(spl[1]).tag === "login") {
- return res.send({
- tag: "login",
- k1: qs.parse(spl[1]).k1,
- callback: url,
- domain: url
- .split("://")[1]
- .split("/")[0]
- .split("@")
- .slice(-1)[0]
- .split(":")[0],
- });
+ res.send(result);
+ } catch (e) {
+ l.error("problem generating login url", e.message);
+ res.status(500).send(e.message);
}
+ })
+);
- const { data: params } = await axios.get(url);
- res.send(params);
- } catch (e) {
- l.error("problem decoding lnurl", e.message);
- res.status(500).send(e.message);
- }
-}));
+app.get(
+ "/decode",
+ ah(async (req, res, next) => {
+ const { text } = req.query;
+
+ try {
+ const url = lnurl.decode(text);
+ let spl = url.split("?");
+ if (spl.length > 1 && qs.parse(spl[1]).tag === "login") {
+ return res.send({
+ tag: "login",
+ k1: qs.parse(spl[1]).k1,
+ callback: url,
+ domain: url
+ .split("://")[1]
+ .split("/")[0]
+ .split("@")
+ .slice(-1)[0]
+ .split(":")[0],
+ });
+ }
+
+ const { data: params } = await axios.get(url);
+ res.send(params);
+ } catch (e) {
+ l.error("problem decoding lnurl", e.message);
+ res.status(500).send(e.message);
+ }
+ })
+);
lnurlServer.bindToHook(
"middleware:signedLnurl:afterCheckSignature",
@@ -221,36 +252,29 @@ lnurlServer.bindToHook(
if (account.balance < amount) {
throw new Error("Insufficient funds");
}
-
- l.info("lnurl payment", user.username, amount);
}
const recipient = recipients[secret];
if (recipient) {
- setTimeout(async () => {
- try {
- let { invoices } = await lnb.listInvoices({
- pending_only: true,
- num_max_invoices: 1,
- reversed: true,
- });
- let i = invoices.find((i) => parseInt(i.value) === amount);
- if (i) {
- await db.Invoice.create({
- user_id: recipient.id,
- text: i.payment_request,
- rate: app.get("rates")[recipient.currency],
- currency: recipient.currency,
- amount: Math.round(amount / 1000),
- tip: 0,
- network: "BTC",
- });
- }
- } catch (e) {
- l.error("problem trying to get create payment invoice", e);
- }
- }, 1000);
+ setTimeout(async () => {
+ let { invoices } = await lnb.listInvoices({
+ pending_only: true,
+ });
+
+ if (invoices.length) {
+ let invoice = await db.Invoice.create({
+ user_id: recipient.id,
+ text: invoices[invoices.length - 1].payment_request,
+ rate: app.get("rates")[recipient.currency],
+ currency: recipient.currency,
+ amount,
+ tip: 0,
+ network: "BTC",
+ });
+ l.info("invoice created", invoice.text, invoice.amount);
+ }
+ }, 100);
}
}
@@ -337,6 +361,7 @@ lnurlServer.bindToHook(
let p = payments.find((p) => p.payment_request === pr);
if (p) {
+ l.info("found payment", pr);
payment.fee = p.fee;
account.balance -= p.fee;
await account.save();
diff --git a/routes/payments.js b/routes/payments.js
index dcd76f2e..d88c95a7 100644
--- a/routes/payments.js
+++ b/routes/payments.js
@@ -95,7 +95,7 @@ const read = require("../lib/read");
"/lightning/channelRequest",
require("./lightning/channelRequest")
);
- app.post("/lightning/invoice", auth, require("./lightning/invoice"));
+ app.post("/lightning/invoice", require("./lightning/invoice"));
app.post("/lightning/query", auth, require("./lightning/query"));
app.post("/lightning/send", auth, require("./lightning/send"));
require("./lightning/receive");
diff --git a/routes/users.js b/routes/users.js
index 1a0f82c1..471a4e5e 100644
--- a/routes/users.js
+++ b/routes/users.js
@@ -294,23 +294,15 @@ app.post(
})
);
-app.post(
+app.get(
"/address",
- auth,
ah(async (req, res) => {
if (!config.bitcoin) return res.status(500).send("Bitcoin not configured");
- let { user } = req;
- delete addresses[user.address];
-
if (config.bitcoin.walletpass)
await bc.walletPassphrase(config.bitcoin.walletpass, 300);
- user.address = await bc.getNewAddress("", req.body.type || "bech32");
- await user.save();
- addresses[user.address] = user.username;
- emit(user.username, "user", user);
- res.send(user.address);
+ res.send(await bc.getNewAddress("", req.query.type || "bech32"));
})
);