mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-09 16:48:50 +00:00
Compare commits
7 Commits
v1.0.0-alp
...
v1.0.0-bet
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
364c37cac5 | ||
|
|
385cdb4ac6 | ||
|
|
3f1025f551 | ||
|
|
482c5affd4 | ||
|
|
679ac0c133 | ||
|
|
b96159ad36 | ||
|
|
6dede4a688 |
17
.github/workflows/npm-publish.yml
vendored
17
.github/workflows/npm-publish.yml
vendored
@@ -1,8 +1,8 @@
|
|||||||
name: publish npm package
|
name: publish npm package
|
||||||
|
|
||||||
on:
|
on:
|
||||||
release:
|
push:
|
||||||
types: [created]
|
tags: [v*]
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
publish-npm:
|
publish-npm:
|
||||||
@@ -11,8 +11,11 @@ jobs:
|
|||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v3
|
||||||
- uses: actions/setup-node@v3
|
- uses: actions/setup-node@v3
|
||||||
with:
|
with:
|
||||||
node-version: 16
|
node-version: 18
|
||||||
registry-url: https://registry.npmjs.org/
|
- run: yarn --ignore-engines
|
||||||
- run: npm publish
|
- run: node build.js
|
||||||
env:
|
- run: yarn test
|
||||||
NODE_AUTH_TOKEN: ${{secrets.npm_token}}
|
- uses: JS-DevTools/npm-publish@v1
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.NPM_TOKEN }}
|
||||||
|
greater-version-only: true
|
||||||
|
|||||||
6
.github/workflows/test.yml
vendored
6
.github/workflows/test.yml
vendored
@@ -1,6 +1,8 @@
|
|||||||
name: test every commit
|
name: test every commit
|
||||||
on:
|
on:
|
||||||
- push
|
push:
|
||||||
|
branches:
|
||||||
|
- master
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
test:
|
test:
|
||||||
@@ -11,5 +13,5 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
node-version: 18
|
node-version: 18
|
||||||
- run: yarn --ignore-engines
|
- run: yarn --ignore-engines
|
||||||
- run: node build.cjs
|
- run: node build.js
|
||||||
- run: yarn test
|
- run: yarn test
|
||||||
|
|||||||
4
.gitignore
vendored
4
.gitignore
vendored
@@ -3,7 +3,5 @@ dist
|
|||||||
yarn.lock
|
yarn.lock
|
||||||
package-lock.json
|
package-lock.json
|
||||||
.envrc
|
.envrc
|
||||||
standalone
|
lib
|
||||||
cjs
|
|
||||||
esm
|
|
||||||
test.html
|
test.html
|
||||||
|
|||||||
46
README.md
46
README.md
@@ -112,6 +112,50 @@ pub.on('failed', reason => {
|
|||||||
await relay.close()
|
await relay.close()
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Querying profile data from a NIP-05 address
|
||||||
|
|
||||||
|
```js
|
||||||
|
import {nip05} from 'nostr-tools'
|
||||||
|
|
||||||
|
let profile = await nip05.queryProfile('jb55.com')
|
||||||
|
console.log(profile.pubkey)
|
||||||
|
// prints: 32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245
|
||||||
|
console.log(profile.relays)
|
||||||
|
// prints: [wss://relay.damus.io]
|
||||||
|
|
||||||
|
// on nodejs, install node-fetch@2 and call this first:
|
||||||
|
nip05.useFetchImplementation(require('node-fetch'))
|
||||||
|
```
|
||||||
|
|
||||||
|
### Encoding and decoding NIP-19 codes
|
||||||
|
|
||||||
|
```js
|
||||||
|
import {nip19, generatePrivateKey, getPublicKey} from 'nostr-tools'
|
||||||
|
|
||||||
|
let sk = generatePrivateKey()
|
||||||
|
let nsec = nip19.nsecEncode(sk)
|
||||||
|
let {type, data} = nip19.decode(nsec)
|
||||||
|
assert(type === 'nsec')
|
||||||
|
assert(data === sk)
|
||||||
|
|
||||||
|
let pk = getPublicKey(generatePrivateKey())
|
||||||
|
let npub = nip19.npubEncode(pk)
|
||||||
|
let {type, data} = nip19.decode(npub)
|
||||||
|
assert(type === 'npub')
|
||||||
|
assert(data === pk)
|
||||||
|
|
||||||
|
let pk = getPublicKey(generatePrivateKey())
|
||||||
|
let relays = [
|
||||||
|
'wss://relay.nostr.example.mydomain.example.com',
|
||||||
|
'wss://nostr.banana.com'
|
||||||
|
]
|
||||||
|
let nprofile = nip19.nprofileEncode({pubkey: pk, relays})
|
||||||
|
let {type, data} = nip19.decode(nprofile)
|
||||||
|
assert(type === 'nprofile')
|
||||||
|
assert(data.pubkey === pk)
|
||||||
|
assert(data.relays.length === 2)
|
||||||
|
```
|
||||||
|
|
||||||
### Encrypting and decrypting direct messages
|
### Encrypting and decrypting direct messages
|
||||||
|
|
||||||
```js
|
```js
|
||||||
@@ -153,7 +197,7 @@ Please consult the tests or [the source code](https://github.com/fiatjaf/nostr-t
|
|||||||
### Using from the browser (if you don't want to use a bundler)
|
### Using from the browser (if you don't want to use a bundler)
|
||||||
|
|
||||||
```html
|
```html
|
||||||
<script src="https://unpkg.com/nostr-tools/standalone/index.js"></script>
|
<script src="https://unpkg.com/nostr-tools/lib/nostr.bundle.js"></script>
|
||||||
<script>
|
<script>
|
||||||
window.NostrTools.generatePrivateKey('...') // and so on
|
window.NostrTools.generatePrivateKey('...') // and so on
|
||||||
</script>
|
</script>
|
||||||
|
|||||||
@@ -15,17 +15,27 @@ let common = {
|
|||||||
}
|
}
|
||||||
|
|
||||||
esbuild
|
esbuild
|
||||||
.build({...common, outdir: 'esm/', format: 'esm', packages: 'external'})
|
.build({
|
||||||
|
...common,
|
||||||
|
outfile: 'lib/nostr.esm.js',
|
||||||
|
format: 'esm',
|
||||||
|
packages: 'external'
|
||||||
|
})
|
||||||
.then(() => console.log('esm build success.'))
|
.then(() => console.log('esm build success.'))
|
||||||
|
|
||||||
esbuild
|
esbuild
|
||||||
.build({...common, outdir: 'cjs/', format: 'cjs', packages: 'external'})
|
.build({
|
||||||
|
...common,
|
||||||
|
outfile: 'lib/nostr.cjs.js',
|
||||||
|
format: 'cjs',
|
||||||
|
packages: 'external'
|
||||||
|
})
|
||||||
.then(() => console.log('cjs build success.'))
|
.then(() => console.log('cjs build success.'))
|
||||||
|
|
||||||
esbuild
|
esbuild
|
||||||
.build({
|
.build({
|
||||||
...common,
|
...common,
|
||||||
outdir: 'standalone/',
|
outfile: 'lib/nostr.bundle.js',
|
||||||
format: 'iife',
|
format: 'iife',
|
||||||
globalName: 'NostrTools',
|
globalName: 'NostrTools',
|
||||||
define: {
|
define: {
|
||||||
@@ -6,7 +6,7 @@ const {
|
|||||||
signEvent,
|
signEvent,
|
||||||
getEventHash,
|
getEventHash,
|
||||||
getPublicKey
|
getPublicKey
|
||||||
} = require('./cjs')
|
} = require('./lib/nostr.cjs')
|
||||||
|
|
||||||
const event = {
|
const event = {
|
||||||
id: 'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027',
|
id: 'd7dd5eb3ab747e16f8d0212d53032ea2a7cadef53837e5a6c66d42849fcb9027',
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-env jest */
|
/* eslint-env jest */
|
||||||
|
|
||||||
const {matchFilters} = require('./cjs')
|
const {matchFilters} = require('./lib/nostr.cjs')
|
||||||
|
|
||||||
test('test if filters match', () => {
|
test('test if filters match', () => {
|
||||||
;[
|
;[
|
||||||
|
|||||||
1
index.ts
1
index.ts
@@ -6,3 +6,4 @@ export * from './filter'
|
|||||||
export * as nip04 from './nip04'
|
export * as nip04 from './nip04'
|
||||||
export * as nip05 from './nip05'
|
export * as nip05 from './nip05'
|
||||||
export * as nip06 from './nip06'
|
export * as nip06 from './nip06'
|
||||||
|
export * as nip19 from './nip19'
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-env jest */
|
/* eslint-env jest */
|
||||||
|
|
||||||
const {generatePrivateKey, getPublicKey} = require('./cjs')
|
const {generatePrivateKey, getPublicKey} = require('./lib/nostr.cjs')
|
||||||
|
|
||||||
test('test private key generation', () => {
|
test('test private key generation', () => {
|
||||||
expect(generatePrivateKey()).toMatch(/[a-f0-9]{64}/)
|
expect(generatePrivateKey()).toMatch(/[a-f0-9]{64}/)
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
/* eslint-env jest */
|
/* eslint-env jest */
|
||||||
|
|
||||||
const {nip04, getPublicKey, generatePrivateKey} = require('./cjs')
|
const {nip04, getPublicKey, generatePrivateKey} = require('./lib/nostr.cjs')
|
||||||
|
|
||||||
test('encrypt and decrypt message', () => {
|
test('encrypt and decrypt message', () => {
|
||||||
let sk1 = generatePrivateKey()
|
let sk1 = generatePrivateKey()
|
||||||
|
|||||||
20
nip05.test.js
Normal file
20
nip05.test.js
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
/* eslint-env jest */
|
||||||
|
|
||||||
|
const fetch = require('node-fetch')
|
||||||
|
const {nip05} = require('./lib/nostr.cjs')
|
||||||
|
|
||||||
|
test('fetch nip05 profiles', async () => {
|
||||||
|
nip05.useFetchImplementation(fetch)
|
||||||
|
|
||||||
|
let p1 = await nip05.queryProfile('jb55.com')
|
||||||
|
expect(p1.pubkey).toEqual(
|
||||||
|
'32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'
|
||||||
|
)
|
||||||
|
expect(p1.relays).toEqual(['wss://relay.damus.io'])
|
||||||
|
|
||||||
|
let p2 = await nip05.queryProfile('jb55@jb55.com')
|
||||||
|
expect(p2.pubkey).toEqual(
|
||||||
|
'32e1827635450ebb3c5a7d12c1f8e7b2b514439ac10a67eef3d9fd9c5c68e245'
|
||||||
|
)
|
||||||
|
expect(p2.relays).toEqual(['wss://relay.damus.io'])
|
||||||
|
})
|
||||||
23
nip05.ts
23
nip05.ts
@@ -1,3 +1,5 @@
|
|||||||
|
import {ProfilePointer} from './nip19'
|
||||||
|
|
||||||
var _fetch = fetch
|
var _fetch = fetch
|
||||||
|
|
||||||
export function useFetchImplementation(fetchImplementation: any) {
|
export function useFetchImplementation(fetchImplementation: any) {
|
||||||
@@ -19,13 +21,28 @@ export async function searchDomain(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function queryName(fullname: string): Promise<string> {
|
export async function queryProfile(
|
||||||
|
fullname: string
|
||||||
|
): Promise<ProfilePointer | null> {
|
||||||
let [name, domain] = fullname.split('@')
|
let [name, domain] = fullname.split('@')
|
||||||
if (!domain) throw new Error('invalid identifier, must contain an @')
|
|
||||||
|
if (!domain) {
|
||||||
|
// if there is no @, it is because it is just a domain, so assume the name is "_"
|
||||||
|
domain = name
|
||||||
|
name = '_'
|
||||||
|
}
|
||||||
|
|
||||||
let res = await (
|
let res = await (
|
||||||
await _fetch(`https://${domain}/.well-known/nostr.json?name=${name}`)
|
await _fetch(`https://${domain}/.well-known/nostr.json?name=${name}`)
|
||||||
).json()
|
).json()
|
||||||
|
|
||||||
return res.names && res.names[name]
|
if (!res?.names?.[name]) return null
|
||||||
|
|
||||||
|
let pubkey = res.names[name] as string
|
||||||
|
let relays = (res.relays?.[pubkey] || []) as string[] // nip35
|
||||||
|
|
||||||
|
return {
|
||||||
|
pubkey,
|
||||||
|
relays
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
36
nip19.test.js
Normal file
36
nip19.test.js
Normal file
@@ -0,0 +1,36 @@
|
|||||||
|
/* eslint-env jest */
|
||||||
|
|
||||||
|
const {nip19, generatePrivateKey, getPublicKey} = require('./lib/nostr.cjs')
|
||||||
|
|
||||||
|
test('encode and decode nsec', () => {
|
||||||
|
let sk = generatePrivateKey()
|
||||||
|
let nsec = nip19.nsecEncode(sk)
|
||||||
|
expect(nsec).toMatch(/nsec1\w+/)
|
||||||
|
let {type, data} = nip19.decode(nsec)
|
||||||
|
expect(type).toEqual('nsec')
|
||||||
|
expect(data).toEqual(sk)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('encode and decode npub', () => {
|
||||||
|
let pk = getPublicKey(generatePrivateKey())
|
||||||
|
let npub = nip19.npubEncode(pk)
|
||||||
|
expect(npub).toMatch(/npub1\w+/)
|
||||||
|
let {type, data} = nip19.decode(npub)
|
||||||
|
expect(type).toEqual('npub')
|
||||||
|
expect(data).toEqual(pk)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('encode and decode nprofile', () => {
|
||||||
|
let pk = getPublicKey(generatePrivateKey())
|
||||||
|
let relays = [
|
||||||
|
'wss://relay.nostr.example.mydomain.example.com',
|
||||||
|
'wss://nostr.banana.com'
|
||||||
|
]
|
||||||
|
let nprofile = nip19.nprofileEncode({pubkey: pk, relays})
|
||||||
|
expect(nprofile).toMatch(/nprofile1\w+/)
|
||||||
|
let {type, data} = nip19.decode(nprofile)
|
||||||
|
expect(type).toEqual('nprofile')
|
||||||
|
expect(data.pubkey).toEqual(pk)
|
||||||
|
expect(data.relays).toContain(relays[0])
|
||||||
|
expect(data.relays).toContain(relays[1])
|
||||||
|
})
|
||||||
126
nip19.ts
Normal file
126
nip19.ts
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
import * as secp256k1 from '@noble/secp256k1'
|
||||||
|
import {bech32} from 'bech32'
|
||||||
|
|
||||||
|
export type ProfilePointer = {
|
||||||
|
pubkey: string // hex
|
||||||
|
relays?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export type EventPointer = {
|
||||||
|
id: string // hex
|
||||||
|
relays?: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
let utf8Decoder = new TextDecoder('utf-8')
|
||||||
|
let utf8Encoder = new TextEncoder()
|
||||||
|
|
||||||
|
export function decode(nip19: string): {
|
||||||
|
type: string
|
||||||
|
data: ProfilePointer | EventPointer | string
|
||||||
|
} {
|
||||||
|
let {prefix, words} = bech32.decode(nip19, 1000)
|
||||||
|
let data = new Uint8Array(bech32.fromWords(words))
|
||||||
|
|
||||||
|
if (prefix === 'nprofile') {
|
||||||
|
let tlv = parseTLV(data)
|
||||||
|
if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for nprofile')
|
||||||
|
if (tlv[0][0].length !== 32) throw new Error('TLV 0 should be 32 bytes')
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'nprofile',
|
||||||
|
data: {
|
||||||
|
pubkey: secp256k1.utils.bytesToHex(tlv[0][0]),
|
||||||
|
relays: tlv[1].map(d => utf8Decoder.decode(d))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix === 'nevent') {
|
||||||
|
let tlv = parseTLV(data)
|
||||||
|
if (!tlv[0]?.[0]) throw new Error('missing TLV 0 for nevent')
|
||||||
|
if (tlv[0][0].length !== 32) throw new Error('TLV 0 should be 32 bytes')
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'nevent',
|
||||||
|
data: {
|
||||||
|
id: secp256k1.utils.bytesToHex(tlv[0][0]),
|
||||||
|
relays: tlv[1].map(d => utf8Decoder.decode(d))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (prefix === 'nsec' || prefix === 'npub' || prefix === 'note') {
|
||||||
|
return {type: prefix, data: secp256k1.utils.bytesToHex(data)}
|
||||||
|
}
|
||||||
|
|
||||||
|
throw new Error(`unknown prefix ${prefix}`)
|
||||||
|
}
|
||||||
|
|
||||||
|
type TLV = {[t: number]: Uint8Array[]}
|
||||||
|
|
||||||
|
function parseTLV(data: Uint8Array): TLV {
|
||||||
|
let result: TLV = {}
|
||||||
|
let rest = data
|
||||||
|
while (rest.length > 0) {
|
||||||
|
let t = rest[0]
|
||||||
|
let l = rest[1]
|
||||||
|
let v = rest.slice(2, 2 + l)
|
||||||
|
rest = rest.slice(2 + l)
|
||||||
|
if (v.length < l) continue
|
||||||
|
result[t] = result[t] || []
|
||||||
|
result[t].push(v)
|
||||||
|
}
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nsecEncode(hex: string): string {
|
||||||
|
return encodeBytes('nsec', hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function npubEncode(hex: string): string {
|
||||||
|
return encodeBytes('npub', hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function noteEncode(hex: string): string {
|
||||||
|
return encodeBytes('note', hex)
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeBytes(prefix: string, hex: string): string {
|
||||||
|
let data = secp256k1.utils.hexToBytes(hex)
|
||||||
|
let words = bech32.toWords(data)
|
||||||
|
return bech32.encode(prefix, words, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function nprofileEncode(profile: ProfilePointer): string {
|
||||||
|
let data = encodeTLV({
|
||||||
|
0: [secp256k1.utils.hexToBytes(profile.pubkey)],
|
||||||
|
1: (profile.relays || []).map(url => utf8Encoder.encode(url))
|
||||||
|
})
|
||||||
|
let words = bech32.toWords(data)
|
||||||
|
return bech32.encode('nprofile', words, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function neventEncode(event: EventPointer): string {
|
||||||
|
let data = encodeTLV({
|
||||||
|
0: [secp256k1.utils.hexToBytes(event.id)],
|
||||||
|
1: (event.relays || []).map(url => utf8Encoder.encode(url))
|
||||||
|
})
|
||||||
|
let words = bech32.toWords(data)
|
||||||
|
return bech32.encode('nevent', words, 1000)
|
||||||
|
}
|
||||||
|
|
||||||
|
function encodeTLV(tlv: TLV): Uint8Array {
|
||||||
|
let entries: Uint8Array[] = []
|
||||||
|
|
||||||
|
Object.entries(tlv).forEach(([t, vs]) => {
|
||||||
|
vs.forEach(v => {
|
||||||
|
let entry = new Uint8Array(v.length + 2)
|
||||||
|
entry.set([parseInt(t)], 0)
|
||||||
|
entry.set([v.length], 1)
|
||||||
|
entry.set(v, 2)
|
||||||
|
entries.push(entry)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return secp256k1.utils.concatBytes(...entries)
|
||||||
|
}
|
||||||
12
package.json
12
package.json
@@ -1,18 +1,19 @@
|
|||||||
{
|
{
|
||||||
"name": "nostr-tools",
|
"name": "nostr-tools",
|
||||||
"version": "1.0.0-alpha",
|
"version": "1.0.0-beta",
|
||||||
"description": "Tools for making a Nostr client.",
|
"description": "Tools for making a Nostr client.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "https://github.com/fiatjaf/nostr-tools.git"
|
"url": "https://github.com/fiatjaf/nostr-tools.git"
|
||||||
},
|
},
|
||||||
"main": "cjs/index.js",
|
"main": "lib/nostr.cjs.js",
|
||||||
"module": "esm/index.js",
|
"module": "lib/nostr.esm.js",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@noble/hashes": "^0.5.7",
|
"@noble/hashes": "^0.5.7",
|
||||||
"@noble/secp256k1": "^1.7.0",
|
"@noble/secp256k1": "^1.7.0",
|
||||||
"@scure/bip32": "^1.1.1",
|
"@scure/bip32": "^1.1.1",
|
||||||
"@scure/bip39": "^1.1.0",
|
"@scure/bip39": "^1.1.0",
|
||||||
|
"bech32": "^2.0.0",
|
||||||
"browserify-cipher": ">=1",
|
"browserify-cipher": ">=1",
|
||||||
"buffer": "^6.0.3",
|
"buffer": "^6.0.3",
|
||||||
"websocket-polyfill": "^0.0.3"
|
"websocket-polyfill": "^0.0.3"
|
||||||
@@ -35,13 +36,14 @@
|
|||||||
"esm-loader-typescript": "^1.0.1",
|
"esm-loader-typescript": "^1.0.1",
|
||||||
"events": "^3.3.0",
|
"events": "^3.3.0",
|
||||||
"jest": "^29.3.1",
|
"jest": "^29.3.1",
|
||||||
|
"node-fetch": "2",
|
||||||
"ts-jest": "^29.0.3",
|
"ts-jest": "^29.0.3",
|
||||||
"tsd": "^0.22.0",
|
"tsd": "^0.22.0",
|
||||||
"typescript": "^4.9.4"
|
"typescript": "^4.9.4"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "node build.cjs",
|
"build": "node build.js",
|
||||||
"pretest": "node build.cjs",
|
"pretest": "node build.js",
|
||||||
"test": "jest"
|
"test": "jest"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -6,10 +6,10 @@ const {
|
|||||||
getPublicKey,
|
getPublicKey,
|
||||||
getEventHash,
|
getEventHash,
|
||||||
signEvent
|
signEvent
|
||||||
} = require('./cjs')
|
} = require('./lib/nostr.cjs')
|
||||||
|
|
||||||
describe('relay interaction', () => {
|
describe('relay interaction', () => {
|
||||||
let relay = relayInit('wss://nostr-pub.wellorder.net/')
|
let relay = relayInit('wss://nostr-pub.semisol.dev/')
|
||||||
|
|
||||||
beforeAll(() => {
|
beforeAll(() => {
|
||||||
relay.connect()
|
relay.connect()
|
||||||
|
|||||||
Reference in New Issue
Block a user