fix nip57?

This commit is contained in:
keyan 2023-02-15 11:20:26 -06:00
parent 9f2c8d64bc
commit 30cde2ea38
7 changed files with 87 additions and 38 deletions

View File

@ -200,7 +200,7 @@ export default {
// set expires at to 3 hours into future
const expiresAt = new Date(new Date().setHours(new Date().getHours() + 3))
const description = `${amount} sats for @${user.name} on stacker.news`
const description = `Funding @${user.name} on stacker.news`
try {
const invoice = await createInvoice({
description: user.hideInvoiceDesc ? undefined : description,
@ -211,7 +211,7 @@ export default {
const [inv] = await serialize(models,
models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, ${invoice.request},
${expiresAt}, ${amount * 1000}, ${me.id})`)
${expiresAt}, ${amount * 1000}, ${me.id}, ${description})`)
return inv
} catch (error) {

View File

@ -1,7 +1,7 @@
import models from '../../../../api/models'
import lnd from '../../../../api/lnd'
import { createInvoice } from 'ln-service'
import { lnurlPayDescriptionHashForUser, lnurlPayMetadataString } from '../../../../lib/lnurl'
import { lnurlPayDescriptionHashForUser } from '../../../../lib/lnurl'
import serialize from '../../../../api/resolvers/serial'
import * as secp256k1 from '@noble/secp256k1'
import { createHash } from 'crypto'
@ -13,22 +13,22 @@ export default async ({ query: { username, amount, nostr } }, res) => {
}
try {
// if nostr, decode, validate sig, check tags, set description hash
let description, descriptionHash
let description, descriptionHash, noteStr
if (nostr) {
const noteStr = decodeURIComponent(nostr)
noteStr = decodeURIComponent(nostr)
const note = JSON.parse(noteStr)
const hasPTag = note.tags?.filter(t => t[0] === 'p').length >= 1
const hasETag = note.tags?.filter(t => t[0] === 'e').length <= 1
if (await secp256k1.schnorr.verify(note.sig, note.id, note.pubkey) &&
hasPTag && hasETag) {
description = noteStr
description = user.hideInvoiceDesc ? undefined : 'zap'
descriptionHash = createHash('sha256').update(noteStr).digest('hex')
} else {
res.status(400).json({ status: 'ERROR', reason: 'invalid NIP-57 note' })
return
}
} else {
description = user.hideInvoiceDesc ? undefined : lnurlPayMetadataString(username)
description = user.hideInvoiceDesc ? undefined : `Funding @${username} on stacker.news`
descriptionHash = lnurlPayDescriptionHashForUser(username)
}
@ -48,7 +48,7 @@ export default async ({ query: { username, amount, nostr } }, res) => {
await serialize(models,
models.$queryRaw`SELECT * FROM create_invoice(${invoice.id}, ${invoice.request},
${expiresAt}, ${Number(amount)}, ${user.id})`)
${expiresAt}, ${Number(amount)}, ${user.id}, ${noteStr || description})`)
return res.status(200).json({
pr: invoice.request,

View File

@ -0,0 +1,2 @@
-- AlterTable
ALTER TABLE "Invoice" ADD COLUMN "desc" TEXT;

View File

@ -0,0 +1,36 @@
DROP FUNCTION create_invoice(hash TEXT, bolt11 TEXT, expires_at timestamp(3) without time zone, msats_req BIGINT, user_id INTEGER);
CREATE OR REPLACE FUNCTION create_invoice(hash TEXT, bolt11 TEXT, expires_at timestamp(3) without time zone, msats_req BIGINT, user_id INTEGER, idesc TEXT)
RETURNS "Invoice"
LANGUAGE plpgsql
AS $$
DECLARE
invoice "Invoice";
limit_reached BOOLEAN;
too_much BOOLEAN;
BEGIN
PERFORM ASSERT_SERIALIZED();
SELECT count(*) >= 10, coalesce(sum("msatsRequested"),0)+coalesce(max(users.msats), 0)+msats_req > 1000000000 INTO limit_reached, too_much
FROM "Invoice"
JOIN users on "userId" = users.id
WHERE "userId" = user_id AND "expiresAt" > now_utc() AND "confirmedAt" is null AND cancelled = false;
-- prevent more than 10 pending invoices
IF limit_reached THEN
RAISE EXCEPTION 'SN_INV_PENDING_LIMIT';
END IF;
-- prevent pending invoices + msats from exceeding 1,000,000 sats
IF too_much THEN
RAISE EXCEPTION 'SN_INV_EXCEED_BALANCE';
END IF;
INSERT INTO "Invoice" (hash, bolt11, "expiresAt", "msatsRequested", "userId", created_at, updated_at, "desc")
VALUES (hash, bolt11, expires_at, msats_req, user_id, now_utc(), now_utc(), idesc) RETURNING * INTO invoice;
INSERT INTO pgboss.job (name, data, retrylimit, retrybackoff, startafter)
VALUES ('checkInvoice', jsonb_build_object('hash', hash), 21, true, now() + interval '10 seconds');
RETURN invoice;
END;
$$;

View File

@ -442,6 +442,7 @@ model Invoice {
hash String @unique
bolt11 String
desc String?
expiresAt DateTime
confirmedAt DateTime?
msatsRequested BigInt

View File

@ -3,13 +3,18 @@ const { Relay, signId, calculateId, getPublicKey } = require('nostr')
const nostrOptions = { startAfter: 5, retryLimit: 21, retryBackoff: true }
function nip57 ({ boss, lnd }) {
function nip57 ({ boss, lnd, models }) {
return async function ({ data: { hash } }) {
console.log('running nip57')
let inv
let inv, lnInv
try {
inv = await getInvoice({ id: hash, lnd })
lnInv = await getInvoice({ id: hash, lnd })
inv = await models.invoice.findUnique({
where: {
hash
}
})
} catch (err) {
console.log(err)
// on lnd related errors, we manually retry which so we don't exponentially backoff
@ -18,7 +23,9 @@ function nip57 ({ boss, lnd }) {
}
try {
const desc = JSON.parse(inv.description)
// if parsing fails it's not a zap
console.log('zapping', inv.desc)
const desc = JSON.parse(inv.desc)
const ptag = desc.tags.filter(t => t?.length >= 2 && t[0] === 'p')[0]
const etag = desc.tags.filter(t => t?.length >= 2 && t[0] === 'e')[0]
const relays = desc.tags.find(t => t?.length >= 2 && t[0] === 'relays').slice(1)
@ -27,45 +34,51 @@ function nip57 ({ boss, lnd }) {
if (etag) {
tags.push(etag)
}
tags.push(['bolt11', inv.request])
tags.push(['description', inv.description])
tags.push(['preimage', inv.secret])
tags.push(['bolt11', lnInv.request])
tags.push(['description', inv.desc])
tags.push(['preimage', lnInv.secret])
const e = {
kind: 9735,
pubkey: getPublicKey(process.env.NOSTR_PRIVATE_KEY),
created_at: Math.floor(new Date(inv.confirmed_at).getTime() / 1000),
created_at: Math.floor(new Date(lnInv.confirmed_at).getTime() / 1000),
content: '',
tags
}
e.id = await calculateId(e)
e.sig = await signId(process.env.NOSTR_PRIVATE_KEY, e.id)
relays.forEach(r => {
const timeout = 1000
const relay = Relay(r)
console.log('zap note', e, relays)
await Promise.allSettled(
relays.map(r => new Promise((resolve, reject) => {
const timeout = 1000
const relay = Relay(r)
function timedout () {
relay.close()
}
function timedout () {
relay.close()
console.log('failed to send to', r)
reject(new Error('relay timeout'))
}
let timer = setTimeout(timedout, timeout)
let timer = setTimeout(timedout, timeout)
relay.on('open', () => {
clearTimeout(timer)
timer = setTimeout(timedout, timeout)
relay.send(['EVENT', e])
})
relay.on('open', () => {
clearTimeout(timer)
timer = setTimeout(timedout, timeout)
relay.send(['EVENT', e])
})
relay.on('ok', () => {
clearTimeout(timer)
relay.close()
})
})
relay.on('ok', () => {
clearTimeout(timer)
relay.close()
console.log('sent zap to', r)
resolve()
})
})))
} catch (e) {
console.log(e)
}
console.log('dont running nip57')
console.log('done running nip57')
}
}

View File

@ -19,10 +19,7 @@ function checkInvoice ({ boss, models, lnd }) {
if (inv.is_confirmed) {
await serialize(models,
models.$executeRaw`SELECT confirm_invoice(${inv.id}, ${Number(inv.received_mtokens)})`)
try {
JSON.parse(inv.description)
await boss.send('nip57', { hash })
} catch {}
await boss.send('nip57', { hash })
} else if (inv.is_canceled) {
// mark as cancelled
await serialize(models,