{"id":13832514,"url":"https://github.com/Cameri/nostream","last_synced_at":"2025-07-09T19:30:31.090Z","repository":{"id":54211911,"uuid":"482383845","full_name":"cameri/nostream","owner":"cameri","description":"A Nostr Relay written in TypeScript","archived":false,"fork":false,"pushed_at":"2024-10-21T00:25:47.000Z","size":1587,"stargazers_count":742,"open_issues_count":77,"forks_count":192,"subscribers_count":25,"default_branch":"main","last_synced_at":"2024-10-21T03:50:04.447Z","etag":null,"topics":["nostr"],"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/cameri.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":"CONTRIBUTING.md","funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":"SECURITY.md","support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2022-04-16T23:33:58.000Z","updated_at":"2024-10-21T00:25:51.000Z","dependencies_parsed_at":"2024-05-29T22:53:15.961Z","dependency_job_id":"73dc9929-56eb-4ad2-8e4c-e67de4536d81","html_url":"https://github.com/cameri/nostream","commit_stats":null,"previous_names":[],"tags_count":48,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cameri%2Fnostream","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cameri%2Fnostream/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cameri%2Fnostream/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/cameri%2Fnostream/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/cameri","download_url":"https://codeload.github.com/cameri/nostream/tar.gz/refs/heads/main","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":225581774,"owners_count":17491788,"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":["nostr"],"created_at":"2024-08-04T11:00:21.665Z","updated_at":"2024-11-20T15:30:48.210Z","avatar_url":"https://github.com/cameri.png","language":"TypeScript","funding_links":[],"categories":["Install from Source","Relays"],"sub_categories":["Nostr","Implementations"],"readme":"# [nostream](https://github.com/cameri/nostream)\n\n\u003cp align=\"center\"\u003e\n  \u003cimg alt=\"nostream logo\" height=\"256px\" width=\"256px\" src=\"https://user-images.githubusercontent.com/378886/198158439-86e0345a-adc8-4efe-b0ab-04ff3f74c1b2.jpg\" /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"https://github.com/cameri/nostream/releases\"\u003e\n    \u003cimg alt=\"GitHub release\" src=\"https://img.shields.io/github/v/release/Cameri/nostream\"\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/cameri/nostream/issues\"\u003e\n    \u003cimg alt=\"GitHub issues\" src=\"https://img.shields.io/github/issues/Cameri/nostream?style=plastic\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/cameri/nostream/stargazers\"\u003e\n    \u003cimg alt=\"GitHub stars\" src=\"https://img.shields.io/github/stars/Cameri/nostream\" /\u003e\n  \u003c/a\u003e\n  \u003cimg alt=\"GitHub top language\" src=\"https://img.shields.io/github/languages/top/Cameri/nostream\"\u003e\n  \u003ca href=\"https://github.com/cameri/nostream/network\"\u003e\n    \u003cimg alt=\"GitHub forks\" src=\"https://img.shields.io/github/forks/Cameri/nostream\" /\u003e\n  \u003c/a\u003e\n  \u003ca href=\"https://github.com/cameri/nostream/blob/main/LICENSE\"\u003e\n    \u003cimg alt=\"GitHub license\" src=\"https://img.shields.io/github/license/Cameri/nostream\" /\u003e\n  \u003c/a\u003e\n  \u003ca href='https://coveralls.io/github/Cameri/nostream?branch=main'\u003e\n    \u003cimg  alt='Coverage Status' src='https://coveralls.io/repos/github/Cameri/nostream/badge.svg?branch=main' /\u003e\n  \u003c/a\u003e\n  \u003ca href='https://sonarcloud.io/project/overview?id=Cameri_nostr-ts-relay'\u003e\n    \u003cimg alt='Sonarcloud quality gate' src='https://sonarcloud.io/api/project_badges/measure?project=Cameri_nostr\u0026metric=alert_status' /\u003e\n  \u003c/a\u003e\n  \u003ca href='https://github.com/cameri/nostream/actions'\u003e\n    \u003cimg alt='Build status' src='https://github.com/cameri/nostream/actions/workflows/checks.yml/badge.svg?branch=main\u0026event=push' /\u003e\n  \u003c/a\u003e\n\u003c/p\u003e\n\nThis is a [nostr](https://github.com/fiatjaf/nostr) relay, written in\nTypescript.\n\nThis implementation is production-ready. See below for supported features.\n\nThe project master repository is available on [GitHub](https://github.com/cameri/nostream).\n\n[![Deploy on Railway](https://railway.app/button.svg)](https://railway.app/new/template/Xfk5F7?referralCode=Kfv2ly)\n\n## Features\n\nNIPs with a relay-specific implementation are listed here.\n\n- [x] NIP-01: Basic protocol flow description\n- [x] NIP-02: Contact list and petnames\n- [x] NIP-04: Encrypted Direct Message\n- [x] NIP-09: Event deletion\n- [x] NIP-11: Relay information document\n- [x] NIP-11a: Relay Information Document Extensions\n- [x] NIP-12: Generic tag queries\n- [x] NIP-13: Proof of Work\n- [x] NIP-15: End of Stored Events Notice\n- [x] NIP-16: Event Treatment\n- [x] NIP-20: Command Results\n- [x] NIP-22: Event `created_at` Limits\n- [ ] NIP-26: Delegated Event Signing (REMOVED)\n- [x] NIP-28: Public Chat\n- [x] NIP-33: Parameterized Replaceable Events\n- [x] NIP-40: Expiration Timestamp\n\n## Requirements\n\n### Standalone setup\n- PostgreSQL 14.0\n- Redis\n- Node v18\n- Typescript\n\n### Docker setups\n- Docker v20.10\n- Docker Compose v2.10\n\n### Local Docker setup\n- Docker Desktop v4.2.0 or newer\n- [mkcert](https://github.com/FiloSottile/mkcert)\n\nWARNING: Docker distributions from Snap, Brew or Debian repositories are NOT SUPPORTED and will result in errors.\nInstall Docker from their [official guide](https://docs.docker.com/engine/install/) ONLY.\n\n## Full Guide\n\n- [Set up a Paid Nostr relay with Nostream and ZEBEDEE](https://docs.zebedee.io/docs/guides/nostr-relay) by [André Neves](https://primal.net/andre) (CTO \u0026 Co-Founder at [ZEBEDEE](https://zebedee.io/))\n- [Set up a Nostr relay in under 5 minutes](https://andreneves.xyz/p/set-up-a-nostr-relay-server-in-under) by [André Neves](https://twitter.com/andreneves) (CTO \u0026 Co-Founder at [ZEBEDEE](https://zebedee.io/))\n\n### Accepting Payments\n\n1. Before you begin\n   - Complete one of the Quick Start guides in this document\n   - Create a `.env` file\n   - On `.nostr/settings.yaml` file make the following changes:\n     - Set `payments.enabled` to `true`\n     - Set `payments.feeSchedules.admission.enabled` to `true`\n     - Set `limits.event.pubkey.minBalance` to the minimum balance in msats required to accept events (i.e. `1000000` to require a balance of `1000` sats)\n   - Choose one of the following payment processors: `zebedee`, `nodeless`, `opennode`, `lnbits`, `lnurl`\n\n2. [ZEBEDEE](https://zebedee.io)\n   - Complete the step \"Before you begin\"\n   - [Sign up for a ZEBEDEE Developer Dashboard account](https://dashboard.zebedee.io/signup), create a new LIVE Project, and get that Project's API Key\n   - Set `ZEBEDEE_API_KEY` environment variable with the API Key above on your `.env` file\n\n    ```\n    ZEBEDEE_API_KEY={YOUR_ZEBEDEE_API_KEY_HERE}\n    ```\n\n   - Follow the required steps for all payments processors\n   - On `.nostr/settings.yaml` file make the following changes:\n     - `payments.processor` to `zebedee`\n     - `paymentsProcessors.zebedee.callbackBaseURL` to match your Nostream URL (e.g. `https://{YOUR_DOMAIN_HERE}/callbacks/zebedee`)\n   - Restart Nostream (`./scripts/stop` followed by `./scripts/start`)\n   - Read the in-depth guide for more information: [Set Up a Paid Nostr Relay with ZEBEDEE API](https://docs.zebedee.io/docs/guides/nostr-relay)\n\n3. [Nodeless](https://nodeless.io/?ref=587f477f-ba1c-4bd3-8986-8302c98f6731)\n   - Complete the step \"Before you begin\"\n   - [Sign up](https://nodeless.io/?ref=587f477f-ba1c-4bd3-8986-8302c98f6731) for a new account, create a new store and take note of the store ID\n   - Go to Profile \u003e API Tokens and generate a new key and take note of it\n   - Create a store webhook with your Nodeless callback URL (e.g. `https://{YOUR_DOMAIN_HERE}/callbacks/nodeless`) and make sure to enable all of the events. Grab the generated store webhook secret\n   - Set `NODELESS_API_KEY` and `NODELESS_WEBHOOK_SECRET` environment variables with generated API key and webhook secret, respectively\n\n    ```\n    NODELESS_API_KEY={YOUR_NODELESS_API_KEY}\n    NODELESS_WEBHOOK_SECRET={YOUR_NODELESS_WEBHOOK_SECRET}\n    ```\n\n   - On your `.nostr/settings.yaml` file make the following changes:\n     - Set `payments.processor` to `nodeless`\n     - Set `paymentsProcessors.nodeless.storeId` to your store ID\n   - Restart Nostream (`./scripts/stop` followed by `./scripts/start`)\n\n4. [OpenNode](https://www.opennode.com/)\n   - Complete the step \"Before you begin\"\n   - Sign up for a new account and get verified\n   - Go to Developers \u003e Integrations and setup two-factor authentication\n   - Create a new API Key with Invoices permission\n   - Set `OPENNODE_API_KEY` environment variable on your `.env` file\n\n     ```\n     OPENNODE_API_KEY={YOUR_OPENNODE_API_KEY}\n     ```\n\n   - On your `.nostr/settings.yaml` file make the following changes:\n     - Set `payments.processor` to `opennode`\n   - Restart Nostream (`./scripts/stop` followed by `./scripts/start`)\n\n5. [LNBITS](https://lnbits.com/)\n    - Complete the step \"Before you begin\"\n    - Create a new wallet on you public LNbits instance\n      - [Demo](https://legend.lnbits.com/) server must not be used for production\n      - Your instance must be accessible from the internet and have a valid SSL/TLS certificate\n    - Get wallet Invoice/read key (in Api docs section of your wallet)\n    - set `LNBITS_API_KEY` environment variable with the Invoice/read key Key above on your `.env` file\n\n      ```\n      LNBITS_API_KEY={YOUR_LNBITS_API_KEY_HERE}\n      ```\n    - On your `.nostr/settings.yaml` file make the following changes:\n      - Set `payments.processor` to `lnbits`\n      - set `lnbits.baseURL` to your LNbits instance URL (e.g. `https://{YOUR_LNBITS_DOMAIN_HERE}/`)\n      - Set `paymentsProcessors.lnbits.callbackBaseURL` to match your Nostream URL (e.g. `https://{YOUR_DOMAIN_HERE}/callbacks/lnbits`)\n    - Restart Nostream (`./scripts/stop` followed by `./scripts/start`)\n\n6. [Alby](https://getalby.com/) or any LNURL Provider with [LNURL-verify](https://github.com/lnurl/luds/issues/182) support\n    - Complete the step \"Before you begin\"\n    - [Create a new account](https://getalby.com/user/new) if you don't have an LNURL\n    - On your `.nostr/settings.yaml` file make the following changes:\n      - Set `payments.processor` to `lnurl`\n      - Set `lnurl.invoiceURL` to your LNURL (e.g. `https://getalby.com/lnurlp/your-username`)\n    - Restart Nostream (`./scripts/stop` followed by `./scripts/start`)\n\n7. Ensure payments are required for your public key\n   - Visit https://{YOUR-DOMAIN}/\n   - You should be presented with a form requesting an admission fee to be paid\n   - Fill out the form and take the necessary steps to pay the invoice\n   - Wait until the screen indicates that payment was received\n   - Add your relay URL to your favorite Nostr client (wss://{YOUR-DOMAIN}) and wait for it to connect\n   - Send a couple notes to test\n   - Go to https://websocketking.com/ and connect to your relay (wss://{YOUR_DOMAIN})\n   - Convert your npub to hexadecimal using a [Key Converter](https://damus.io/key/)\n   - Send the following JSON message: `[\"REQ\", \"payment-test\", {\"authors\":[\"your-pubkey-in-hexadecimal\"]}]`\n   - You should get back the few notes you sent earlier\n\n## Quick Start (Docker Compose)\n\nInstall Docker following the [official guide](https://docs.docker.com/engine/install/).\nYou may have to uninstall Docker if you installed it using a different guide.\n\nClone repository and enter directory:\n  ```\n  git clone git@github.com:Cameri/nostream.git\n  cd nostream\n  ```\n\nGenerate a secret with: `openssl rand -hex 128`\nCopy the output and paste it into an `.env` file:\n\n  ```\n  SECRET=aaabbbccc...dddeeefff\n  # Secret shortened for brevity\n  ```\n\nStart:\n  ```\n  ./scripts/start\n  ```\n  or\n  ```\n  ./scripts/start_with_tor\n  ```\n\nStop the server with:\n  ```\n  ./scripts/stop\n  ```\n\nPrint the Tor hostname:\n  ```\n  ./scripts/print_tor_hostname\n  ```\n\n### Running as a Service\n\nBy default this server will run continuously until you stop it with Ctrl+C or until the system restarts.\n\nYou can [install as a systemd service](https://www.swissrouting.com/nostr.html#installing-as-a-service) if you want the server to run again automatically whenever the system is restarted. For example:\n\n  ```\n  $ nano /etc/systemd/system/nostream.service\n\n  # Note: replace \"User=...\" with your username, and\n  # \"/home/nostr/nostream\" with the directory where you cloned the repo.\n\n  [Unit]\n  Description=Nostr TS Relay\n  After=network.target\n  StartLimitIntervalSec=0\n\n  [Service]\n  Type=simple\n  Restart=always\n  RestartSec=5\n  User=nostr\n  WorkingDirectory=/home/nostr/nostream\n  ExecStart=/home/nostr/nostream/scripts/start\n  ExecStop=/home/nostr/nostream/scripts/stop\n\n  [Install]\n  WantedBy=multi-user.target\n  ```\n\nAnd then:\n\n  ```\n  systemctl enable nostream\n  systemctl start nostream\n  ```\n\nThe logs can be viewed with:\n\n  ```\n  journalctl -u nostream\n  ```\n\n## Quick Start (Standalone)\n\nSet the following environment variables:\n\n  ```\n  DB_URI=\"postgresql://postgres:postgres@localhost:5432/nostr_ts_relay_test\"\n  DB_USER=postgres\n  ```\n  or\n  ```\n  DB_HOST=localhost\n  DB_PORT=5432\n  DB_NAME=nostr_ts_relay\n  DB_USER=postgres\n  DB_PASSWORD=postgres\n  ```\n\n  ```\n  REDIS_URI=\"redis://default:nostr_ts_relay@localhost:6379\"\n\n  REDIS_HOST=localhost\n  REDIS_PORT=6379\n  REDIS_USER=default\n  REDIS_PASSWORD=nostr_ts_relay\n  ```\n\nGenerate a long random secret and set SECRET:\nYou may want to use `openssl rand -hex 128` to generate a secret.\n\n  ```\n  SECRET=aaabbbccc...dddeeefff\n  # Secret shortened for brevity\n  ```\n\n### Initializing the database\n\nCreate `nostr_ts_relay` database:\n\n  ```\n  $ psql -h $DB_HOST -p $DB_PORT -U $DB_USER -W\n  postgres=# create database nostr_ts_relay;\n  postgres=# quit\n  ```\n\nStart Redis and use `redis-cli` to set the default password and verify:\n  ```\n  $ redis-cli\n  127.0.0.1:6379\u003e CONFIG SET requirepass \"nostr_ts_relay\"\n  OK\n  127.0.0.1:6379\u003e AUTH nostr_ts_relay\n  Ok\n  ```\n\nClone repository and enter directory:\n  ```\n  git clone git@github.com:Cameri/nostream.git\n  cd nostream\n  ```\n\nInstall dependencies:\n\n  ```\n  npm install -g knex\n  npm install\n  ```\n\nRun migrations (at least once and after pulling new changes):\n\n  ```\n  NODE_OPTIONS=\"-r dotenv/config\" npm run db:migrate\n  ```\n\nCreate .nostr folder inside nostream project folder and copy over the settings file:\n\n  ```\n  mkdir .nostr\n  cp resources/default-settings.yaml .nostr/settings.yaml\n  ```\n\nTo start in development mode:\n\n  ```\n  npm run dev\n  ```\n\nOr, start in production mode:\n\n  ```\n  npm run start\n  ```\n\nTo clean up the build, coverage and test reports run:\n\n  ```\n  npm run clean\n  ```\n## Development Quick Start (Docker Compose)\n\nInstall Docker Desktop following the [official guide](https://docs.docker.com/desktop/).\nYou may have to uninstall Docker on your machine if you installed it using a different guide.\n\nClone repository and enter directory:\n  ```\n  git clone git@github.com:Cameri/nostream.git\n  cd nostream\n  ```\n\nStart:\n  ```\n  ./scripts/start_local\n  ```\n\n  This will run in the foreground of the terminal until you stop it with Ctrl+C.\n\n## Tests\n\n### Unit tests\n\nOpen a terminal and change to the project's directory:\n  ```\n  cd /path/to/nostream\n  ```\n\nRun unit tests with:\n\n  ```\n  npm run test:unit\n  ```\n\nOr, run unit tests in watch mode:\n\n  ```\n  npm run test:unit:watch\n  ```\n\nTo get unit test coverage run:\n\n  ```\n  npm run cover:unit\n  ```\n\nTo see the unit tests report open `.test-reports/unit/index.html` with a browser:\n  ```\n  open .test-reports/unit/index.html\n  ```\n\nTo see the unit tests coverage report open `.coverage/unit/lcov-report/index.html` with a browser:\n  ```\n  open .coverage/unit/lcov-report/index.html\n  ```\n\n### Integration tests (Docker Compose)\n\nOpen a terminal and change to the project's directory:\n  ```\n  cd /path/to/nostream\n  ```\n\nRun integration tests with:\n\n  ```\n  npm run docker:test:integration\n  ```\n\nAnd to get integration test coverage run:\n\n  ```\n  npm run docker:cover:integration\n  ```\n\n### Integration tests (Standalone)\n\nOpen a terminal and change to the project's directory:\n  ```\n  cd /path/to/nostream\n  ```\n\nSet the following environment variables:\n\n  ```\n  DB_URI=\"postgresql://postgres:postgres@localhost:5432/nostr_ts_relay_test\"\n\n  or\n\n  DB_HOST=localhost\n  DB_PORT=5432\n  DB_NAME=nostr_ts_relay_test\n  DB_USER=postgres\n  DB_PASSWORD=postgres\n  DB_MIN_POOL_SIZE=1\n  DB_MAX_POOL_SIZE=2\n  ```\n\nThen run the integration tests:\n\n  ```\n  npm run test:integration\n  ```\n\nTo see the integration tests report open `.test-reports/integration/report.html` with a browser:\n  ```\n  open .test-reports/integration/report.html\n  ```\n\nTo get the integration test coverage run:\n\n  ```\n  npm run cover:integration\n  ```\n\nTo see the integration test coverage report open `.coverage/integration/lcov-report/index.html` with a browser.\n\n  ```\n  open .coverage/integration/lcov-report/index.html\n  ```\n\n## Configuration\n\nYou can change the default folder by setting the `NOSTR_CONFIG_DIR` environment variable to a different path.\n\nRun nostream using one of the quick-start guides at least once and `nostream/.nostr/settings.json` will be created.\nAny changes made to the settings file will be read on the next start.\n\nDefault settings can be found under `resources/default-settings.yaml`. Feel free to copy it to `nostream/.nostr/settings.yaml` if you would like to have a settings file before running the relay first.\n\nSee [CONFIGURATION.md](CONFIGURATION.md) for a detailed explanation of each environment variable and setting.\n## Dev Channel\n\nFor development discussions, please use the [Nostr Typescript Relay Dev Group](https://t.me/nostream_dev).\n\nFor discussions about the protocol, please feel free to use the [Nostr Telegram Group](https://t.me/nostr_protocol).\n\n# Author\n\nI'm Cameri on most social networks. You can find me on Nostr by npub1qqqqqqyz0la2jjl752yv8h7wgs3v098mh9nztd4nr6gynaef6uqqt0n47m.\n\n# Contributors (A-Z)\n\n- Anton Livaja\n- Juan Angel\n- Kevin Smith\n- Saransh Sharma\n- swissrouting\n- André Neves\n- Semisol\n\n## License\n\nThis project is MIT licensed.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCameri%2Fnostream","html_url":"https://awesome.ecosyste.ms/projects/github.com%2FCameri%2Fnostream","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2FCameri%2Fnostream/lists"}