Nostr Ruby
A ruby library to interact with the Nostr Protocol.
Warning
This version in work in progress and breaks the v0.2.0 API
Installation
Add this line to your application's Gemfile:
# Gemfile
gem 'nostr_ruby'
And then execute:
$ bundle
Or install it yourself as:
$ gem install nostr_ruby
Usage
Manage keys
require "nostr_ruby"
sk = Nostr::Key.generate_private_key
# => "8090fb3fe26e27d539ee349d70890d338c5e2e8b459e04c8e97658f03d2f9f33"
pk = Nostr::Key.get_public_key(sk)
# => "e7ded9bd42e7c74fcc6465962b919b7efcd5774ac6bea2ae6b81b2caa9d4d2e6"
Decode entities
puplic_key = Nostr::Bech32.decode(npub)
# => {:hrp=>"npub", :data=>"e7ded9bd42e7c74fcc6465962b919b7efcd5774ac6bea2ae6b81b2caa9d4d2e6"}
nprofile_data = Nostr::Bech32.decode("nprofile1qqs8hhhhhc3dmrje73squpz255ape7t448w86f7ltqemca7m0p99spgprpmhxue69uhkgar0dehkutnwdaehgu339e3k7mf06ras84")
# => {:hrp=>"nprofile", :data=>{:pubkey=>["7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805"], :relay=>["wss://dtonon.nostr1.com/"]}}
note_data = Nostr::Bech32.decode("note1xzce08egncw3mcm8l8edas6rrhgfj9l5uwwv2hz03zym0m9eg5hsxuyajp")
# => {:hrp=>"note", :data=>"30b1979f289e1d1de367f9f2dec3431dd09917f4e39cc55c4f8889b7ecb9452f"}
nevent_data = Nostr::Bech32.decode("nevent1qqsrpvvhnu5fu8gaudnlnuk7cdp3m5yezl6w88x9t38c3zdhaju52tcpzpmhxue69uhkztnwdaejumr0dshsz9nhwden5te0vfjhvmewdehhxarjxyhxxmmd9uq3wamnwvaz7tmzd96xxmmfdejhytnnda3kjctv9ulrdeva")
# => {:hrp=>"nevent", :data=> {:id=>["30b1979f289e1d1de367f9f2dec3431dd09917f4e39cc55c4f8889b7ecb9452f"], :relay=>["wss://a.nos.lol/", "wss://bevo.nostr1.com/", "wss://bitcoiner.social/"]}}
naddr_data = Nostr::Bech32.decode("naddr1qvzqqqr4gupzq77777lz9hvwt86xqrsyf2jn588ewk5aclf8mavr80rhmduy5kq9qqdkc6t5w3kx2ttvdamx2ttxdaez6mr0denj6en0wfkkzaqxq5r99")
# => => {:hrp=>"naddr", :data=>{:kind=>[30023], :author=>["7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805"], :identifier=>["little-love-for-long-format"]}}
Encode entities
nsec = Nostr::Bech32.encode_nsec(sk)
# => "nsec1szg0k0lzdcna2w0wxjwhpzgdxwx9ut5tgk0qfj8fwev0q0f0nuessml5ur"
npub = Nostr::Bech32.encode_npub(pk)
# => "npub1ul0dn02zulr5lnryvktzhyvm0m7d2a62c6l29tntsxev42w56tnqksrtfu"
nprofile = Nostr::Bech32.encode_nprofile(
pubkey: "7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805",
relays: ["wss://dtonon.nostr1.com"],
)
# => "nprofile1qqs8hhhhhc3dmrje73squpz255ape7t448w86f7ltqemca7m0p99spgpzamhxue69uhkgar0dehkutnwdaehgu339e3k7mg60me8x"
note = Nostr::Bech32.encode_note("30b1979f289e1d1de367f9f2dec3431dd09917f4e39cc55c4f8889b7ecb9452f")
# => "note1xzce08egncw3mcm8l8edas6rrhgfj9l5uwwv2hz03zym0m9eg5hsxuyajp"
nevent = Nostr::Bech32.encode_nevent(
id: "30b1979f289e1d1de367f9f2dec3431dd09917f4e39cc55c4f8889b7ecb9452f",
relays: ["wss://nos.lol"],
)
# => "nevent1qqsrpvvhnu5fu8gaudnlnuk7cdp3m5yezl6w88x9t38c3zdhaju52tcpp4mhxue69uhkummn9ekx7mqrqsqqqqqpux7e9q"
naddr = Nostr::Bech32.encode_naddr(
author: "7bdef7be22dd8e59f4600e044aa53a1cf975a9dc7d27df5833bc77db784a5805",
identifier: "little-love-for-long-format",
kind: 30023
)
# => "naddr1qgs8hhhhhc3dmrje73squpz255ape7t448w86f7ltqemca7m0p99spgrqsqqqa28qqdkc6t5w3kx2ttvdamx2ttxdaez6mr0denj6en0wfkkzaqn2tjdj"
Initialize Client
require "nostr_ruby"
# Detailed version
s = Nostr::Signer.new(private_key: Nostr::Key.generate_private_key)
c = Nostr::Client.new(signer: s)
# Compact version, under the hood Client creates a Signer
c = Nostr::Client.new(private_key: Nostr::Key.generate_private_key)
c.private_key
# => "7402b4b1ee09fb37b64ec2a958f1b7815d904c6dd44227bdef7912ef201af97d"
c.public_key
# => "a19f3c16b6e857d2b673c67eea293431fc175895513ca2f687a717152a5da466"
c.nsec
# => "nsec1wsptfv0wp8an0djwc2543udhs9weqnrd63pz00000yfw7gq6l97snckpdq"
c.npub
# => "npub15x0nc94kapta9dnncelw52f5x87pwky42y729a585ut322ja53nq72yrcr"
Create, sign and send an event
# Initialize a client, under the hood Client creates a Signer
c = Nostr::Client.new(
private_key: "7402b4b1ee09fb37b64ec2a958f1b7815d904c6dd44227bdef7912ef201af97d",
relay: "wss://nos.lol"
)
# Initialize an event
e = Nostr::Event.new(
kind: ...,
pubkey: ...,
created_at: ...,
tags: ...,
content: ...,
pow: ...,
delegation: ...,
recipient: ...,
)
# Sign the event
e = c.sign(e)
# - - - - - - - - - - - - - -
# Full async mode
# Set the open callback
c.on :connect do |event|
puts 'Publish event...'
c.publish(e)
end
# Set the response callback
c.on :message do |event|
puts "Response: #{event}"
end
# Connect and send the event
c.connect
c.publish(e)
c.stop
# - - - - - - - - - - - - - -
# Compact sync mode
c.connect
c.publish_and_wait(e)
c.stop
Set the profile
metadata = {
name: "Mr Robot",
about: "I walk around the city",
picture: "https://upload.wikimedia.org/wikipedia/commons/3/35/Mr_robot_photo.jpg",
nip05: "mrrobot@mrrobot.com"
}
e = Nostr::Event.new(
kind: Nostr::Kind::METADATA,
pubkey: c.public_key,
content: metadata.to_json,
)
Post a note
e = Nostr::Event.new(
kind: Nostr::Kind::SHORT_NOTE,
pubkey: c.public_key,
content: "Hello Nostr!",
)
Share a contact list
contact_list = [
["54399b6d8200813bfc53177ad4f13d6ab712b6b23f91aefbf5da45aeb5c96b08", "wss://alicerelay.com/", "alice"],
["850708b7099215bf9a1356d242c2354939e9a844c1359d3b5209592a0b420452", "wss://bobrelay.com/nostr", "bob"],
["f7f4b0072368460a09138bf3966fb1c59d0bdadfc3aff4e59e6896194594a82a", "ws://carolrelay.com/ws", "carol"]
]
e = Nostr::Event.new(
kind: Nostr::Kind::CONTACT_LIST,
pubkey: c.public_key,
tags: contact_list.map { |c| ['p'] + c },
)
Delete an event
event_to_delete = "b91b3fb40128112c38dc54168b9f601c22bf8fcae6e70bb2a5f53e7f3ae44388"
e = Nostr::Event.new(
kind: Nostr::Kind::DELETION,
pubkey: c.public_key,
tags: [["e", event_to_delete]],
)
React to an event
e = Nostr::Event.new(
kind: Nostr::Kind::REACTION,
pubkey: c.public_key,
content: "+",
tags: [["e", target_event]],
)
# You can also use emoji
e2 = Nostr::Event.new(
kind: Nostr::Kind::REACTION,
pubkey: c.public_key,
content: "🔥",
tags: [["e", target_event]],
)
Create events with a PoW difficulty
# Just add the `pow` argument
e = Nostr::Event.new(
kind: Nostr::Kind::SHORT_NOTE,
pubkey: c.public_key,
content: "Hello Nostr!",
pow: 15,
)
Create an event with a NIP-26 delegation
delegator = Nostr::Client.new(private_key: delegator_key)
delegatee = "b1d8dfd69fe8795042dbbc4d3f85938a01d4740c54d2daf11088c75c50ff19d9"
conditions = "kind=1&created_at>#{Time.now.to_i}&created_at<#{(Time.now + 60*60).to_i}"
delegation_tag = delegator.generate_delegation_tag(
to: delegatee,
conditions: conditions
)
# The `delegation_tag` is given to the delegatee so it can use it
delegatee = Nostr::Client.new(private_key: delegatee_key)
e = Nostr::Event.new(
kind: Nostr::Kind::SHORT_NOTE,
pubkey: delegatee.public_key,
content: "Hello Nostr!",
delegation: delegation_tag,
)
delegatee.sign(e)
Send a direct message
Warning: This uses NIP-04, that will be deprecated in favor of NIP-17
recipient = "npub1ul0dn02zulr5lnryvktzhyvm0m7d2a62c6l29tntsxev42w56tnqksrtfu"
e = Nostr::Event.new(
kind: Nostr::Kind::DIRECT_MESSAGE,
pubkey: c.public_key,
recipient: Nostr::Bech32.decode(recipient)[:data],
content: "Hello Alice!"
)
e = Nostr::Event.new(
kind: Nostr::Kind::DIRECT_MESSAGE,
pubkey: c.public_key,
tags: [["p", Nostr::Bech32.decode(recipient)[:data]]],
content: "Hello Alice!"
)
Decrypt a direct message
Warning: This uses NIP-04, that will be deprecated in favor of NIP-17
payload = {
:kind=>4,
:pubkey=>"a19f3c16b6e857d2b673c67eea293431fc175895513ca2f687a717152a5da466",
:created_at=>1725387307,
:tags=>[["p", "e7ded9bd42e7c74fcc6465962b919b7efcd5774ac6bea2ae6b81b2caa9d4d2e6"]],
:content=>"Nd7n/wId1oiprUCC4WWwNw==?iv=7gIRExcyO1xystretLIPnQ==",
:id=>"b91b3fb40128112c38dc54168b9f601c22bf8fcae6e70bb2a5f53e7f3ae44388",
:sig=>"73edf5a6acbefdd3d76f28ba90faaabe348a24c798f8fa33797eec29e2404c33a455815a59472ecd023441df38d815f83d81b95b8cb2f2c88a52982c8f7301e9"
}
e = Nostr::Event.new(**payload)
c.decrypt(e)
puts e.content
=> "Hello Alice!"
Create a subscribtion to receive events
filter = Nostr::Filter.new(
kinds: [1],
authors: ["a19f3c16b6e857d2b673c67eea293431fc175895513ca2f687a717152a5da466"],
since: Time.now - (60*60*24), # 24 hours ago
limit: 10
)
c.on :message do |message|
puts ">> #{message}"
end
c.subscribe(filter: filter)
c.connect