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
@@ -1,163 +0,0 @@
1
- /*
2
- Integration tests for ManageSubscriptionUseCase with real adapter.
3
- These tests require a running Nostr relay.
4
- */
5
-
6
- // npm libraries
7
- import { assert } from 'chai'
8
-
9
- // Unit under test
10
- import Adapters from '../../../src/adapters/index.js'
11
- import ManageSubscriptionUseCase from '../../../src/use-cases/manage-subscription.js'
12
-
13
- describe('#manage-subscription-integration.js', () => {
14
- let adapters
15
- let uut
16
-
17
- before(async () => {
18
- // Initialize adapters (will connect to real relay)
19
- adapters = new Adapters()
20
- await adapters.start()
21
-
22
- uut = new ManageSubscriptionUseCase({ adapters })
23
- })
24
-
25
- after(async () => {
26
- // Clean up all subscriptions and disconnect from all relays
27
- // Note: This is a simplified cleanup - in production you'd track all subscriptions
28
- if (adapters && adapters.nostrRelays) {
29
- await Promise.allSettled(
30
- adapters.nostrRelays.map(relay => relay.disconnect())
31
- )
32
- }
33
- })
34
-
35
- describe('#createSubscription()', () => {
36
- it('should successfully create a subscription', async () => {
37
- const subscriptionId = 'test-sub-' + Date.now()
38
- const filters = [{ kinds: [1], limit: 5 }]
39
-
40
- let eventReceived = false
41
- let eoseReceived = false
42
-
43
- const onEvent = (event) => {
44
- eventReceived = true
45
- assert.property(event, 'id')
46
- assert.property(event, 'kind')
47
- }
48
-
49
- const onEose = () => {
50
- eoseReceived = true
51
- }
52
-
53
- const onClosed = () => {
54
- // Handler for closed events
55
- }
56
-
57
- await uut.createSubscription(subscriptionId, filters, onEvent, onEose, onClosed)
58
-
59
- // Assert subscription exists
60
- assert.isTrue(uut.hasSubscription(subscriptionId))
61
-
62
- // Wait a bit for events/EOSE
63
- await new Promise(resolve => setTimeout(resolve, 2000))
64
-
65
- // EOSE should be received (or events)
66
- // Note: May not receive events if none exist, but EOSE should come
67
- assert.isTrue(eoseReceived || eventReceived)
68
-
69
- // Clean up
70
- if (uut.hasSubscription(subscriptionId)) {
71
- await uut.closeSubscription(subscriptionId)
72
- }
73
- })
74
-
75
- it('should prevent duplicate subscriptions', async () => {
76
- const subscriptionId = 'test-dup-' + Date.now()
77
- const filters = [{ kinds: [1] }]
78
-
79
- await uut.createSubscription(subscriptionId, filters)
80
-
81
- try {
82
- await uut.createSubscription(subscriptionId, filters)
83
- assert.equal(true, false, 'unexpected result')
84
- } catch (err) {
85
- assert.include(err.message, 'already exists')
86
- }
87
-
88
- // Clean up
89
- if (uut.hasSubscription(subscriptionId)) {
90
- await uut.closeSubscription(subscriptionId)
91
- }
92
- })
93
-
94
- it('should handle subscription with no events', async () => {
95
- const subscriptionId = 'test-empty-' + Date.now()
96
- const filters = [{ kinds: [99999], limit: 1 }] // Unlikely to have events
97
-
98
- let eoseReceived = false
99
-
100
- const onEose = () => {
101
- eoseReceived = true
102
- }
103
-
104
- await uut.createSubscription(subscriptionId, filters, null, onEose, null)
105
-
106
- // Wait for EOSE
107
- await new Promise(resolve => setTimeout(resolve, 2000))
108
-
109
- // Should receive EOSE even with no events
110
- assert.isTrue(eoseReceived)
111
-
112
- // Clean up
113
- if (uut.hasSubscription(subscriptionId)) {
114
- await uut.closeSubscription(subscriptionId)
115
- }
116
- })
117
- })
118
-
119
- describe('#closeSubscription()', () => {
120
- it('should successfully close a subscription', async () => {
121
- const subscriptionId = 'test-close-' + Date.now()
122
- const filters = [{ kinds: [1] }]
123
-
124
- await uut.createSubscription(subscriptionId, filters)
125
- assert.isTrue(uut.hasSubscription(subscriptionId))
126
-
127
- await uut.closeSubscription(subscriptionId)
128
-
129
- // Assert subscription is removed
130
- assert.isFalse(uut.hasSubscription(subscriptionId))
131
- })
132
-
133
- it('should return successfully when closing non-existent subscription (idempotent)', async () => {
134
- const subscriptionId = 'non-existent-sub'
135
-
136
- // Should not throw - idempotent operation
137
- await uut.closeSubscription(subscriptionId)
138
-
139
- // Should return successfully without error
140
- assert.isTrue(true, 'closeSubscription should succeed for non-existent subscription')
141
- })
142
- })
143
-
144
- describe('#hasSubscription()', () => {
145
- it('should return false for non-existent subscription', () => {
146
- assert.isFalse(uut.hasSubscription('non-existent'))
147
- })
148
-
149
- it('should return true for existing subscription', async () => {
150
- const subscriptionId = 'test-has-' + Date.now()
151
- const filters = [{ kinds: [1] }]
152
-
153
- assert.isFalse(uut.hasSubscription(subscriptionId))
154
- await uut.createSubscription(subscriptionId, filters)
155
- assert.isTrue(uut.hasSubscription(subscriptionId))
156
-
157
- // Clean up
158
- if (uut.hasSubscription(subscriptionId)) {
159
- await uut.closeSubscription(subscriptionId)
160
- }
161
- })
162
- })
163
- })
@@ -1,104 +0,0 @@
1
- /*
2
- Integration tests for PublishEventUseCase with real adapter.
3
- These tests require a running Nostr relay.
4
- */
5
-
6
- // npm libraries
7
- import { assert } from 'chai'
8
-
9
- // Unit under test
10
- import Adapters from '../../../src/adapters/index.js'
11
- import PublishEventUseCase from '../../../src/use-cases/publish-event.js'
12
- import { finalizeEvent, generateSecretKey } from 'nostr-tools/pure'
13
-
14
- describe('#publish-event-integration.js', () => {
15
- let adapters
16
- let uut
17
-
18
- before(async () => {
19
- // Initialize adapters (will connect to real relay)
20
- adapters = new Adapters()
21
- await adapters.start()
22
-
23
- uut = new PublishEventUseCase({ adapters })
24
- })
25
-
26
- after(async () => {
27
- // Clean up adapters - disconnect from all relays
28
- if (adapters && adapters.nostrRelays) {
29
- await Promise.allSettled(
30
- adapters.nostrRelays.map(relay => relay.disconnect())
31
- )
32
- }
33
- })
34
-
35
- describe('#execute()', () => {
36
- it('should successfully publish a valid event', async () => {
37
- // Generate keys
38
- const sk = generateSecretKey()
39
-
40
- // Create event template
41
- const eventTemplate = {
42
- kind: 1,
43
- created_at: Math.floor(Date.now() / 1000),
44
- tags: [],
45
- content: 'Integration test post from use case'
46
- }
47
-
48
- // Sign the event
49
- const signedEvent = finalizeEvent(eventTemplate, sk)
50
-
51
- // Execute use case
52
- const result = await uut.execute(signedEvent)
53
-
54
- // Assert result
55
- assert.property(result, 'accepted')
56
- assert.property(result, 'message')
57
- assert.property(result, 'eventId')
58
- assert.equal(result.eventId, signedEvent.id)
59
- })
60
-
61
- it('should reject invalid event structure', async () => {
62
- const invalidEvent = {
63
- id: 'invalid',
64
- pubkey: 'invalid',
65
- created_at: Math.floor(Date.now() / 1000),
66
- kind: 1,
67
- tags: [],
68
- content: 'Test',
69
- sig: 'invalid'
70
- }
71
-
72
- try {
73
- await uut.execute(invalidEvent)
74
- assert.equal(true, false, 'unexpected result')
75
- } catch (err) {
76
- assert.include(err.message, 'Invalid event structure')
77
- }
78
- })
79
-
80
- it('should handle relay rejection', async () => {
81
- // Generate keys
82
- const sk = generateSecretKey()
83
-
84
- // Create a duplicate event (if we send same event twice)
85
- const eventTemplate = {
86
- kind: 1,
87
- created_at: Math.floor(Date.now() / 1000),
88
- tags: [],
89
- content: 'Duplicate test post'
90
- }
91
-
92
- const signedEvent = finalizeEvent(eventTemplate, sk)
93
-
94
- // Publish first time
95
- const result1 = await uut.execute(signedEvent)
96
- assert.property(result1, 'accepted')
97
-
98
- // Try to publish again (may be rejected as duplicate)
99
- const result2 = await uut.execute(signedEvent)
100
- assert.property(result2, 'accepted')
101
- // Result may be accepted or rejected depending on relay
102
- })
103
- })
104
- })
@@ -1,95 +0,0 @@
1
- /*
2
- Integration tests for QueryEventsUseCase with real adapter.
3
- These tests require a running Nostr relay.
4
- */
5
-
6
- // npm libraries
7
- import { assert } from 'chai'
8
-
9
- // Unit under test
10
- import Adapters from '../../../src/adapters/index.js'
11
- import QueryEventsUseCase from '../../../src/use-cases/query-events.js'
12
-
13
- describe('#query-events-integration.js', () => {
14
- let adapters
15
- let uut
16
-
17
- before(async () => {
18
- // Initialize adapters (will connect to real relay)
19
- adapters = new Adapters()
20
- await adapters.start()
21
-
22
- uut = new QueryEventsUseCase({ adapters })
23
- })
24
-
25
- after(async () => {
26
- // Clean up adapters - disconnect from all relays
27
- if (adapters && adapters.nostrRelays) {
28
- await Promise.allSettled(
29
- adapters.nostrRelays.map(relay => relay.disconnect())
30
- )
31
- }
32
- })
33
-
34
- describe('#execute()', () => {
35
- it('should successfully query events', async () => {
36
- const filters = [{ kinds: [1], limit: 5 }]
37
- const subscriptionId = 'test-query-' + Date.now()
38
-
39
- const events = await uut.execute(filters, subscriptionId)
40
-
41
- // Assert result is an array
42
- assert.isArray(events)
43
-
44
- // If events are returned, verify structure
45
- if (events.length > 0) {
46
- assert.property(events[0], 'id')
47
- assert.property(events[0], 'pubkey')
48
- assert.property(events[0], 'created_at')
49
- assert.property(events[0], 'kind')
50
- assert.property(events[0], 'content')
51
- assert.equal(events[0].kind, 1)
52
- }
53
- })
54
-
55
- it('should handle empty results', async () => {
56
- // Query for events that likely don't exist
57
- const filters = [{ kinds: [99999], limit: 1 }]
58
- const subscriptionId = 'test-empty-' + Date.now()
59
-
60
- const events = await uut.execute(filters, subscriptionId)
61
-
62
- // Should return empty array, not throw
63
- assert.isArray(events)
64
- assert.equal(events.length, 0)
65
- })
66
-
67
- it('should handle multiple filters', async function () {
68
- // Increase timeout for this test - needs to be longer than use case timeout (30s)
69
- this.timeout(35000)
70
-
71
- const filters = [
72
- { kinds: [1], limit: 2 },
73
- { kinds: [3], limit: 2 }
74
- ]
75
- const subscriptionId = 'test-multi-' + Date.now()
76
-
77
- const events = await uut.execute(filters, subscriptionId)
78
-
79
- // Should return array (may be empty)
80
- assert.isArray(events)
81
- })
82
-
83
- it('should timeout if EOSE not received', async () => {
84
- // This test may take up to 30 seconds
85
- // Use a filter that might not return EOSE quickly
86
- const filters = [{ kinds: [1] }] // No limit, might timeout
87
- const subscriptionId = 'test-timeout-' + Date.now()
88
-
89
- // Should eventually return (even if empty)
90
- const events = await uut.execute(filters, subscriptionId)
91
-
92
- assert.isArray(events)
93
- })
94
- })
95
- })
@@ -1,139 +0,0 @@
1
- /*
2
- Unit tests for the Event entity.
3
- */
4
-
5
- // npm libraries
6
- import { assert } from 'chai'
7
-
8
- // Mocking data libraries
9
- import {
10
- mockKind0Event,
11
- mockKind1Event,
12
- mockKind3Event,
13
- mockKind7Event,
14
- mockInvalidEventMissingId,
15
- mockInvalidEventWrongIdLength,
16
- mockInvalidEventMissingPubkey,
17
- mockInvalidEventWrongPubkeyLength,
18
- mockInvalidEventMissingCreatedAt,
19
- mockInvalidEventWrongCreatedAtType,
20
- mockInvalidEventMissingKind,
21
- mockInvalidEventKindOutOfRange,
22
- mockInvalidEventMissingSig,
23
- mockInvalidEventWrongSigLength,
24
- mockInvalidEventTagsNotArray
25
- } from '../mocks/event-mocks.js'
26
-
27
- // Unit under test
28
- import Event from '../../../src/entities/event.js'
29
-
30
- describe('#event.js', () => {
31
- describe('#isValid()', () => {
32
- it('should return true for valid kind 0 event', () => {
33
- const event = new Event(mockKind0Event)
34
- assert.isTrue(event.isValid())
35
- })
36
-
37
- it('should return true for valid kind 1 event', () => {
38
- const event = new Event(mockKind1Event)
39
- assert.isTrue(event.isValid())
40
- })
41
-
42
- it('should return true for valid kind 3 event', () => {
43
- const event = new Event(mockKind3Event)
44
- assert.isTrue(event.isValid())
45
- })
46
-
47
- it('should return true for valid kind 7 event', () => {
48
- const event = new Event(mockKind7Event)
49
- assert.isTrue(event.isValid())
50
- })
51
-
52
- it('should return false for event missing id', () => {
53
- const event = new Event(mockInvalidEventMissingId)
54
- assert.isFalse(event.isValid())
55
- })
56
-
57
- it('should return false for event with wrong id length', () => {
58
- const event = new Event(mockInvalidEventWrongIdLength)
59
- assert.isFalse(event.isValid())
60
- })
61
-
62
- it('should return false for event missing pubkey', () => {
63
- const event = new Event(mockInvalidEventMissingPubkey)
64
- assert.isFalse(event.isValid())
65
- })
66
-
67
- it('should return false for event with wrong pubkey length', () => {
68
- const event = new Event(mockInvalidEventWrongPubkeyLength)
69
- assert.isFalse(event.isValid())
70
- })
71
-
72
- it('should return false for event missing created_at', () => {
73
- const event = new Event(mockInvalidEventMissingCreatedAt)
74
- assert.isFalse(event.isValid())
75
- })
76
-
77
- it('should return false for event with wrong created_at type', () => {
78
- const event = new Event(mockInvalidEventWrongCreatedAtType)
79
- assert.isFalse(event.isValid())
80
- })
81
-
82
- it('should return false for event missing kind', () => {
83
- const event = new Event(mockInvalidEventMissingKind)
84
- assert.isFalse(event.isValid())
85
- })
86
-
87
- it('should return false for event with kind out of range', () => {
88
- const event = new Event(mockInvalidEventKindOutOfRange)
89
- assert.isFalse(event.isValid())
90
- })
91
-
92
- it('should return false for event missing sig', () => {
93
- const event = new Event(mockInvalidEventMissingSig)
94
- assert.isFalse(event.isValid())
95
- })
96
-
97
- it('should return false for event with wrong sig length', () => {
98
- const event = new Event(mockInvalidEventWrongSigLength)
99
- assert.isFalse(event.isValid())
100
- })
101
-
102
- it('should return false for event with tags not an array', () => {
103
- const event = new Event(mockInvalidEventTagsNotArray)
104
- assert.isFalse(event.isValid())
105
- })
106
- })
107
-
108
- describe('#toJSON()', () => {
109
- it('should serialize event to JSON correctly', () => {
110
- const event = new Event(mockKind1Event)
111
- const json = event.toJSON()
112
-
113
- assert.property(json, 'id')
114
- assert.property(json, 'pubkey')
115
- assert.property(json, 'created_at')
116
- assert.property(json, 'kind')
117
- assert.property(json, 'tags')
118
- assert.property(json, 'content')
119
- assert.property(json, 'sig')
120
-
121
- assert.equal(json.id, mockKind1Event.id)
122
- assert.equal(json.pubkey, mockKind1Event.pubkey)
123
- assert.equal(json.created_at, mockKind1Event.created_at)
124
- assert.equal(json.kind, mockKind1Event.kind)
125
- assert.deepEqual(json.tags, mockKind1Event.tags)
126
- assert.equal(json.content, mockKind1Event.content)
127
- assert.equal(json.sig, mockKind1Event.sig)
128
- })
129
-
130
- it('should serialize event with tags correctly', () => {
131
- const event = new Event(mockKind3Event)
132
- const json = event.toJSON()
133
-
134
- assert.isArray(json.tags)
135
- assert.equal(json.tags.length, 1)
136
- assert.deepEqual(json.tags, mockKind3Event.tags)
137
- })
138
- })
139
- })