{"_id":"@ai-chen2050/nostr-tools","name":"@ai-chen2050/nostr-tools","dist-tags":{"latest":"2.12.0"},"versions":{"2.12.0":{"type":"module","name":"@ai-chen2050/nostr-tools","version":"2.12.0","description":"Tools for making a Nostr client.","repository":{"type":"git","url":"git+https://github.com/nbd-wtf/nostr-tools.git"},"sideEffects":false,"module":"./lib/esm/index.js","main":"./lib/cjs/index.js","types":"./lib/types/index.d.ts","exports":{".":{"import":"./lib/esm/index.js","require":"./lib/cjs/index.js","types":"./lib/types/index.d.ts"},"./core":{"import":"./lib/esm/core.js","require":"./lib/cjs/core.js","types":"./lib/types/core.d.ts"},"./pure":{"import":"./lib/esm/pure.js","require":"./lib/cjs/pure.js","types":"./lib/types/pure.d.ts"},"./wasm":{"import":"./lib/esm/wasm.js","require":"./lib/cjs/wasm.js","types":"./lib/types/wasm.d.ts"},"./kinds":{"import":"./lib/esm/kinds.js","require":"./lib/cjs/kinds.js","types":"./lib/types/kinds.d.ts"},"./filter":{"import":"./lib/esm/filter.js","require":"./lib/cjs/filter.js","types":"./lib/types/filter.d.ts"},"./abstract-relay":{"import":"./lib/esm/abstract-relay.js","require":"./lib/cjs/abstract-relay.js","types":"./lib/types/abstract-relay.d.ts"},"./relay":{"import":"./lib/esm/relay.js","require":"./lib/cjs/relay.js","types":"./lib/types/relay.d.ts"},"./abstract-pool":{"import":"./lib/esm/abstract-pool.js","require":"./lib/cjs/abstract-pool.js","types":"./lib/types/abstract-pool.d.ts"},"./pool":{"import":"./lib/esm/pool.js","require":"./lib/cjs/pool.js","types":"./lib/types/pool.d.ts"},"./references":{"import":"./lib/esm/references.js","require":"./lib/cjs/references.js","types":"./lib/types/references.d.ts"},"./nip04":{"import":"./lib/esm/nip04.js","require":"./lib/cjs/nip04.js","types":"./lib/types/nip04.d.ts"},"./nip05":{"import":"./lib/esm/nip05.js","require":"./lib/cjs/nip05.js","types":"./lib/types/nip05.d.ts"},"./nip06":{"import":"./lib/esm/nip06.js","require":"./lib/cjs/nip06.js","types":"./lib/types/nip06.d.ts"},"./nip07":{"types":"./lib/types/nip07.d.ts"},"./nip10":{"import":"./lib/esm/nip10.js","require":"./lib/cjs/nip10.js","types":"./lib/types/nip10.d.ts"},"./nip11":{"import":"./lib/esm/nip11.js","require":"./lib/cjs/nip11.js","types":"./lib/types/nip11.d.ts"},"./nip13":{"import":"./lib/esm/nip13.js","require":"./lib/cjs/nip13.js","types":"./lib/types/nip13.d.ts"},"./nip17":{"import":"./lib/esm/nip17.js","require":"./lib/cjs/nip17.js","types":"./lib/types/nip17.d.ts"},"./nip18":{"import":"./lib/esm/nip18.js","require":"./lib/cjs/nip18.js","types":"./lib/types/nip18.d.ts"},"./nip19":{"import":"./lib/esm/nip19.js","require":"./lib/cjs/nip19.js","types":"./lib/types/nip19.d.ts"},"./nip21":{"import":"./lib/esm/nip21.js","require":"./lib/cjs/nip21.js","types":"./lib/types/nip21.d.ts"},"./nip25":{"import":"./lib/esm/nip25.js","require":"./lib/cjs/nip25.js","types":"./lib/types/nip25.d.ts"},"./nip27":{"import":"./lib/esm/nip27.js","require":"./lib/cjs/nip27.js","types":"./lib/types/nip27.d.ts"},"./nip28":{"import":"./lib/esm/nip28.js","require":"./lib/cjs/nip28.js","types":"./lib/types/nip28.d.ts"},"./nip29":{"import":"./lib/esm/nip29.js","require":"./lib/cjs/nip29.js","types":"./lib/types/nip29.d.ts"},"./nip30":{"import":"./lib/esm/nip30.js","require":"./lib/cjs/nip30.js","types":"./lib/types/nip30.d.ts"},"./nip39":{"import":"./lib/esm/nip39.js","require":"./lib/cjs/nip39.js","types":"./lib/types/nip39.d.ts"},"./nip42":{"import":"./lib/esm/nip42.js","require":"./lib/cjs/nip42.js","types":"./lib/types/nip42.d.ts"},"./nip44":{"import":"./lib/esm/nip44.js","require":"./lib/cjs/nip44.js","types":"./lib/types/nip44.d.ts"},"./nip46":{"import":"./lib/esm/nip46.js","require":"./lib/cjs/nip46.js","types":"./lib/types/nip46.d.ts"},"./nip49":{"import":"./lib/esm/nip49.js","require":"./lib/cjs/nip49.js","types":"./lib/types/nip49.d.ts"},"./nip54":{"import":"./lib/esm/nip54.js","require":"./lib/cjs/nip54.js","types":"./lib/types/nip54.d.ts"},"./nip57":{"import":"./lib/esm/nip57.js","require":"./lib/cjs/nip57.js","types":"./lib/types/nip57.d.ts"},"./nip59":{"import":"./lib/esm/nip59.js","require":"./lib/cjs/nip59.js","types":"./lib/types/nip59.d.ts"},"./nip58":{"import":"./lib/esm/nip58.js","require":"./lib/cjs/nip58.js","types":"./lib/types/nip58.d.ts"},"./nip75":{"import":"./lib/esm/nip75.js","require":"./lib/cjs/nip75.js","types":"./lib/types/nip75.d.ts"},"./nip94":{"import":"./lib/esm/nip94.js","require":"./lib/cjs/nip94.js","types":"./lib/types/nip94.d.ts"},"./nip96":{"import":"./lib/esm/nip96.js","require":"./lib/cjs/nip96.js","types":"./lib/types/nip96.d.ts"},"./nip98":{"import":"./lib/esm/nip98.js","require":"./lib/cjs/nip98.js","types":"./lib/types/nip98.d.ts"},"./nip99":{"import":"./lib/esm/nip99.js","require":"./lib/cjs/nip99.js","types":"./lib/types/nip99.d.ts"},"./fakejson":{"import":"./lib/esm/fakejson.js","require":"./lib/cjs/fakejson.js","types":"./lib/types/fakejson.d.ts"},"./utils":{"import":"./lib/esm/utils.js","require":"./lib/cjs/utils.js","types":"./lib/types/utils.d.ts"}},"license":"Unlicense","dependencies":{"@noble/ciphers":"^0.5.1","@noble/curves":"1.2.0","@noble/hashes":"1.3.1","@scure/base":"1.1.1","@scure/bip32":"1.3.1","@scure/bip39":"1.2.1","glob":"^11.0.2","jest":"^29.7.0","micro-eth-signer":"^0.14.0","ws":"^8.18.1","nostr-wasm":"0.1.0"},"optionalDependencies":{"nostr-wasm":"0.1.0"},"peerDependencies":{"typescript":">=5.0.0"},"peerDependenciesMeta":{"typescript":{"optional":true}},"keywords":["decentralization","social","censorship-resistance","client","nostr"],"devDependencies":{"@types/node":"^18.13.0","@types/node-fetch":"^2.6.3","@typescript-eslint/eslint-plugin":"^6.5.0","@typescript-eslint/parser":"^6.5.0","bun-types":"^1.0.18","esbuild":"0.16.9","eslint":"^8.56.0","eslint-config-prettier":"^9.0.0","events":"^3.3.0","mitata":"^0.1.6","mock-socket":"^9.3.1","node-fetch":"^2.6.9","prettier":"^3.0.3","typescript":"^5.8.2"},"scripts":{"prepublish":"just build"},"publishConfig":{"registry":"https://npm.pkg.github.com"},"gitHead":"7175ebb673437ad618956d5d012388f5b3a87d48","bugs":{"url":"https://github.com/nbd-wtf/nostr-tools/issues"},"homepage":"https://github.com/nbd-wtf/nostr-tools#readme","_id":"@ai-chen2050/nostr-tools@2.12.0","_nodeVersion":"18.15.0","_npmVersion":"9.5.0","dist":{"integrity":"sha512-4/8pOsHsrPU1HW8bedNbPcb1qR/Pg/JYjPcYCELEZTGUWu4bRbt4XPKKVyw5XXzvnSr229lJqvBWQ6AFpd9tVw==","shasum":"4bc53eb8072ade805f79377d7f143f4a0721f753","tarball":"https://registry.npmjs.org/@ai-chen2050/nostr-tools/-/nostr-tools-2.12.0.tgz","fileCount":294,"unpackedSize":4228429,"signatures":[{"keyid":"SHA256:DhQ8wR5APBvFHLF/+Tc+AYvPOdTpcIDqOhxsBHRwC7U","sig":"MEUCIHgpZE/HNdX36rucVy4ImJd0BBbFoog5wpbsKbkcIJ5DAiEAgZVf+PTAOIkTMb0kh0VGXPaJRziXhA53ly7WVnoGZmI="}]},"_npmUser":{"name":"ai-chen2050","email":"1033467071@qq.com"},"directories":{},"maintainers":[{"name":"ai-chen2050","email":"1033467071@qq.com"}],"_npmOperationalInternal":{"host":"s3://npm-registry-packages-npm-production","tmp":"tmp/nostr-tools_2.12.0_1745837578988_0.6672395494149737"},"_hasShrinkwrap":false}},"time":{"created":"2025-04-28T10:52:58.874Z","2.12.0":"2025-04-28T10:52:59.399Z","modified":"2025-04-28T10:52:59.699Z"},"maintainers":[{"name":"ai-chen2050","email":"1033467071@qq.com"}],"description":"Tools for making a Nostr client.","homepage":"https://github.com/nbd-wtf/nostr-tools#readme","keywords":["decentralization","social","censorship-resistance","client","nostr"],"repository":{"type":"git","url":"git+https://github.com/nbd-wtf/nostr-tools.git"},"bugs":{"url":"https://github.com/nbd-wtf/nostr-tools/issues"},"license":"Unlicense","readme":"## Overview\n\nThis project combines three key technologies:\n\n1.  Nostr (decentralized messaging protocol)\n2.  Ethereum EIP-191 signatures (secure cryptographic signing)\n3.  VLC (Verifiable Logical Clock)\n\nTogether, these components enable:\n-   Decentralized authentication\n-   Secure message signing using Ethereum standards\n-   Distributed event ordering and causality tracking\n-   Message consistency in distributed systems\n\nThis integration creates a robust system for secure, decentralized communication with verifiable event ordering.\n\n## Installation\n\n```bash\n# bun\ncurl -fsSL https://bun.sh/install | bash\n\n# install just for linux\ncurl -LO https://github.com/casey/just/releases/download/1.28.0/just-1.28.0-x86_64-unknown-linux-musl.tar.gz\ntar -xvf just-1.28.0-x86_64-unknown-linux-musl.tar.gz\nsudo mv just /usr/local/bin/\n\n# or for macOS\nbrew install just\n\n# compile by yourself\njust build\n\n# or install by npm\nnpm install --save nostr-tools\n\n# jsr\nnpx jsr add @nostr/tools\n```\n\nIf using TypeScript, this package requires TypeScript >= 5.0.\n\n## Usage\n\n### Generating a private key and a public key\n\n```js\nimport { generateSecretKey, getPublicKey } from 'nostr-tools/pure'\n\nlet sk = generateSecretKey() // `sk` is a Uint8Array\nlet pk = getPublicKey(sk) // `pk` is a hex string\n```\n\nTo get the secret key in hex format, use\n\n```js\nimport { bytesToHex, hexToBytes } from '@noble/hashes/utils' // already an installed dependency\n\nlet skHex = bytesToHex(sk)\nlet backToBytes = hexToBytes(skHex)\n```\n\n### Creating, signing and verifying events\n\n```js\nimport { finalizeEvent, verifyEvent } from 'nostr-tools/pure'\n\nlet event = finalizeEvent({\n  kind: 1,\n  created_at: Math.floor(Date.now() / 1000),\n  tags: [],\n  content: 'hello',\n}, sk)\n\nlet isGood = verifyEvent(event)\n```\n\n### Interacting with one or multiple relays\n\nDoesn't matter what you do, you always should be using a `SimplePool`:\n\n```js\nimport { finalizeEvent, generateSecretKey, getPublicKey } from 'nostr-tools/pure'\nimport { SimplePool } from 'nostr-tools/pool'\n\nconst pool = new SimplePool()\n\n// let's query for an event that exists\nconst event = relay.get(\n  ['wss://relay.example.com'],\n  {\n    ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'],\n  },\n)\nif (event) {\n  console.log('it exists indeed on this relay:', event)\n}\n\n// let's publish a new event while simultaneously monitoring the relay for it\nlet sk = generateSecretKey()\nlet pk = getPublicKey(sk)\n\npool.subscribe(\n  ['wss://a.com', 'wss://b.com', 'wss://c.com'],\n  {\n    kinds: [1],\n    authors: [pk],\n  },\n  {\n    onevent(event) {\n      console.log('got event:', event)\n    }\n  }\n)\n\nlet eventTemplate = {\n  kind: 1,\n  created_at: Math.floor(Date.now() / 1000),\n  tags: [],\n  content: 'hello world',\n}\n\n// this assigns the pubkey, calculates the event id and signs the event in a single step\nconst signedEvent = finalizeEvent(eventTemplate, sk)\nawait Promise.any(pool.publish(['wss://a.com', 'wss://b.com'], signedEvent))\n\nrelay.close()\n```\n\nTo use this on Node.js you first must install `ws` and call something like this:\n\n```js\nimport { useWebSocketImplementation } from 'nostr-tools/pool'\n// or import { useWebSocketImplementation } from 'nostr-tools/relay' if you're using the Relay directly\n\nimport WebSocket from 'ws'\nuseWebSocketImplementation(WebSocket)\n```\n\n### Parsing references (mentions) from a content based on NIP-27\n\n```js\nimport * as nip27 from '@nostr/tools/nip27'\n\nfor (let block of nip27.parse(evt.content)) {\n  switch (block.type) {\n    case 'text':\n      console.log(block.text)\n      break\n    case 'reference': {\n      if ('id' in block.pointer) {\n        console.log(\"it's a nevent1 uri\", block.pointer)\n      } else if ('identifier' in block.pointer) {\n        console.log(\"it's a naddr1 uri\", block.pointer)\n      } else {\n        console.log(\"it's an npub1 or nprofile1 uri\", block.pointer)\n      }\n      break\n    }\n    case 'url': {\n      console.log(\"it's a normal url:\", block.url)\n      break\n    }\n    case 'image':\n    case 'video':\n    case 'audio':\n      console.log(\"it's a media url:\", block.url)\n    case 'relay':\n      console.log(\"it's a websocket url, probably a relay address:\", block.url)\n    default:\n      break\n  }\n}\n```\n\n### Connecting to a bunker using NIP-46\n\n```js\nimport { generateSecretKey, getPublicKey } from '@nostr/tools/pure'\nimport { BunkerSigner, parseBunkerInput } from '@nostr/tools/nip46'\nimport { SimplePool } from '@nostr/tools/pool'\n\n// the client needs a local secret key (which is generally persisted) for communicating with the bunker\nconst localSecretKey = generateSecretKey()\n\n// parse a bunker URI\nconst bunkerPointer = await parseBunkerInput('bunker://abcd...?relay=wss://relay.example.com')\nif (!bunkerPointer) {\n  throw new Error('Invalid bunker input')\n}\n\n// create the bunker instance\nconst pool = new SimplePool()\nconst bunker = new BunkerSigner(localSecretKey, bunkerPointer, { pool })\nawait bunker.connect()\n\n// and use it\nconst pubkey = await bunker.getPublicKey()\nconst event = await bunker.signEvent({\n  kind: 1,\n  created_at: Math.floor(Date.now() / 1000),\n  tags: [],\n  content: 'Hello from bunker!'\n})\n\n// cleanup\nawait signer.close()\npool.close([])\n```\n\n### Parsing thread from any note based on NIP-10\n\n```js\nimport * as nip10 from '@nostr/tools/nip10'\n\n// event is a nostr event with tags\nconst refs = nip10.parse(event)\n\n// get the root event of the thread\nif (refs.root) {\n  console.log('root event:', refs.root.id)\n  console.log('root event relay hints:', refs.root.relays)\n  console.log('root event author:', refs.root.author)\n}\n\n// get the immediate parent being replied to\nif (refs.reply) {\n  console.log('reply to:', refs.reply.id)\n  console.log('reply relay hints:', refs.reply.relays)\n  console.log('reply author:', refs.reply.author)\n}\n\n// get any mentioned events\nfor (let mention of refs.mentions) {\n  console.log('mentioned event:', mention.id)\n  console.log('mention relay hints:', mention.relays)\n  console.log('mention author:', mention.author)\n}\n\n// get any quoted events\nfor (let quote of refs.quotes) {\n  console.log('quoted event:', quote.id)\n  console.log('quote relay hints:', quote.relays)\n}\n\n// get any referenced profiles\nfor (let profile of refs.profiles) {\n  console.log('referenced profile:', profile.pubkey)\n  console.log('profile relay hints:', profile.relays)\n}\n```\n\n### Querying profile data from a NIP-05 address\n\n```js\nimport { queryProfile } from 'nostr-tools/nip05'\n\nlet profile = await queryProfile('jb55.com')\nconsole.log(profile.pubkey)\n// prints: 32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245\nconsole.log(profile.relays)\n// prints: [wss://relay.damus.io]\n```\n\nTo use this on Node.js < v18, you first must install `node-fetch@2` and call something like this:\n\n```js\nimport { useFetchImplementation } from 'nostr-tools/nip05'\nuseFetchImplementation(require('node-fetch'))\n```\n\n### Including NIP-07 types\n```js\nimport type { WindowNostr } from 'nostr-tools/nip07'\n\ndeclare global {\n  interface Window {\n    nostr?: WindowNostr;\n  }\n}\n```\n\n### Encoding and decoding NIP-19 codes\n\n```js\nimport { generateSecretKey, getPublicKey } from 'nostr-tools/pure'\nimport * as nip19 from 'nostr-tools/nip19'\n\nlet sk = generateSecretKey()\nlet nsec = nip19.nsecEncode(sk)\nlet { type, data } = nip19.decode(nsec)\nassert(type === 'nsec')\nassert(data === sk)\n\nlet pk = getPublicKey(generateSecretKey())\nlet npub = nip19.npubEncode(pk)\nlet { type, data } = nip19.decode(npub)\nassert(type === 'npub')\nassert(data === pk)\n\nlet pk = getPublicKey(generateSecretKey())\nlet relays = ['wss://relay.nostr.example.mydomain.example.com', 'wss://nostr.banana.com']\nlet nprofile = nip19.nprofileEncode({ pubkey: pk, relays })\nlet { type, data } = nip19.decode(nprofile)\nassert(type === 'nprofile')\nassert(data.pubkey === pk)\nassert(data.relays.length === 2)\n```\n\n### Using it with `nostr-wasm`\n\n[`nostr-wasm`](https://github.com/fiatjaf/nostr-wasm) is a thin wrapper over [libsecp256k1](https://github.com/bitcoin-core/secp256k1) compiled to WASM just for hashing, signing and verifying Nostr events.\n\n```js\nimport { setNostrWasm, generateSecretKey, finalizeEvent, verifyEvent } from 'nostr-tools/wasm'\nimport { initNostrWasm } from 'nostr-wasm'\n\n// make sure this promise resolves before your app starts calling finalizeEvent or verifyEvent\ninitNostrWasm().then(setNostrWasm)\n\n// or use 'nostr-wasm/gzipped' or even 'nostr-wasm/headless',\n// see https://www.npmjs.com/package/nostr-wasm for options\n```\n\nIf you're going to use `Relay` and `SimplePool` you must also import `nostr-tools/abstract-relay` and/or `nostr-tools/abstract-pool` instead of the defaults and then instantiate them by passing the `verifyEvent`:\n\n```js\nimport { setNostrWasm, verifyEvent } from 'nostr-tools/wasm'\nimport { AbstractRelay } from 'nostr-tools/abstract-relay'\nimport { AbstractSimplePool } from 'nostr-tools/abstract-pool'\nimport { initNostrWasm } from 'nostr-wasm'\n\ninitNostrWasm().then(setNostrWasm)\n\nconst relay = AbstractRelay.connect('wss://relayable.org', { verifyEvent })\nconst pool = new AbstractSimplePool({ verifyEvent })\n```\n\nThis may be faster than the pure-JS [noble libraries](https://paulmillr.com/noble/) used by default and in `nostr-tools/pure`. Benchmarks:\n\n```\nbenchmark      time (avg)             (min … max)       p75       p99      p995\n------------------------------------------------- -----------------------------\n• relay read message and verify event (many events)\n------------------------------------------------- -----------------------------\nwasm        34.94 ms/iter   (34.61 ms … 35.73 ms)  35.07 ms  35.73 ms  35.73 ms\npure js     239.7 ms/iter (235.41 ms … 243.69 ms) 240.51 ms 243.69 ms 243.69 ms\ntrusted    402.71 µs/iter   (344.57 µs … 2.98 ms) 407.39 µs 745.62 µs 812.59 µs\n\nsummary for relay read message and verify event\n  wasm\n   86.77x slower than trusted\n   6.86x faster than pure js\n```\n\n### Using from the browser (if you don't want to use a bundler)\n\n```html\n<script src=\"https://unpkg.com/nostr-tools/lib/nostr.bundle.js\"></script>\n<script>\n  window.NostrTools.generateSecretKey('...') // and so on\n</script>\n```\n\n## Plumbing\n\nTo develop `nostr-tools`, install [`just`](https://just.systems/) and run `just -l` to see commands available.\n","readmeFilename":"README.md","_rev":"1-e9984fa486f581fd4f65236ae5b9883b"}