mirror of
https://github.com/Permissionless-Software-Foundation/psf-bch-api.git
synced 2026-09-21 16:52:00 -07:00
first commit
This commit is contained in:
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
Example script creating a key-pair for a Nostr account and publishing profile metadata.
|
||||
Refactored to use REST API instead of WebSocket.
|
||||
|
||||
Run the server with `npm start` in the main directory, before running this example.
|
||||
*/
|
||||
|
||||
import { generateSecretKey, getPublicKey, finalizeEvent } from 'nostr-tools/pure'
|
||||
import * as nip19 from 'nostr-tools/nip19'
|
||||
import { bytesToHex } from '@noble/hashes/utils.js'
|
||||
|
||||
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
||||
|
||||
// Generate keys
|
||||
const sk = generateSecretKey() // `sk` is a Uint8Array
|
||||
const nsec = nip19.nsecEncode(sk)
|
||||
const skHex = bytesToHex(sk)
|
||||
|
||||
const pk = getPublicKey(sk) // `pk` is a hex string
|
||||
const npub = nip19.npubEncode(pk)
|
||||
|
||||
console.log('private key:', skHex)
|
||||
console.log('encoded private key:', nsec)
|
||||
console.log()
|
||||
console.log('public key:', pk)
|
||||
console.log('encoded public key:', npub)
|
||||
console.log()
|
||||
|
||||
// Create profile metadata event (kind 0)
|
||||
const profileMetadata = {
|
||||
name: 'Alice',
|
||||
about: 'Hello, I am Alice!',
|
||||
picture: 'https://example.com/alice.jpg'
|
||||
}
|
||||
|
||||
const eventTemplate = {
|
||||
kind: 0,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [],
|
||||
content: JSON.stringify(profileMetadata)
|
||||
}
|
||||
|
||||
// Sign the event
|
||||
const signedEvent = finalizeEvent(eventTemplate, sk)
|
||||
console.log('Signed event:', JSON.stringify(signedEvent, null, 2))
|
||||
|
||||
// Publish to REST API
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/event`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(signedEvent)
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
console.log('Publish result:', result)
|
||||
|
||||
if (result.accepted) {
|
||||
console.log('Profile metadata published successfully!')
|
||||
} else {
|
||||
console.error('Failed to publish:', result.message)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error publishing event:', err)
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
/*
|
||||
Example script for reading posts (kind 1 events) from a relay.
|
||||
Refactored to use REST API instead of WebSocket.
|
||||
|
||||
Run the server with `npm start` in the main directory, before running this example.
|
||||
*/
|
||||
|
||||
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
||||
|
||||
// JB55's public key
|
||||
const jb55 = '32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'
|
||||
|
||||
// Create subscription ID
|
||||
const subId = 'read-posts-' + Date.now()
|
||||
|
||||
// Create filters - read posts from JB55
|
||||
const filters = {
|
||||
limit: 2,
|
||||
kinds: [1],
|
||||
authors: [jb55]
|
||||
}
|
||||
|
||||
try {
|
||||
// Query events using GET /req/:subId
|
||||
const filtersJson = encodeURIComponent(JSON.stringify([filters]))
|
||||
const url = `${API_URL}/req/${subId}?filters=${filtersJson}`
|
||||
|
||||
console.log(`Querying events from ${API_URL}`)
|
||||
console.log('Filters:', JSON.stringify(filters, null, 2))
|
||||
|
||||
const response = await fetch(url)
|
||||
const events = await response.json()
|
||||
|
||||
console.log(`\nReceived ${events.length} events:`)
|
||||
events.forEach((ev, index) => {
|
||||
console.log(`\nEvent ${index + 1}:`)
|
||||
console.log(' ID:', ev.id)
|
||||
console.log(' Author:', ev.pubkey)
|
||||
console.log(' Created:', new Date(ev.created_at * 1000).toISOString())
|
||||
console.log(' Content:', ev.content)
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Error reading posts:', err)
|
||||
}
|
||||
@@ -0,0 +1,55 @@
|
||||
/*
|
||||
Example script for writing a post to a relay.
|
||||
Refactored to use REST API instead of WebSocket.
|
||||
|
||||
Run the server with `npm start` in the main directory, before running this example.
|
||||
*/
|
||||
|
||||
import { finalizeEvent, getPublicKey } from 'nostr-tools/pure'
|
||||
import { hexToBytes } from '@noble/hashes/utils.js'
|
||||
|
||||
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
||||
|
||||
// Alice is our user making the post.
|
||||
const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0'
|
||||
const alicePrivKeyBin = hexToBytes(alicePrivKeyHex)
|
||||
const alicePubKey = getPublicKey(alicePrivKeyBin)
|
||||
console.log(`Alice Public Key: ${alicePubKey}`)
|
||||
|
||||
const now = new Date()
|
||||
|
||||
// Generate a post.
|
||||
const eventTemplate = {
|
||||
kind: 1,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: [],
|
||||
content: `This is a test message posted at ${now.toLocaleString()}`
|
||||
}
|
||||
console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`)
|
||||
|
||||
// Sign the post
|
||||
const signedEvent = finalizeEvent(eventTemplate, alicePrivKeyBin)
|
||||
console.log('signedEvent:', JSON.stringify(signedEvent, null, 2))
|
||||
|
||||
// Publish to REST API
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/event`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(signedEvent)
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
console.log('result:', result)
|
||||
|
||||
if (result.accepted) {
|
||||
console.log('Post published successfully!')
|
||||
console.log('Event ID:', result.eventId)
|
||||
} else {
|
||||
console.error('Failed to publish:', result.message)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error publishing post:', err)
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
/*
|
||||
Example script for reading posts from user Alice.
|
||||
Refactored to use REST API instead of WebSocket.
|
||||
|
||||
Run the server with `npm start` in the main directory, before running this example.
|
||||
*/
|
||||
|
||||
import { getPublicKey } from 'nostr-tools/pure'
|
||||
import { hexToBytes } from '@noble/hashes/utils.js'
|
||||
|
||||
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
||||
|
||||
// Alice is our user.
|
||||
const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0'
|
||||
const alicePrivKeyBin = hexToBytes(alicePrivKeyHex)
|
||||
const alicePubKey = getPublicKey(alicePrivKeyBin)
|
||||
console.log(`Alice Public Key: ${alicePubKey}`)
|
||||
|
||||
// Create subscription ID
|
||||
const subId = 'read-alice-posts-' + Date.now()
|
||||
|
||||
// Create filters - read posts from Alice
|
||||
const filters = {
|
||||
limit: 2,
|
||||
kinds: [1],
|
||||
authors: [alicePubKey]
|
||||
}
|
||||
|
||||
try {
|
||||
// Query events using GET /req/:subId
|
||||
const filtersJson = encodeURIComponent(JSON.stringify([filters]))
|
||||
const url = `${API_URL}/req/${subId}?filters=${filtersJson}`
|
||||
|
||||
console.log(`Querying events from ${API_URL}`)
|
||||
console.log('Filters:', JSON.stringify(filters, null, 2))
|
||||
|
||||
const response = await fetch(url)
|
||||
const events = await response.json()
|
||||
|
||||
console.log(`\nReceived ${events.length} events from Alice:`)
|
||||
events.forEach((ev, index) => {
|
||||
console.log(`\nEvent ${index + 1}:`)
|
||||
console.log(' ID:', ev.id)
|
||||
console.log(' Created:', new Date(ev.created_at * 1000).toISOString())
|
||||
console.log(' Content:', ev.content)
|
||||
})
|
||||
} catch (err) {
|
||||
console.error('Error reading Alice posts:', err)
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
/*
|
||||
Example script for getting a follow list (kind 3 events).
|
||||
Refactored to use REST API instead of WebSocket.
|
||||
|
||||
Run the server with `npm start` in the main directory, before running this example.
|
||||
*/
|
||||
|
||||
import { getPublicKey } from 'nostr-tools/pure'
|
||||
import { hexToBytes } from '@noble/hashes/utils.js'
|
||||
|
||||
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
||||
|
||||
// Alice is our user to get the follow list.
|
||||
const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0'
|
||||
const alicePrivKeyBin = hexToBytes(alicePrivKeyHex)
|
||||
const alicePubKey = getPublicKey(alicePrivKeyBin)
|
||||
console.log(`Alice Public Key: ${alicePubKey}`)
|
||||
|
||||
// Create subscription ID
|
||||
const subId = 'get-follow-list-' + Date.now()
|
||||
|
||||
// Create filters - get follow list (kind 3) from Alice
|
||||
const filters = {
|
||||
limit: 5,
|
||||
kinds: [3],
|
||||
authors: [alicePubKey]
|
||||
}
|
||||
|
||||
try {
|
||||
// Query events using GET /req/:subId
|
||||
const filtersJson = encodeURIComponent(JSON.stringify([filters]))
|
||||
const url = `${API_URL}/req/${subId}?filters=${filtersJson}`
|
||||
|
||||
console.log(`Querying follow list from ${API_URL}`)
|
||||
console.log('Filters:', JSON.stringify(filters, null, 2))
|
||||
|
||||
const response = await fetch(url)
|
||||
const events = await response.json()
|
||||
|
||||
if (events.length > 0) {
|
||||
// Get the most recent follow list (kind 3 events are replaceable)
|
||||
const followListEvent = events[0]
|
||||
const aliceFollowList = followListEvent.tags.filter(tag => tag[0] === 'p')
|
||||
console.log(`\nAlice Follow list (${aliceFollowList.length} followed users):`)
|
||||
aliceFollowList.forEach((tag, index) => {
|
||||
console.log(` ${index + 1}. ${tag[1]}${tag[3] ? ` (${tag[3]})` : ''}`)
|
||||
})
|
||||
} else {
|
||||
console.log('\nNo follow list found for Alice')
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error getting follow list:', err)
|
||||
}
|
||||
@@ -0,0 +1,63 @@
|
||||
/*
|
||||
Example to update the follow list with a new list of people to follow.
|
||||
Refactored to use REST API instead of WebSocket.
|
||||
|
||||
Run the server with `npm start` in the main directory, before running this example.
|
||||
*/
|
||||
|
||||
import { finalizeEvent, getPublicKey } from 'nostr-tools/pure'
|
||||
import { hexToBytes } from '@noble/hashes/utils.js'
|
||||
|
||||
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
||||
|
||||
// Alice wants to update her follow list
|
||||
const alicePrivKeyHex = '3292a48aa331aeccce003d50d70fbd79617ba91860abbd2c78fa4a8301e36bc0'
|
||||
const alicePrivKeyBin = hexToBytes(alicePrivKeyHex)
|
||||
const alicePubKey = getPublicKey(alicePrivKeyBin)
|
||||
console.log(`Alice Public Key: ${alicePubKey}`)
|
||||
|
||||
// Bob is the person to be added to the new follow list
|
||||
const bobPrivKeyHex = 'd2e71a977bc3900d6b0f787421e3d1a666cd12ca625482b0d9eeffd23489c99f'
|
||||
const bobPrivKeyBin = hexToBytes(bobPrivKeyHex)
|
||||
const bobPubKey = getPublicKey(bobPrivKeyBin)
|
||||
console.log(`Bob Public Key: ${bobPubKey}`)
|
||||
|
||||
const psf = 'wss://nostr-relay.psfoundation.info'
|
||||
|
||||
const followList = [
|
||||
['p', bobPubKey, psf, 'bob']
|
||||
]
|
||||
|
||||
// Generate a follow list event (kind 3)
|
||||
const eventTemplate = {
|
||||
kind: 3,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
tags: followList,
|
||||
content: ''
|
||||
}
|
||||
console.log(`eventTemplate: ${JSON.stringify(eventTemplate, null, 2)}`)
|
||||
|
||||
// Sign the event
|
||||
const signedEvent = finalizeEvent(eventTemplate, alicePrivKeyBin)
|
||||
|
||||
// Publish to REST API
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/event`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(signedEvent)
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
console.log('Publish result:', result)
|
||||
|
||||
if (result.accepted) {
|
||||
console.log('Follow list updated successfully!')
|
||||
} else {
|
||||
console.error('Failed to update follow list:', result.message)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error updating follow list:', err)
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
/*
|
||||
Example script for adding a reaction (like) to an event.
|
||||
Refactored to use REST API instead of WebSocket.
|
||||
https://github.com/nostr-protocol/nips/blob/master/25.md
|
||||
|
||||
Run the server with `npm start` in the main directory, before running this example.
|
||||
*/
|
||||
|
||||
import { hexToBytes } from '@noble/hashes/utils.js'
|
||||
import { finalizeEvent, getPublicKey } from 'nostr-tools/pure'
|
||||
|
||||
const API_URL = process.env.API_URL || 'http://localhost:5942'
|
||||
|
||||
const bobPrivKeyHex = 'd2e71a977bc3900d6b0f787421e3d1a666cd12ca625482b0d9eeffd23489c99f'
|
||||
const bobPrivKeyBin = hexToBytes(bobPrivKeyHex)
|
||||
const bobPubKey = getPublicKey(bobPrivKeyBin)
|
||||
|
||||
const psf = 'wss://nostr-relay.psfoundation.info'
|
||||
|
||||
const evIdToLike = 'd09b4c5da59be3cd2768aa53fa78b77bf4859084c94f3bf26d401f004a9c8167'
|
||||
const evIdAuthorPubKey = '2c7e76c0f8dc1dca9d0197c7d19be580a8d074ccada6a2f6ebe056ae41092e92'
|
||||
|
||||
// Generate like event (kind 7)
|
||||
const likeEventTemplate = {
|
||||
kind: 7,
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
pubkey: bobPubKey,
|
||||
tags: [
|
||||
['e', evIdToLike, psf], // "e" tag includes event id, relay reference
|
||||
['p', evIdAuthorPubKey, psf] // "p" tag includes author pubkey, relay reference
|
||||
],
|
||||
content: '+'
|
||||
}
|
||||
|
||||
// Sign the event
|
||||
const signedEvent = finalizeEvent(likeEventTemplate, bobPrivKeyBin)
|
||||
console.log('signedEvent:', JSON.stringify(signedEvent, null, 2))
|
||||
|
||||
// Publish to REST API
|
||||
try {
|
||||
const response = await fetch(`${API_URL}/event`, {
|
||||
method: 'POST',
|
||||
headers: {
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: JSON.stringify(signedEvent)
|
||||
})
|
||||
|
||||
const result = await response.json()
|
||||
console.log('result:', result)
|
||||
|
||||
if (result.accepted) {
|
||||
console.log('Like published successfully!')
|
||||
} else {
|
||||
console.error('Failed to publish like:', result.message)
|
||||
}
|
||||
} catch (err) {
|
||||
console.error('Error publishing like:', err)
|
||||
}
|
||||
@@ -0,0 +1,90 @@
|
||||
# REST2NOSTR Examples
|
||||
|
||||
This directory contains examples refactored from the `nostr-sandbox/` directory to use the REST API instead of WebSocket connections.
|
||||
|
||||
## Prerequisites
|
||||
|
||||
1. Install dependencies:
|
||||
```bash
|
||||
npm install nostr-tools @noble/hashes
|
||||
```
|
||||
|
||||
2. Start the REST2NOSTR proxy server:
|
||||
```bash
|
||||
npm start
|
||||
```
|
||||
|
||||
3. Set the API_URL environment variable if the server is not running on localhost:3000:
|
||||
```bash
|
||||
export API_URL=http://localhost:3000
|
||||
```
|
||||
|
||||
## Examples
|
||||
|
||||
### 01-create-account.js
|
||||
Creates a new Nostr account keypair and publishes profile metadata (kind 0 event).
|
||||
|
||||
```bash
|
||||
node examples/01-create-account.js
|
||||
```
|
||||
|
||||
### 02-read-posts.js
|
||||
Reads posts (kind 1 events) from a specific author using GET /req/:subId.
|
||||
|
||||
```bash
|
||||
node examples/02-read-posts.js
|
||||
```
|
||||
|
||||
### 03-write-post.js
|
||||
Publishes a text post (kind 1 event) using POST /event.
|
||||
|
||||
```bash
|
||||
node examples/03-write-post.js
|
||||
```
|
||||
|
||||
### 04-read-alice-posts.js
|
||||
Reads posts from Alice's account using GET /req/:subId with author filter.
|
||||
|
||||
```bash
|
||||
node examples/04-read-alice-posts.js
|
||||
```
|
||||
|
||||
### 14-get-follow-list.js
|
||||
Retrieves a user's follow list (kind 3 event) using GET /req/:subId.
|
||||
|
||||
```bash
|
||||
node examples/05-get-follow-list.js
|
||||
```
|
||||
|
||||
### 15-update-follow-list.js
|
||||
Updates a user's follow list (kind 3 event) using POST /event.
|
||||
|
||||
```bash
|
||||
node examples/06-update-follow-list.js
|
||||
```
|
||||
|
||||
### 17-liking-event.js
|
||||
Adds a reaction/like to an event (kind 7 event) using POST /event.
|
||||
|
||||
```bash
|
||||
node examples/07-liking-event.js
|
||||
```
|
||||
|
||||
## API Endpoints Used
|
||||
|
||||
- **POST /event**: Publish events to the relay
|
||||
- **GET /req/:subId**: Stateless query for events (returns immediately)
|
||||
|
||||
## Differences from WebSocket Examples
|
||||
|
||||
1. **No WebSocket connections**: All communication is via HTTP REST API
|
||||
2. **Stateless queries**: GET /req/:subId returns events immediately rather than streaming
|
||||
3. **Event publishing**: POST /event returns immediately with acceptance status
|
||||
4. **No subscription management**: For stateless queries, subscriptions are automatically closed after EOSE
|
||||
|
||||
## Notes
|
||||
|
||||
- These examples use the same private keys as the original sandbox examples for consistency
|
||||
- The REST API handles WebSocket connections to relays internally
|
||||
- For real-time streaming, use POST /req/:subId which supports Server-Sent Events (SSE)
|
||||
|
||||
Reference in New Issue
Block a user