complete nip 57 support

This commit is contained in:
keyan 2023-02-14 16:58:12 -06:00
parent ef5346000b
commit 93d4581360
6 changed files with 108 additions and 12 deletions

14
package-lock.json generated
View File

@ -7689,6 +7689,11 @@
}
}
},
"noble-secp256k1": {
"version": "1.2.14",
"resolved": "https://registry.npmjs.org/noble-secp256k1/-/noble-secp256k1-1.2.14.tgz",
"integrity": "sha512-GSCXyoZBUaaPwVWdYncMEmzlSUjF9J/YeEHpklYJCyg8wPuJP3NzDx0BkiwArzINkdX2HJHvUJhL6vVWPOQQcg=="
},
"node-abort-controller": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/node-abort-controller/-/node-abort-controller-3.0.1.tgz",
@ -7821,6 +7826,15 @@
"resolved": "https://registry.npmjs.org/normalize-url/-/normalize-url-7.2.0.tgz",
"integrity": "sha512-uhXOdZry0L6M2UIo9BTt7FdpBDiAGN/7oItedQwPKh8jh31ZlvC8U9Xl/EJ3aijDHaywXTW3QbZ6LuCocur1YA=="
},
"nostr": {
"version": "0.2.7",
"resolved": "https://registry.npmjs.org/nostr/-/nostr-0.2.7.tgz",
"integrity": "sha512-Yq5tkiCTJtohs7H7Fx+Ki83bR8b3xapdwYi17HunhW5EQfZ9rI0cIXFyihj2ZRkv/0YYvVBiCp/KIaLYt5LyfQ==",
"requires": {
"noble-secp256k1": "^1.2.14",
"ws": "^8.8.1"
}
},
"nprogress": {
"version": "0.2.0",
"resolved": "https://registry.npmjs.org/nprogress/-/nprogress-0.2.0.tgz",

View File

@ -46,6 +46,7 @@
"next-seo": "^4.29.0",
"nextjs-progressbar": "0.0.16",
"node-s3-url-encode": "^0.0.4",
"nostr": "^0.2.7",
"opentimestamps": "^0.4.9",
"page-metadata-parser": "^1.1.4",
"pageres": "^7.1.0",

View File

@ -1,7 +1,7 @@
import models from '../../../../api/models'
import lnd from '../../../../api/lnd'
import { createInvoice } from 'ln-service'
import { lnurlPayDescriptionHashForUser } from '../../../../lib/lnurl'
import { lnurlPayDescriptionHashForUser, lnurlPayMetadataString } from '../../../../lib/lnurl'
import serialize from '../../../../api/resolvers/serial'
import * as secp256k1 from '@noble/secp256k1'
import { createHash } from 'crypto'
@ -21,14 +21,14 @@ export default async ({ query: { username, amount, nostr } }, res) => {
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 = user.hideInvoiceDesc ? undefined : `${amount} msats for @${user.name} on stacker.news via NIP-57`
description = noteStr
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 : `${amount} msats for @${user.name} on stacker.news`
description = user.hideInvoiceDesc ? undefined : lnurlPayMetadataString(username)
descriptionHash = lnurlPayDescriptionHashForUser(username)
}

View File

@ -11,7 +11,10 @@ const { ApolloClient, HttpLink, InMemoryCache } = require('@apollo/client')
const { indexItem, indexAllItems } = require('./search')
const { timestampItem } = require('./ots')
const { computeStreaks, checkStreak } = require('./streak')
const { nip57 } = require('./nostr')
const fetch = require('cross-fetch')
const { authenticatedLndGrpc } = require('ln-service')
async function work () {
const boss = new PgBoss(process.env.DATABASE_URL)
@ -34,7 +37,13 @@ async function work () {
}
})
const args = { boss, models, apollo }
const { lnd } = authenticatedLndGrpc({
cert: process.env.LND_CERT,
macaroon: process.env.LND_MACAROON,
socket: process.env.LND_SOCKET
})
const args = { boss, models, apollo, lnd }
boss.on('error', error => console.error(error))
@ -50,6 +59,7 @@ async function work () {
await boss.work('earn', earn(args))
await boss.work('streak', computeStreaks(args))
await boss.work('checkStreak', checkStreak(args))
await boss.work('nip57', nip57(args))
console.log('working jobs')
}

72
worker/nostr.js Normal file
View File

@ -0,0 +1,72 @@
const { getInvoice } = require('ln-service')
const { Relay, signId, calculateId, getPublicKey } = require('nostr')
const nostrOptions = { startAfter: 5, retryLimit: 21, retryBackoff: true }
function nip57 ({ boss, lnd }) {
return async function ({ data: { hash } }) {
console.log('running nip57')
let inv
try {
inv = await getInvoice({ id: hash, lnd })
} catch (err) {
console.log(err)
// on lnd related errors, we manually retry which so we don't exponentially backoff
await boss.send('nip57', { hash }, nostrOptions)
return
}
try {
const desc = JSON.parse(inv.description)
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)
const tags = [ptag]
if (etag) {
tags.push(etag)
}
tags.push(['bolt11', inv.request])
tags.push(['description', inv.description])
tags.push(['preimage', inv.secret])
const e = {
kind: 9735,
pubkey: getPublicKey(process.env.NOSTR_PRIVATE_KEY),
created_at: Math.floor(new Date(inv.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)
function timedout () {
relay.close()
}
let timer = setTimeout(timedout, timeout)
relay.on('open', () => {
clearTimeout(timer)
timer = setTimeout(timedout, timeout)
relay.send(['EVENT', e])
})
relay.on('ok', () => {
clearTimeout(timer)
relay.close()
})
})
} catch (e) {
console.log(e)
}
console.log('dont running nip57')
}
}
module.exports = { nip57 }

View File

@ -1,14 +1,9 @@
const serialize = require('../api/resolvers/serial')
const { authenticatedLndGrpc, getInvoice, getPayment } = require('ln-service')
const { getInvoice, getPayment } = require('ln-service')
const { lnd } = authenticatedLndGrpc({
cert: process.env.LND_CERT,
macaroon: process.env.LND_MACAROON,
socket: process.env.LND_SOCKET
})
const walletOptions = { startAfter: 5, retryLimit: 21, retryBackoff: true }
function checkInvoice ({ boss, models }) {
function checkInvoice ({ boss, models, lnd }) {
return async function ({ data: { hash } }) {
let inv
try {
@ -24,6 +19,10 @@ function checkInvoice ({ boss, models }) {
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 {}
} else if (inv.is_canceled) {
// mark as cancelled
await serialize(models,
@ -42,7 +41,7 @@ function checkInvoice ({ boss, models }) {
}
}
function checkWithdrawal ({ boss, models }) {
function checkWithdrawal ({ boss, models, lnd }) {
return async function ({ data: { id, hash } }) {
let wdrwl
let notFound = false