nostr 0.1.0 → 0.3.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 669ffcd3d585116e340888ace801690a0c596bb6d20f12acc80a7a8c933fa0e1
4
- data.tar.gz: 1abe2ac66bcdd51c175e2ebb7b676d1d8a3508c12e783e674252d33948336215
3
+ metadata.gz: 371ed11c0474fd944cc55a054d553945623f7439f67c55f3eadc564805d2fb11
4
+ data.tar.gz: f7fbe86119bd7816e7066ea3603263dee7327b06daeb4b92f9e90977f52ef8ae
5
5
  SHA512:
6
- metadata.gz: fb8845670cf90e66204225260aa76d50bcf76f3e8cd9ccae27bbb73e3384bb23fc411fdabcdbe6a2cbdfd1e5023d45ce5c4f8b42077b73c3c1e865e87fa5cce5
7
- data.tar.gz: 5e02009ec661e34507a96ddb3c1866e76d3aab0a2f19c80f4228a53c783e01a1b226e76d39a35ef8de27b2be9c7a915435ab55666b7770d98cc54cdf1f387478
6
+ metadata.gz: fa602354304ce9e77377b80cab60146f51ac8943b1399070a87ce81a5e44582f0a23b50f1736fafd9d7d0f13042b8b7292b9d29684077ad878e2439ddf7970ef
7
+ data.tar.gz: 83fc3d535a1e35438522779ef7dc3e101b4fea3a3f9874db9457b4f6c61d74469ded6ba973923d731789d3c50d7aa66b1c0770d62451794d45b52720c7928ebf
data/.rubocop.yml CHANGED
@@ -1,3 +1,5 @@
1
+ inherit_from: .rubocop_todo.yml
2
+
1
3
  require:
2
4
  - rubocop-rake
3
5
  - rubocop-rspec
data/.rubocop_todo.yml ADDED
@@ -0,0 +1,23 @@
1
+ # This configuration was generated by
2
+ # `rubocop --auto-gen-config`
3
+ # on 2023-01-12 10:00:10 UTC using RuboCop version 1.42.0.
4
+ # The point is for the user to remove these configuration records
5
+ # one by one as the offenses are removed from the code base.
6
+ # Note that changes in the inspected code, or installation of new
7
+ # versions of RuboCop, may require this file to be generated again.
8
+
9
+ # Offense count: 2
10
+ # Configuration parameters: AllowedMethods, AllowedPatterns, IgnoredMethods, CountRepeatedAttributes.
11
+ Metrics/AbcSize:
12
+ Max: 23
13
+
14
+ # Offense count: 2
15
+ # Configuration parameters: CountComments, CountAsOne, ExcludedMethods, AllowedMethods, AllowedPatterns, IgnoredMethods.
16
+ Metrics/MethodLength:
17
+ Max: 16
18
+
19
+ # Offense count: 4
20
+ # Configuration parameters: AssignmentOnly.
21
+ RSpec/InstanceVariable:
22
+ Exclude:
23
+ - 'spec/nostr/client_spec.rb'
data/CHANGELOG.md CHANGED
@@ -4,6 +4,22 @@ All notable changes to this project will be documented in this file.
4
4
  The format is based on [Keep a Changelog](http://keepachangelog.com/en/1.0.0/)
5
5
  and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.html).
6
6
 
7
+ ## [0.3.0] - 2023-02-15
8
+
9
+ ### Added
10
+
11
+ - Client compliance wth [NIP-02](https://github.com/nostr-protocol/nips/blob/master/02.md) (manage contact lists)
12
+
13
+ ## [0.2.0] - 2023-01-12
14
+
15
+ ### Added
16
+
17
+ - [NIP-01](https://github.com/nostr-protocol/nips/blob/master/01.md) compliant client
18
+
7
19
  ## [0.1.0] - 2023-01-06
8
20
 
9
21
  - Initial release
22
+
23
+ [0.3.0]: https://github.com/wilsonsilva/nostr/compare/v0.2.0...v0.3.0
24
+ [0.2.0]: https://github.com/wilsonsilva/nostr/compare/v0.1.0...v0.2.0
25
+ [0.1.0]: https://github.com/wilsonsilva/nostr/compare/7fded5...v0.1.0
data/README.md CHANGED
@@ -1,11 +1,11 @@
1
1
  # Nostr
2
2
 
3
3
  [![Gem Version](https://badge.fury.io/rb/nostr.svg)](https://badge.fury.io/rb/nostr)
4
- [![Security](https://hakiri.io/github/wilsonsilva/nostr/master.svg)](https://hakiri.io/github/wilsonsilva/nostr/master)
5
- [![Inline docs](http://inch-ci.org/github/wilsonsilva/nostr.svg?branch=master)](http://inch-ci.org/github/wilsonsilva/nostr)
4
+ [![Maintainability](https://api.codeclimate.com/v1/badges/c7633eb2c89eb95ee7f2/maintainability)](https://codeclimate.com/github/wilsonsilva/nostr/maintainability)
5
+ [![Test Coverage](https://api.codeclimate.com/v1/badges/c7633eb2c89eb95ee7f2/test_coverage)](https://codeclimate.com/github/wilsonsilva/nostr/test_coverage)
6
6
 
7
- Nostr client. Please note that the API is likely to change as the gem is still in development and has not yet reached a
8
- stable release. Use with caution.
7
+ Asynchronous Nostr client. Please note that the API is likely to change as the gem is still in development and
8
+ has not yet reached a stable release. Use with caution.
9
9
 
10
10
  ## Installation
11
11
 
@@ -19,10 +19,212 @@ If bundler is not being used to manage dependencies, install the gem by executin
19
19
 
20
20
  ## Usage
21
21
 
22
+ ### Requiring the gem
23
+
24
+ All examples below assume that the gem has been required.
25
+
22
26
  ```ruby
23
27
  require 'nostr'
24
28
  ```
25
29
 
30
+ ### Generating a keypair
31
+
32
+ ```ruby
33
+ keygen = Nostr::Keygen.new
34
+ keypair = keygen.generate_keypair
35
+
36
+ keypair.private_key
37
+ keypair.public_key
38
+ ```
39
+
40
+ ### Generating a private key and a public key
41
+
42
+ ```ruby
43
+ keygen = Nostr::Keygen.new
44
+
45
+ private_key = keygen.generate_private_key
46
+ public_key = keygen.extract_public_key(private_key)
47
+ ```
48
+
49
+ ### Connecting to a Relay
50
+
51
+ Clients can connect to multiple Relays. In this version, a Client can only connect to a single Relay at a time.
52
+
53
+ You may instantiate multiple Clients and multiple Relays.
54
+
55
+ ```ruby
56
+ client = Nostr::Client.new
57
+ relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
58
+
59
+ client.connect(relay)
60
+ ```
61
+
62
+ ### WebSocket events
63
+
64
+ All communication between clients and relays happen in WebSockets.
65
+
66
+ The `:connect` event is fired when a connection with a WebSocket is opened. You must call `Nostr::Client#connect` first.
67
+
68
+ ```ruby
69
+ client.on :connect do
70
+ # all the code goes here
71
+ end
72
+ ```
73
+
74
+ The `:close` event is fired when a connection with a WebSocket has been closed because of an error.
75
+
76
+ ```ruby
77
+ client.on :error do |error_message|
78
+ puts error_message
79
+ end
80
+
81
+ # > Network error: wss://rsslay.fiatjaf.com: Unable to verify the server certificate for 'rsslay.fiatjaf.com'
82
+ ```
83
+
84
+ The `:message` event is fired when data is received through a WebSocket.
85
+
86
+ ```ruby
87
+ client.on :message do |message|
88
+ puts message
89
+ end
90
+
91
+ # [
92
+ # "EVENT",
93
+ # "d34107357089bfc9882146d3bfab0386",
94
+ # {
95
+ # "content":"",
96
+ # "created_at":1676456512,
97
+ # "id":"18f63550da74454c5df7caa2a349edc5b2a6175ea4c5367fa4b4212781e5b310",
98
+ # "kind":3,
99
+ # "pubkey":"117a121fa41dc2caa0b3d6c5b9f42f90d114f1301d39f9ee96b646ebfee75e36",
100
+ # "sig":"d171420bd62cf981e8f86f2dd8f8f86737ea2bbe2d98da88db092991d125535860d982139a3c4be39886188613a9912ef380be017686a0a8b74231dc6e0b03cb",
101
+ # "tags":[
102
+ # ["p","1cc821cc2d47191b15fcfc0f73afed39a86ac6fb34fbfa7993ee3e0f0186ef7c"]
103
+ # ]
104
+ # }
105
+ # ]
106
+ ```
107
+
108
+ The `:close` event is fired when a connection with a WebSocket is closed.
109
+
110
+ ```ruby
111
+ client.on :close do |code, reason|
112
+ # you may attempt to reconnect
113
+
114
+ client.connect(relay)
115
+ end
116
+ ```
117
+
118
+ ### Requesting for events / creating a subscription
119
+
120
+ A client can request events and subscribe to new updates after it has established a connection with the Relay.
121
+
122
+ You may use a `Nostr::Filter` instance with as many attributes as you wish:
123
+
124
+ ```ruby
125
+ client.on :connect do
126
+ filter = Nostr::Filter.new(
127
+ ids: ['8535d5e2d7b9dc07567f676fbe70428133c9884857e1915f5b1cc6514c2fdff8'],
128
+ authors: ['ae00f88a885ce76afad5cbb2459ef0dcf0df0907adc6e4dac16e1bfbd7074577'],
129
+ kinds: [Nostr::EventKind::TEXT_NOTE],
130
+ e: ["f111593a72cc52a7f0978de5ecf29b4653d0cf539f1fa50d2168fc1dc8280e52"],
131
+ p: ["f1f9b0996d4ff1bf75e79e4cc8577c89eb633e68415c7faf74cf17a07bf80bd8"],
132
+ since: 1230981305,
133
+ until: 1292190341,
134
+ limit: 420,
135
+ )
136
+
137
+ subscription = client.subscribe('a_random_subscription_id', filter)
138
+ end
139
+ ```
140
+
141
+ With just a few:
142
+
143
+ ```ruby
144
+ client.on :connect do
145
+ filter = Nostr::Filter.new(kinds: [Nostr::EventKind::TEXT_NOTE])
146
+ subscription = client.subscribe('a_random_subscription_id', filter)
147
+ end
148
+ ```
149
+
150
+ Or omit the filter:
151
+
152
+ ```ruby
153
+ client.on :connect do
154
+ subscription = client.subscribe('a_random_subscription_id')
155
+ end
156
+ ```
157
+
158
+ Or even omit the subscription id:
159
+
160
+ ```ruby
161
+ client.on :connect do
162
+ subscription = client.subscribe('a_random_subscription_id')
163
+ end
164
+ ```
165
+
166
+ ### Stop previous subscriptions
167
+
168
+ You can stop receiving messages from a subscription by calling `#unsubscribe`:
169
+
170
+ ```ruby
171
+ client.unsubscribe('your_subscription_id')
172
+ ```
173
+
174
+ ### Publishing an event
175
+
176
+ To publish an event you need a keypair.
177
+
178
+ ```ruby
179
+ # Set up the private key
180
+ private_key = 'a630b06e2f883378d0aa335b9adaf7734603e00433350b684fe53e184f08c58f'
181
+ user = Nostr::User.new(private_key)
182
+
183
+ # Create a signed event
184
+ event = user.create_event(
185
+ created_at: 1667422587, # optional, defaults to the current time
186
+ kind: Nostr::EventKind::TEXT_NOTE,
187
+ tags: [], # optional, defaults to []
188
+ content: 'Your feedback is appreciated, now pay $8'
189
+ )
190
+
191
+ # Send it to the Relay
192
+ client.publish(event)
193
+ ```
194
+
195
+ ### Creating/updating your contact list
196
+
197
+ Every new contact list that gets published overwrites the past ones, so it should contain all entries.
198
+
199
+ ```ruby
200
+ # Creating a contact list event with 2 contacts
201
+ update_contacts_event = user.create_event(
202
+ kind: Nostr::EventKind::CONTACT_LIST,
203
+ tags: [
204
+ [
205
+ "p", # mandatory
206
+ "32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245", # public key of the user to add to the contacts
207
+ "wss://alicerelay.com/", # can be an empty string or can be omitted
208
+ "alice" # can be an empty string or can be omitted
209
+ ],
210
+ [
211
+ "p", # mandatory
212
+ "3efdaebb1d8923ebd99c9e7ace3b4194ab45512e2be79c1b7d68d9243e0d2681", # public key of the user to add to the contacts
213
+ "wss://bobrelay.com/nostr", # can be an empty string or can be omitted
214
+ "bob" # can be an empty string or can be omitted
215
+ ],
216
+ ],
217
+ )
218
+
219
+ # Send it to the Relay
220
+ client.publish(update_contacts_event)
221
+ ```
222
+
223
+ ## NIPS
224
+
225
+ - [x] [NIP-01 - Client](https://github.com/nostr-protocol/nips/blob/master/01.md)
226
+ - [x] [NIP-02 - Client](https://github.com/nostr-protocol/nips/blob/master/02.md)
227
+
26
228
  ## Development
27
229
 
28
230
  After checking out the repo, run `bin/setup` to install dependencies.
@@ -39,17 +241,34 @@ The health and maintainability of the codebase is ensured through a set of
39
241
  Rake tasks to test, lint and audit the gem for security vulnerabilities and documentation:
40
242
 
41
243
  ```
42
- rake bundle:audit # Checks for vulnerable versions of gems
43
- rake qa # Test, lint and perform security and documentation audits
44
- rake rubocop # Lint the codebase with RuboCop
45
- rake rubocop:auto_correct # Auto-correct RuboCop offenses
46
- rake spec # Run RSpec code examples
47
- rake verify_measurements # Verify that yardstick coverage is at least 100%
48
- rake yard # Generate YARD Documentation
49
- rake yard:junk # Check the junk in your YARD Documentation
50
- rake yardstick_measure # Measure docs in lib/**/*.rb with yardstick
244
+ rake build # Build nostr.gem into the pkg directory
245
+ rake build:checksum # Generate SHA512 checksum if nostr.gem into the checksums directory
246
+ rake bundle:audit:check # Checks the Gemfile.lock for insecure dependencies
247
+ rake bundle:audit:update # Updates the bundler-audit vulnerability database
248
+ rake clean # Remove any temporary products
249
+ rake clobber # Remove any generated files
250
+ rake coverage # Run spec with coverage
251
+ rake install # Build and install nostr.gem into system gems
252
+ rake install:local # Build and install nostr.gem into system gems without network access
253
+ rake qa # Test, lint and perform security and documentation audits
254
+ rake release[remote] # Create a tag, build and push nostr.gem to rubygems.org
255
+ rake rubocop # Run RuboCop
256
+ rake rubocop:autocorrect # Autocorrect RuboCop offenses (only when it's safe)
257
+ rake rubocop:autocorrect_all # Autocorrect RuboCop offenses (safe and unsafe)
258
+ rake spec # Run RSpec code examples
259
+ rake verify_measurements # Verify that yardstick coverage is at least 100%
260
+ rake yard # Generate YARD Documentation
261
+ rake yard:junk # Check the junk in your YARD Documentation
262
+ rake yardstick_measure # Measure docs in lib/**/*.rb with yardstick
51
263
  ```
52
264
 
265
+ ### Type checking
266
+
267
+ This gem leverages [RBS](https://github.com/ruby/rbs), a language to describe the structure of Ruby programs. It is
268
+ used to provide type checking and autocompletion in your editor. Run `bundle exec typeprof FILENAME` to generate
269
+ an RBS definition for the given Ruby file. And validate all definitions using [Steep](https://github.com/soutaro/steep)
270
+ with the command `bundle exec steep check`.
271
+
53
272
  ## Contributing
54
273
 
55
274
  Bug reports and pull requests are welcome on GitHub at https://github.com/wilsonsilva/nostr.
data/Steepfile ADDED
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ target :lib do
4
+ signature 'sig'
5
+
6
+ check 'lib'
7
+
8
+ # Core libraries
9
+ library 'digest'
10
+ library 'securerandom'
11
+
12
+ # Gems
13
+ library 'json'
14
+ end
@@ -0,0 +1,176 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'event_emitter'
4
+ require 'faye/websocket'
5
+
6
+ module Nostr
7
+ # Clients can talk with relays and can subscribe to any set of events using a subscription filters.
8
+ # The filter represents all the set of nostr events that a client is interested in.
9
+ #
10
+ # There is no sign-up or account creation for a client. Every time a client connects to a relay, it submits its
11
+ # subscription filters and the relay streams the "interested events" to the client as long as they are connected.
12
+ #
13
+ class Client
14
+ include EventEmitter
15
+
16
+ # Instantiates a new Client
17
+ #
18
+ # @api public
19
+ #
20
+ # @example Instantiating a client that logs all the events it sends and receives
21
+ # client = Nostr::Client.new(debug: true)
22
+ #
23
+ def initialize
24
+ @subscriptions = {}
25
+
26
+ initialize_channels
27
+ end
28
+
29
+ # Connects to the Relay's websocket endpoint
30
+ #
31
+ # @api public
32
+ #
33
+ # @example Connecting to a relay
34
+ # relay = Nostr::Relay.new(url: 'wss://relay.damus.io', name: 'Damus')
35
+ # client.connect(relay)
36
+ #
37
+ # @param [Relay] relay The relay to connect to
38
+ #
39
+ # @return [void]
40
+ #
41
+ def connect(relay)
42
+ execute_within_an_em_thread do
43
+ client = Faye::WebSocket::Client.new(relay.url, [], { tls: { verify_peer: false } })
44
+ parent_to_child_channel.subscribe { |msg| client.send(msg) }
45
+
46
+ client.on :open do
47
+ child_to_parent_channel.push(type: :open)
48
+ end
49
+
50
+ client.on :message do |event|
51
+ child_to_parent_channel.push(type: :message, data: event.data)
52
+ end
53
+
54
+ client.on :error do |event|
55
+ child_to_parent_channel.push(type: :error, message: event.message)
56
+ end
57
+
58
+ client.on :close do |event|
59
+ child_to_parent_channel.push(type: :close, code: event.code, reason: event.reason)
60
+ end
61
+ end
62
+ end
63
+
64
+ # Subscribes to a set of events using a filter
65
+ #
66
+ # @api public
67
+ #
68
+ # @example Creating a subscription with no id and no filters
69
+ # subscription = client.subscribe
70
+ #
71
+ # @example Creating a subscription with an ID
72
+ # subscription = client.subscribe(subscription_id: 'my-subscription')
73
+ #
74
+ # @example Subscribing to all events created after a certain time
75
+ # subscription = client.subscribe(filter: Nostr::Filter.new(since: 1230981305))
76
+ #
77
+ # @param subscription_id [String] The subscription id. A random string.
78
+ # @param filter [Filter] A set of attributes that represent the events that the client is interested in.
79
+ #
80
+ # @return [Subscription] The subscription object
81
+ #
82
+ def subscribe(subscription_id: SecureRandom.hex, filter: Filter.new)
83
+ subscriptions[subscription_id] = Subscription.new(id: subscription_id, filter:)
84
+ parent_to_child_channel.push([ClientMessageType::REQ, subscription_id, filter.to_h].to_json)
85
+ subscriptions[subscription_id]
86
+ end
87
+
88
+ # Stops a previous subscription
89
+ #
90
+ # @api public
91
+ #
92
+ # @example Stopping a subscription
93
+ # client.unsubscribe(subscription.id)
94
+ #
95
+ # @example Stopping a subscription
96
+ # client.unsubscribe('my-subscription')
97
+ #
98
+ # @param subscription_id [String] ID of a previously created subscription.
99
+ #
100
+ # @return [void]
101
+ #
102
+ def unsubscribe(subscription_id)
103
+ subscriptions.delete(subscription_id)
104
+ parent_to_child_channel.push([ClientMessageType::CLOSE, subscription_id].to_json)
105
+ end
106
+
107
+ # Sends an event to a Relay
108
+ #
109
+ # @api public
110
+ #
111
+ # @example Sending an event to a relay
112
+ # client.publish(event)
113
+ #
114
+ # @param event [Event] The event to be sent to a Relay
115
+ #
116
+ # @return [void]
117
+ #
118
+ def publish(event)
119
+ parent_to_child_channel.push([ClientMessageType::EVENT, event.to_h].to_json)
120
+ end
121
+
122
+ private
123
+
124
+ # The subscriptions that the client has created
125
+ #
126
+ # @api private
127
+ #
128
+ # @return [Hash{String=>Subscription}>]
129
+ #
130
+ attr_reader :subscriptions
131
+
132
+ # The channel that the parent thread uses to send messages to the child thread
133
+ #
134
+ # @api private
135
+ #
136
+ # @return [EventMachine::Channel]
137
+ #
138
+ attr_reader :parent_to_child_channel
139
+
140
+ # The channel that the child thread uses to send messages to the parent thread
141
+ #
142
+ # @api private
143
+ #
144
+ # @return [EventMachine::Channel]
145
+ #
146
+ attr_reader :child_to_parent_channel
147
+
148
+ # Executes a block of code within the EventMachine thread
149
+ #
150
+ # @api private
151
+ #
152
+ # @return [Thread]
153
+ #
154
+ def execute_within_an_em_thread(&block)
155
+ Thread.new { EventMachine.run(block) }
156
+ end
157
+
158
+ # Creates the communication channels between threads
159
+ #
160
+ # @api private
161
+ #
162
+ # @return [void]
163
+ #
164
+ def initialize_channels
165
+ @parent_to_child_channel = EventMachine::Channel.new
166
+ @child_to_parent_channel = EventMachine::Channel.new
167
+
168
+ child_to_parent_channel.subscribe do |msg|
169
+ emit :connect if msg[:type] == :open
170
+ emit :message, msg[:data] if msg[:type] == :message
171
+ emit :error, msg[:message] if msg[:type] == :error
172
+ emit :close, msg[:code], msg[:reason] if msg[:type] == :close
173
+ end
174
+ end
175
+ end
176
+ end
@@ -0,0 +1,15 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # Clients can send 3 types of messages, which must be JSON arrays
5
+ module ClientMessageType
6
+ # @return [String] Used to publish events
7
+ EVENT = 'EVENT'
8
+
9
+ # @return [String] Used to request events and subscribe to new updates
10
+ REQ = 'REQ'
11
+
12
+ # @return [String] Used to stop previous subscriptions
13
+ CLOSE = 'CLOSE'
14
+ end
15
+ end
@@ -0,0 +1,93 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Nostr
4
+ # The only object type that exists in Nostr is an event. Events are immutable.
5
+ class Event < EventFragment
6
+ # 32-bytes sha256 of the the serialized event data.
7
+ # To obtain the event.id, we sha256 the serialized event. The serialization is done over the UTF-8 JSON-serialized
8
+ # string (with no white space or line breaks)
9
+ #
10
+ # @api public
11
+ #
12
+ # @example Getting the event id
13
+ # event.id # => 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460'
14
+ #
15
+ # @return [String]
16
+ #
17
+ attr_reader :id
18
+
19
+ # 64-bytes signature of the sha256 hash of the serialized event data, which is
20
+ # the same as the "id" field
21
+ #
22
+ # @api public
23
+ #
24
+ # @example Getting the event signature
25
+ # event.sig # => ''
26
+ #
27
+ # @return [String]
28
+ #
29
+ attr_reader :sig
30
+
31
+ # Instantiates a new Event
32
+ #
33
+ # @api public
34
+ #
35
+ # @example Instantiating a new event
36
+ # Nostr::Event.new(
37
+ # id: 'ccf9fdf3e1466d7c20969c71ec98defcf5f54aee088513e1b73ccb7bd770d460',
38
+ # pubkey: '48df4af6e240ac5f7c5de89bf5941b249880be0e87d03685b178ccb1a315f52e',
39
+ # created_at: 1230981305,
40
+ # kind: 1,
41
+ # tags: [],
42
+ # content: 'Your feedback is appreciated, now pay $8',
43
+ # sig: '123ac2923b792ce730b3da34f16155470ab13c8f97f9c53eaeb334f1fb3a5dc9a7f643
44
+ # 937c6d6e9855477638f5655c5d89c9aa5501ea9b578a66aced4f1cd7b3'
45
+ # )
46
+ #
47
+ #
48
+ # @param id [String] 32-bytes sha256 of the the serialized event data.
49
+ # @param sig [String] 64-bytes signature of the sha256 hash of the serialized event data, which is
50
+ # the same as the "id" field
51
+ #
52
+ def initialize(id:, sig:, **kwargs)
53
+ super(**kwargs)
54
+
55
+ @id = id
56
+ @sig = sig
57
+ end
58
+
59
+ # Converts the event to a hash
60
+ #
61
+ # @api public
62
+ #
63
+ # @example Converting the event to a hash
64
+ # event.to_h
65
+ #
66
+ # @return [Hash] The event as a hash.
67
+ #
68
+ def to_h
69
+ {
70
+ id:,
71
+ pubkey:,
72
+ created_at:,
73
+ kind:,
74
+ tags:,
75
+ content:,
76
+ sig:
77
+ }
78
+ end
79
+
80
+ # Compares two events. Returns true if all attributes are equal and false otherwise
81
+ #
82
+ # @api public
83
+ #
84
+ # @example
85
+ # event1 == event2 # => true
86
+ #
87
+ # @return [Boolean] True if all attributes are equal and false otherwise
88
+ #
89
+ def ==(other)
90
+ to_h == other.to_h
91
+ end
92
+ end
93
+ end