psf-bch-api 1.2.0 → 1.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.
Files changed (45) hide show
  1. package/.env-local +9 -0
  2. package/bin/server.js +2 -1
  3. package/package.json +4 -1
  4. package/src/adapters/fulcrum-api.js +124 -0
  5. package/src/adapters/full-node-rpc.js +2 -6
  6. package/src/adapters/index.js +4 -0
  7. package/src/adapters/slp-indexer-api.js +124 -0
  8. package/src/config/env/common.js +21 -24
  9. package/src/controllers/rest-api/fulcrum/controller.js +563 -0
  10. package/src/controllers/rest-api/fulcrum/router.js +64 -0
  11. package/src/controllers/rest-api/full-node/blockchain/controller.js +4 -4
  12. package/src/controllers/rest-api/full-node/mining/controller.js +99 -0
  13. package/src/controllers/rest-api/full-node/mining/router.js +52 -0
  14. package/src/controllers/rest-api/full-node/rawtransactions/controller.js +333 -0
  15. package/src/controllers/rest-api/full-node/rawtransactions/router.js +58 -0
  16. package/src/controllers/rest-api/index.js +19 -3
  17. package/src/controllers/rest-api/slp/controller.js +218 -0
  18. package/src/controllers/rest-api/slp/router.js +55 -0
  19. package/src/controllers/timer-controller.js +1 -1
  20. package/src/use-cases/fulcrum-use-cases.js +155 -0
  21. package/src/use-cases/full-node-mining-use-cases.js +28 -0
  22. package/src/use-cases/full-node-rawtransactions-use-cases.js +121 -0
  23. package/src/use-cases/index.js +8 -0
  24. package/src/use-cases/slp-use-cases.js +321 -0
  25. package/test/unit/controllers/blockchain-controller-unit.js +2 -3
  26. package/test/unit/controllers/fulcrum-controller-unit.js +481 -0
  27. package/test/unit/controllers/mining-controller-unit.js +139 -0
  28. package/test/unit/controllers/rawtransactions-controller-unit.js +388 -0
  29. package/test/unit/controllers/rest-api-index-unit.js +59 -3
  30. package/test/unit/controllers/slp-controller-unit.js +312 -0
  31. package/test/unit/use-cases/fulcrum-use-cases-unit.js +297 -0
  32. package/test/unit/use-cases/full-node-mining-use-cases-unit.js +84 -0
  33. package/test/unit/use-cases/full-node-rawtransactions-use-cases-unit.js +267 -0
  34. package/test/unit/use-cases/slp-use-cases-unit.js +296 -0
  35. package/src/entities/event.js +0 -71
  36. package/test/integration/api/event-integration.js +0 -250
  37. package/test/integration/api/req-integration.js +0 -173
  38. package/test/integration/api/subscription-integration.js +0 -198
  39. package/test/integration/use-cases/manage-subscription-integration.js +0 -163
  40. package/test/integration/use-cases/publish-event-integration.js +0 -104
  41. package/test/integration/use-cases/query-events-integration.js +0 -95
  42. package/test/unit/entities/event-unit.js +0 -139
  43. /package/src/controllers/rest-api/full-node/blockchain/{index.js → router.js} +0 -0
  44. /package/src/controllers/rest-api/full-node/control/{index.js → router.js} +0 -0
  45. /package/src/controllers/rest-api/full-node/dsproof/{index.js → router.js} +0 -0
@@ -0,0 +1,267 @@
1
+ /*
2
+ Unit tests for RawTransactionsUseCases.
3
+ */
4
+
5
+ import { assert } from 'chai'
6
+
7
+ import RawTransactionsUseCases from '../../../src/use-cases/full-node-rawtransactions-use-cases.js'
8
+
9
+ describe('#full-node-rawtransactions-use-cases.js', () => {
10
+ let mockAdapters
11
+ let uut
12
+
13
+ beforeEach(() => {
14
+ mockAdapters = {
15
+ fullNode: {
16
+ call: async () => ({})
17
+ }
18
+ }
19
+
20
+ uut = new RawTransactionsUseCases({ adapters: mockAdapters })
21
+ })
22
+
23
+ describe('#constructor()', () => {
24
+ it('should require adapters', () => {
25
+ assert.throws(() => {
26
+ // eslint-disable-next-line no-new
27
+ new RawTransactionsUseCases()
28
+ }, /Adapters instance required/)
29
+ })
30
+
31
+ it('should require full node adapter', () => {
32
+ assert.throws(() => {
33
+ // eslint-disable-next-line no-new
34
+ new RawTransactionsUseCases({ adapters: {} })
35
+ }, /Full node adapter required/)
36
+ })
37
+ })
38
+
39
+ describe('#decodeRawTransaction()', () => {
40
+ it('should call full node adapter with correct method', async () => {
41
+ let capturedMethod = ''
42
+ let capturedParams = []
43
+ mockAdapters.fullNode.call = async (method, params) => {
44
+ capturedMethod = method
45
+ capturedParams = params
46
+ return { txid: 'abc123', version: 2 }
47
+ }
48
+
49
+ const result = await uut.decodeRawTransaction({ hex: '01000000' })
50
+
51
+ assert.equal(capturedMethod, 'decoderawtransaction')
52
+ assert.deepEqual(capturedParams, ['01000000'])
53
+ assert.deepEqual(result, { txid: 'abc123', version: 2 })
54
+ })
55
+ })
56
+
57
+ describe('#decodeRawTransactions()', () => {
58
+ it('should call full node adapter for each hex in parallel', async () => {
59
+ const callCount = { count: 0 }
60
+ mockAdapters.fullNode.call = async (method, params) => {
61
+ callCount.count++
62
+ return { txid: `tx${callCount.count}`, hex: params[0] }
63
+ }
64
+
65
+ const hexes = ['hex1', 'hex2', 'hex3']
66
+ const result = await uut.decodeRawTransactions({ hexes })
67
+
68
+ assert.equal(callCount.count, 3)
69
+ assert.equal(result.length, 3)
70
+ assert.equal(result[0].txid, 'tx1')
71
+ assert.equal(result[1].txid, 'tx2')
72
+ assert.equal(result[2].txid, 'tx3')
73
+ })
74
+ })
75
+
76
+ describe('#decodeScript()', () => {
77
+ it('should call full node adapter with correct method', async () => {
78
+ let capturedMethod = ''
79
+ let capturedParams = []
80
+ mockAdapters.fullNode.call = async (method, params) => {
81
+ capturedMethod = method
82
+ capturedParams = params
83
+ return { asm: 'OP_DUP OP_HASH160', type: 'pubkeyhash' }
84
+ }
85
+
86
+ const result = await uut.decodeScript({ hex: '76a914' })
87
+
88
+ assert.equal(capturedMethod, 'decodescript')
89
+ assert.deepEqual(capturedParams, ['76a914'])
90
+ assert.deepEqual(result, { asm: 'OP_DUP OP_HASH160', type: 'pubkeyhash' })
91
+ })
92
+ })
93
+
94
+ describe('#decodeScripts()', () => {
95
+ it('should call full node adapter for each hex in parallel', async () => {
96
+ const callCount = { count: 0 }
97
+ mockAdapters.fullNode.call = async (method, params) => {
98
+ callCount.count++
99
+ return { asm: `script${callCount.count}`, hex: params[0] }
100
+ }
101
+
102
+ const hexes = ['script1', 'script2']
103
+ const result = await uut.decodeScripts({ hexes })
104
+
105
+ assert.equal(callCount.count, 2)
106
+ assert.equal(result.length, 2)
107
+ })
108
+ })
109
+
110
+ describe('#getRawTransaction()', () => {
111
+ it('should call full node adapter with verbose=false by default', async () => {
112
+ let capturedParams = []
113
+ mockAdapters.fullNode.call = async (method, params) => {
114
+ capturedParams = params
115
+ return '01000000'
116
+ }
117
+
118
+ await uut.getRawTransaction({ txid: 'abc123' })
119
+
120
+ assert.deepEqual(capturedParams, ['abc123', 0])
121
+ })
122
+
123
+ it('should call full node adapter with verbose=true when specified', async () => {
124
+ let capturedParams = []
125
+ mockAdapters.fullNode.call = async (method, params) => {
126
+ capturedParams = params
127
+ return { txid: 'abc123', version: 2 }
128
+ }
129
+
130
+ await uut.getRawTransaction({ txid: 'abc123', verbose: true })
131
+
132
+ assert.deepEqual(capturedParams, ['abc123', 1])
133
+ })
134
+ })
135
+
136
+ describe('#getRawTransactions()', () => {
137
+ it('should call full node adapter for each txid in parallel', async () => {
138
+ const callCount = { count: 0 }
139
+ mockAdapters.fullNode.call = async (method, params) => {
140
+ callCount.count++
141
+ return { txid: params[0], version: 2 }
142
+ }
143
+
144
+ const txids = ['tx1', 'tx2']
145
+ const result = await uut.getRawTransactions({ txids, verbose: true })
146
+
147
+ assert.equal(callCount.count, 2)
148
+ assert.equal(result.length, 2)
149
+ })
150
+ })
151
+
152
+ describe('#getRawTransactionWithHeight()', () => {
153
+ it('should return transaction without height when verbose=false', async () => {
154
+ mockAdapters.fullNode.call = async (method, params) => {
155
+ if (method === 'getrawtransaction') {
156
+ return '01000000'
157
+ }
158
+ return {}
159
+ }
160
+
161
+ const result = await uut.getRawTransactionWithHeight({ txid: 'abc123', verbose: false })
162
+
163
+ assert.equal(result, '01000000')
164
+ })
165
+
166
+ it('should fetch and append height when verbose=true and blockhash exists', async () => {
167
+ let callCount = 0
168
+ mockAdapters.fullNode.call = async (method, params) => {
169
+ callCount++
170
+ if (method === 'getrawtransaction') {
171
+ return { txid: 'abc123', blockhash: 'block123' }
172
+ }
173
+ if (method === 'getblockheader') {
174
+ return { height: 100, hash: 'block123' }
175
+ }
176
+ return {}
177
+ }
178
+
179
+ const result = await uut.getRawTransactionWithHeight({ txid: 'abc123', verbose: true })
180
+
181
+ assert.equal(callCount, 2)
182
+ assert.equal(result.height, 100)
183
+ assert.equal(result.txid, 'abc123')
184
+ })
185
+
186
+ it('should handle block header lookup failure gracefully', async () => {
187
+ let callCount = 0
188
+ mockAdapters.fullNode.call = async (method, params) => {
189
+ callCount++
190
+ if (method === 'getrawtransaction') {
191
+ return { txid: 'abc123', blockhash: 'block123' }
192
+ }
193
+ if (method === 'getblockheader') {
194
+ throw new Error('Block not found')
195
+ }
196
+ return {}
197
+ }
198
+
199
+ const result = await uut.getRawTransactionWithHeight({ txid: 'abc123', verbose: true })
200
+
201
+ assert.equal(callCount, 2)
202
+ assert.isNull(result.height)
203
+ assert.equal(result.txid, 'abc123')
204
+ })
205
+ })
206
+
207
+ describe('#getBlockHeader()', () => {
208
+ it('should call full node adapter with correct method', async () => {
209
+ let capturedMethod = ''
210
+ let capturedParams = []
211
+ mockAdapters.fullNode.call = async (method, params) => {
212
+ capturedMethod = method
213
+ capturedParams = params
214
+ return { height: 100, hash: 'block123' }
215
+ }
216
+
217
+ const result = await uut.getBlockHeader({ blockHash: 'block123', verbose: true })
218
+
219
+ assert.equal(capturedMethod, 'getblockheader')
220
+ assert.deepEqual(capturedParams, ['block123', true])
221
+ assert.deepEqual(result, { height: 100, hash: 'block123' })
222
+ })
223
+ })
224
+
225
+ describe('#sendRawTransaction()', () => {
226
+ it('should call full node adapter with correct method', async () => {
227
+ let capturedMethod = ''
228
+ let capturedParams = []
229
+ mockAdapters.fullNode.call = async (method, params) => {
230
+ capturedMethod = method
231
+ capturedParams = params
232
+ return 'txid123'
233
+ }
234
+
235
+ const result = await uut.sendRawTransaction({ hex: '01000000' })
236
+
237
+ assert.equal(capturedMethod, 'sendrawtransaction')
238
+ assert.deepEqual(capturedParams, ['01000000'])
239
+ assert.equal(result, 'txid123')
240
+ })
241
+ })
242
+
243
+ describe('#sendRawTransactions()', () => {
244
+ it('should send transactions serially, not in parallel', async () => {
245
+ const callOrder = []
246
+ mockAdapters.fullNode.call = async (method, params) => {
247
+ callOrder.push(params[0])
248
+ // Simulate some async work
249
+ await new Promise(resolve => setTimeout(resolve, 10))
250
+ return `txid-${params[0]}`
251
+ }
252
+
253
+ const hexes = ['hex1', 'hex2', 'hex3']
254
+ const startTime = Date.now()
255
+ const result = await uut.sendRawTransactions({ hexes })
256
+ const endTime = Date.now()
257
+
258
+ // Should take at least 30ms if serial (3 * 10ms)
259
+ assert.isAtLeast(endTime - startTime, 25)
260
+ assert.deepEqual(callOrder, ['hex1', 'hex2', 'hex3'])
261
+ assert.equal(result.length, 3)
262
+ assert.equal(result[0], 'txid-hex1')
263
+ assert.equal(result[1], 'txid-hex2')
264
+ assert.equal(result[2], 'txid-hex3')
265
+ })
266
+ })
267
+ })
@@ -0,0 +1,296 @@
1
+ /*
2
+ Unit tests for SlpUseCases.
3
+ */
4
+
5
+ import { assert } from 'chai'
6
+ import sinon from 'sinon'
7
+ import BCHJS from '@psf/bch-js'
8
+
9
+ import SlpUseCases from '../../../src/use-cases/slp-use-cases.js'
10
+
11
+ describe('#slp-use-cases.js', () => {
12
+ let sandbox
13
+ let mockAdapters
14
+ let mockConfig
15
+ let uut
16
+ let mockBchjs
17
+ let mockWallet
18
+ let mockSlpTokenMedia
19
+
20
+ beforeEach(() => {
21
+ sandbox = sinon.createSandbox()
22
+ mockConfig = {
23
+ restURL: 'http://localhost:3000/v5/',
24
+ ipfsGateway: 'p2wdb-gateway-678.fullstack.cash'
25
+ }
26
+
27
+ mockAdapters = {
28
+ slpIndexer: {
29
+ get: sandbox.stub().resolves({}),
30
+ post: sandbox.stub().resolves({})
31
+ }
32
+ }
33
+
34
+ // Create mock BCHJS
35
+ mockBchjs = new BCHJS()
36
+ mockBchjs.Electrumx = {
37
+ txData: sandbox.stub().resolves({
38
+ details: {
39
+ vout: [
40
+ {
41
+ scriptPubKey: {
42
+ hex: '6a0c48656c6c6f20576f726c6421'
43
+ }
44
+ }
45
+ ]
46
+ }
47
+ })
48
+ }
49
+ mockBchjs.Script = {
50
+ toASM: sandbox.stub().returns('OP_RETURN 48656c6c6f20576f726c6421')
51
+ }
52
+
53
+ // Create mock wallet
54
+ mockWallet = {
55
+ walletInfoPromise: Promise.resolve(),
56
+ getTransactions: sandbox.stub().resolves([]),
57
+ getTxData: sandbox.stub().resolves([{
58
+ vin: [{
59
+ address: 'bitcoincash:test123'
60
+ }]
61
+ }])
62
+ }
63
+
64
+ // Create mock SlpTokenMedia
65
+ mockSlpTokenMedia = {
66
+ getIcon: sandbox.stub().resolves({ tokenIcon: 'test-icon.png' })
67
+ }
68
+
69
+ // Mock the imports
70
+ uut = new SlpUseCases({
71
+ adapters: mockAdapters,
72
+ bchjs: mockBchjs,
73
+ config: mockConfig
74
+ })
75
+
76
+ // Replace the wallet initialization with our mocks
77
+ uut.wallet = mockWallet
78
+ uut.slpTokenMedia = mockSlpTokenMedia
79
+ uut.walletInitialized = true
80
+ })
81
+
82
+ afterEach(() => {
83
+ sandbox.restore()
84
+ })
85
+
86
+ describe('#constructor()', () => {
87
+ it('should require adapters', () => {
88
+ assert.throws(() => {
89
+ // eslint-disable-next-line no-new
90
+ new SlpUseCases()
91
+ }, /Adapters instance required/)
92
+ })
93
+
94
+ it('should require slpIndexer adapter', () => {
95
+ assert.throws(() => {
96
+ // eslint-disable-next-line no-new
97
+ new SlpUseCases({ adapters: {} })
98
+ }, /SLP Indexer adapter required/)
99
+ })
100
+ })
101
+
102
+ describe('#getStatus()', () => {
103
+ it('should call slpIndexer adapter get method', async () => {
104
+ mockAdapters.slpIndexer.get.resolves({ status: 'ok' })
105
+
106
+ const result = await uut.getStatus()
107
+
108
+ assert.isTrue(mockAdapters.slpIndexer.get.calledOnceWith('slp/status/'))
109
+ assert.deepEqual(result, { status: 'ok' })
110
+ })
111
+ })
112
+
113
+ describe('#getAddress()', () => {
114
+ it('should call slpIndexer adapter post method', async () => {
115
+ const address = 'bitcoincash:qrdka2205f4hyukutc2g0s6lykperc8nsu5u2ddpqf'
116
+ mockAdapters.slpIndexer.post.resolves({ balance: 1000 })
117
+
118
+ const result = await uut.getAddress({ address })
119
+
120
+ assert.isTrue(
121
+ mockAdapters.slpIndexer.post.calledOnceWith('slp/address/', { address })
122
+ )
123
+ assert.deepEqual(result, { balance: 1000 })
124
+ })
125
+ })
126
+
127
+ describe('#getTxid()', () => {
128
+ it('should call slpIndexer adapter post method', async () => {
129
+ const txid = 'a'.repeat(64)
130
+ mockAdapters.slpIndexer.post.resolves({ txid })
131
+
132
+ const result = await uut.getTxid({ txid })
133
+
134
+ assert.isTrue(
135
+ mockAdapters.slpIndexer.post.calledOnceWith('slp/tx/', { txid })
136
+ )
137
+ assert.deepEqual(result, { txid })
138
+ })
139
+ })
140
+
141
+ describe('#getTokenStats()', () => {
142
+ it('should call slpIndexer adapter post method', async () => {
143
+ const tokenId = 'a'.repeat(64)
144
+ const withTxHistory = false
145
+ mockAdapters.slpIndexer.post.resolves({ tokenData: {} })
146
+
147
+ const result = await uut.getTokenStats({ tokenId, withTxHistory })
148
+
149
+ assert.isTrue(
150
+ mockAdapters.slpIndexer.post.calledOnceWith('slp/token/', { tokenId, withTxHistory })
151
+ )
152
+ assert.deepEqual(result, { tokenData: {} })
153
+ })
154
+ })
155
+
156
+ describe('#getTokenData()', () => {
157
+ it('should get token data with mutable and immutable data', async () => {
158
+ const tokenId = 'a'.repeat(64)
159
+ const tokenStats = {
160
+ tokenData: {
161
+ documentUri: 'ipfs://test123',
162
+ documentHash: 'b'.repeat(64)
163
+ }
164
+ }
165
+ mockAdapters.slpIndexer.post.resolves(tokenStats)
166
+
167
+ // Mock decodeOpReturn to return JSON with mda
168
+ sandbox.stub(uut, 'decodeOpReturn').resolves(JSON.stringify({ mda: 'bitcoincash:test123' }))
169
+ sandbox.stub(uut, 'getMutableCid').resolves('mutable-cid-123')
170
+
171
+ const result = await uut.getTokenData({ tokenId })
172
+
173
+ assert.property(result, 'genesisData')
174
+ assert.property(result, 'immutableData')
175
+ assert.property(result, 'mutableData')
176
+ })
177
+
178
+ it('should handle errors when getting mutable data', async () => {
179
+ const tokenId = 'a'.repeat(64)
180
+ const tokenStats = {
181
+ tokenData: {
182
+ documentUri: 'ipfs://test123',
183
+ documentHash: 'b'.repeat(64)
184
+ }
185
+ }
186
+ mockAdapters.slpIndexer.post.resolves(tokenStats)
187
+
188
+ sandbox.stub(uut, 'getMutableCid').rejects(new Error('Test error'))
189
+
190
+ const result = await uut.getTokenData({ tokenId })
191
+
192
+ assert.property(result, 'genesisData')
193
+ assert.property(result, 'immutableData')
194
+ assert.equal(result.mutableData, '')
195
+ })
196
+ })
197
+
198
+ describe('#decodeOpReturn()', () => {
199
+ it('should decode OP_RETURN data from transaction', async () => {
200
+ const txid = 'a'.repeat(64)
201
+ const mockTxData = {
202
+ details: {
203
+ vout: [
204
+ {
205
+ scriptPubKey: {
206
+ hex: '6a0c48656c6c6f20576f726c6421'
207
+ }
208
+ }
209
+ ]
210
+ }
211
+ }
212
+ mockBchjs.Electrumx.txData.resolves(mockTxData)
213
+ mockBchjs.Script.toASM.returns('OP_RETURN 48656c6c6f20576f726c6421')
214
+
215
+ const result = await uut.decodeOpReturn({ txid })
216
+
217
+ assert.isTrue(mockBchjs.Electrumx.txData.calledOnceWith(txid))
218
+ assert.isString(result)
219
+ })
220
+
221
+ it('should throw error if txid is not a string', async () => {
222
+ try {
223
+ await uut.decodeOpReturn({ txid: null })
224
+ assert.fail('Should have thrown an error')
225
+ } catch (err) {
226
+ assert.include(err.message, 'txid must be a string')
227
+ }
228
+ })
229
+ })
230
+
231
+ describe('#getCIDData()', () => {
232
+ it('should fetch IPFS data from CID', async () => {
233
+ const cid = 'ipfs://test123'
234
+ const mockData = { name: 'Test Token' }
235
+
236
+ // Mock axios
237
+ const axios = await import('axios')
238
+ sandbox.stub(axios.default, 'get').resolves({ data: mockData })
239
+
240
+ const result = await uut.getCIDData({ cid })
241
+
242
+ assert.deepEqual(result, mockData)
243
+ })
244
+
245
+ it('should throw error if cid is not a string', async () => {
246
+ try {
247
+ await uut.getCIDData({ cid: null })
248
+ assert.fail('Should have thrown an error')
249
+ } catch (err) {
250
+ assert.include(err.message, 'cid must be a string')
251
+ }
252
+ })
253
+ })
254
+
255
+ describe('#getMutableCid()', () => {
256
+ it('should extract mutable CID from token stats', async () => {
257
+ const tokenStats = {
258
+ documentHash: 'a'.repeat(64)
259
+ }
260
+
261
+ const mockOpReturn = JSON.stringify({ mda: 'bitcoincash:test123' })
262
+ sandbox.stub(uut, 'decodeOpReturn').resolves(mockOpReturn)
263
+
264
+ mockWallet.getTransactions.resolves([
265
+ {
266
+ tx_hash: 'b'.repeat(64),
267
+ height: 100
268
+ }
269
+ ])
270
+
271
+ mockWallet.getTxData.resolves([{
272
+ vin: [{
273
+ address: 'bitcoincash:test123'
274
+ }]
275
+ }])
276
+
277
+ // Mock decodeOpReturn for the transaction
278
+ uut.decodeOpReturn.onSecondCall().resolves(JSON.stringify({ cid: 'ipfs://mutable-cid-123', ts: 1234567890 }))
279
+
280
+ const result = await uut.getMutableCid({ tokenStats })
281
+
282
+ assert.isString(result)
283
+ })
284
+
285
+ it('should return false if no documentHash in tokenStats', async () => {
286
+ const tokenStats = {}
287
+
288
+ try {
289
+ await uut.getMutableCid({ tokenStats })
290
+ assert.fail('Should have thrown an error')
291
+ } catch (err) {
292
+ assert.include(err.message, 'No documentHash property found')
293
+ }
294
+ })
295
+ })
296
+ })
@@ -1,71 +0,0 @@
1
- /*
2
- Event entity - represents a Nostr event.
3
- This is a domain model following Clean Architecture principles.
4
- */
5
-
6
- class Event {
7
- constructor (data) {
8
- this.id = data.id
9
- this.pubkey = data.pubkey
10
- this.created_at = data.created_at
11
- this.kind = data.kind
12
- this.tags = data.tags || []
13
- this.content = data.content
14
- this.sig = data.sig
15
- }
16
-
17
- /**
18
- * Validates the event structure
19
- * @returns {boolean} True if valid
20
- */
21
- isValid () {
22
- if (!this.id || !this.pubkey || !this.created_at || this.kind === undefined || !this.sig) {
23
- return false
24
- }
25
-
26
- // Basic type checks
27
- if (typeof this.id !== 'string' || this.id.length !== 64) {
28
- return false
29
- }
30
-
31
- if (typeof this.pubkey !== 'string' || this.pubkey.length !== 64) {
32
- return false
33
- }
34
-
35
- if (typeof this.created_at !== 'number') {
36
- return false
37
- }
38
-
39
- if (typeof this.kind !== 'number' || this.kind < 0 || this.kind > 65535) {
40
- return false
41
- }
42
-
43
- if (typeof this.sig !== 'string' || this.sig.length !== 128) {
44
- return false
45
- }
46
-
47
- if (!Array.isArray(this.tags)) {
48
- return false
49
- }
50
-
51
- return true
52
- }
53
-
54
- /**
55
- * Convert to plain object
56
- * @returns {Object} Plain event object
57
- */
58
- toJSON () {
59
- return {
60
- id: this.id,
61
- pubkey: this.pubkey,
62
- created_at: this.created_at,
63
- kind: this.kind,
64
- tags: this.tags,
65
- content: this.content,
66
- sig: this.sig
67
- }
68
- }
69
- }
70
-
71
- export default Event