Nostr
Asynchronous Nostr client. Please note that the API is likely to change as the gem is still in development and has not yet reached a stable release. Use with caution.
Table of contents
Installation
Install the gem and add to the application's Gemfile by executing:
$ bundle add nostr
If bundler is not being used to manage dependencies, install the gem by executing:
$ gem install nostr
Usage
Requiring the gem
All examples below assume that the gem has been required.
require 'nostr'
Generating a keypair
keygen = Nostr::Keygen.new
keypair = keygen.generate_key_pair
keypair.private_key
keypair.public_key
Generating a private key and a public key
keygen = Nostr::Keygen.new
private_key = keygen.generate_private_key
public_key = keygen.extract_public_key(private_key)
Connecting to a Relay
Clients can connect to multiple Relays. In this version, a Client can only connect to a single Relay at a time.
You may instantiate multiple Clients and multiple Relays.
client = Nostr::Client.new
relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
client.connect(relay)
WebSocket events
All communication between clients and relays happen in WebSockets.
The :connect
event is fired when a connection with a WebSocket is opened. You must call Nostr::Client#connect
first.
client.on :connect do
# all the code goes here
end
The :close
event is fired when a connection with a WebSocket has been closed because of an error.
client.on :error do ||
puts
end
# > Network error: wss://rsslay.fiatjaf.com: Unable to verify the server certificate for 'rsslay.fiatjaf.com'
The :message
event is fired when data is received through a WebSocket.
client.on :message do ||
puts
end
# [
# "EVENT",
# "d34107357089bfc9882146d3bfab0386",
# {
# "content":"",
# "created_at":1676456512,
# "id":"18f63550da74454c5df7caa2a349edc5b2a6175ea4c5367fa4b4212781e5b310",
# "kind":3,
# "pubkey":"117a121fa41dc2caa0b3d6c5b9f42f90d114f1301d39f9ee96b646ebfee75e36",
# "sig":"d171420bd62cf981e8f86f2dd8f8f86737ea2bbe2d98da88db092991d125535860d982139a3c4be39886188613a9912ef380be017686a0a8b74231dc6e0b03cb",
# "tags":[
# ["p","1cc821cc2d47191b15fcfc0f73afed39a86ac6fb34fbfa7993ee3e0f0186ef7c"]
# ]
# }
# ]
The :close
event is fired when a connection with a WebSocket is closed.
client.on :close do |code, reason|
# you may attempt to reconnect
client.connect(relay)
end
Requesting for events / creating a subscription
A client can request events and subscribe to new updates after it has established a connection with the Relay.
You may use a Nostr::Filter
instance with as many attributes as you wish:
client.on :connect do
filter = Nostr::Filter.new(
ids: ['8535d5e2d7b9dc07567f676fbe70428133c9884857e1915f5b1cc6514c2fdff8'],
authors: ['ae00f88a885ce76afad5cbb2459ef0dcf0df0907adc6e4dac16e1bfbd7074577'],
kinds: [Nostr::EventKind::TEXT_NOTE],
e: ["f111593a72cc52a7f0978de5ecf29b4653d0cf539f1fa50d2168fc1dc8280e52"],
p: ["f1f9b0996d4ff1bf75e79e4cc8577c89eb633e68415c7faf74cf17a07bf80bd8"],
since: 1230981305,
until: 1292190341,
limit: 420,
)
subscription = client.subscribe('a_random_subscription_id', filter)
end
With just a few:
client.on :connect do
filter = Nostr::Filter.new(kinds: [Nostr::EventKind::TEXT_NOTE])
subscription = client.subscribe('a_random_subscription_id', filter)
end
Or omit the filter:
client.on :connect do
subscription = client.subscribe('a_random_subscription_id')
end
Or even omit the subscription id:
client.on :connect do
subscription = client.subscribe('a_random_subscription_id')
end
Stop previous subscriptions
You can stop receiving messages from a subscription by calling #unsubscribe
:
client.unsubscribe('your_subscription_id')
Publishing an event
To publish an event you need a keypair.
# Create a keypair
keypair = Nostr::KeyPair.new(
private_key: '893c4cc8088924796b41dc788f7e2f746734497010b1a9f005c1faad7074b900',
public_key: '2d7661527d573cc8e84f665fa971dd969ba51e2526df00c149ff8e40a58f9558',
)
# Add the keypair to the user facade
user = Nostr::User.new(keypair: keypair)
# Create a signed event
event = user.create_event(
created_at: 1667422587, # optional, defaults to the current time
kind: Nostr::EventKind::TEXT_NOTE,
tags: [], # optional, defaults to []
content: 'Your feedback is appreciated, now pay $8'
)
# Send it to the Relay
client.publish(event)
Creating/updating your contact list
Every new contact list that gets published overwrites the past ones, so it should contain all entries.
# Creating a contact list event with 2 contacts
update_contacts_event = user.create_event(
kind: Nostr::EventKind::CONTACT_LIST,
tags: [
[
"p", # mandatory
"32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", # public key of the user to add to the contacts
"wss://alicerelay.com/", # can be an empty string or can be omitted
"alice" # can be an empty string or can be omitted
],
[
"p", # mandatory
"3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", # public key of the user to add to the contacts
"wss://bobrelay.com/nostr", # can be an empty string or can be omitted
"bob" # can be an empty string or can be omitted
],
],
)
# Send it to the Relay
client.publish(update_contacts_event)
Sending an encrypted direct message
sender_private_key = '3185a47e3802f956ca5a2b4ea606c1d51c7610f239617e8f0f218d55bdf2b757'
= Nostr::Events::EncryptedDirectMessage.new(
sender_private_key: sender_private_key,
recipient_public_key: '6c31422248998e300a1a457167565da7d15d0da96651296ee2791c29c11b6aa0',
plain_text: 'Your feedback is appreciated, now pay $8',
previous_direct_message: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460' # optional
)
.sign(sender_private_key)
# #<Nostr::Events::EncryptedDirectMessage:0x0000000104c9fa68
# @content="mjIFNo1sSP3KROE6QqhWnPSGAZRCuK7Np9X+88HSVSwwtFyiZ35msmEVoFgRpKx4?iv=YckChfS2oWCGpMt1uQ4GbQ==",
# @created_at=1676456512,
# @id="daac98826d5eb29f7c013b6160986c4baf4fe6d4b995df67c1b480fab1839a9b",
# @kind=4,
# @pubkey="8a9d69c56e3c691bec8f9565e4dcbe38ae1d88fffeec3ce66b9f47558a3aa8ca",
# @sig="028bb5f5bab0396e2065000c84a4bcce99e68b1a79bb1b91a84311546f49c5b67570b48d4a328a1827e7a8419d74451347d4f55011a196e71edab31aa3d6bdac",
# @tags=[["p", "6c31422248998e300a1a457167565da7d15d0da96651296ee2791c29c11b6aa0"], ["e", "ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460"]]>
# Send it to the Relay
client.publish()
Implemented NIPs
- [x] NIP-01 - Client
- [x] NIP-02 - Client
- [x] NIP-04 - Client
Development
After checking out the repo, run bin/setup
to install dependencies.
To install this gem onto your local machine, run bundle exec rake install
.
You can also run bin/console
for an interactive prompt that will allow you to experiment.
To release a new version, update the version number in version.rb
, and then run bundle exec rake release
,
which will create a git tag for the version, push git commits and the created tag, and push the .gem
file
to rubygems.org.
The health and maintainability of the codebase is ensured through a set of Rake tasks to test, lint and audit the gem for security vulnerabilities and documentation:
rake build # Build nostr.gem into the pkg directory
rake build:checksum # Generate SHA512 checksum if nostr.gem into the checksums directory
rake bundle:audit:check # Checks the Gemfile.lock for insecure dependencies
rake bundle:audit:update # Updates the bundler-audit vulnerability database
rake clean # Remove any temporary products
rake clobber # Remove any generated files
rake coverage # Run spec with coverage
rake install # Build and install nostr.gem into system gems
rake install:local # Build and install nostr.gem into system gems without network access
rake qa # Test, lint and perform security and documentation audits
rake release[remote] # Create a tag, build and push nostr.gem to rubygems.org
rake rubocop # Run RuboCop
rake rubocop:autocorrect # Autocorrect RuboCop offenses (only when it's safe)
rake rubocop:autocorrect_all # Autocorrect RuboCop offenses (safe and unsafe)
rake spec # Run RSpec code examples
rake verify_measurements # Verify that yardstick coverage is at least 100%
rake yard # Generate YARD Documentation
rake yard:junk # Check the junk in your YARD Documentation
rake yardstick_measure # Measure docs in lib/**/*.rb with yardstick
Type checking
This gem leverages RBS, a language to describe the structure of Ruby programs. It is
used to provide type checking and autocompletion in your editor. Run bundle exec typeprof FILENAME
to generate
an RBS definition for the given Ruby file. And validate all definitions using Steep
with the command bundle exec steep check
.
Contributing
Bug reports and pull requests are welcome on GitHub at https://github.com/wilsonsilva/nostr. This project is intended to be a safe, welcoming space for collaboration, and contributors are expected to adhere to the code of conduct.
License
The gem is available as open source under the terms of the MIT License.
Code of Conduct
Everyone interacting in the Nostr project's codebases, issue trackers, chat rooms and mailing lists is expected to follow the code of conduct.