├── .gitignore ├── README.md ├── index.js ├── package-lock.json └── package.json /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | tip_lastpay_index 3 | .envrc 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | 2 | # cln-nostr-zapper 3 | 4 | A CLN script that sends nostr lightning zaps (NIP-57) 5 | 6 | This script listens for lightning invoice payments and sends zap notes if the invoice contains a zap request note. See NIP-57 for the full details. 7 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | const {RelayPool, Relay, signId, calculateId, getPublicKey} = require('nostr') 4 | const fs = require('fs').promises 5 | const {spawn} = require('node:child_process') 6 | 7 | function relay_send(ev, url, opts) { 8 | const timeout = (opts && opts.timeout != null && opts.timeout) || 1000 9 | 10 | return new Promise((resolve, reject) => { 11 | const relay = Relay(url) 12 | 13 | function timeout_reached() { 14 | relay.close() 15 | reject(new Error("Request timeout")) 16 | } 17 | 18 | let timer = setTimeout(timeout_reached, timeout) 19 | 20 | relay.on('open', () => { 21 | clearTimeout(timer) 22 | timer = setTimeout(timeout_reached, timeout) 23 | relay.send(['EVENT', ev]) 24 | }) 25 | 26 | relay.on('ok', (evid, ok, msg) => { 27 | clearTimeout(timer) 28 | relay.close() 29 | resolve({evid, ok, msg}) 30 | }) 31 | }) 32 | } 33 | 34 | async function send_note(urls, {privkey, pubkey}, ev) 35 | { 36 | try { 37 | const tasks = urls.map(relay_send.bind(null, ev)) 38 | await Promise.all(tasks) 39 | } catch (e) { 40 | //log? 41 | console.log(e) 42 | } 43 | } 44 | 45 | function get_zapreq(desc) { 46 | if (!desc) 47 | return null 48 | 49 | if (desc.kind === 9734) 50 | return desc 51 | 52 | // TODO: handle private zaps 53 | 54 | // This is a deprecated old form, you don't need this 55 | const found = desc.find(tag => tag && tag.length >= 2 && tag[0] == "application/nostr") 56 | if (found && found[1]) 57 | return found[1] 58 | 59 | return null 60 | } 61 | 62 | async function process_invoice_payment(privkey, invoice) 63 | { 64 | const pubkey = getPublicKey(privkey) 65 | const keypair = {privkey, pubkey} 66 | // Parse the invoice metadata 67 | let desc 68 | try { 69 | desc = JSON.parse(invoice.description) 70 | } catch { 71 | //log(`Could not parse description as json`) 72 | return 73 | } 74 | const label = invoice.label 75 | if (!desc) { 76 | //log(`Could not parse metadata description as json for ${label}`) 77 | return 78 | } 79 | // Get the nostr note entry in the metadata 80 | const zapreq = get_zapreq(desc) 81 | if (!zapreq) { 82 | console.log(`Could not find zap request note in metadata for ${label}`) 83 | return 84 | } 85 | 86 | // Make sure there are tags on the note 87 | if (!zapreq.tags || zapreq.tags.length === 0) { 88 | console.log(`No tags found in ${label}`) 89 | return 90 | } 91 | // Make sure we only have one p tag 92 | const ptags = zapreq.tags.filter(t => t && t.length && t.length >= 2 && t[0] === "p") 93 | if (ptags.length !== 1) { 94 | console.log(`None or multiple p tags found in ${label}`) 95 | return 96 | } 97 | // Make sure we have 0 or 1 etag (for note zapping) 98 | const etags = zapreq.tags.filter(t => t && t.length && t.length >= 2 && t[0] === "e") 99 | if (!(etags.length === 0 || etags.length === 1)) { 100 | console.log(`Expected none or 1 e tags in ${label}`) 101 | return 102 | } 103 | // Look for the relays tag, we will broadcast to these relays 104 | const relays_tag = zapreq.tags.find(t => t && t.length && t.length >= 2 && t[0] === "relays") 105 | if (!relays_tag) { 106 | console.log(`No relays tag found in ${label}`) 107 | return 108 | } 109 | 110 | const relays = relays_tag.slice(1).filter(r => r && r.startsWith && r.startsWith("ws")) 111 | const ptag = ptags[0] 112 | const etag = etags.length > 0 && etags[0] 113 | const data = {zapreq, invoice, keypair, ptag, etag} 114 | const zap_note = await make_zap_note(data) 115 | console.log(`Sending lightning zap note ${zap_note.id} to ${relays.join(", ")}`) 116 | await send_note(relays, keypair, zap_note) 117 | console.log(`done`) 118 | } 119 | 120 | async function make_zap_note({keypair, invoice, zapreq, ptag, etag}) { 121 | const kind = 9735 122 | const created_at = invoice.paid_at 123 | const pubkey = keypair.pubkey 124 | const privkey = keypair.privkey 125 | const content = zapreq.content 126 | 127 | let tags = [ ptag ] 128 | if (etag) 129 | tags.push(etag) 130 | 131 | tags.push(["P", zapreq.pubkey]) 132 | tags.push(["bolt11", invoice.bolt11]) 133 | tags.push(["description", invoice.description]) 134 | tags.push(["preimage", invoice.payment_preimage]) 135 | 136 | let ev = {pubkey, kind, created_at, content, tags} 137 | 138 | ev.id = await calculateId(ev) 139 | ev.sig = await signId(privkey, ev.id) 140 | 141 | return ev 142 | } 143 | 144 | async function get_invoice(label) 145 | { 146 | const {invoices} = await callrpc("listinvoices", {label}) 147 | return invoices && invoices[0] 148 | } 149 | 150 | function dospawn(cmd, ...args) 151 | { 152 | return new Promise((resolve, reject) => { 153 | const proc = spawn(cmd, [...args]) 154 | proc.stdout.on('data', (data) => { 155 | resolve(data.toString("utf8").trim()) 156 | }) 157 | proc.on('close', code => { 158 | if (code !== 0) 159 | reject(code) 160 | else 161 | resolve(code) 162 | }); 163 | }) 164 | } 165 | 166 | 167 | async function callrpc(rpc, args) { 168 | const params = Object.keys(args).map(key => `${key}=${args[key]}`) 169 | const res = await dospawn("lightning-cli", rpc, params) 170 | return JSON.parse(res) 171 | } 172 | 173 | async function waitanyinvoice(index) { 174 | const res = await callrpc("waitanyinvoice", index) 175 | return res 176 | } 177 | 178 | async function run_zapper(args) { 179 | const privkey = process.env.NOSTR_KEY 180 | if (!privkey) { 181 | console.log("set NOSTR_KEY") 182 | return 183 | } 184 | let lastpay_index = parseInt(args[0]) || await read_lastpay_index() 185 | while (true) { 186 | const params = {lastpay_index} 187 | console.log("waitanyinvoice %o", params) 188 | try { 189 | const invoice = await waitanyinvoice(params) 190 | if (!invoice || invoice === "") { 191 | console.log("invoice fail", invoice) 192 | process.exit(5) 193 | } 194 | console.log("done waitanyinvoice", params) 195 | await process_invoice_payment(privkey, invoice) 196 | } catch(e) { 197 | console.log("process threw an error", e) 198 | process.exit(1) 199 | } 200 | console.log("%s done processing", format_date(new Date())) 201 | lastpay_index += 1 202 | await write_lastpay_index(lastpay_index) 203 | } 204 | } 205 | 206 | const lastpay_file = "tip_lastpay_index" 207 | 208 | async function read_lastpay_index() { 209 | try { 210 | const res = await fs.readFile(lastpay_file, 'utf8') 211 | return parseInt(res) 212 | } catch { 213 | return 0 214 | } 215 | } 216 | 217 | async function write_lastpay_index(lastpay_index) { 218 | await fs.writeFile(lastpay_file, lastpay_index.toString()) 219 | } 220 | 221 | function format_date(date) { 222 | const year = date.getFullYear(); 223 | const month = (date.getMonth() + 1).toString().padStart(2, '0'); 224 | const day = date.getDate().toString().padStart(2, '0'); 225 | const hours = date.getHours().toString().padStart(2, '0'); 226 | const minutes = date.getMinutes().toString().padStart(2, '0'); 227 | return `${year}-${month}-${day}T${hours}:${minutes}`; 228 | }; 229 | 230 | run_zapper(process.argv.slice(2)) 231 | 232 | 233 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nostr-tip", 3 | "version": "0.1.0", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "nostr-tip", 9 | "version": "0.1.0", 10 | "dependencies": { 11 | "nostr": "^0.2.7", 12 | "ws": "^8.12.1" 13 | }, 14 | "devDependencies": { 15 | "tap": "~0.2.5" 16 | } 17 | }, 18 | "node_modules/abbrev": { 19 | "version": "1.1.1", 20 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 21 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 22 | "dev": true 23 | }, 24 | "node_modules/buffer-equal": { 25 | "version": "0.0.2", 26 | "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.2.tgz", 27 | "integrity": "sha512-4hr0gS7+NK47X6WbA/okVFrN5qGh3WLT7N3hMRv7+hlkXnbUIdU2u05n6r0RQv6cq6xke06nVl70r0NW0WM2OQ==", 28 | "dev": true, 29 | "engines": { 30 | "node": ">=0.4.0" 31 | } 32 | }, 33 | "node_modules/bunker": { 34 | "version": "0.1.2", 35 | "resolved": "https://registry.npmjs.org/bunker/-/bunker-0.1.2.tgz", 36 | "integrity": "sha512-YnahkcXBNT522S46k5LUA9P18lzvgkunbMl0qIJQ8oeRMQ+dAg3YI3k32q5TnO+AAUErFHO6R768To6jslgYmQ==", 37 | "dev": true, 38 | "dependencies": { 39 | "burrito": ">=0.2.5 <0.3" 40 | }, 41 | "engines": { 42 | "node": "*" 43 | } 44 | }, 45 | "node_modules/burrito": { 46 | "version": "0.2.12", 47 | "resolved": "https://registry.npmjs.org/burrito/-/burrito-0.2.12.tgz", 48 | "integrity": "sha512-ZhhT5iVTAgzQ+s8rily7m45Swxe/cU3dVCHTzqmHVWD/cc0Ds3W4Q4MExbkevY+fm0Me3lEwpehIy6TH7p+ehw==", 49 | "dev": true, 50 | "dependencies": { 51 | "traverse": "~0.5.1", 52 | "uglify-js": "~1.1.1" 53 | }, 54 | "engines": { 55 | "node": ">=0.4.0" 56 | } 57 | }, 58 | "node_modules/burrito/node_modules/traverse": { 59 | "version": "0.5.2", 60 | "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.5.2.tgz", 61 | "integrity": "sha512-PUBVcfB3RqgLpzgTRGNiqK4duqrDbgGa1bobbUtzUwLiBNAjZ7vd5eCOdBxqZ/Fgezagr9o69IxP2fZp41RGFA==", 62 | "dev": true, 63 | "engines": { 64 | "node": "*" 65 | } 66 | }, 67 | "node_modules/charm": { 68 | "version": "0.1.2", 69 | "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", 70 | "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", 71 | "dev": true 72 | }, 73 | "node_modules/deep-equal": { 74 | "version": "0.0.0", 75 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.0.0.tgz", 76 | "integrity": "sha512-p1bI/kkDPT6auUI0U+WLuIIrzmDIDo80I406J8tT4y6I4ZGtBuMeTudrKDtBdMJFAcxqrQdx27gosqPVyY3IvQ==", 77 | "dev": true, 78 | "engines": { 79 | "node": "*" 80 | } 81 | }, 82 | "node_modules/deep-is": { 83 | "version": "0.1.4", 84 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 85 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 86 | "dev": true 87 | }, 88 | "node_modules/difflet": { 89 | "version": "0.2.6", 90 | "resolved": "https://registry.npmjs.org/difflet/-/difflet-0.2.6.tgz", 91 | "integrity": "sha512-ruldDDRmY1t678UOAJBng6sL77f62SqjHj0498YC0EJhxIe2yKkqJn2qEchwG3eU/dqJ/RxPZkAnYjePS4pDCw==", 92 | "dev": true, 93 | "dependencies": { 94 | "charm": "0.1.x", 95 | "deep-is": "0.1.x", 96 | "traverse": "0.6.x" 97 | }, 98 | "engines": { 99 | "node": ">=0.4.0" 100 | } 101 | }, 102 | "node_modules/mkdirp": { 103 | "version": "0.3.5", 104 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", 105 | "integrity": "sha512-8OCq0De/h9ZxseqzCH8Kw/Filf5pF/vMI6+BH7Lu0jXz2pqYCjTAQRolSxRIi+Ax+oCCjlxoJMP0YQ4XlrQNHg==", 106 | "deprecated": "Legacy versions of mkdirp are no longer supported. Please update to mkdirp 1.x. (Note that the API surface has changed to use Promises in 1.x.)", 107 | "dev": true 108 | }, 109 | "node_modules/noble-secp256k1": { 110 | "version": "1.2.14", 111 | "resolved": "https://registry.npmjs.org/noble-secp256k1/-/noble-secp256k1-1.2.14.tgz", 112 | "integrity": "sha512-GSCXyoZBUaaPwVWdYncMEmzlSUjF9J/YeEHpklYJCyg8wPuJP3NzDx0BkiwArzINkdX2HJHvUJhL6vVWPOQQcg==", 113 | "deprecated": "Switch to namespaced @noble/secp256k1 for security and feature updates" 114 | }, 115 | "node_modules/nopt": { 116 | "version": "2.2.1", 117 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.2.1.tgz", 118 | "integrity": "sha512-gIOTA/uJuhPwFqp+spY7VQ1satbnGlD+iQVZxI18K6hs8Evq4sX81Ml7BB5byP/LsbR2yBVtmvdEmhi7evJ6Aw==", 119 | "dev": true, 120 | "dependencies": { 121 | "abbrev": "1" 122 | }, 123 | "bin": { 124 | "nopt": "bin/nopt.js" 125 | } 126 | }, 127 | "node_modules/nostr": { 128 | "version": "0.2.7", 129 | "resolved": "https://registry.npmjs.org/nostr/-/nostr-0.2.7.tgz", 130 | "integrity": "sha512-Yq5tkiCTJtohs7H7Fx+Ki83bR8b3xapdwYi17HunhW5EQfZ9rI0cIXFyihj2ZRkv/0YYvVBiCp/KIaLYt5LyfQ==", 131 | "dependencies": { 132 | "noble-secp256k1": "^1.2.14", 133 | "ws": "^8.8.1" 134 | } 135 | }, 136 | "node_modules/runforcover": { 137 | "version": "0.0.2", 138 | "resolved": "https://registry.npmjs.org/runforcover/-/runforcover-0.0.2.tgz", 139 | "integrity": "sha512-yarCIK2HcAOadqnKW419+FA38qpWDCKcOr5RZU+jnyLL/hn3No9BHZY+YJDEzvQ0k8Oyl7ffLjZv9ZUxvyKoLQ==", 140 | "dev": true, 141 | "dependencies": { 142 | "bunker": "0.1.X" 143 | }, 144 | "engines": { 145 | "node": "*" 146 | } 147 | }, 148 | "node_modules/slide": { 149 | "version": "1.1.6", 150 | "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", 151 | "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", 152 | "dev": true, 153 | "engines": { 154 | "node": "*" 155 | } 156 | }, 157 | "node_modules/tap": { 158 | "version": "0.2.6", 159 | "resolved": "https://registry.npmjs.org/tap/-/tap-0.2.6.tgz", 160 | "integrity": "sha512-uLvaKbh3+A4nh+P3SrfX52kWGkVvP37UYI7LxKjRkd6Bjdqbyc7MARaPFGl7SdwyNwWtZHlUxQX3w9pVNf3FKQ==", 161 | "bundleDependencies": [ 162 | "inherits", 163 | "tap-consumer", 164 | "yamlish" 165 | ], 166 | "dev": true, 167 | "dependencies": { 168 | "buffer-equal": "~0.0.0", 169 | "deep-equal": "~0.0.0", 170 | "difflet": "~0.2.0", 171 | "inherits": "*", 172 | "mkdirp": "~0.3", 173 | "nopt": "~2", 174 | "runforcover": "~0.0.2", 175 | "slide": "*", 176 | "yamlish": "*" 177 | }, 178 | "bin": { 179 | "tap": "bin/tap.js" 180 | } 181 | }, 182 | "node_modules/tap/node_modules/inherits": { 183 | "version": "1.0.0", 184 | "dev": true, 185 | "inBundle": true, 186 | "license": "WTFPL2" 187 | }, 188 | "node_modules/tap/node_modules/yamlish": { 189 | "version": "0.0.5", 190 | "dev": true, 191 | "inBundle": true, 192 | "license": "MIT" 193 | }, 194 | "node_modules/traverse": { 195 | "version": "0.6.7", 196 | "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", 197 | "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", 198 | "dev": true, 199 | "funding": { 200 | "url": "https://github.com/sponsors/ljharb" 201 | } 202 | }, 203 | "node_modules/uglify-js": { 204 | "version": "1.1.1", 205 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.1.1.tgz", 206 | "integrity": "sha512-YYY9Dle1leC+btgrHnAR05eq0aRdcPJsXlYYD+SYw2lqc5HFuFNHg3wWEW4SNE0iXXEUl0fz43gTQ3r1YK76rg==", 207 | "dev": true, 208 | "bin": { 209 | "uglifyjs": "bin/uglifyjs" 210 | }, 211 | "engines": { 212 | "node": "*" 213 | } 214 | }, 215 | "node_modules/ws": { 216 | "version": "8.12.1", 217 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", 218 | "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", 219 | "engines": { 220 | "node": ">=10.0.0" 221 | }, 222 | "peerDependencies": { 223 | "bufferutil": "^4.0.1", 224 | "utf-8-validate": ">=5.0.2" 225 | }, 226 | "peerDependenciesMeta": { 227 | "bufferutil": { 228 | "optional": true 229 | }, 230 | "utf-8-validate": { 231 | "optional": true 232 | } 233 | } 234 | } 235 | }, 236 | "dependencies": { 237 | "abbrev": { 238 | "version": "1.1.1", 239 | "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", 240 | "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==", 241 | "dev": true 242 | }, 243 | "buffer-equal": { 244 | "version": "0.0.2", 245 | "resolved": "https://registry.npmjs.org/buffer-equal/-/buffer-equal-0.0.2.tgz", 246 | "integrity": "sha512-4hr0gS7+NK47X6WbA/okVFrN5qGh3WLT7N3hMRv7+hlkXnbUIdU2u05n6r0RQv6cq6xke06nVl70r0NW0WM2OQ==", 247 | "dev": true 248 | }, 249 | "bunker": { 250 | "version": "0.1.2", 251 | "resolved": "https://registry.npmjs.org/bunker/-/bunker-0.1.2.tgz", 252 | "integrity": "sha512-YnahkcXBNT522S46k5LUA9P18lzvgkunbMl0qIJQ8oeRMQ+dAg3YI3k32q5TnO+AAUErFHO6R768To6jslgYmQ==", 253 | "dev": true, 254 | "requires": { 255 | "burrito": ">=0.2.5 <0.3" 256 | } 257 | }, 258 | "burrito": { 259 | "version": "0.2.12", 260 | "resolved": "https://registry.npmjs.org/burrito/-/burrito-0.2.12.tgz", 261 | "integrity": "sha512-ZhhT5iVTAgzQ+s8rily7m45Swxe/cU3dVCHTzqmHVWD/cc0Ds3W4Q4MExbkevY+fm0Me3lEwpehIy6TH7p+ehw==", 262 | "dev": true, 263 | "requires": { 264 | "traverse": "~0.5.1", 265 | "uglify-js": "~1.1.1" 266 | }, 267 | "dependencies": { 268 | "traverse": { 269 | "version": "0.5.2", 270 | "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.5.2.tgz", 271 | "integrity": "sha512-PUBVcfB3RqgLpzgTRGNiqK4duqrDbgGa1bobbUtzUwLiBNAjZ7vd5eCOdBxqZ/Fgezagr9o69IxP2fZp41RGFA==", 272 | "dev": true 273 | } 274 | } 275 | }, 276 | "charm": { 277 | "version": "0.1.2", 278 | "resolved": "https://registry.npmjs.org/charm/-/charm-0.1.2.tgz", 279 | "integrity": "sha512-syedaZ9cPe7r3hoQA9twWYKu5AIyCswN5+szkmPBe9ccdLrj4bYaCnLVPTLd2kgVRc7+zoX4tyPgRnFKCj5YjQ==", 280 | "dev": true 281 | }, 282 | "deep-equal": { 283 | "version": "0.0.0", 284 | "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-0.0.0.tgz", 285 | "integrity": "sha512-p1bI/kkDPT6auUI0U+WLuIIrzmDIDo80I406J8tT4y6I4ZGtBuMeTudrKDtBdMJFAcxqrQdx27gosqPVyY3IvQ==", 286 | "dev": true 287 | }, 288 | "deep-is": { 289 | "version": "0.1.4", 290 | "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", 291 | "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", 292 | "dev": true 293 | }, 294 | "difflet": { 295 | "version": "0.2.6", 296 | "resolved": "https://registry.npmjs.org/difflet/-/difflet-0.2.6.tgz", 297 | "integrity": "sha512-ruldDDRmY1t678UOAJBng6sL77f62SqjHj0498YC0EJhxIe2yKkqJn2qEchwG3eU/dqJ/RxPZkAnYjePS4pDCw==", 298 | "dev": true, 299 | "requires": { 300 | "charm": "0.1.x", 301 | "deep-is": "0.1.x", 302 | "traverse": "0.6.x" 303 | } 304 | }, 305 | "mkdirp": { 306 | "version": "0.3.5", 307 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.3.5.tgz", 308 | "integrity": "sha512-8OCq0De/h9ZxseqzCH8Kw/Filf5pF/vMI6+BH7Lu0jXz2pqYCjTAQRolSxRIi+Ax+oCCjlxoJMP0YQ4XlrQNHg==", 309 | "dev": true 310 | }, 311 | "noble-secp256k1": { 312 | "version": "1.2.14", 313 | "resolved": "https://registry.npmjs.org/noble-secp256k1/-/noble-secp256k1-1.2.14.tgz", 314 | "integrity": "sha512-GSCXyoZBUaaPwVWdYncMEmzlSUjF9J/YeEHpklYJCyg8wPuJP3NzDx0BkiwArzINkdX2HJHvUJhL6vVWPOQQcg==" 315 | }, 316 | "nopt": { 317 | "version": "2.2.1", 318 | "resolved": "https://registry.npmjs.org/nopt/-/nopt-2.2.1.tgz", 319 | "integrity": "sha512-gIOTA/uJuhPwFqp+spY7VQ1satbnGlD+iQVZxI18K6hs8Evq4sX81Ml7BB5byP/LsbR2yBVtmvdEmhi7evJ6Aw==", 320 | "dev": true, 321 | "requires": { 322 | "abbrev": "1" 323 | } 324 | }, 325 | "nostr": { 326 | "version": "0.2.7", 327 | "resolved": "https://registry.npmjs.org/nostr/-/nostr-0.2.7.tgz", 328 | "integrity": "sha512-Yq5tkiCTJtohs7H7Fx+Ki83bR8b3xapdwYi17HunhW5EQfZ9rI0cIXFyihj2ZRkv/0YYvVBiCp/KIaLYt5LyfQ==", 329 | "requires": { 330 | "noble-secp256k1": "^1.2.14", 331 | "ws": "^8.8.1" 332 | } 333 | }, 334 | "runforcover": { 335 | "version": "0.0.2", 336 | "resolved": "https://registry.npmjs.org/runforcover/-/runforcover-0.0.2.tgz", 337 | "integrity": "sha512-yarCIK2HcAOadqnKW419+FA38qpWDCKcOr5RZU+jnyLL/hn3No9BHZY+YJDEzvQ0k8Oyl7ffLjZv9ZUxvyKoLQ==", 338 | "dev": true, 339 | "requires": { 340 | "bunker": "0.1.X" 341 | } 342 | }, 343 | "slide": { 344 | "version": "1.1.6", 345 | "resolved": "https://registry.npmjs.org/slide/-/slide-1.1.6.tgz", 346 | "integrity": "sha512-NwrtjCg+lZoqhFU8fOwl4ay2ei8PaqCBOUV3/ektPY9trO1yQ1oXEfmHAhKArUVUr/hOHvy5f6AdP17dCM0zMw==", 347 | "dev": true 348 | }, 349 | "tap": { 350 | "version": "0.2.6", 351 | "resolved": "https://registry.npmjs.org/tap/-/tap-0.2.6.tgz", 352 | "integrity": "sha512-uLvaKbh3+A4nh+P3SrfX52kWGkVvP37UYI7LxKjRkd6Bjdqbyc7MARaPFGl7SdwyNwWtZHlUxQX3w9pVNf3FKQ==", 353 | "dev": true, 354 | "requires": { 355 | "buffer-equal": "~0.0.0", 356 | "deep-equal": "~0.0.0", 357 | "difflet": "~0.2.0", 358 | "inherits": "*", 359 | "mkdirp": "~0.3", 360 | "nopt": "~2", 361 | "runforcover": "~0.0.2", 362 | "slide": "*", 363 | "yamlish": "*" 364 | }, 365 | "dependencies": { 366 | "inherits": { 367 | "version": "1.0.0", 368 | "bundled": true, 369 | "dev": true 370 | }, 371 | "yamlish": { 372 | "version": "0.0.5", 373 | "bundled": true, 374 | "dev": true 375 | } 376 | } 377 | }, 378 | "traverse": { 379 | "version": "0.6.7", 380 | "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.6.7.tgz", 381 | "integrity": "sha512-/y956gpUo9ZNCb99YjxG7OaslxZWHfCHAUUfshwqOXmxUIvqLjVO581BT+gM59+QV9tFe6/CGG53tsA1Y7RSdg==", 382 | "dev": true 383 | }, 384 | "uglify-js": { 385 | "version": "1.1.1", 386 | "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-1.1.1.tgz", 387 | "integrity": "sha512-YYY9Dle1leC+btgrHnAR05eq0aRdcPJsXlYYD+SYw2lqc5HFuFNHg3wWEW4SNE0iXXEUl0fz43gTQ3r1YK76rg==", 388 | "dev": true 389 | }, 390 | "ws": { 391 | "version": "8.12.1", 392 | "resolved": "https://registry.npmjs.org/ws/-/ws-8.12.1.tgz", 393 | "integrity": "sha512-1qo+M9Ba+xNhPB+YTWUlK6M17brTut5EXbcBaMRN5pH5dFrXz7lzz1ChFSUq3bOUl8yEvSenhHmYUNJxFzdJew==", 394 | "requires": {} 395 | } 396 | } 397 | } 398 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nostr-tip", 3 | "description": "nostr cln tipping plugin", 4 | "version": "0.1.0", 5 | "repository": { 6 | "url": "https://github.com/jb55/nostr-tip" 7 | }, 8 | "main": "index.js", 9 | "scripts": { 10 | "test": "tap test/*.js" 11 | }, 12 | "dependencies": { 13 | "nostr": "^0.2.7", 14 | "ws": "^8.12.1" 15 | }, 16 | "devDependencies": { 17 | "tap": "~0.2.5" 18 | } 19 | } 20 | --------------------------------------------------------------------------------