Skip to content
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

Add LNURL payRequest #291

Closed
wants to merge 1 commit into from
Closed
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
35 changes: 35 additions & 0 deletions class/User.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ export class User {
this._bitcoindrpc = bitcoindrpc;
this._lightning = lightning;
this._userid = false;
this._publicid = false;
this._login = false;
this._password = false;
this._balance = 0;
Expand All @@ -43,6 +44,18 @@ export class User {
return this._refresh_token;
}

async loadByPublicId(publicId) {
if (!publicId) return false;
let userid = await this._redis.get('userid_for_' + publicId);

if (userid) {
this._userid = userid;
Copy link

Choose a reason for hiding this comment

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

shouldn't you also set this._publicid?

Copy link

Choose a reason for hiding this comment

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

Also, all the loadBy* methods should also fetch _publicid and store it in the _publicid.

return true;
}

return false;
}

async loadByAuthorization(authorization) {
if (!authorization) return false;
let access_token = authorization.replace('Bearer ', '');
Expand Down Expand Up @@ -76,9 +89,14 @@ export class User {

buffer = crypto.randomBytes(24);
let userid = buffer.toString('hex');

buffer = crypto.randomBytes(24);
let publicid = buffer.toString('hex');

this._login = login;
this._password = password;
this._userid = userid;
this._publicid = publicid;
await this._saveUserToDatabase();
}

Expand Down Expand Up @@ -131,6 +149,21 @@ export class User {
if (config.bitcoind) return this._bitcoindrpc.request('importaddress', [address, address, false]);
}

async getPublicId() {
if (!this._userid) return;
Copy link

Choose a reason for hiding this comment

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

if this._publicid exists, it should probably return early.


// Old user without publicid compatibility
let publicid = await this._redis.get('publicid_for_' + this._userid);
if (!publicid) {
const buffer = crypto.randomBytes(24);
publicid = buffer.toString('hex');
await this._redis.set('publicid_for_' + this._userid, publicid);
await this._redis.set('userid_for_' + publicid, this._userid);
}

return publicid;
}

/**
* LndHub no longer relies on redis balance as source of truth, this is
* more a cache now. See `this.getCalculatedBalance()` to get correct balance.
Expand Down Expand Up @@ -494,6 +527,8 @@ export class User {
async _saveUserToDatabase() {
let key;
await this._redis.set((key = 'user_' + this._login + '_' + this._hash(this._password)), this._userid);
await this._redis.set('userid_for_' + this._publicid, this._userid);
await this._redis.set('publicid_for_' + this._userid, this._publicid);
}

/**
Expand Down
78 changes: 78 additions & 0 deletions controllers/api.js
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ const config = require('../config');
let express = require('express');
let router = express.Router();
let logger = require('../utils/logger');
let crypto = require('crypto');
let { bech32 } = require('bech32');
const MIN_BTC_BLOCK = 670000;
console.log('using config', JSON.stringify(config));

Expand Down Expand Up @@ -164,6 +166,17 @@ router.post('/auth', postLimiter, async function (req, res) {
}
});

router.get('/publicid', postLimiter, async function (req, res) {
logger.log('/publicid', [req.id]);
let u = new User(redis, bitcoinclient, lightning);
if (!(await u.loadByAuthorization(req.headers.authorization))) {
return errorBadAuth(res);
}

logger.log('/publicid', [req.id, 'userid: ' + u.getUserId(), 'invoice: ' + req.body.invoice]);
res.send(await u.getPublicId());
Copy link

Choose a reason for hiding this comment

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

Shouldn't you return json here?

Copy link

Choose a reason for hiding this comment

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

Also, you might want to return the public id in the response on account creation.

});

router.post('/addinvoice', postLimiter, async function (req, res) {
logger.log('/addinvoice', [req.id]);
let u = new User(redis, bitcoinclient, lightning);
Expand Down Expand Up @@ -192,6 +205,71 @@ router.post('/addinvoice', postLimiter, async function (req, res) {
);
});

router.get('/lnurlp/:publicid/qr', postLimiter, async function (req, res) {
Copy link
Contributor

Choose a reason for hiding this comment

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

Suggested change
router.get('/lnurlp/:publicid/qr', postLimiter, async function (req, res) {
router.get('/lnurlp/:publicid/qr', postLimiter, async function (req, res) {

With this, Lightning addresses [email protected] would be possible.

logger.log('/lnurlp/:publicid', [req.id]);
let u = new User(redis, bitcoinclient, lightning);
const publicid = req.params.publicid;
if (!(await u.loadByPublicId(publicid))) {
return errorBadArguments(res);
}

let host = req.headers.host;
const url = req.protocol + '://' + host + '/lnurlp/' + publicid;
const words = bech32.toWords(Buffer.from(url, 'utf8'));
const lnurlp = bech32.encode('lnurl', words, 1023);
res.send({ lnurlp });
});

router.get('/lnurlp/:publicid', postLimiter, async function (req, res) {
logger.log('/lnurlp/:publicid', [req.id]);
let u = new User(redis, bitcoinclient, lightning);
const publicid = req.params.publicid;
if (!(await u.loadByPublicId(publicid))) {
return errorBadArguments(res);
}

const metadata = JSON.stringify([['text/plain', 'Static QR code provided by BlueWallet.io']]);

if (!req.query.amount) {
let host = req.headers.host;
return res.send({
callback: req.protocol + '://' + host + '/lnurlp/' + publicid,
maxSendable: 1000000000,
minSendable: 1000,
metadata,
tag: 'payRequest',
});
}

const satAmount = req.query.amount / 1000;
Copy link

Choose a reason for hiding this comment

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

Note: Currently the largest channel is 10 BTC, so in millisatoshis, that would fit within Number.MAX_SAFE_INTEGER, but keep in mind that once you pass 90071.99 BTC this will break in weird ways silently.

I am sure other parts of LNDHub assume all integers will be fine too... so this is probably not a problem worth fixing right now.

Copy link

Choose a reason for hiding this comment

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

Also, this should enforce the minSendable / maxSendable you return above.

Return an error if they try to request an amount outside the parameters.


const invoice = new Invo(redis, bitcoinclient, lightning);
const r_preimage = invoice.makePreimageHex();
lightning.addInvoice(
{
memo: '',
value: satAmount,
expiry: 3600 * 24,
r_preimage: Buffer.from(r_preimage, 'hex').toString('base64'),
description_hash: crypto.createHash('sha256').update(metadata).digest(),
},
async function (err, info) {
if (err) return errorLnd(res);

await u.saveUserInvoice(info);
await invoice.savePreimage(r_preimage);

res.send({
status: 'OK',
successAction: { tag: 'message', message: 'Payment received!' },
routes: [],
pr: info.payment_request,
disposable: false,
});
},
);
});

router.post('/payinvoice', async function (req, res) {
let u = new User(redis, bitcoinclient, lightning);
if (!(await u.loadByAuthorization(req.headers.authorization))) {
Expand Down
18 changes: 15 additions & 3 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

1 change: 1 addition & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@
"@babel/register": "^7.14.5",
"@grpc/grpc-js": "^1.3.7",
"@grpc/proto-loader": "^0.6.4",
"bech32": "^2.0.0",
"bignumber.js": "^9.0.1",
"bitcoinjs-lib": "^5.2.0",
"bolt11": "^1.3.2",
Expand Down