{"id":20687608,"url":"https://github.com/susumuota/nostrain","last_synced_at":"2026-05-08T02:18:04.304Z","repository":{"id":149987964,"uuid":"622509461","full_name":"susumuota/nostrain","owner":"susumuota","description":"Nostr client library with no strain.","archived":false,"fork":false,"pushed_at":"2023-04-23T11:36:55.000Z","size":90,"stargazers_count":2,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-02-18T19:09:46.013Z","etag":null,"topics":["client","cryptography","nodejs","nostr","protocol","relay","sns","social-media","social-network","typescript","vite"],"latest_commit_sha":null,"homepage":"","language":"TypeScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/susumuota.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2023-04-02T10:31:56.000Z","updated_at":"2024-09-04T12:52:10.000Z","dependencies_parsed_at":null,"dependency_job_id":"ab18529a-7825-4f8d-88d1-5c90e6916afc","html_url":"https://github.com/susumuota/nostrain","commit_stats":null,"previous_names":[],"tags_count":5,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/susumuota%2Fnostrain","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/susumuota%2Fnostrain/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/susumuota%2Fnostrain/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/susumuota%2Fnostrain/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/susumuota","download_url":"https://codeload.github.com/susumuota/nostrain/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":242945840,"owners_count":20210762,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["client","cryptography","nodejs","nostr","protocol","relay","sns","social-media","social-network","typescript","vite"],"created_at":"2024-11-16T22:57:46.755Z","updated_at":"2025-12-14T19:58:32.677Z","avatar_url":"https://github.com/susumuota.png","language":"TypeScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# nostrain: Nostr client library with no strain\n\n[![npm](https://img.shields.io/npm/v/nostrain?color=blue)](https://www.npmjs.com/package/nostrain)\n[![GitHub](https://img.shields.io/github/license/susumuota/nostrain)](https://github.com/susumuota/nostrain/blob/main/LICENSE)\n[![GitHub Workflow Status](https://img.shields.io/github/actions/workflow/status/susumuota/nostrain/build.yaml)](https://github.com/susumuota/nostrain/actions/workflows/build.yaml)\n[![GitHub last commit](https://img.shields.io/github/last-commit/susumuota/nostrain)](https://github.com/susumuota/nostrain/commits)\n\u0026emsp;\nEN |\n[JA](https://github-com.translate.goog/susumuota/nostrain/blob/main/README.md?_x_tr_sl=en\u0026_x_tr_tl=ja\u0026_x_tr_hl=ja\u0026_x_tr_pto=wapp) |\n[ES](https://github-com.translate.goog/susumuota/nostrain/blob/main/README.md?_x_tr_sl=en\u0026_x_tr_tl=es\u0026_x_tr_hl=es\u0026_x_tr_pto=wapp) |\n[ZH](https://github-com.translate.goog/susumuota/nostrain/blob/main/README.md?_x_tr_sl=en\u0026_x_tr_tl=zh-CN\u0026_x_tr_hl=zh-CN\u0026_x_tr_pto=wapp)\n\n- A Nostr client library with modern TypeScript style.\n- Most of the functions are refactored from [nostr-tools](https://github.com/nbd-wtf/nostr-tools) and are compatible with it.\n- Only depends on [@noble](https://github.com/paulmillr/noble-curves) and [@scure](https://github.com/paulmillr/scure-base) packages.\n\n## Installation\n\n```bash\nnpm install nostrain\n```\n\n## Usage\n\n- See [examples](https://github.com/susumuota/nostrain/tree/main/examples) directory.\n\n### Generating a private key and a public key\n\n```javascript\nimport { generatePrivateKey, getPublicKey } from 'nostrain';\n\nconst sk = generatePrivateKey(); // `sk` is a hex string\nconst pk = getPublicKey(sk); // `pk` is a hex string\n\nconsole.log({ sk, pk });\n```\n\n### Creating, signing and verifying events\n\n```javascript\nimport { validateEvent, verifySignature, signEvent, getEventHash, generatePrivateKey, getPublicKey } from 'nostrain';\n\nconst privateKey = generatePrivateKey();\n\nconst event = {\n  kind: 1,\n  created_at: Math.floor(Date.now() / 1000),\n  tags: [],\n  content: 'hello',\n  pubkey: getPublicKey(privateKey),\n  id: '',\n  sig: '',\n};\n\nevent.id = getEventHash(event);\nevent.sig = signEvent(event, privateKey);\nconst validateOk = validateEvent(event);\nconst verifyOk = verifySignature(event);\n\nconsole.log({ event, validateOk, verifyOk });\n```\n\n### Interacting with a relay\n\n```javascript\nimport crypto from 'node:crypto';\nglobalThis.crypto = crypto;\n\nimport 'websocket-polyfill';\nimport { relayInit, generatePrivateKey, getPublicKey, getEventHash, signEvent } from 'nostrain';\n\nconst relay = relayInit('wss://relay.damus.io');\n\nrelay.on('connect', () =\u003e {\n  console.log(`connected to ${relay.url}`);\n});\nrelay.on('error', () =\u003e {\n  console.log(`failed to connect to ${relay.url}`);\n});\n\nawait relay.connect();\n\n{\n  // let's query for an event that exists\n  const sub = relay.sub([{ ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'] }]);\n\n  sub.on('event', event =\u003e {\n    console.log('we got the event we wanted:', event);\n  });\n  sub.on('eose', () =\u003e {\n    sub.unsub();\n  });\n}\n\n{\n  // let's publish a new event while simultaneously monitoring the relay for it\n  const sk = generatePrivateKey();\n  const pk = getPublicKey(sk);\n\n  const sub = relay.sub([{ kinds: [1], authors: [pk] }]);\n\n  sub.on('event', event =\u003e {\n    console.log('got event:', event);\n  });\n\n  const event = {\n    kind: 1,\n    pubkey: pk,\n    created_at: Math.floor(Date.now() / 1000),\n    tags: [],\n    content: 'hello world',\n  };\n  event.id = getEventHash(event);\n  event.sig = signEvent(event, sk);\n\n  const pub = relay.publish(event);\n  pub.on('ok', () =\u003e {\n    console.log(`${relay.url} has accepted our event`);\n  });\n  pub.on('failed', reason =\u003e {\n    console.log(`failed to publish to ${relay.url}: ${reason}`);\n  });\n}\n\n{\n  // let's query for events by list and get\n  const events = await relay.list([{ kinds: [0, 1] }]);\n  console.log(events.length);\n\n  const event = await relay.get({ ids: ['d7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027'] });\n  console.log({ event });\n}\n\nrelay.close();\n```\n\nTo use this on Node.js you first must install `websocket-polyfill` and import it:\n\n```javascript\nimport 'websocket-polyfill';\n```\n\n### Interacting with multiple relays\n\nTODO: work in progress\n\n### Parsing references (mentions) from a content using NIP-10 and NIP-27\n\nSee [src/references.test.ts](https://github.com/susumuota/nostrain/blob/main/src/references.test.ts).\n\n### Querying profile data from a NIP-05 address\n\n```javascript\nimport { nip05 } from 'nostrain';\n\nconst profile = await nip05.queryProfile('jb55.com');\n\nconsole.log({ profile });\n```\n\n### Encoding and decoding NIP-19 codes\n\n```javascript\nimport { nip19, generatePrivateKey, getPublicKey } from 'nostrain';\n\n{\n  const sk = generatePrivateKey();\n  const nsec = nip19.nsecEncode(sk);\n  const { type, data } = nip19.decode(nsec);\n  console.log({ sk, nsec, type, data });\n}\n\n{\n  const pk = getPublicKey(generatePrivateKey());\n  const npub = nip19.npubEncode(pk);\n  const { type, data } = nip19.decode(npub);\n  console.log({ pk, npub, type, data });\n}\n\n{\n  const pk = getPublicKey(generatePrivateKey());\n  const relays = ['wss://relay.nostr.example.mydomain.example.com', 'wss://nostr.banana.com'];\n  const nprofile = nip19.nprofileEncode({ pubkey: pk, relays });\n  const { type, data } = nip19.decode(nprofile);\n  console.log({ pk, relays, nprofile, type, data });\n}\n```\n\n### Encrypting and decrypting direct messages\n\n```javascript\nimport crypto from 'node:crypto';\n\nglobalThis.crypto = crypto;\n\nimport { nip04, getPublicKey, generatePrivateKey } from 'nostrain';\n\n// sender\nconst sk1 = generatePrivateKey();\nconst pk1 = getPublicKey(sk1);\n\n// receiver\nconst sk2 = generatePrivateKey();\nconst pk2 = getPublicKey(sk2);\n\n// on the sender side\nconst message = 'hello';\nconst ciphertext = await nip04.encrypt(sk1, pk2, message);\n\n// on the receiver side\nconst plaintext = await nip04.decrypt(sk2, pk1, ciphertext);\n\nconsole.log({ message, ciphertext, plaintext });\n```\n\n### Performing and checking for delegation\n\n```javascript\nimport { nip26, getPublicKey, generatePrivateKey } from 'nostrain';\n\n// delegator\nconst sk1 = generatePrivateKey();\nconst pk1 = getPublicKey(sk1);\n\n// delegatee\nconst sk2 = generatePrivateKey();\nconst pk2 = getPublicKey(sk2);\n\n// generate delegation\nconst delegation = nip26.createDelegation(sk1, {\n  pubkey: pk2,\n  kind: 1,\n  since: Math.round(Date.now() / 1000) - 1, // 1 second ago\n  until: Math.round(Date.now() / 1000) + 60 * 60 * 24 * 30, // 30 days\n});\n\nconsole.log({ delegation });\n\n// the delegatee uses the delegation when building an event\nconst event = {\n  pubkey: pk2,\n  kind: 1,\n  created_at: Math.round(Date.now() / 1000),\n  content: 'hello from a delegated key',\n  tags: [['delegation', delegation.from, delegation.cond, delegation.sig]],\n};\n\nconsole.log({ tags: event.tags });\n\n// finally any receiver of this event can check for the presence of a valid delegation tag\nconst delegator = nip26.getDelegator(event);\n\n// will be null if there is no delegation tag or if it is invalid\nconsole.log({ delegator, pk1, success: delegator === pk1 });\n```\n\n## Development\n\n```bash\ngit clone https://github.com/susumuota/nostrain.git\ncd nostrain\nnpm ci\nnpm run build  # or npm run watch\nnpm run test\nnpm run format\n```\n\n## Source code\n\n- https://github.com/susumuota/nostrain\n\n## Related Links\n\n- [Nostr](https://github.com/nostr-protocol/nostr): The simplest open protocol that is able to create a censorship-resistant global \"social\" network once and for all.\n- [NIPs](https://github.com/nostr-protocol/nips): NIPs stand for Nostr Implementation Possibilities. They exist to document what may be implemented by Nostr-compatible relay and client software.\n- [nostr-tools](https://github.com/nbd-wtf/nostr-tools): Tools for developing Nostr clients.\n- [noble-curves](https://github.com/paulmillr/noble-curves): Audited \u0026 minimal JavaScript implementation of elliptic curve cryptography.\n- [scure-base](https://github.com/paulmillr/scure-base): Secure, audited and 0-dep implementation of bech32, etc.\n\n## License\n\nMIT License, see [LICENSE](LICENSE) file.\n\n## Author\n\nS. Ota\n\n- nostr: [npub1susumuq8u7v0sp2f5jl3wjuh8hpc3cqe2tc2j5h4gu7ze7z20asq2w0yu8](https://iris.to/s_ota)\n- GitHub: [susumuota](https://github.com/susumuota)\n- Twitter: [@susumuota](https://twitter.com/susumuota)\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsusumuota%2Fnostrain","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fsusumuota%2Fnostrain","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fsusumuota%2Fnostrain/lists"}