fix nip57?
This commit is contained in:
parent
9f2c8d64bc
commit
30cde2ea38
|
@ -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) {
|
||||
|
|
|
@ -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,
|
||||
|
|
|
@ -0,0 +1,2 @@
|
|||
-- AlterTable
|
||||
ALTER TABLE "Invoice" ADD COLUMN "desc" TEXT;
|
|
@ -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;
|
||||
$$;
|
|
@ -442,6 +442,7 @@ model Invoice {
|
|||
|
||||
hash String @unique
|
||||
bolt11 String
|
||||
desc String?
|
||||
expiresAt DateTime
|
||||
confirmedAt DateTime?
|
||||
msatsRequested BigInt
|
||||
|
|
|
@ -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')
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -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,
|
||||
|
|
Loading…
Reference in New Issue