Home

Awesome

Nostr client and relay

Nostr client and relay is a nostr C++ engine that allows to build Nostr applications for command line, desktop or web.

https://github.com/pedro-vicente/nostr_client_relay

See BUILDING.md for source code build instructions. There are 3 non exclusive modes of build (command line, desktop and web). Default is command line only.

Nostr_client_relay allows to build Nostr clients and Nostr relays. It includes:

Nostro

Nostro is a Nostr command line client. Usage is:

./nostro [OPTIONS]
[OPTIONS]:
  --uri <wss URI>      Wss URI to send
  --req                message is a request (REQ). EVENT parameters are ignored
REQ OPTIONS: These are for the REQ filter, per NIP-01
  --authors <string>   a list of pubkeys or prefixes
EVENT OPTIONS: These are to publish an EVENT, per NIP-01
  --content <string>   the content of the note
  --kind <number>      set kind
  --sec <hex seckey>   set the secret key for signing, otherwise one will be randomly generated

Vostro

Vostro is a Nostr relay. At the momment it uses a plain text JSON database (for development purposes). A database is a JSON array of events read and saved to filesystem in JSON format. Vostro is a command line application. To start Vostro, open a shell and do (the output is from the Vostro log output, Vostro is a WebSockets server):

./vostro
vostro:04:23:00 Listening on port: 8080

Examples

This example shows 2 Nostro calls: publishing an EVENT and doing a REQ on the relay database. If no <code>--uri</code> parameter is set, then Nostro publishes to a Nostr relay listening in <code>localhost</code>. To publish an event signed with your private key, with the content 'hello world', we use

./nostro --sec <seckey> --content 'hello world' --kind 1

This call generated the following entry on the Vostro JSON database. To note that the key 'pubkey' is the Nostr public key associated with the private key used by Nostro to sign the event.

[
  {
    "content": "hello world",
    "created_at": 1688794190,
    "id": "c3a4a0a20712db7249aa4d07598d5f88e31d77f95a4cc8e7bb41bd64348011f8",
    "kind": 1,
    "pubkey": "4ea843d54a8fdab39aa45f61f19f3ff79cc19385370f6a272dda81fade0a052b",
    "sig": "4337b5361f11decd00b96bc37a87324f707f370687a7cab066c4e001145d5ee3312a3f1c1cfa6f3638e3461c8dec77468fcf35b465a16c6358ef313fca748730",
    "tags": []
  }
]

To query for the event we just inserted in the datbase, we do

./nostro  --req --authors 4ea843d54a8fdab39aa45f61f19f3ff79cc19385370f6a272dda81fade0a052b

where the <code>--authors</code> paramenter is the public key corresponding to the private key used to sign the event. This call generates the following message to send

[
 "REQ",
 "5FBD876B-EA8C-454C-9E6F-590A8A5B6DC0",
 {
  "authors": [
   "4ea843d54a8fdab39aa45f61f19f3ff79cc19385370f6a272dda81fade0a052b"
  ],
  "kinds": [
   1
  ],
  "limit": 0
 }
]

Vostro responds withe following EVENT, that was found in the database by comparing the <code>--authors</code> field.

[
 "EVENT",
 "031007A3-FC06-452F-8727-8160F4B9A17A",
 {
  "content": "hello world",
  "created_at": 1688794190,
  "id": "c3a4a0a20712db7249aa4d07598d5f88e31d77f95a4cc8e7bb41bd64348011f8",
  "kind": 1,
  "pubkey": "4ea843d54a8fdab39aa45f61f19f3ff79cc19385370f6a272dda81fade0a052b",
  "sig": "4337b5361f11decd00b96bc37a87324f707f370687a7cab066c4e001145d5ee3312a3f1c1cfa6f3638e3461c8dec77468fcf35b465a16c6358ef313fca748730",
  "tags": []
 }
]

Running on web

At command line

./wostro --http-address=0.0.0.0 --http-port=8080 --docroot=.

Open a browser at localhost port 8080

http://127.0.0.1/8080

Web interface

The nostro web interface at this time allows input of a limited set of the command line options.

Nostro on the web

It is available at

[https://nostro.cloud/]

API

Nostr_client_relay allows an easy integration between C++ objects like strings and vectors and Nostr JSON entities like events and filters, defined in NIP-01, using the JSON for modern C++ library

std::string make_request(const std::string& subscription_id, const filter_t& filter);
Type get_message_type(const std::string& json);
int parse_event(const std::string& json, std::string& event_id, nostr::event_t& ev);
int parse_request(const std::string& json, std::string& request_id, nostr::filter_t& filter);
int relay_to(const std::string& uri, const std::string& json)

Examples

Make a request

const std::string pubkey("4ea843d54a8fdab39aa45f61f19f3ff79cc19385370f6a272dda81fade0a052b");
std::string subscription_id = "my_id";
nostr::filter_t filter;
filter.authors.push_back(pubkey);
filter.kinds.push_back(1);
filter.limit = 1;
std::string json = nostr::make_request(subscription_id, filter);

The following JSON is generated, where the pubkey was inserted as an item in the filter's authors array.

[
 "REQ",
 "my_id",
 {
  "authors": [
   "4ea843d54a8fdab39aa45f61f19f3ff79cc19385370f6a272dda81fade0a052b"
  ],
  "kinds": [
   1
  ],
  "limit": 1
 }
]

Make event

std::optional<std::string> seckey;
nostr::event_t ev;
ev.content = "hello world";
ev.kind = 1;
std::string json = nostr::make_event(ev, seckey);

The following JSON is generated

[
 "EVENT",
 {
  "content": "hello world",
  "created_at": 1688543634,
  "id": "d4675a05eb2720b44bee08bd7c1131786f2d17ef7c1f35ee69005d5ca3377242",
  "kind": 1,
  "pubkey": "e7328fe0f6b936457b0d3fdc0e1a264e8ac80e0416f239009345750609fdc0d8",
  "sig": "472acc460529a2cf56a4ff45f6726d5aa84ff556635fc56855911ee20f055689c508f05d3c64067e919d4335076e9014f47614cd2e7d5b66ba31d8c19973b21c",
  "tags": []
 }
]

Parse a request

std::string json = R"([
 "REQ",
 "34E8C71B-C0FB-4D6D-9CBB-694A091D6A2D",
 {
  "authors": [
   "4ea843d54a8fdab39aa45f61f19f3ff79cc19385370f6a272dda81fade0a052b"
  ],
  "kinds": [
   1
  ],
  "limit": 1
 }
])";
std::string request_id;
nostr::filter_t filter;
nostr::parse_request(json, request_id, filter);
std::cout << request_id << std::endl;
std::cout << filter.authors.at(0) << std::endl;

Relay to

std::string json = R"([
 "REQ",
 "34E8C71B-C0FB-4D6D-9CBB-694A091D6A2D",
 {
  "authors": [
   "4ea843d54a8fdab39aa45f61f19f3ff79cc19385370f6a272dda81fade0a052b"
  ],
  "kinds": [
   1
  ],
  "limit": 1
 }
])";
std::string uri = "nos.lol";
std::vector&ltstd::string&gt& response;
nostr::relay_to(uri, json, response);
comm::to_file("response.txt", response);
for (int idx = 0; idx < response.size(); idx++)
{
  std::string message = response.at(idx);
  std::cout << message << std::endl;
}

The above call returned these 2 messages

["EVENT","34E8C71B-C0FB-4D6D-9CBB-694A091D6A2D",{"content":"API version 1 released. Nostr_client_relay is a Nostr C++ engine that allows to build Nostr applications for command line, desktop or web. https://pedro-vicente.net/nostro.html","created_at":1688194430,"id":"9d05a7d271e63dd47dcda1f7c7058f1ce4c903fd24dfe6fdfd72034a040a9923","kind":1,"pubkey":"4ea843d54a8fdab39aa45f61f19f3ff79cc19385370f6a272dda81fade0a052b","sig":"c0aeca949da8b444f80009c81120b2d0059516c0c324b0d6cf523dfd1a20f78bdceec0324d0d3f9940d74b146d7e2d45b105dddd38658c4b07ec2b37ec89ff12","tags":[]}]
["EOSE","34E8C71B-C0FB-4D6D-9CBB-694A091D6A2D"]

Get list of follows

To get a list of follows we start with a public key that we want to get the follows from and a relay address where the list is stored

const std::string pubkey("4ea843d54a8fdab39aa45f61f19f3ff79cc19385370f6a272dda81fade0a052b");
const std::string uri("nos.lol");

To define the request (REQ) we define a filter that has as "authors" our public key and has kind "3", defined as list of contacts in NIP-02. A random and unique subscription ID for the request must also ge generated, with:

std::string subscription_id = uuid::generate_uuid_v4();
nostr::filter_t filter;
filter.authors.push_back(pubkey);
filter.kinds.push_back(3);

This generates the following JSON, optinally formatted for display with indentation

[
 "REQ",
 "9B874BC0-8372-4A41-9F5B-3DD6859F37F0",
 {
  "authors": [
   "4ea843d54a8fdab39aa45f61f19f3ff79cc19385370f6a272dda81fade0a052b"
  ],
  "kinds": [
   3
  ],
  "limit": 1
 }
]

Finally the JSON string can be generated from the C++ objects and sent to the wire simply with

std::string json = nostr::make_request(subscription_id, filter);
nostr::relay_to(uri, json);

And for that request a response is obtained according to <a href="https://github.com/nostr-protocol/nips/blob/master/02.md"> NIP-02 </a>

[
 "EVENT",
 "5B695BFF-2A99-48AD-AAAE-4DF13F2DB7E4",
 {
  "content": "{\"wss://nos.lol/\":{\"write\":true,\"read\":true}}",
  "created_at": 1687486321,
  "id": "b8826c41b78bf8e4545706914e0d921b77a86192393ffb3df47f5623e6fa5b8f",
  "kind": 3,
  "pubkey": "4ea843d54a8fdab39aa45f61f19f3ff79cc19385370f6a272dda81fade0a052b",
  "sig": "b1d004f1c0f8e72c9ac0c4492086c4dfc60478156feb5d6e7075923bb6e0d738d69ed61d16d65503d6df1f09b363dccd8013bd56a5354ebbbb029f558363997e",
  "tags": [
   [
    "p",
    "c708943ea349519dcf56b2a5c138fd9ed064ad65ddecae6394eabd87a62f1770"
   ],
   [
    "p",
    "fa984bd7dbb282f07e16e7ae87b26a2a7b9b90b7246a44771f0cf5ae58018f52"
   ]
  ]
 }
]

This file is part of 'Nostr_client_relay'
Copyright (c) 2023, Space Research Software LLC, Pedro Vicente. All rights reserved.
See file LICENSE for full license details.