nostr-gem/README.md

12 KiB

Nostr

Gem Version Maintainability Test Coverage

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 |error_message|
  puts error_message
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 |message|
  puts message
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'

encrypted_direct_message = 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
)

encrypted_direct_message.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(encrypted_direct_message)

Implemented NIPs

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.