psf-bch-api 1.1.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.
- package/.env-local +4 -0
- package/LICENSE.md +8 -0
- package/README.md +8 -0
- package/apidoc.json +9 -0
- package/bin/server.js +183 -0
- package/dev-docs/README.md +4 -0
- package/dev-docs/creation-prompt.md +34 -0
- package/dev-docs/rest2nostr-poxy-api.plan.md +163 -0
- package/dev-docs/test-plan-for-rest2nostr.plan.md +161 -0
- package/dev-docs/unit-test-prompt.md +13 -0
- package/examples/01-create-account.js +67 -0
- package/examples/02-read-posts.js +44 -0
- package/examples/03-write-post.js +55 -0
- package/examples/04-read-alice-posts.js +49 -0
- package/examples/05-get-follow-list.js +53 -0
- package/examples/06-update-follow-list.js +63 -0
- package/examples/07-liking-event.js +59 -0
- package/examples/README.md +90 -0
- package/index.js +11 -0
- package/package.json +37 -0
- package/production/docker/Dockerfile +85 -0
- package/production/docker/cleanup-images.sh +5 -0
- package/production/docker/docker-compose.yml +19 -0
- package/production/docker/start-rest2nostr.sh +3 -0
- package/src/adapters/full-node-rpc.js +133 -0
- package/src/adapters/index.js +217 -0
- package/src/adapters/wlogger.js +79 -0
- package/src/config/env/common.js +64 -0
- package/src/config/env/development.js +7 -0
- package/src/config/env/production.js +7 -0
- package/src/config/index.js +14 -0
- package/src/controllers/index.js +56 -0
- package/src/controllers/rest-api/full-node/blockchain/controller.js +553 -0
- package/src/controllers/rest-api/full-node/blockchain/index.js +66 -0
- package/src/controllers/rest-api/index.js +55 -0
- package/src/controllers/timer-controller.js +72 -0
- package/src/entities/event.js +71 -0
- package/src/use-cases/full-node-blockchain-use-cases.js +134 -0
- package/src/use-cases/index.js +29 -0
- package/test/integration/api/event-integration.js +250 -0
- package/test/integration/api/req-integration.js +173 -0
- package/test/integration/api/subscription-integration.js +198 -0
- package/test/integration/use-cases/manage-subscription-integration.js +163 -0
- package/test/integration/use-cases/publish-event-integration.js +104 -0
- package/test/integration/use-cases/query-events-integration.js +95 -0
- package/test/unit/adapters/full-node-rpc-unit.js +122 -0
- package/test/unit/bin/server-unit.js +63 -0
- package/test/unit/controllers/blockchain-controller-unit.js +215 -0
- package/test/unit/controllers/rest-api-index-unit.js +85 -0
- package/test/unit/entities/event-unit.js +139 -0
- package/test/unit/mocks/controller-mocks.js +98 -0
- package/test/unit/mocks/event-mocks.js +194 -0
- package/test/unit/use-cases/full-node-blockchain-use-cases-unit.js +137 -0
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Example script for reading posts (kind 1 events) from a relay.
|
|
3
|
+
Refactored to use REST API instead of WebSocket.
|
|
4
|
+
|
|
5
|
+
Run the server with `npm start` in the main directory, before running this example.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
|
9
|
+
|
|
10
|
+
// JB55's public key
|
|
11
|
+
const jb55 = '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'
|
|
12
|
+
|
|
13
|
+
// Create subscription ID
|
|
14
|
+
const subId = 'read-posts-' + Date.now()
|
|
15
|
+
|
|
16
|
+
// Create filters - read posts from JB55
|
|
17
|
+
const filters = {
|
|
18
|
+
limit: 2,
|
|
19
|
+
kinds: [1],
|
|
20
|
+
authors: [jb55]
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
try {
|
|
24
|
+
// Query events using GET /req/:subId
|
|
25
|
+
const filtersJson = encodeURIComponent(JSON.stringify([filters]))
|
|
26
|
+
const url = `${API_URL}/req/${subId}?filters=${filtersJson}`
|
|
27
|
+
|
|
28
|
+
console.log(`Querying events from ${API_URL}`)
|
|
29
|
+
console.log('Filters:', JSON.stringify(filters, null, 2))
|
|
30
|
+
|
|
31
|
+
const response = await fetch(url)
|
|
32
|
+
const events = await response.json()
|
|
33
|
+
|
|
34
|
+
console.log(`\nReceived ${events.length} events:`)
|
|
35
|
+
events.forEach((ev, index) => {
|
|
36
|
+
console.log(`\nEvent ${index + 1}:`)
|
|
37
|
+
console.log(' ID:', ev.id)
|
|
38
|
+
console.log(' Author:', ev.pubkey)
|
|
39
|
+
console.log(' Created:', new Date(ev.created_at * 1000).toISOString())
|
|
40
|
+
console.log(' Content:', ev.content)
|
|
41
|
+
})
|
|
42
|
+
} catch (err) {
|
|
43
|
+
console.error('Error reading posts:', err)
|
|
44
|
+
}
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Example script for writing a post to a relay.
|
|
3
|
+
Refactored to use REST API instead of WebSocket.
|
|
4
|
+
|
|
5
|
+
Run the server with `npm start` in the main directory, before running this example.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { finalizeEvent, getPublicKey } from 'nostr-tools/pure'
|
|
9
|
+
import { hexToBytes } from '@noble/hashes/utils.js'
|
|
10
|
+
|
|
11
|
+
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
|
12
|
+
|
|
13
|
+
// Alice is our user making the post.
|
|
14
|
+
const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0'
|
|
15
|
+
const alicePrivKeyBin = hexToBytes(alicePrivKeyHex)
|
|
16
|
+
const alicePubKey = getPublicKey(alicePrivKeyBin)
|
|
17
|
+
console.log(`Alice Public Key: ${alicePubKey}`)
|
|
18
|
+
|
|
19
|
+
const now = new Date()
|
|
20
|
+
|
|
21
|
+
// Generate a post.
|
|
22
|
+
const eventTemplate = {
|
|
23
|
+
kind: 1,
|
|
24
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
25
|
+
tags: [],
|
|
26
|
+
content: `This is a test message posted at ${now.toLocaleString()}`
|
|
27
|
+
}
|
|
28
|
+
console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`)
|
|
29
|
+
|
|
30
|
+
// Sign the post
|
|
31
|
+
const signedEvent = finalizeEvent(eventTemplate, alicePrivKeyBin)
|
|
32
|
+
console.log('signedEvent:', JSON.stringify(signedEvent, null, 2))
|
|
33
|
+
|
|
34
|
+
// Publish to REST API
|
|
35
|
+
try {
|
|
36
|
+
const response = await fetch(`${API_URL}/event`, {
|
|
37
|
+
method: 'POST',
|
|
38
|
+
headers: {
|
|
39
|
+
'Content-Type': 'application/json'
|
|
40
|
+
},
|
|
41
|
+
body: JSON.stringify(signedEvent)
|
|
42
|
+
})
|
|
43
|
+
|
|
44
|
+
const result = await response.json()
|
|
45
|
+
console.log('result:', result)
|
|
46
|
+
|
|
47
|
+
if (result.accepted) {
|
|
48
|
+
console.log('Post published successfully!')
|
|
49
|
+
console.log('Event ID:', result.eventId)
|
|
50
|
+
} else {
|
|
51
|
+
console.error('Failed to publish:', result.message)
|
|
52
|
+
}
|
|
53
|
+
} catch (err) {
|
|
54
|
+
console.error('Error publishing post:', err)
|
|
55
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Example script for reading posts from user Alice.
|
|
3
|
+
Refactored to use REST API instead of WebSocket.
|
|
4
|
+
|
|
5
|
+
Run the server with `npm start` in the main directory, before running this example.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getPublicKey } from 'nostr-tools/pure'
|
|
9
|
+
import { hexToBytes } from '@noble/hashes/utils.js'
|
|
10
|
+
|
|
11
|
+
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
|
12
|
+
|
|
13
|
+
// Alice is our user.
|
|
14
|
+
const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0'
|
|
15
|
+
const alicePrivKeyBin = hexToBytes(alicePrivKeyHex)
|
|
16
|
+
const alicePubKey = getPublicKey(alicePrivKeyBin)
|
|
17
|
+
console.log(`Alice Public Key: ${alicePubKey}`)
|
|
18
|
+
|
|
19
|
+
// Create subscription ID
|
|
20
|
+
const subId = 'read-alice-posts-' + Date.now()
|
|
21
|
+
|
|
22
|
+
// Create filters - read posts from Alice
|
|
23
|
+
const filters = {
|
|
24
|
+
limit: 2,
|
|
25
|
+
kinds: [1],
|
|
26
|
+
authors: [alicePubKey]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Query events using GET /req/:subId
|
|
31
|
+
const filtersJson = encodeURIComponent(JSON.stringify([filters]))
|
|
32
|
+
const url = `${API_URL}/req/${subId}?filters=${filtersJson}`
|
|
33
|
+
|
|
34
|
+
console.log(`Querying events from ${API_URL}`)
|
|
35
|
+
console.log('Filters:', JSON.stringify(filters, null, 2))
|
|
36
|
+
|
|
37
|
+
const response = await fetch(url)
|
|
38
|
+
const events = await response.json()
|
|
39
|
+
|
|
40
|
+
console.log(`\nReceived ${events.length} events from Alice:`)
|
|
41
|
+
events.forEach((ev, index) => {
|
|
42
|
+
console.log(`\nEvent ${index + 1}:`)
|
|
43
|
+
console.log(' ID:', ev.id)
|
|
44
|
+
console.log(' Created:', new Date(ev.created_at * 1000).toISOString())
|
|
45
|
+
console.log(' Content:', ev.content)
|
|
46
|
+
})
|
|
47
|
+
} catch (err) {
|
|
48
|
+
console.error('Error reading Alice posts:', err)
|
|
49
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Example script for getting a follow list (kind 3 events).
|
|
3
|
+
Refactored to use REST API instead of WebSocket.
|
|
4
|
+
|
|
5
|
+
Run the server with `npm start` in the main directory, before running this example.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { getPublicKey } from 'nostr-tools/pure'
|
|
9
|
+
import { hexToBytes } from '@noble/hashes/utils.js'
|
|
10
|
+
|
|
11
|
+
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
|
12
|
+
|
|
13
|
+
// Alice is our user to get the follow list.
|
|
14
|
+
const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0'
|
|
15
|
+
const alicePrivKeyBin = hexToBytes(alicePrivKeyHex)
|
|
16
|
+
const alicePubKey = getPublicKey(alicePrivKeyBin)
|
|
17
|
+
console.log(`Alice Public Key: ${alicePubKey}`)
|
|
18
|
+
|
|
19
|
+
// Create subscription ID
|
|
20
|
+
const subId = 'get-follow-list-' + Date.now()
|
|
21
|
+
|
|
22
|
+
// Create filters - get follow list (kind 3) from Alice
|
|
23
|
+
const filters = {
|
|
24
|
+
limit: 5,
|
|
25
|
+
kinds: [3],
|
|
26
|
+
authors: [alicePubKey]
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
try {
|
|
30
|
+
// Query events using GET /req/:subId
|
|
31
|
+
const filtersJson = encodeURIComponent(JSON.stringify([filters]))
|
|
32
|
+
const url = `${API_URL}/req/${subId}?filters=${filtersJson}`
|
|
33
|
+
|
|
34
|
+
console.log(`Querying follow list from ${API_URL}`)
|
|
35
|
+
console.log('Filters:', JSON.stringify(filters, null, 2))
|
|
36
|
+
|
|
37
|
+
const response = await fetch(url)
|
|
38
|
+
const events = await response.json()
|
|
39
|
+
|
|
40
|
+
if (events.length > 0) {
|
|
41
|
+
// Get the most recent follow list (kind 3 events are replaceable)
|
|
42
|
+
const followListEvent = events[0]
|
|
43
|
+
const aliceFollowList = followListEvent.tags.filter(tag => tag[0] === 'p')
|
|
44
|
+
console.log(`\nAlice Follow list (${aliceFollowList.length} followed users):`)
|
|
45
|
+
aliceFollowList.forEach((tag, index) => {
|
|
46
|
+
console.log(` ${index + 1}. ${tag[1]}${tag[3] ? ` (${tag[3]})` : ''}`)
|
|
47
|
+
})
|
|
48
|
+
} else {
|
|
49
|
+
console.log('\nNo follow list found for Alice')
|
|
50
|
+
}
|
|
51
|
+
} catch (err) {
|
|
52
|
+
console.error('Error getting follow list:', err)
|
|
53
|
+
}
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Example to update the follow list with a new list of people to follow.
|
|
3
|
+
Refactored to use REST API instead of WebSocket.
|
|
4
|
+
|
|
5
|
+
Run the server with `npm start` in the main directory, before running this example.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { finalizeEvent, getPublicKey } from 'nostr-tools/pure'
|
|
9
|
+
import { hexToBytes } from '@noble/hashes/utils.js'
|
|
10
|
+
|
|
11
|
+
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
|
12
|
+
|
|
13
|
+
// Alice wants to update her follow list
|
|
14
|
+
const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0'
|
|
15
|
+
const alicePrivKeyBin = hexToBytes(alicePrivKeyHex)
|
|
16
|
+
const alicePubKey = getPublicKey(alicePrivKeyBin)
|
|
17
|
+
console.log(`Alice Public Key: ${alicePubKey}`)
|
|
18
|
+
|
|
19
|
+
// Bob is the person to be added to the new follow list
|
|
20
|
+
const bobPrivKeyHex = 'd2e71a977bc3900d6b0f787421e3d1a666cd12ca625482b0d9eeffd23489c99f'
|
|
21
|
+
const bobPrivKeyBin = hexToBytes(bobPrivKeyHex)
|
|
22
|
+
const bobPubKey = getPublicKey(bobPrivKeyBin)
|
|
23
|
+
console.log(`Bob Public Key: ${bobPubKey}`)
|
|
24
|
+
|
|
25
|
+
const psf = 'wss://nostr-relay.psfoundation.info'
|
|
26
|
+
|
|
27
|
+
const followList = [
|
|
28
|
+
['p', bobPubKey, psf, 'bob']
|
|
29
|
+
]
|
|
30
|
+
|
|
31
|
+
// Generate a follow list event (kind 3)
|
|
32
|
+
const eventTemplate = {
|
|
33
|
+
kind: 3,
|
|
34
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
35
|
+
tags: followList,
|
|
36
|
+
content: ''
|
|
37
|
+
}
|
|
38
|
+
console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`)
|
|
39
|
+
|
|
40
|
+
// Sign the event
|
|
41
|
+
const signedEvent = finalizeEvent(eventTemplate, alicePrivKeyBin)
|
|
42
|
+
|
|
43
|
+
// Publish to REST API
|
|
44
|
+
try {
|
|
45
|
+
const response = await fetch(`${API_URL}/event`, {
|
|
46
|
+
method: 'POST',
|
|
47
|
+
headers: {
|
|
48
|
+
'Content-Type': 'application/json'
|
|
49
|
+
},
|
|
50
|
+
body: JSON.stringify(signedEvent)
|
|
51
|
+
})
|
|
52
|
+
|
|
53
|
+
const result = await response.json()
|
|
54
|
+
console.log('Publish result:', result)
|
|
55
|
+
|
|
56
|
+
if (result.accepted) {
|
|
57
|
+
console.log('Follow list updated successfully!')
|
|
58
|
+
} else {
|
|
59
|
+
console.error('Failed to update follow list:', result.message)
|
|
60
|
+
}
|
|
61
|
+
} catch (err) {
|
|
62
|
+
console.error('Error updating follow list:', err)
|
|
63
|
+
}
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/*
|
|
2
|
+
Example script for adding a reaction (like) to an event.
|
|
3
|
+
Refactored to use REST API instead of WebSocket.
|
|
4
|
+
https://github.com/nostr-protocol/nips/blob/master/25.md
|
|
5
|
+
|
|
6
|
+
Run the server with `npm start` in the main directory, before running this example.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import { hexToBytes } from '@noble/hashes/utils.js'
|
|
10
|
+
import { finalizeEvent, getPublicKey } from 'nostr-tools/pure'
|
|
11
|
+
|
|
12
|
+
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
|
13
|
+
|
|
14
|
+
const bobPrivKeyHex = 'd2e71a977bc3900d6b0f787421e3d1a666cd12ca625482b0d9eeffd23489c99f'
|
|
15
|
+
const bobPrivKeyBin = hexToBytes(bobPrivKeyHex)
|
|
16
|
+
const bobPubKey = getPublicKey(bobPrivKeyBin)
|
|
17
|
+
|
|
18
|
+
const psf = 'wss://nostr-relay.psfoundation.info'
|
|
19
|
+
|
|
20
|
+
const evIdToLike = 'd09b4c5da59be3cd2768aa53fa78b77bf4859084c94f3bf26d401f004a9c8167'
|
|
21
|
+
const evIdAuthorPubKey = '2c7e76c0f8dc1dca9d0197c7d19be580a8d074ccada6a2f6ebe056ae41092e92'
|
|
22
|
+
|
|
23
|
+
// Generate like event (kind 7)
|
|
24
|
+
const likeEventTemplate = {
|
|
25
|
+
kind: 7,
|
|
26
|
+
created_at: Math.floor(Date.now() / 1000),
|
|
27
|
+
pubkey: bobPubKey,
|
|
28
|
+
tags: [
|
|
29
|
+
['e', evIdToLike, psf], // "e" tag includes event id, relay reference
|
|
30
|
+
['p', evIdAuthorPubKey, psf] // "p" tag includes author pubkey, relay reference
|
|
31
|
+
],
|
|
32
|
+
content: '+'
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Sign the event
|
|
36
|
+
const signedEvent = finalizeEvent(likeEventTemplate, bobPrivKeyBin)
|
|
37
|
+
console.log('signedEvent:', JSON.stringify(signedEvent, null, 2))
|
|
38
|
+
|
|
39
|
+
// Publish to REST API
|
|
40
|
+
try {
|
|
41
|
+
const response = await fetch(`${API_URL}/event`, {
|
|
42
|
+
method: 'POST',
|
|
43
|
+
headers: {
|
|
44
|
+
'Content-Type': 'application/json'
|
|
45
|
+
},
|
|
46
|
+
body: JSON.stringify(signedEvent)
|
|
47
|
+
})
|
|
48
|
+
|
|
49
|
+
const result = await response.json()
|
|
50
|
+
console.log('result:', result)
|
|
51
|
+
|
|
52
|
+
if (result.accepted) {
|
|
53
|
+
console.log('Like published successfully!')
|
|
54
|
+
} else {
|
|
55
|
+
console.error('Failed to publish like:', result.message)
|
|
56
|
+
}
|
|
57
|
+
} catch (err) {
|
|
58
|
+
console.error('Error publishing like:', err)
|
|
59
|
+
}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
# REST2NOSTR Examples
|
|
2
|
+
|
|
3
|
+
This directory contains examples refactored from the `nostr-sandbox/` directory to use the REST API instead of WebSocket connections.
|
|
4
|
+
|
|
5
|
+
## Prerequisites
|
|
6
|
+
|
|
7
|
+
1. Install dependencies:
|
|
8
|
+
```bash
|
|
9
|
+
npm install nostr-tools @noble/hashes
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
2. Start the REST2NOSTR proxy server:
|
|
13
|
+
```bash
|
|
14
|
+
npm start
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
3. Set the API_URL environment variable if the server is not running on localhost:3000:
|
|
18
|
+
```bash
|
|
19
|
+
export API_URL=http://localhost:3000
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## Examples
|
|
23
|
+
|
|
24
|
+
### 01-create-account.js
|
|
25
|
+
Creates a new Nostr account keypair and publishes profile metadata (kind 0 event).
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
node examples/01-create-account.js
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
### 02-read-posts.js
|
|
32
|
+
Reads posts (kind 1 events) from a specific author using GET /req/:subId.
|
|
33
|
+
|
|
34
|
+
```bash
|
|
35
|
+
node examples/02-read-posts.js
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### 03-write-post.js
|
|
39
|
+
Publishes a text post (kind 1 event) using POST /event.
|
|
40
|
+
|
|
41
|
+
```bash
|
|
42
|
+
node examples/03-write-post.js
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
### 04-read-alice-posts.js
|
|
46
|
+
Reads posts from Alice's account using GET /req/:subId with author filter.
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
node examples/04-read-alice-posts.js
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
### 14-get-follow-list.js
|
|
53
|
+
Retrieves a user's follow list (kind 3 event) using GET /req/:subId.
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
node examples/05-get-follow-list.js
|
|
57
|
+
```
|
|
58
|
+
|
|
59
|
+
### 15-update-follow-list.js
|
|
60
|
+
Updates a user's follow list (kind 3 event) using POST /event.
|
|
61
|
+
|
|
62
|
+
```bash
|
|
63
|
+
node examples/06-update-follow-list.js
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
### 17-liking-event.js
|
|
67
|
+
Adds a reaction/like to an event (kind 7 event) using POST /event.
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
node examples/07-liking-event.js
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
## API Endpoints Used
|
|
74
|
+
|
|
75
|
+
- **POST /event**: Publish events to the relay
|
|
76
|
+
- **GET /req/:subId**: Stateless query for events (returns immediately)
|
|
77
|
+
|
|
78
|
+
## Differences from WebSocket Examples
|
|
79
|
+
|
|
80
|
+
1. **No WebSocket connections**: All communication is via HTTP REST API
|
|
81
|
+
2. **Stateless queries**: GET /req/:subId returns events immediately rather than streaming
|
|
82
|
+
3. **Event publishing**: POST /event returns immediately with acceptance status
|
|
83
|
+
4. **No subscription management**: For stateless queries, subscriptions are automatically closed after EOSE
|
|
84
|
+
|
|
85
|
+
## Notes
|
|
86
|
+
|
|
87
|
+
- These examples use the same private keys as the original sandbox examples for consistency
|
|
88
|
+
- The REST API handles WebSocket connections to relays internally
|
|
89
|
+
- For real-time streaming, use POST /req/:subId which supports Server-Sent Events (SSE)
|
|
90
|
+
|
package/index.js
ADDED
package/package.json
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "psf-bch-api",
|
|
3
|
+
"version": "1.1.0",
|
|
4
|
+
"main": "index.js",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"scripts": {
|
|
7
|
+
"start": "node bin/server.js",
|
|
8
|
+
"docs": "./node_modules/.bin/apidoc -i src/ -o docs",
|
|
9
|
+
"lint": "standard --env mocha --fix",
|
|
10
|
+
"test": "npm run lint && TEST=unit c8 mocha 'test/unit/**/*.js' --exit",
|
|
11
|
+
"test:integration": "mocha --timeout 25000 'test/integration/**/*.js' --exit",
|
|
12
|
+
"coverage": "c8 --reporter=html mocha 'test/unit/**/*.js' --exit"
|
|
13
|
+
},
|
|
14
|
+
"author": "Chris Troutner <chris.troutner@gmail.com>",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"description": "REST API proxy to Bitcoin Cash infrastructure",
|
|
17
|
+
"dependencies": {
|
|
18
|
+
"axios": "1.7.7",
|
|
19
|
+
"cors": "2.8.5",
|
|
20
|
+
"dotenv": "16.3.1",
|
|
21
|
+
"express": "5.1.0",
|
|
22
|
+
"winston": "3.11.0",
|
|
23
|
+
"winston-daily-rotate-file": "4.7.1"
|
|
24
|
+
},
|
|
25
|
+
"devDependencies": {
|
|
26
|
+
"apidoc": "1.2.0",
|
|
27
|
+
"c8": "10.1.3",
|
|
28
|
+
"chai": "6.2.0",
|
|
29
|
+
"mocha": "11.7.4",
|
|
30
|
+
"sinon": "21.0.0",
|
|
31
|
+
"standard": "17.1.2"
|
|
32
|
+
},
|
|
33
|
+
"apidoc": {
|
|
34
|
+
"title": "psf-bch-api",
|
|
35
|
+
"url": "http://localhost:3070"
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
# Create a Dockerized API server
|
|
2
|
+
#
|
|
3
|
+
|
|
4
|
+
#IMAGE BUILD COMMANDS
|
|
5
|
+
# ct-base-ubuntu = ubuntu 18.04 + nodejs v10 LTS
|
|
6
|
+
#FROM christroutner/ct-base-ubuntu
|
|
7
|
+
FROM ubuntu:22.04
|
|
8
|
+
MAINTAINER Chris Troutner <chris.troutner@gmail.com>
|
|
9
|
+
|
|
10
|
+
#Update the OS and install any OS packages needed.
|
|
11
|
+
RUN apt-get update
|
|
12
|
+
RUN apt-get install -y sudo git curl nano gnupg wget zip unzip python3
|
|
13
|
+
|
|
14
|
+
#Install Node and NPM
|
|
15
|
+
RUN curl -sL https://deb.nodesource.com/setup_20.x -o nodesource_setup.sh
|
|
16
|
+
RUN bash nodesource_setup.sh
|
|
17
|
+
RUN apt-get install -y nodejs build-essential
|
|
18
|
+
|
|
19
|
+
#Create the user 'safeuser' and add them to the sudo group.
|
|
20
|
+
RUN useradd -ms /bin/bash safeuser
|
|
21
|
+
RUN adduser safeuser sudo
|
|
22
|
+
|
|
23
|
+
#Set password to 'password' change value below if you want a different password
|
|
24
|
+
RUN echo safeuser:password | chpasswd
|
|
25
|
+
|
|
26
|
+
#Set the working directory to be the home directory
|
|
27
|
+
WORKDIR /home/safeuser
|
|
28
|
+
|
|
29
|
+
#Setup NPM for non-root global install
|
|
30
|
+
RUN mkdir /home/safeuser/.npm-global
|
|
31
|
+
RUN chown -R safeuser .npm-global
|
|
32
|
+
RUN echo "export PATH=~/.npm-global/bin:$PATH" >> /home/safeuser/.profile
|
|
33
|
+
RUN runuser -l safeuser -c "npm config set prefix '~/.npm-global'"
|
|
34
|
+
|
|
35
|
+
# Update to the latest version of npm.
|
|
36
|
+
#RUN npm install -g npm@8.3.0
|
|
37
|
+
|
|
38
|
+
# npm mirror to prevent direct dependency on npm.
|
|
39
|
+
#RUN npm set registry http://94.130.170.209:4873/
|
|
40
|
+
|
|
41
|
+
# Switch to user account.
|
|
42
|
+
#USER safeuser
|
|
43
|
+
# Prep 'sudo' commands.
|
|
44
|
+
#RUN echo 'abcd8765' | sudo -S pwd
|
|
45
|
+
|
|
46
|
+
#RUN npm install -g node-gyp
|
|
47
|
+
|
|
48
|
+
# Clone the rest.bitcoin.com repository
|
|
49
|
+
WORKDIR /home/safeuser
|
|
50
|
+
RUN git clone https://github.com/christroutner/REST2NOSTR
|
|
51
|
+
|
|
52
|
+
# Switch to the desired branch. `master` is usually stable,
|
|
53
|
+
# and `stage` has the most up-to-date changes.
|
|
54
|
+
WORKDIR /home/safeuser/REST2NOSTR
|
|
55
|
+
|
|
56
|
+
# For development: switch to unstable branch
|
|
57
|
+
#RUN git checkout pin-ipfs
|
|
58
|
+
|
|
59
|
+
# Install dependencies
|
|
60
|
+
RUN npm install
|
|
61
|
+
|
|
62
|
+
# Generate the API docs
|
|
63
|
+
RUN npm run docs
|
|
64
|
+
|
|
65
|
+
#VOLUME /home/safeuser/keys
|
|
66
|
+
|
|
67
|
+
# Make leveldb folders
|
|
68
|
+
#RUN mkdir leveldb
|
|
69
|
+
#WORKDIR /home/safeuser/psf-slp-indexer/leveldb
|
|
70
|
+
#RUN mkdir current
|
|
71
|
+
#RUN mkdir zips
|
|
72
|
+
#RUN mkdir backup
|
|
73
|
+
#WORKDIR /home/safeuser/psf-slp-indexer/leveldb/zips
|
|
74
|
+
#COPY restore-auto.sh restore-auto.sh
|
|
75
|
+
#WORKDIR /home/safeuser/psf-slp-indexer
|
|
76
|
+
|
|
77
|
+
# Expose the port the API will be served on.
|
|
78
|
+
#EXPOSE 5011
|
|
79
|
+
|
|
80
|
+
# Start the application.
|
|
81
|
+
#COPY start-production.sh start-production.sh
|
|
82
|
+
VOLUME start-rest2nostr.sh
|
|
83
|
+
CMD ["./start-rest2nostr.sh"]
|
|
84
|
+
|
|
85
|
+
#CMD ["npm", "start"]
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Start the service with the command 'docker-compose up -d'
|
|
2
|
+
|
|
3
|
+
services:
|
|
4
|
+
rest2nostr:
|
|
5
|
+
build: .
|
|
6
|
+
container_name: rest2nostr
|
|
7
|
+
logging:
|
|
8
|
+
driver: 'json-file'
|
|
9
|
+
options:
|
|
10
|
+
max-size: '10m'
|
|
11
|
+
max-file: '10'
|
|
12
|
+
#mem_limit: 500mb
|
|
13
|
+
#links:
|
|
14
|
+
# - mongo-slp-indexer
|
|
15
|
+
ports:
|
|
16
|
+
- '5942:5942' # <host port>:<container port>
|
|
17
|
+
volumes:
|
|
18
|
+
- ./start-rest2nostr.sh:/home/safeuser/REST2NOSTR/start-rest2nostr.sh
|
|
19
|
+
restart: always
|