NDK (#1590)
* ndk * fix: remove duplicated zap note event template * don't init Nip07 signer by default * Update wallets/nwc/server.js Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * nwc protocol parsing workaround * WebSocket polyfill for worker * increase nwc timeout * remove NDKNip46Signer type * fix type annotation * move eslint-disable camelcase to the top * pass event args to the constructor * fix error handling * Update wallets/nwc/index.js Co-authored-by: ekzyis <ek@stacker.news> * Fix type annotation --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> Co-authored-by: ekzyis <ek@stacker.news> Co-authored-by: Keyan <34140557+huumn@users.noreply.github.com>
This commit is contained in:
parent
52734940a3
commit
d73f6323ff
|
@ -1,8 +1,7 @@
|
|||
import { useCallback } from 'react'
|
||||
import { useToast } from './toast'
|
||||
import { Button } from 'react-bootstrap'
|
||||
import { DEFAULT_CROSSPOSTING_RELAYS, crosspost } from '@/lib/nostr'
|
||||
import { callWithTimeout } from '@/lib/time'
|
||||
import Nostr, { DEFAULT_CROSSPOSTING_RELAYS } from '@/lib/nostr'
|
||||
import { gql, useMutation, useQuery, useLazyQuery } from '@apollo/client'
|
||||
import { SETTINGS } from '@/fragments/users'
|
||||
import { ITEM_FULL_FIELDS, POLL_FIELDS } from '@/fragments/items'
|
||||
|
@ -204,7 +203,7 @@ export default function useCrossposter () {
|
|||
|
||||
do {
|
||||
try {
|
||||
const result = await crosspost(event, failedRelays || relays)
|
||||
const result = await Nostr.crosspost(event, { relays: failedRelays || relays })
|
||||
|
||||
if (result.error) {
|
||||
failedRelays = []
|
||||
|
@ -239,13 +238,6 @@ export default function useCrossposter () {
|
|||
}
|
||||
|
||||
const handleCrosspost = useCallback(async (itemId) => {
|
||||
try {
|
||||
const pubkey = await callWithTimeout(() => window.nostr.getPublicKey(), 10000)
|
||||
if (!pubkey) throw new Error('failed to get pubkey')
|
||||
} catch (e) {
|
||||
throw new Error(`Nostr extension error: ${e.message}`)
|
||||
}
|
||||
|
||||
let noteId
|
||||
|
||||
try {
|
||||
|
|
296
lib/nostr.js
296
lib/nostr.js
|
@ -1,8 +1,6 @@
|
|||
import { bech32 } from 'bech32'
|
||||
import { nip19 } from 'nostr-tools'
|
||||
import WebSocket from 'isomorphic-ws'
|
||||
import { callWithTimeout, withTimeout } from '@/lib/time'
|
||||
import crypto from 'crypto'
|
||||
import NDK, { NDKEvent, NDKRelaySet, NDKPrivateKeySigner, NDKNip07Signer } from '@nostr-dev-kit/ndk'
|
||||
|
||||
export const NOSTR_PUBKEY_HEX = /^[0-9a-fA-F]{64}$/
|
||||
export const NOSTR_PUBKEY_BECH32 = /^npub1[02-9ac-hj-np-z]+$/
|
||||
|
@ -17,155 +15,147 @@ export const DEFAULT_CROSSPOSTING_RELAYS = [
|
|||
'wss://nostr.mutinywallet.com/',
|
||||
'wss://relay.mutinywallet.com/'
|
||||
]
|
||||
export const RELAYS_BLACKLIST = []
|
||||
|
||||
export class Relay {
|
||||
constructor (relayUrl) {
|
||||
const ws = new WebSocket(relayUrl)
|
||||
/* eslint-disable camelcase */
|
||||
|
||||
ws.onmessage = (msg) => {
|
||||
const [type, notice] = JSON.parse(msg.data)
|
||||
if (type === 'NOTICE') {
|
||||
console.log('relay notice:', notice)
|
||||
}
|
||||
}
|
||||
/**
|
||||
* @import {NDKSigner} from '@nostr-dev-kit/ndk'
|
||||
* @import { NDK } from '@nostr-dev-kit/ndk'
|
||||
* @import {NDKNwc} from '@nostr-dev-kit/ndk'
|
||||
* @typedef {Object} Nostr
|
||||
* @property {NDK} ndk
|
||||
* @property {function(string, {logger: Object}): Promise<NDKNwc>} nwc
|
||||
* @property {function(Object, {privKey: string, signer: NDKSigner}): Promise<NDKEvent>} sign
|
||||
* @property {function(Object, {relays: Array<string>, privKey: string, signer: NDKSigner}): Promise<NDKEvent>} publish
|
||||
*/
|
||||
export class Nostr {
|
||||
/**
|
||||
* @type {NDK}
|
||||
*/
|
||||
_ndk = null
|
||||
|
||||
ws.onerror = (err) => {
|
||||
console.error('websocket error:', err.message)
|
||||
this.error = err.message
|
||||
}
|
||||
|
||||
this.ws = ws
|
||||
this.url = relayUrl
|
||||
this.error = null
|
||||
constructor ({ privKey, defaultSigner, relays, supportNip07 = false, ...ndkOptions } = {}) {
|
||||
this._ndk = new NDK({
|
||||
explicitRelayUrls: relays,
|
||||
blacklistRelayUrls: RELAYS_BLACKLIST,
|
||||
autoConnectUserRelays: false,
|
||||
autoFetchUserMutelist: false,
|
||||
clientName: 'stacker.news',
|
||||
signer: defaultSigner ?? this.getSigner({ privKey, supportNip07 }),
|
||||
...ndkOptions
|
||||
})
|
||||
}
|
||||
|
||||
static async connect (url, { timeout } = {}) {
|
||||
const relay = new Relay(url)
|
||||
await relay.waitUntilConnected({ timeout })
|
||||
return relay
|
||||
/**
|
||||
* @type {NDK}
|
||||
*/
|
||||
get ndk () {
|
||||
return this._ndk
|
||||
}
|
||||
|
||||
get connected () {
|
||||
return this.ws.readyState === WebSocket.OPEN
|
||||
/**
|
||||
*
|
||||
* @param {Object} args
|
||||
* @param {string} [args.privKey] - private key to use for signing
|
||||
* @param {boolean} [args.supportNip07] - whether to use NIP-07 signer if available
|
||||
* @returns {NDKPrivateKeySigner | NDKNip07Signer | null} - a signer instance
|
||||
*/
|
||||
getSigner ({ privKey, supportNip07 = true } = {}) {
|
||||
if (privKey) return new NDKPrivateKeySigner(privKey)
|
||||
if (supportNip07 && typeof window !== 'undefined' && window?.nostr) return new NDKNip07Signer()
|
||||
return null
|
||||
}
|
||||
|
||||
get closed () {
|
||||
return this.ws.readyState === WebSocket.CLOSING || this.ws.readyState === WebSocket.CLOSED
|
||||
/**
|
||||
* @param {Object} rawEvent
|
||||
* @param {number} rawEvent.kind
|
||||
* @param {number} rawEvent.created_at
|
||||
* @param {string} rawEvent.content
|
||||
* @param {Array<Array<string>>} rawEvent.tags
|
||||
* @param {Object} context
|
||||
* @param {string} context.privKey
|
||||
* @param {NDKSigner} context.signer
|
||||
* @returns {Promise<NDKEvent>}
|
||||
*/
|
||||
async sign ({ kind, created_at, content, tags }, { signer } = {}) {
|
||||
const event = new NDKEvent(this.ndk, {
|
||||
kind,
|
||||
created_at,
|
||||
content,
|
||||
tags
|
||||
})
|
||||
signer ??= this.ndk.signer
|
||||
if (!signer) throw new Error('no way to sign this event, please provide a signer or private key')
|
||||
await event.sign(signer)
|
||||
return event
|
||||
}
|
||||
|
||||
async waitUntilConnected ({ timeout } = {}) {
|
||||
let interval
|
||||
/**
|
||||
* @param {Object} rawEvent
|
||||
* @param {number} rawEvent.kind
|
||||
* @param {number} rawEvent.created_at
|
||||
* @param {string} rawEvent.content
|
||||
* @param {Array<Array<string>>} rawEvent.tags
|
||||
* @param {Object} context
|
||||
* @param {Array<string>} context.relays
|
||||
* @param {string} context.privKey
|
||||
* @param {NDKSigner} context.signer
|
||||
* @param {number} context.timeout
|
||||
* @returns {Promise<NDKEvent>}
|
||||
*/
|
||||
async publish ({ created_at, content, tags = [], kind }, { relays, signer, timeout } = {}) {
|
||||
const event = await this.sign({ kind, created_at, content, tags }, { signer })
|
||||
|
||||
const checkPromise = new Promise((resolve, reject) => {
|
||||
interval = setInterval(() => {
|
||||
if (this.connected) {
|
||||
resolve()
|
||||
}
|
||||
if (this.closed) {
|
||||
reject(new Error(`failed to connect to ${this.url}: ` + this.error))
|
||||
}
|
||||
}, 100)
|
||||
const successfulRelays = []
|
||||
const failedRelays = []
|
||||
|
||||
const relaySet = NDKRelaySet.fromRelayUrls(relays, this.ndk, true)
|
||||
|
||||
event.on('relay:publish:failed', (relay, error) => {
|
||||
failedRelays.push({ relay: relay.url, error })
|
||||
})
|
||||
|
||||
for (const relay of (await relaySet.publish(event, timeout))) {
|
||||
successfulRelays.push(relay.url)
|
||||
}
|
||||
|
||||
return {
|
||||
event,
|
||||
successfulRelays,
|
||||
failedRelays
|
||||
}
|
||||
}
|
||||
|
||||
async crosspost ({ created_at, content, tags = [], kind }, { relays = DEFAULT_CROSSPOSTING_RELAYS, signer, timeout } = {}) {
|
||||
try {
|
||||
return await withTimeout(checkPromise, timeout)
|
||||
} catch (err) {
|
||||
this.close()
|
||||
throw err
|
||||
} finally {
|
||||
clearInterval(interval)
|
||||
}
|
||||
}
|
||||
signer ??= this.getSigner({ supportNip07: true })
|
||||
const { event: signedEvent, successfulRelays, failedRelays } = await this.publish({ created_at, content, tags, kind }, { relays, signer, timeout })
|
||||
|
||||
close () {
|
||||
const state = this.ws.readyState
|
||||
if (state !== WebSocket.CLOSING && state !== WebSocket.CLOSED) {
|
||||
this.ws.close()
|
||||
}
|
||||
}
|
||||
|
||||
async publish (event, { timeout } = {}) {
|
||||
const ws = this.ws
|
||||
|
||||
let listener
|
||||
const ackPromise = new Promise((resolve, reject) => {
|
||||
listener = function onmessage (msg) {
|
||||
const [type, eventId, accepted, reason] = JSON.parse(msg.data)
|
||||
|
||||
if (type !== 'OK' || eventId !== event.id) return
|
||||
|
||||
if (accepted) {
|
||||
resolve(eventId)
|
||||
} else {
|
||||
reject(new Error(reason || `event rejected: ${eventId}`))
|
||||
}
|
||||
let noteId = null
|
||||
if (signedEvent.kind !== 1) {
|
||||
noteId = await nip19.naddrEncode({
|
||||
kind: signedEvent.kind,
|
||||
pubkey: signedEvent.pubkey,
|
||||
identifier: signedEvent.tags[0][1]
|
||||
})
|
||||
} else {
|
||||
noteId = hexToBech32(signedEvent.id, 'note')
|
||||
}
|
||||
|
||||
ws.addEventListener('message', listener)
|
||||
|
||||
ws.send(JSON.stringify(['EVENT', event]))
|
||||
})
|
||||
|
||||
try {
|
||||
return await withTimeout(ackPromise, timeout)
|
||||
} finally {
|
||||
ws.removeEventListener('message', listener)
|
||||
}
|
||||
}
|
||||
|
||||
async fetch (filter, { timeout } = {}) {
|
||||
const ws = this.ws
|
||||
|
||||
let listener
|
||||
const ackPromise = new Promise((resolve, reject) => {
|
||||
const id = crypto.randomBytes(16).toString('hex')
|
||||
|
||||
const events = []
|
||||
let eose = false
|
||||
|
||||
listener = function onmessage (msg) {
|
||||
const [type, subId, event] = JSON.parse(msg.data)
|
||||
|
||||
if (subId !== id) return
|
||||
|
||||
if (type === 'EVENT') {
|
||||
events.push(event)
|
||||
if (eose) {
|
||||
// EOSE was already received:
|
||||
// return first event after EOSE
|
||||
resolve(events)
|
||||
}
|
||||
return
|
||||
}
|
||||
|
||||
if (type === 'CLOSED') {
|
||||
return resolve(events)
|
||||
}
|
||||
|
||||
if (type === 'EOSE') {
|
||||
eose = true
|
||||
if (events.length > 0) {
|
||||
// we already received events before EOSE:
|
||||
// return all events before EOSE
|
||||
ws.send(JSON.stringify(['CLOSE', id]))
|
||||
return resolve(events)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
ws.addEventListener('message', listener)
|
||||
|
||||
ws.send(JSON.stringify(['REQ', id, ...filter]))
|
||||
})
|
||||
|
||||
try {
|
||||
return await withTimeout(ackPromise, timeout)
|
||||
} finally {
|
||||
ws.removeEventListener('message', listener)
|
||||
return { successfulRelays, failedRelays, noteId }
|
||||
} catch (error) {
|
||||
console.error('Crosspost error:', error)
|
||||
return { error }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @type {Nostr}
|
||||
*/
|
||||
export default new Nostr()
|
||||
|
||||
export function hexToBech32 (hex, prefix = 'npub') {
|
||||
return bech32.encode(prefix, bech32.toWords(Buffer.from(hex, 'hex')))
|
||||
}
|
||||
|
@ -186,49 +176,3 @@ export function nostrZapDetails (zap) {
|
|||
|
||||
return { npub, content, note }
|
||||
}
|
||||
|
||||
async function publishNostrEvent (signedEvent, relayUrl) {
|
||||
const timeout = 3000
|
||||
const relay = await Relay.connect(relayUrl, { timeout })
|
||||
try {
|
||||
await relay.publish(signedEvent, { timeout })
|
||||
} finally {
|
||||
relay.close()
|
||||
}
|
||||
}
|
||||
|
||||
export async function crosspost (event, relays = DEFAULT_CROSSPOSTING_RELAYS) {
|
||||
try {
|
||||
const signedEvent = await callWithTimeout(() => window.nostr.signEvent(event), 10000)
|
||||
if (!signedEvent) throw new Error('failed to sign event')
|
||||
|
||||
const promises = relays.map(r => publishNostrEvent(signedEvent, r))
|
||||
const results = await Promise.allSettled(promises)
|
||||
const successfulRelays = []
|
||||
const failedRelays = []
|
||||
|
||||
results.forEach((result, index) => {
|
||||
if (result.status === 'fulfilled') {
|
||||
successfulRelays.push(relays[index])
|
||||
} else {
|
||||
failedRelays.push({ relay: relays[index], error: result.reason })
|
||||
}
|
||||
})
|
||||
|
||||
let noteId = null
|
||||
if (signedEvent.kind !== 1) {
|
||||
noteId = await nip19.naddrEncode({
|
||||
kind: signedEvent.kind,
|
||||
pubkey: signedEvent.pubkey,
|
||||
identifier: signedEvent.tags[0][1]
|
||||
})
|
||||
} else {
|
||||
noteId = hexToBech32(signedEvent.id, 'note')
|
||||
}
|
||||
|
||||
return { successfulRelays, failedRelays, noteId }
|
||||
} catch (error) {
|
||||
console.error('Crosspost error:', error)
|
||||
return { error }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -203,12 +203,12 @@ export function parseNwcUrl (walletConnectUrl) {
|
|||
const params = {}
|
||||
params.walletPubkey = url.host
|
||||
const secret = url.searchParams.get('secret')
|
||||
const relayUrl = url.searchParams.get('relay')
|
||||
const relayUrls = url.searchParams.getAll('relay')
|
||||
if (secret) {
|
||||
params.secret = secret
|
||||
}
|
||||
if (relayUrl) {
|
||||
params.relayUrl = relayUrl
|
||||
if (relayUrls) {
|
||||
params.relayUrls = relayUrls
|
||||
}
|
||||
return params
|
||||
}
|
||||
|
|
|
@ -147,15 +147,15 @@ addMethod(string, 'nwcUrl', function () {
|
|||
// inspired by https://github.com/jquense/yup/issues/851#issuecomment-1049705180
|
||||
try {
|
||||
string().matches(/^nostr\+?walletconnect:\/\//, { message: 'must start with nostr+walletconnect://' }).validateSync(nwcUrl)
|
||||
let relayUrl, walletPubkey, secret
|
||||
let relayUrls, walletPubkey, secret
|
||||
try {
|
||||
({ relayUrl, walletPubkey, secret } = parseNwcUrl(nwcUrl))
|
||||
({ relayUrls, walletPubkey, secret } = parseNwcUrl(nwcUrl))
|
||||
} catch {
|
||||
// invalid URL error. handle as if pubkey validation failed to not confuse user.
|
||||
throw new Error('pubkey must be 64 hex chars')
|
||||
}
|
||||
string().required('pubkey required').trim().matches(NOSTR_PUBKEY_HEX, 'pubkey must be 64 hex chars').validateSync(walletPubkey)
|
||||
string().required('relay url required').trim().wss('relay must use wss://').validateSync(relayUrl)
|
||||
array().of(string().required('relay url required').trim().wss('relay must use wss://')).min(1, 'at least one relay required').validateSync(relayUrls)
|
||||
string().required('secret required').trim().matches(/^[0-9a-fA-F]{64}$/, 'secret must be 64 hex chars').validateSync(secret)
|
||||
} catch (err) {
|
||||
return context.createError({ message: err.message })
|
||||
|
|
|
@ -15,6 +15,7 @@
|
|||
"@graphql-tools/schema": "^10.0.6",
|
||||
"@lightninglabs/lnc-web": "^0.3.2-alpha",
|
||||
"@noble/curves": "^1.6.0",
|
||||
"@nostr-dev-kit/ndk": "^2.10.5",
|
||||
"@opensearch-project/opensearch": "^2.12.0",
|
||||
"@prisma/client": "^5.20.0",
|
||||
"@slack/web-api": "^7.6.0",
|
||||
|
@ -4371,6 +4372,15 @@
|
|||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@noble/secp256k1": {
|
||||
"version": "2.1.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/secp256k1/-/secp256k1-2.1.0.tgz",
|
||||
"integrity": "sha512-XLEQQNdablO0XZOIniFQimiXsZDNwaYgL96dZwC54Q30imSbAOFf3NKtepc+cXyuZf5Q1HCgbqgZ2UFFuHVcEw==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@nodelib/fs.scandir": {
|
||||
"version": "2.1.5",
|
||||
"resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
|
||||
|
@ -4406,6 +4416,49 @@
|
|||
"node": ">= 8"
|
||||
}
|
||||
},
|
||||
"node_modules/@nostr-dev-kit/ndk": {
|
||||
"version": "2.10.5",
|
||||
"resolved": "https://registry.npmjs.org/@nostr-dev-kit/ndk/-/ndk-2.10.5.tgz",
|
||||
"integrity": "sha512-QEnarJL9BGCxeenSIE9jxNSDyYQYjzD30YL3sVJ9cNybNZX8tl/I1/vhEUeRRMBz/qjROLtt0M2RV68rZ205tg==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@noble/curves": "^1.6.0",
|
||||
"@noble/hashes": "^1.5.0",
|
||||
"@noble/secp256k1": "^2.1.0",
|
||||
"@scure/base": "^1.1.9",
|
||||
"debug": "^4.3.6",
|
||||
"light-bolt11-decoder": "^3.2.0",
|
||||
"nostr-tools": "^2.7.1",
|
||||
"tseep": "^1.2.2",
|
||||
"typescript-lru-cache": "^2.0.0",
|
||||
"utf8-buffer": "^1.0.0",
|
||||
"websocket-polyfill": "^0.0.3"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=16"
|
||||
}
|
||||
},
|
||||
"node_modules/@nostr-dev-kit/ndk/node_modules/@noble/hashes": {
|
||||
"version": "1.5.0",
|
||||
"resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.5.0.tgz",
|
||||
"integrity": "sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": "^14.21.3 || >=16"
|
||||
},
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@nostr-dev-kit/ndk/node_modules/@scure/base": {
|
||||
"version": "1.1.9",
|
||||
"resolved": "https://registry.npmjs.org/@scure/base/-/base-1.1.9.tgz",
|
||||
"integrity": "sha512-8YKhl8GHiNI/pU2VMaofa2Tor7PJRAjwQLBBuilkJ9L5+13yVbC7JO/wS7piioAvPSwR3JKM1IJ/u4xQzbcXKg==",
|
||||
"license": "MIT",
|
||||
"funding": {
|
||||
"url": "https://paulmillr.com/funding/"
|
||||
}
|
||||
},
|
||||
"node_modules/@opensearch-project/opensearch": {
|
||||
"version": "2.12.0",
|
||||
"resolved": "https://registry.npmjs.org/@opensearch-project/opensearch/-/opensearch-2.12.0.tgz",
|
||||
|
@ -7310,6 +7363,19 @@
|
|||
"node": ">=4"
|
||||
}
|
||||
},
|
||||
"node_modules/bufferutil": {
|
||||
"version": "4.0.8",
|
||||
"resolved": "https://registry.npmjs.org/bufferutil/-/bufferutil-4.0.8.tgz",
|
||||
"integrity": "sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/builtins": {
|
||||
"version": "5.0.1",
|
||||
"resolved": "https://registry.npmjs.org/builtins/-/builtins-5.0.1.tgz",
|
||||
|
@ -8089,6 +8155,19 @@
|
|||
"node": ">= 10"
|
||||
}
|
||||
},
|
||||
"node_modules/d": {
|
||||
"version": "1.0.2",
|
||||
"resolved": "https://registry.npmjs.org/d/-/d-1.0.2.tgz",
|
||||
"integrity": "sha512-MOqHvMWF9/9MX6nza0KgvFH4HpMU0EF5uUDXqX/BtxtU8NfB0QzRtJ8Oe/6SuS4kbhyzVJwjd97EA4PKrzJ8bw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"es5-ext": "^0.10.64",
|
||||
"type": "^2.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/d3-array": {
|
||||
"version": "3.2.4",
|
||||
"resolved": "https://registry.npmjs.org/d3-array/-/d3-array-3.2.4.tgz",
|
||||
|
@ -8968,6 +9047,46 @@
|
|||
"url": "https://github.com/sponsors/ljharb"
|
||||
}
|
||||
},
|
||||
"node_modules/es5-ext": {
|
||||
"version": "0.10.64",
|
||||
"resolved": "https://registry.npmjs.org/es5-ext/-/es5-ext-0.10.64.tgz",
|
||||
"integrity": "sha512-p2snDhiLaXe6dahss1LddxqEm+SkuDvV8dnIQG0MWjyHpcMNfXKPE+/Cc0y+PhxJX3A4xGNeFCj5oc0BUh6deg==",
|
||||
"hasInstallScript": true,
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"es6-iterator": "^2.0.3",
|
||||
"es6-symbol": "^3.1.3",
|
||||
"esniff": "^2.0.1",
|
||||
"next-tick": "^1.1.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-iterator": {
|
||||
"version": "2.0.3",
|
||||
"resolved": "https://registry.npmjs.org/es6-iterator/-/es6-iterator-2.0.3.tgz",
|
||||
"integrity": "sha512-zw4SRzoUkd+cl+ZoE15A9o1oQd920Bb0iOJMQkQhl3jNc03YqVjAhG7scf9C5KWRU/R13Orf588uCC6525o02g==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"d": "1",
|
||||
"es5-ext": "^0.10.35",
|
||||
"es6-symbol": "^3.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/es6-symbol": {
|
||||
"version": "3.1.4",
|
||||
"resolved": "https://registry.npmjs.org/es6-symbol/-/es6-symbol-3.1.4.tgz",
|
||||
"integrity": "sha512-U9bFFjX8tFiATgtkJ1zg25+KviIXpgRvRHS8sau3GfhVzThRQrOeksPeT0BWW2MNZs1OEWJ1DPXOQMn0KKRkvg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d": "^1.0.2",
|
||||
"ext": "^1.7.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.12"
|
||||
}
|
||||
},
|
||||
"node_modules/esbuild": {
|
||||
"version": "0.23.1",
|
||||
"resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.23.1.tgz",
|
||||
|
@ -9581,6 +9700,21 @@
|
|||
"node": ">=6"
|
||||
}
|
||||
},
|
||||
"node_modules/esniff": {
|
||||
"version": "2.0.1",
|
||||
"resolved": "https://registry.npmjs.org/esniff/-/esniff-2.0.1.tgz",
|
||||
"integrity": "sha512-kTUIGKQ/mDPFoJ0oVfcmyJn4iBDRptjNVIzwIFR7tqWXdVI9xfA2RMwY/gbSpJG3lkdWNEjLap/NqVHZiJsdfg==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"d": "^1.0.1",
|
||||
"es5-ext": "^0.10.62",
|
||||
"event-emitter": "^0.3.5",
|
||||
"type": "^2.7.2"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=0.10"
|
||||
}
|
||||
},
|
||||
"node_modules/espree": {
|
||||
"version": "10.2.0",
|
||||
"resolved": "https://registry.npmjs.org/espree/-/espree-10.2.0.tgz",
|
||||
|
@ -9675,6 +9809,16 @@
|
|||
"node": ">= 0.6"
|
||||
}
|
||||
},
|
||||
"node_modules/event-emitter": {
|
||||
"version": "0.3.5",
|
||||
"resolved": "https://registry.npmjs.org/event-emitter/-/event-emitter-0.3.5.tgz",
|
||||
"integrity": "sha512-D9rRn9y7kLPnJ+hMq7S/nhvoKwwvVJahBi2BPmx3bvbsEdK3W9ii8cBSGjP+72/LnM4n6fo3+dkCX5FeTQruXA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"d": "1",
|
||||
"es5-ext": "~0.10.14"
|
||||
}
|
||||
},
|
||||
"node_modules/eventemitter3": {
|
||||
"version": "4.0.7",
|
||||
"resolved": "https://registry.npmjs.org/eventemitter3/-/eventemitter3-4.0.7.tgz",
|
||||
|
@ -9829,6 +9973,15 @@
|
|||
}
|
||||
]
|
||||
},
|
||||
"node_modules/ext": {
|
||||
"version": "1.7.0",
|
||||
"resolved": "https://registry.npmjs.org/ext/-/ext-1.7.0.tgz",
|
||||
"integrity": "sha512-6hxeJYaL110a9b5TEJSj0gojyHQAmA2ch5Os+ySCiA1QGdS697XWY1pzsrSjqA9LDEEgdB/KypIlR59RcLuHYw==",
|
||||
"license": "ISC",
|
||||
"dependencies": {
|
||||
"type": "^2.7.2"
|
||||
}
|
||||
},
|
||||
"node_modules/extend": {
|
||||
"version": "3.0.2",
|
||||
"resolved": "https://registry.npmjs.org/extend/-/extend-3.0.2.tgz",
|
||||
|
@ -14154,6 +14307,15 @@
|
|||
"node": ">= 0.8.0"
|
||||
}
|
||||
},
|
||||
"node_modules/light-bolt11-decoder": {
|
||||
"version": "3.2.0",
|
||||
"resolved": "https://registry.npmjs.org/light-bolt11-decoder/-/light-bolt11-decoder-3.2.0.tgz",
|
||||
"integrity": "sha512-3QEofgiBOP4Ehs9BI+RkZdXZNtSys0nsJ6fyGeSiAGCBsMwHGUDS/JQlY/sTnWs91A2Nh0S9XXfA8Sy9g6QpuQ==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@scure/base": "1.1.1"
|
||||
}
|
||||
},
|
||||
"node_modules/lightning": {
|
||||
"version": "10.22.0",
|
||||
"resolved": "https://registry.npmjs.org/lightning/-/lightning-10.22.0.tgz",
|
||||
|
@ -15606,6 +15768,12 @@
|
|||
"react-dom": ">=16.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/next-tick": {
|
||||
"version": "1.1.0",
|
||||
"resolved": "https://registry.npmjs.org/next-tick/-/next-tick-1.1.0.tgz",
|
||||
"integrity": "sha512-CXdUiJembsNjuToQvxayPZF9Vqht7hewsvy2sOWafLvi2awflj9mOC6bHIg50orX8IJvWKY9wYQ/zB2kogPslQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/no-case": {
|
||||
"version": "3.0.4",
|
||||
"resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz",
|
||||
|
@ -19408,11 +19576,23 @@
|
|||
"resolved": "https://registry.npmjs.org/tsdef/-/tsdef-0.0.14.tgz",
|
||||
"integrity": "sha512-UjMD4XKRWWFlFBfwKVQmGFT5YzW/ZaF8x6KpCDf92u9wgKeha/go3FU0e5WqDjXsCOdfiavCkfwfVHNDxRDGMA=="
|
||||
},
|
||||
"node_modules/tseep": {
|
||||
"version": "1.3.1",
|
||||
"resolved": "https://registry.npmjs.org/tseep/-/tseep-1.3.1.tgz",
|
||||
"integrity": "sha512-ZPtfk1tQnZVyr7BPtbJ93qaAh2lZuIOpTMjhrYa4XctT8xe7t4SAW9LIxrySDuYMsfNNayE51E/WNGrNVgVicQ==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tslib": {
|
||||
"version": "2.6.0",
|
||||
"resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz",
|
||||
"integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA=="
|
||||
},
|
||||
"node_modules/tstl": {
|
||||
"version": "2.5.16",
|
||||
"resolved": "https://registry.npmjs.org/tstl/-/tstl-2.5.16.tgz",
|
||||
"integrity": "sha512-+O2ybLVLKcBwKm4HymCEwZIT0PpwS3gCYnxfSDEjJEKADvIFruaQjd3m7CAKNU1c7N3X3WjVz87re7TA2A5FUw==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/tsx": {
|
||||
"version": "4.19.1",
|
||||
"resolved": "https://registry.npmjs.org/tsx/-/tsx-4.19.1.tgz",
|
||||
|
@ -19452,6 +19632,12 @@
|
|||
"resolved": "https://registry.npmjs.org/tweetnacl-util/-/tweetnacl-util-0.15.1.tgz",
|
||||
"integrity": "sha512-RKJBIj8lySrShN4w6i/BonWp2Z/uxwC3h4y7xsRrpP59ZboCd0GpEVsOnMDYLMmKBpYhb5TgHzZXy7wTfYFBRw=="
|
||||
},
|
||||
"node_modules/type": {
|
||||
"version": "2.7.3",
|
||||
"resolved": "https://registry.npmjs.org/type/-/type-2.7.3.tgz",
|
||||
"integrity": "sha512-8j+1QmAbPvLZow5Qpi6NCaN8FB60p/6x8/vfNqOk/hC+HuvFZhL4+WfekuhQLiqFZXOgQdrs3B+XxEmCc6b3FQ==",
|
||||
"license": "ISC"
|
||||
},
|
||||
"node_modules/type-check": {
|
||||
"version": "0.4.0",
|
||||
"resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
|
||||
|
@ -19574,11 +19760,26 @@
|
|||
"node": ">= 18"
|
||||
}
|
||||
},
|
||||
"node_modules/typedarray-to-buffer": {
|
||||
"version": "3.1.5",
|
||||
"resolved": "https://registry.npmjs.org/typedarray-to-buffer/-/typedarray-to-buffer-3.1.5.tgz",
|
||||
"integrity": "sha512-zdu8XMNEDepKKR+XYOXAVPtWui0ly0NtohUscw+UmaHiAWT8hrV1rr//H6V+0DvJ3OQ19S979M0laLfX8rm82Q==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"is-typedarray": "^1.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/typeforce": {
|
||||
"version": "1.18.0",
|
||||
"resolved": "https://registry.npmjs.org/typeforce/-/typeforce-1.18.0.tgz",
|
||||
"integrity": "sha512-7uc1O8h1M1g0rArakJdf0uLRSSgFcYexrVoKo+bzJd32gd4gDy2L/Z+8/FjPnU9ydY3pEnVPtr9FyscYY60K1g=="
|
||||
},
|
||||
"node_modules/typescript-lru-cache": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/typescript-lru-cache/-/typescript-lru-cache-2.0.0.tgz",
|
||||
"integrity": "sha512-Jp57Qyy8wXeMkdNuZiglE6v2Cypg13eDA1chHwDG6kq51X7gk4K7P7HaDdzZKCxkegXkVHNcPD0n5aW6OZH3aA==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/uint8array-tools": {
|
||||
"version": "0.0.7",
|
||||
"resolved": "https://registry.npmjs.org/uint8array-tools/-/uint8array-tools-0.0.7.tgz",
|
||||
|
@ -19997,6 +20198,28 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
"node_modules/utf-8-validate": {
|
||||
"version": "5.0.10",
|
||||
"resolved": "https://registry.npmjs.org/utf-8-validate/-/utf-8-validate-5.0.10.tgz",
|
||||
"integrity": "sha512-Z6czzLq4u8fPOyx7TU6X3dvUZVvoJmxSQ+IcrlmagKhilxlhZgxPK6C5Jqbkw1IDUmFTM+cz9QDnnLTwDz/2gQ==",
|
||||
"hasInstallScript": true,
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"node-gyp-build": "^4.3.0"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=6.14.2"
|
||||
}
|
||||
},
|
||||
"node_modules/utf8-buffer": {
|
||||
"version": "1.0.0",
|
||||
"resolved": "https://registry.npmjs.org/utf8-buffer/-/utf8-buffer-1.0.0.tgz",
|
||||
"integrity": "sha512-ueuhzvWnp5JU5CiGSY4WdKbiN/PO2AZ/lpeLiz2l38qwdLy/cW40XobgyuIWucNyum0B33bVB0owjFCeGBSLqg==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=8"
|
||||
}
|
||||
},
|
||||
"node_modules/util": {
|
||||
"version": "0.12.4",
|
||||
"resolved": "https://registry.npmjs.org/util/-/util-0.12.4.tgz",
|
||||
|
@ -20320,6 +20543,47 @@
|
|||
"npm": ">=3.10.0"
|
||||
}
|
||||
},
|
||||
"node_modules/websocket": {
|
||||
"version": "1.0.35",
|
||||
"resolved": "https://registry.npmjs.org/websocket/-/websocket-1.0.35.tgz",
|
||||
"integrity": "sha512-/REy6amwPZl44DDzvRCkaI1q1bIiQB0mEFQLUrhz3z2EK91cp3n72rAjUlrTP0zV22HJIUOVHQGPxhFRjxjt+Q==",
|
||||
"license": "Apache-2.0",
|
||||
"dependencies": {
|
||||
"bufferutil": "^4.0.1",
|
||||
"debug": "^2.2.0",
|
||||
"es5-ext": "^0.10.63",
|
||||
"typedarray-to-buffer": "^3.1.5",
|
||||
"utf-8-validate": "^5.0.2",
|
||||
"yaeti": "^0.0.6"
|
||||
},
|
||||
"engines": {
|
||||
"node": ">=4.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/websocket-polyfill": {
|
||||
"version": "0.0.3",
|
||||
"resolved": "https://registry.npmjs.org/websocket-polyfill/-/websocket-polyfill-0.0.3.tgz",
|
||||
"integrity": "sha512-pF3kR8Uaoau78MpUmFfzbIRxXj9PeQrCuPepGE6JIsfsJ/o/iXr07Q2iQNzKSSblQJ0FiGWlS64N4pVSm+O3Dg==",
|
||||
"dependencies": {
|
||||
"tstl": "^2.0.7",
|
||||
"websocket": "^1.0.28"
|
||||
}
|
||||
},
|
||||
"node_modules/websocket/node_modules/debug": {
|
||||
"version": "2.6.9",
|
||||
"resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz",
|
||||
"integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"ms": "2.0.0"
|
||||
}
|
||||
},
|
||||
"node_modules/websocket/node_modules/ms": {
|
||||
"version": "2.0.0",
|
||||
"resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz",
|
||||
"integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==",
|
||||
"license": "MIT"
|
||||
},
|
||||
"node_modules/whatwg-encoding": {
|
||||
"version": "3.1.1",
|
||||
"resolved": "https://registry.npmjs.org/whatwg-encoding/-/whatwg-encoding-3.1.1.tgz",
|
||||
|
@ -20896,6 +21160,15 @@
|
|||
"node": ">=10"
|
||||
}
|
||||
},
|
||||
"node_modules/yaeti": {
|
||||
"version": "0.0.6",
|
||||
"resolved": "https://registry.npmjs.org/yaeti/-/yaeti-0.0.6.tgz",
|
||||
"integrity": "sha512-MvQa//+KcZCUkBTIC9blM+CU9J2GzuTytsOUwf2lidtvkx/6gnEp1QvJv34t9vdjhFmha/mUiNDbN0D0mJWdug==",
|
||||
"license": "MIT",
|
||||
"engines": {
|
||||
"node": ">=0.10.32"
|
||||
}
|
||||
},
|
||||
"node_modules/yallist": {
|
||||
"version": "4.0.0",
|
||||
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
|
||||
|
|
|
@ -20,6 +20,7 @@
|
|||
"@graphql-tools/schema": "^10.0.6",
|
||||
"@lightninglabs/lnc-web": "^0.3.2-alpha",
|
||||
"@noble/curves": "^1.6.0",
|
||||
"@nostr-dev-kit/ndk": "^2.10.5",
|
||||
"@opensearch-project/opensearch": "^2.12.0",
|
||||
"@prisma/client": "^5.20.0",
|
||||
"@slack/web-api": "^7.6.0",
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
import { nwcCall, supportedMethods } from '@/wallets/nwc'
|
||||
import { getNwc, supportedMethods, nwcTryRun } from '@/wallets/nwc'
|
||||
export * from '@/wallets/nwc'
|
||||
|
||||
export async function testSendPayment ({ nwcUrl }, { logger }) {
|
||||
|
@ -11,11 +11,7 @@ export async function testSendPayment ({ nwcUrl }, { logger }) {
|
|||
}
|
||||
|
||||
export async function sendPayment (bolt11, { nwcUrl }, { logger }) {
|
||||
const result = await nwcCall({
|
||||
nwcUrl,
|
||||
method: 'pay_invoice',
|
||||
params: { invoice: bolt11 }
|
||||
},
|
||||
{ logger })
|
||||
const nwc = await getNwc(nwcUrl)
|
||||
const result = await nwcTryRun(() => nwc.payInvoice(bolt11))
|
||||
return result.preimage
|
||||
}
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
import { Relay } from '@/lib/nostr'
|
||||
import { parseNwcUrl } from '@/lib/url'
|
||||
import Nostr from '@/lib/nostr'
|
||||
import { string } from '@/lib/yup'
|
||||
import { finalizeEvent, nip04, verifyEvent } from 'nostr-tools'
|
||||
import { parseNwcUrl } from '@/lib/url'
|
||||
import { NDKNwc } from '@nostr-dev-kit/ndk'
|
||||
|
||||
export const name = 'nwc'
|
||||
export const walletType = 'NWC'
|
||||
|
@ -33,61 +33,38 @@ export const card = {
|
|||
subtitle: 'use Nostr Wallet Connect for payments'
|
||||
}
|
||||
|
||||
export async function nwcCall ({ nwcUrl, method, params }, { logger, timeout } = {}) {
|
||||
const { relayUrl, walletPubkey, secret } = parseNwcUrl(nwcUrl)
|
||||
|
||||
const relay = await Relay.connect(relayUrl, { timeout })
|
||||
logger?.ok(`connected to ${relayUrl}`)
|
||||
export async function getNwc (nwcUrl, { timeout = 5e4 } = {}) {
|
||||
const ndk = Nostr.ndk
|
||||
const { walletPubkey, secret, relayUrls } = parseNwcUrl(nwcUrl)
|
||||
const nwc = new NDKNwc({
|
||||
ndk,
|
||||
pubkey: walletPubkey,
|
||||
relayUrls,
|
||||
secret
|
||||
})
|
||||
await nwc.blockUntilReady(timeout)
|
||||
return nwc
|
||||
}
|
||||
|
||||
/**
|
||||
* Run a nwc function and throw if it errors
|
||||
* (workaround to handle ambiguous NDK error handling)
|
||||
* @param {function} fun - the nwc function to run
|
||||
* @returns - the result of the nwc function
|
||||
*/
|
||||
export async function nwcTryRun (fun) {
|
||||
try {
|
||||
const payload = { method, params }
|
||||
const encrypted = await nip04.encrypt(secret, walletPubkey, JSON.stringify(payload))
|
||||
|
||||
const request = finalizeEvent({
|
||||
kind: 23194,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [['p', walletPubkey]],
|
||||
content: encrypted
|
||||
}, secret)
|
||||
|
||||
// we need to subscribe to the response before publishing the request
|
||||
// since NWC events are ephemeral (20000 <= kind < 30000)
|
||||
const subscription = relay.fetch([{
|
||||
kinds: [23195],
|
||||
authors: [walletPubkey],
|
||||
'#e': [request.id]
|
||||
}], { timeout })
|
||||
|
||||
await relay.publish(request, { timeout })
|
||||
|
||||
logger?.info(`published ${method} request`)
|
||||
|
||||
logger?.info(`waiting for ${method} response ...`)
|
||||
|
||||
const [response] = await subscription
|
||||
|
||||
if (!response) {
|
||||
throw new Error(`no ${method} response`)
|
||||
}
|
||||
|
||||
logger?.ok(`${method} response received`)
|
||||
|
||||
if (!verifyEvent(response)) throw new Error(`invalid ${method} response: failed to verify`)
|
||||
|
||||
const decrypted = await nip04.decrypt(secret, walletPubkey, response.content)
|
||||
const content = JSON.parse(decrypted)
|
||||
|
||||
if (content.error) throw new Error(content.error.message)
|
||||
if (content.result) return content.result
|
||||
|
||||
throw new Error(`invalid ${method} response: missing error or result`)
|
||||
} finally {
|
||||
relay?.close()
|
||||
logger?.info(`closed connection to ${relayUrl}`)
|
||||
const { error, result } = await fun()
|
||||
if (error) throw new Error(error.message || error.code)
|
||||
return result
|
||||
} catch (e) {
|
||||
if (e.error) throw new Error(e.error.message || e.error.code)
|
||||
throw e
|
||||
}
|
||||
}
|
||||
|
||||
export async function supportedMethods (nwcUrl, { logger, timeout } = {}) {
|
||||
const result = await nwcCall({ nwcUrl, method: 'get_info' }, { logger, timeout })
|
||||
const nwc = await getNwc(nwcUrl, { timeout })
|
||||
const result = await nwcTryRun(() => nwc.getInfo())
|
||||
return result.methods
|
||||
}
|
||||
|
|
|
@ -1,5 +1,5 @@
|
|||
import { withTimeout } from '@/lib/time'
|
||||
import { nwcCall, supportedMethods } from '@/wallets/nwc'
|
||||
import { getNwc, supportedMethods, nwcTryRun } from '@/wallets/nwc'
|
||||
export * from '@/wallets/nwc'
|
||||
|
||||
export async function testCreateInvoice ({ nwcUrlRecv }, { logger }) {
|
||||
|
@ -23,17 +23,8 @@ export async function testCreateInvoice ({ nwcUrlRecv }, { logger }) {
|
|||
return await withTimeout(createInvoice({ msats: 1000, expiry: 1 }, { nwcUrlRecv }, { logger }), timeout)
|
||||
}
|
||||
|
||||
export async function createInvoice (
|
||||
{ msats, description, expiry },
|
||||
{ nwcUrlRecv }, { logger }) {
|
||||
const result = await nwcCall({
|
||||
nwcUrl: nwcUrlRecv,
|
||||
method: 'make_invoice',
|
||||
params: {
|
||||
amount: msats,
|
||||
description,
|
||||
expiry
|
||||
}
|
||||
}, { logger })
|
||||
export async function createInvoice ({ msats, description, expiry }, { nwcUrlRecv }, { logger }) {
|
||||
const nwc = await getNwc(nwcUrlRecv)
|
||||
const result = await nwcTryRun(() => nwc.sendReq('make_invoice', { amount: msats, description, expiry }))
|
||||
return result.invoice
|
||||
}
|
||||
|
|
|
@ -38,6 +38,12 @@ import { expireBoost } from './expireBoost'
|
|||
import { payingActionConfirmed, payingActionFailed } from './payingAction'
|
||||
import { autoDropBolt11s } from './autoDropBolt11'
|
||||
|
||||
// WebSocket polyfill
|
||||
import ws from 'isomorphic-ws'
|
||||
if (typeof WebSocket === 'undefined') {
|
||||
global.WebSocket = ws
|
||||
}
|
||||
|
||||
async function work () {
|
||||
const boss = new PgBoss(process.env.DATABASE_URL)
|
||||
const models = createPrisma({
|
||||
|
|
|
@ -1,5 +1,4 @@
|
|||
import { signId, calculateId, getPublicKey } from 'nostr'
|
||||
import { Relay } from '@/lib/nostr'
|
||||
import Nostr from '@/lib/nostr'
|
||||
|
||||
const nostrOptions = { startAfter: 5, retryLimit: 21, retryBackoff: true }
|
||||
|
||||
|
@ -40,26 +39,18 @@ export async function nip57 ({ data: { hash }, boss, lnd, models }) {
|
|||
|
||||
const e = {
|
||||
kind: 9735,
|
||||
pubkey: getPublicKey(process.env.NOSTR_PRIVATE_KEY),
|
||||
created_at: Math.floor(new Date(inv.confirmedAt).getTime() / 1000),
|
||||
content: '',
|
||||
tags
|
||||
}
|
||||
e.id = await calculateId(e)
|
||||
e.sig = await signId(process.env.NOSTR_PRIVATE_KEY, e.id)
|
||||
|
||||
console.log('zap note', e, relays)
|
||||
await Promise.allSettled(
|
||||
relays.map(async r => {
|
||||
const timeout = 1000
|
||||
const relay = await Relay.connect(r, { timeout })
|
||||
try {
|
||||
await relay.publish(e, { timeout })
|
||||
} finally {
|
||||
relay.close()
|
||||
}
|
||||
})
|
||||
)
|
||||
const signer = Nostr.getSigner({ privKey: process.env.NOSTR_PRIVATE_KEY })
|
||||
await Nostr.publish(e, {
|
||||
relays,
|
||||
signer,
|
||||
timeout: 1000
|
||||
})
|
||||
} catch (e) {
|
||||
console.log(e)
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue