mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-08 16:28:49 +00:00
Compare commits
26 Commits
alexgleaso
...
v2.4.0
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c7e9c8f36 | ||
|
|
1d7620a057 | ||
|
|
e5cda3509c | ||
|
|
02da1dc036 | ||
|
|
7aed747bb2 | ||
|
|
747a7944d7 | ||
|
|
9f8b7274b3 | ||
|
|
ee565db7f5 | ||
|
|
e9ee8258e7 | ||
|
|
ad07d260ab | ||
|
|
632184afb8 | ||
|
|
d7d5d30f41 | ||
|
|
387ce2c335 | ||
|
|
b62b8f88af | ||
|
|
6b43533f2e | ||
|
|
e30e08d8e2 | ||
|
|
59426d9f35 | ||
|
|
5429142858 | ||
|
|
564c9bca17 | ||
|
|
0190ae94a7 | ||
|
|
e1bde08ff3 | ||
|
|
71456feb20 | ||
|
|
ca928c697b | ||
|
|
7b98cae7fa | ||
|
|
db53f37161 | ||
|
|
1691f0b51d |
18
.github/workflows/publish.yml
vendored
18
.github/workflows/publish.yml
vendored
@@ -1,18 +0,0 @@
|
||||
name: Publish
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: read
|
||||
id-token: write # The OIDC ID token is used for authentication with JSR.
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: oven-sh/setup-bun@v1
|
||||
- run: bun i
|
||||
- run: npx jsr publish
|
||||
@@ -25,6 +25,15 @@ let sk = generateSecretKey() // `sk` is a Uint8Array
|
||||
let pk = getPublicKey(sk) // `pk` is a hex string
|
||||
```
|
||||
|
||||
To get the secret key in hex format, use
|
||||
|
||||
```js
|
||||
import { bytestohex, hexToBytes } from '@noble/hashes/utils' // already an installed dependency
|
||||
|
||||
let skHex = bytestohex(sk)
|
||||
let backToBytes = hexToBytes(skHex)
|
||||
```
|
||||
|
||||
### Creating, signing and verifying events
|
||||
|
||||
```js
|
||||
|
||||
@@ -24,6 +24,9 @@ export class AbstractRelay {
|
||||
public onclose: (() => void) | null = null
|
||||
public onnotice: (msg: string) => void = msg => console.debug(`NOTICE from ${this.url}: ${msg}`)
|
||||
|
||||
// this is exposed just to help in ndk migration, shouldn't be relied upon
|
||||
public _onauth: ((challenge: string) => void) | null = null
|
||||
|
||||
public baseEoseTimeout: number = 4400
|
||||
public connectionTimeout: number = 4400
|
||||
public openSubs: Map<string, Subscription> = new Map()
|
||||
@@ -99,17 +102,20 @@ export class AbstractRelay {
|
||||
this.ws.onerror = ev => {
|
||||
reject((ev as any).message)
|
||||
if (this._connected) {
|
||||
this._connected = false
|
||||
this.connectionPromise = undefined
|
||||
this.onclose?.()
|
||||
this.closeAllSubscriptions('relay connection errored')
|
||||
this._connected = false
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.onclose = async () => {
|
||||
this.connectionPromise = undefined
|
||||
this.onclose?.()
|
||||
this.closeAllSubscriptions('relay connection closed')
|
||||
this._connected = false
|
||||
if (this._connected) {
|
||||
this._connected = false
|
||||
this.connectionPromise = undefined
|
||||
this.onclose?.()
|
||||
this.closeAllSubscriptions('relay connection closed')
|
||||
}
|
||||
}
|
||||
|
||||
this.ws.onmessage = this._onmessage.bind(this)
|
||||
@@ -212,6 +218,7 @@ export class AbstractRelay {
|
||||
return
|
||||
case 'AUTH': {
|
||||
this.challenge = data[1] as string
|
||||
this._onauth?.(data[1] as string)
|
||||
return
|
||||
}
|
||||
}
|
||||
@@ -338,7 +345,7 @@ export class Subscription {
|
||||
}
|
||||
|
||||
public close(reason: string = 'closed by caller') {
|
||||
if (!this.closed) {
|
||||
if (!this.closed && this.relay.connected) {
|
||||
// if the connection was closed by the user calling .close() we will send a CLOSE message
|
||||
// otherwise this._open will be already set to false so we will skip this
|
||||
this.relay.send('["CLOSE",' + JSON.stringify(this.id) + ']')
|
||||
|
||||
@@ -1,8 +1,8 @@
|
||||
import { run, bench, group, baseline } from 'mitata'
|
||||
import { initNostrWasm } from 'nostr-wasm'
|
||||
import { NostrEvent } from './core'
|
||||
import { finalizeEvent, generateSecretKey } from './pure'
|
||||
import { setNostrWasm, verifyEvent } from './wasm'
|
||||
import { NostrEvent } from './core.ts'
|
||||
import { finalizeEvent, generateSecretKey } from './pure.ts'
|
||||
import { setNostrWasm, verifyEvent } from './wasm.ts'
|
||||
import { AbstractRelay } from './abstract-relay.ts'
|
||||
import { Relay as PureRelay } from './relay.ts'
|
||||
import { alwaysTrue } from './helpers.ts'
|
||||
|
||||
43
jsr.json
43
jsr.json
@@ -1,5 +1,44 @@
|
||||
{
|
||||
"name": "@nostr/tools",
|
||||
"version": "2.3.1",
|
||||
"exports": "./index.ts"
|
||||
"version": "2.3.2",
|
||||
"exports": {
|
||||
".": "./index.ts",
|
||||
"./core": "./core.ts",
|
||||
"./pure": "./pure.ts",
|
||||
"./wasm": "./wasm.ts",
|
||||
"./kinds": "./kinds.ts",
|
||||
"./filter": "./filter.ts",
|
||||
"./abstract-relay": "./abstract-relay.ts",
|
||||
"./relay": "./relay.ts",
|
||||
"./abstract-pool": "./abstract-pool.ts",
|
||||
"./pool": "./pool.ts",
|
||||
"./references": "./references.ts",
|
||||
"./nip04": "./nip04.ts",
|
||||
"./nip05": "./nip05.ts",
|
||||
"./nip06": "./nip06.ts",
|
||||
"./nip10": "./nip10.ts",
|
||||
"./nip11": "./nip11.ts",
|
||||
"./nip13": "./nip13.ts",
|
||||
"./nip18": "./nip18.ts",
|
||||
"./nip19": "./nip19.ts",
|
||||
"./nip21": "./nip21.ts",
|
||||
"./nip25": "./nip25.ts",
|
||||
"./nip27": "./nip27.ts",
|
||||
"./nip28": "./nip28.ts",
|
||||
"./nip29": "./nip29.ts",
|
||||
"./nip30": "./nip30.ts",
|
||||
"./nip39": "./nip39.ts",
|
||||
"./nip42": "./nip42.ts",
|
||||
"./nip44": "./nip44.ts",
|
||||
"./nip46": "./nip46.ts",
|
||||
"./nip49": "./nip49.ts",
|
||||
"./nip57": "./nip57.ts",
|
||||
"./nip75": "./nip75.ts",
|
||||
"./nip94": "./nip94.ts",
|
||||
"./nip96": "./nip96.ts",
|
||||
"./nip98": "./nip98.ts",
|
||||
"./nip99": "./nip99.ts",
|
||||
"./fakejson": "./fakejson.ts",
|
||||
"./utils": "./utils.ts"
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,16 @@
|
||||
import { describe, test, expect } from 'bun:test'
|
||||
import fetch from 'node-fetch'
|
||||
import { useFetchImplementation, fetchRelayInformation } from './nip11'
|
||||
import { useFetchImplementation, fetchRelayInformation } from './nip11.ts'
|
||||
|
||||
// TODO: replace with a mock
|
||||
describe('requesting relay as for NIP11', () => {
|
||||
useFetchImplementation(fetch)
|
||||
|
||||
test('testing a relay', async () => {
|
||||
const info = await fetchRelayInformation('wss://atlas.nostr.land')
|
||||
expect(info.name).toEqual('nostr.land')
|
||||
expect(info.description).toContain('nostr.land family')
|
||||
expect(info.fees).toBeTruthy()
|
||||
const info = await fetchRelayInformation('wss://nos.lol')
|
||||
expect(info.name).toEqual('nos.lol')
|
||||
expect(info.description).toContain('Generally accepts notes, except spammy ones.')
|
||||
expect(info.supported_nips).toEqual([1, 2, 4, 9, 11, 12, 16, 20, 22, 28, 33, 40])
|
||||
expect(info.software).toEqual('custom')
|
||||
expect(info.software).toEqual('git+https://github.com/hoytech/strfry.git')
|
||||
})
|
||||
})
|
||||
|
||||
14
nip29.ts
14
nip29.ts
@@ -1,10 +1,10 @@
|
||||
import { AbstractSimplePool } from './abstract-pool'
|
||||
import { Subscription } from './abstract-relay'
|
||||
import { decode } from './nip19'
|
||||
import type { Event } from './core'
|
||||
import { fetchRelayInformation } from './nip11'
|
||||
import { normalizeURL } from './utils'
|
||||
import { AddressPointer } from './nip19'
|
||||
import { AbstractSimplePool } from './abstract-pool.ts'
|
||||
import { Subscription } from './abstract-relay.ts'
|
||||
import { decode } from './nip19.ts'
|
||||
import type { Event } from './core.ts'
|
||||
import { fetchRelayInformation } from './nip11.ts'
|
||||
import { normalizeURL } from './utils.ts'
|
||||
import { AddressPointer } from './nip19.ts'
|
||||
|
||||
export function subscribeRelayGroups(
|
||||
pool: AbstractSimplePool,
|
||||
|
||||
2
nip46.ts
2
nip46.ts
@@ -193,7 +193,7 @@ export class BunkerSigner {
|
||||
* Calls the "connect" method on the bunker.
|
||||
*/
|
||||
async connect(): Promise<void> {
|
||||
await this.sendRequest('connect', [getPublicKey(this.secretKey), this.bp.secret || ''])
|
||||
await this.sendRequest('connect', [this.bp.pubkey, this.bp.secret || ''])
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
import { describe, test, expect } from 'bun:test'
|
||||
import { hexToBytes } from '@noble/hashes/utils'
|
||||
import { makeNwcRequestEvent, parseConnectionString } from './nip47'
|
||||
import { makeNwcRequestEvent, parseConnectionString } from './nip47.ts'
|
||||
import { decrypt } from './nip04.ts'
|
||||
import { NWCWalletRequest } from './kinds.ts'
|
||||
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
import { test, expect } from 'bun:test'
|
||||
import { decrypt, encrypt } from './nip49'
|
||||
import { decrypt, encrypt } from './nip49.ts'
|
||||
import { hexToBytes } from '@noble/hashes/utils'
|
||||
|
||||
test('encrypt and decrypt', () => {
|
||||
|
||||
2
nip49.ts
2
nip49.ts
@@ -1,7 +1,7 @@
|
||||
import { scrypt } from '@noble/hashes/scrypt'
|
||||
import { xchacha20poly1305 } from '@noble/ciphers/chacha'
|
||||
import { concatBytes, randomBytes } from '@noble/hashes/utils'
|
||||
import { Bech32MaxSize, encodeBytes } from './nip19'
|
||||
import { Bech32MaxSize, encodeBytes } from './nip19.ts'
|
||||
import { bech32 } from '@scure/base'
|
||||
|
||||
export function encrypt(sec: Uint8Array, password: string, logn: number = 16, ksb: 0x00 | 0x01 | 0x02 = 0x02): string {
|
||||
|
||||
357
nip58.test.ts
Normal file
357
nip58.test.ts
Normal file
@@ -0,0 +1,357 @@
|
||||
import { expect, test } from 'bun:test'
|
||||
|
||||
import { EventTemplate } from './core.ts'
|
||||
import {
|
||||
BadgeAward as BadgeAwardKind,
|
||||
BadgeDefinition as BadgeDefinitionKind,
|
||||
ProfileBadges as ProfileBadgesKind,
|
||||
} from './kinds.ts'
|
||||
import { finalizeEvent, generateSecretKey } from './pure.ts'
|
||||
|
||||
import {
|
||||
BadgeAward,
|
||||
BadgeDefinition,
|
||||
ProfileBadges,
|
||||
generateBadgeAwardEventTemplate,
|
||||
generateBadgeDefinitionEventTemplate,
|
||||
generateProfileBadgesEventTemplate,
|
||||
validateBadgeAwardEvent,
|
||||
validateBadgeDefinitionEvent,
|
||||
validateProfileBadgesEvent,
|
||||
} from './nip58.ts'
|
||||
|
||||
test('BadgeDefinition has required property "d"', () => {
|
||||
const badge: BadgeDefinition = {
|
||||
d: 'badge-id',
|
||||
}
|
||||
expect(badge.d).toEqual('badge-id')
|
||||
})
|
||||
|
||||
test('BadgeDefinition has optional property "name"', () => {
|
||||
const badge: BadgeDefinition = {
|
||||
d: 'badge-id',
|
||||
name: 'Badge Name',
|
||||
}
|
||||
expect(badge.name).toEqual('Badge Name')
|
||||
})
|
||||
|
||||
test('BadgeDefinition has optional property "description"', () => {
|
||||
const badge: BadgeDefinition = {
|
||||
d: 'badge-id',
|
||||
description: 'Badge Description',
|
||||
}
|
||||
expect(badge.description).toEqual('Badge Description')
|
||||
})
|
||||
|
||||
test('BadgeDefinition has optional property "image"', () => {
|
||||
const badge: BadgeDefinition = {
|
||||
d: 'badge-id',
|
||||
image: ['https://example.com/badge.png', '1024x1024'],
|
||||
}
|
||||
expect(badge.image).toEqual(['https://example.com/badge.png', '1024x1024'])
|
||||
})
|
||||
|
||||
test('BadgeDefinition has optional property "thumbs"', () => {
|
||||
const badge: BadgeDefinition = {
|
||||
d: 'badge-id',
|
||||
thumbs: [
|
||||
['https://example.com/thumb.png', '100x100'],
|
||||
['https://example.com/thumb2.png', '200x200'],
|
||||
],
|
||||
}
|
||||
expect(badge.thumbs).toEqual([
|
||||
['https://example.com/thumb.png', '100x100'],
|
||||
['https://example.com/thumb2.png', '200x200'],
|
||||
])
|
||||
})
|
||||
|
||||
test('BadgeAward has required property "a"', () => {
|
||||
const badgeAward: BadgeAward = {
|
||||
a: 'badge-definition-address',
|
||||
p: [
|
||||
['pubkey1', 'relay1'],
|
||||
['pubkey2', 'relay2'],
|
||||
],
|
||||
}
|
||||
expect(badgeAward.a).toEqual('badge-definition-address')
|
||||
})
|
||||
|
||||
test('BadgeAward has required property "p"', () => {
|
||||
const badgeAward: BadgeAward = {
|
||||
a: 'badge-definition-address',
|
||||
p: [
|
||||
['pubkey1', 'relay1'],
|
||||
['pubkey2', 'relay2'],
|
||||
],
|
||||
}
|
||||
expect(badgeAward.p).toEqual([
|
||||
['pubkey1', 'relay1'],
|
||||
['pubkey2', 'relay2'],
|
||||
])
|
||||
})
|
||||
|
||||
test('ProfileBadges has required property "d"', () => {
|
||||
const profileBadges: ProfileBadges = {
|
||||
d: 'profile_badges',
|
||||
badges: [],
|
||||
}
|
||||
expect(profileBadges.d).toEqual('profile_badges')
|
||||
})
|
||||
|
||||
test('ProfileBadges has required property "badges"', () => {
|
||||
const profileBadges: ProfileBadges = {
|
||||
d: 'profile_badges',
|
||||
badges: [],
|
||||
}
|
||||
expect(profileBadges.badges).toEqual([])
|
||||
})
|
||||
|
||||
test('ProfileBadges badges array contains objects with required properties "a" and "e"', () => {
|
||||
const profileBadges: ProfileBadges = {
|
||||
d: 'profile_badges',
|
||||
badges: [
|
||||
{
|
||||
a: 'badge-definition-address',
|
||||
e: ['badge-award-event-id'],
|
||||
},
|
||||
],
|
||||
}
|
||||
expect(profileBadges.badges[0].a).toEqual('badge-definition-address')
|
||||
expect(profileBadges.badges[0].e).toEqual(['badge-award-event-id'])
|
||||
})
|
||||
|
||||
test('generateBadgeDefinitionEventTemplate generates EventTemplate with mandatory tags', () => {
|
||||
const badge: BadgeDefinition = {
|
||||
d: 'badge-id',
|
||||
}
|
||||
const eventTemplate = generateBadgeDefinitionEventTemplate(badge)
|
||||
expect(eventTemplate.tags).toEqual([['d', 'badge-id']])
|
||||
})
|
||||
|
||||
test('generateBadgeDefinitionEventTemplate generates EventTemplate with optional tags', () => {
|
||||
const badge: BadgeDefinition = {
|
||||
d: 'badge-id',
|
||||
name: 'Badge Name',
|
||||
description: 'Badge Description',
|
||||
image: ['https://example.com/badge.png', '1024x1024'],
|
||||
thumbs: [
|
||||
['https://example.com/thumb.png', '100x100'],
|
||||
['https://example.com/thumb2.png', '200x200'],
|
||||
],
|
||||
}
|
||||
const eventTemplate = generateBadgeDefinitionEventTemplate(badge)
|
||||
expect(eventTemplate.tags).toEqual([
|
||||
['d', 'badge-id'],
|
||||
['name', 'Badge Name'],
|
||||
['description', 'Badge Description'],
|
||||
['image', 'https://example.com/badge.png', '1024x1024'],
|
||||
['thumb', 'https://example.com/thumb.png', '100x100'],
|
||||
['thumb', 'https://example.com/thumb2.png', '200x200'],
|
||||
])
|
||||
})
|
||||
|
||||
test('generateBadgeDefinitionEventTemplate generates EventTemplate without optional tags', () => {
|
||||
const badge: BadgeDefinition = {
|
||||
d: 'badge-id',
|
||||
}
|
||||
const eventTemplate = generateBadgeDefinitionEventTemplate(badge)
|
||||
expect(eventTemplate.tags).toEqual([['d', 'badge-id']])
|
||||
})
|
||||
|
||||
test('validateBadgeDefinitionEvent returns true for valid BadgeDefinition event', () => {
|
||||
const sk = generateSecretKey()
|
||||
const eventTemplate: EventTemplate = {
|
||||
content: '',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: BadgeDefinitionKind,
|
||||
tags: [
|
||||
['d', 'badge-id'],
|
||||
['name', 'Badge Name'],
|
||||
['description', 'Badge Description'],
|
||||
['image', 'https://example.com/badge.png', '1024x1024'],
|
||||
['thumb', 'https://example.com/thumb.png', '100x100'],
|
||||
['thumb', 'https://example.com/thumb2.png', '200x200'],
|
||||
],
|
||||
}
|
||||
const event = finalizeEvent(eventTemplate, sk)
|
||||
const isValid = validateBadgeDefinitionEvent(event)
|
||||
|
||||
expect(isValid).toBe(true)
|
||||
})
|
||||
|
||||
test('validateBadgeDefinitionEvent returns false for invalid BadgeDefinition event', () => {
|
||||
const sk = generateSecretKey()
|
||||
const eventTemplate: EventTemplate = {
|
||||
content: '',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: BadgeDefinitionKind,
|
||||
tags: [],
|
||||
}
|
||||
const event = finalizeEvent(eventTemplate, sk)
|
||||
const isValid = validateBadgeDefinitionEvent(event)
|
||||
|
||||
expect(isValid).toBe(false)
|
||||
})
|
||||
|
||||
test('generateBadgeAwardEventTemplate generates EventTemplate with mandatory tags', () => {
|
||||
const badgeAward: BadgeAward = {
|
||||
a: 'badge-definition-address',
|
||||
p: [
|
||||
['pubkey1', 'relay1'],
|
||||
['pubkey2', 'relay2'],
|
||||
],
|
||||
}
|
||||
const eventTemplate = generateBadgeAwardEventTemplate(badgeAward)
|
||||
expect(eventTemplate.tags).toEqual([
|
||||
['a', 'badge-definition-address'],
|
||||
['p', 'pubkey1', 'relay1'],
|
||||
['p', 'pubkey2', 'relay2'],
|
||||
])
|
||||
})
|
||||
|
||||
test('generateBadgeAwardEventTemplate generates EventTemplate without optional tags', () => {
|
||||
const badgeAward: BadgeAward = {
|
||||
a: 'badge-definition-address',
|
||||
p: [
|
||||
['pubkey1', 'relay1'],
|
||||
['pubkey2', 'relay2'],
|
||||
],
|
||||
}
|
||||
const eventTemplate = generateBadgeAwardEventTemplate(badgeAward)
|
||||
expect(eventTemplate.tags).toEqual([
|
||||
['a', 'badge-definition-address'],
|
||||
['p', 'pubkey1', 'relay1'],
|
||||
['p', 'pubkey2', 'relay2'],
|
||||
])
|
||||
})
|
||||
|
||||
test('generateBadgeAwardEventTemplate generates EventTemplate with optional tags', () => {
|
||||
const badgeAward: BadgeAward = {
|
||||
a: 'badge-definition-address',
|
||||
p: [
|
||||
['pubkey1', 'relay1'],
|
||||
['pubkey2', 'relay2'],
|
||||
],
|
||||
}
|
||||
const eventTemplate = generateBadgeAwardEventTemplate(badgeAward)
|
||||
expect(eventTemplate.tags).toEqual([
|
||||
['a', 'badge-definition-address'],
|
||||
['p', 'pubkey1', 'relay1'],
|
||||
['p', 'pubkey2', 'relay2'],
|
||||
])
|
||||
})
|
||||
|
||||
test('validateBadgeAwardEvent returns true for valid BadgeAward event', () => {
|
||||
const sk = generateSecretKey()
|
||||
const eventTemplate: EventTemplate = {
|
||||
content: '',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: BadgeAwardKind,
|
||||
tags: [
|
||||
['a', 'badge-definition-address'],
|
||||
['p', 'pubkey1', 'relay1'],
|
||||
['p', 'pubkey2', 'relay2'],
|
||||
],
|
||||
}
|
||||
const event = finalizeEvent(eventTemplate, sk)
|
||||
const isValid = validateBadgeAwardEvent(event)
|
||||
|
||||
expect(isValid).toBe(true)
|
||||
})
|
||||
|
||||
test('validateBadgeAwardEvent returns false for invalid BadgeAward event', () => {
|
||||
const sk = generateSecretKey()
|
||||
const eventTemplate: EventTemplate = {
|
||||
content: '',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: BadgeAwardKind,
|
||||
tags: [],
|
||||
}
|
||||
const event = finalizeEvent(eventTemplate, sk)
|
||||
const isValid = validateBadgeAwardEvent(event)
|
||||
|
||||
expect(isValid).toBe(false)
|
||||
})
|
||||
|
||||
test('generateProfileBadgesEventTemplate generates EventTemplate with mandatory tags', () => {
|
||||
const profileBadges: ProfileBadges = {
|
||||
d: 'profile_badges',
|
||||
badges: [],
|
||||
}
|
||||
const eventTemplate = generateProfileBadgesEventTemplate(profileBadges)
|
||||
expect(eventTemplate.tags).toEqual([['d', 'profile_badges']])
|
||||
})
|
||||
|
||||
test('generateProfileBadgesEventTemplate generates EventTemplate with optional tags', () => {
|
||||
const profileBadges: ProfileBadges = {
|
||||
d: 'profile_badges',
|
||||
badges: [
|
||||
{
|
||||
a: 'badge-definition-address',
|
||||
e: ['badge-award-event-id'],
|
||||
},
|
||||
],
|
||||
}
|
||||
const eventTemplate = generateProfileBadgesEventTemplate(profileBadges)
|
||||
expect(eventTemplate.tags).toEqual([
|
||||
['d', 'profile_badges'],
|
||||
['a', 'badge-definition-address'],
|
||||
['e', 'badge-award-event-id'],
|
||||
])
|
||||
})
|
||||
|
||||
test('generateProfileBadgesEventTemplate generates EventTemplate with multiple optional tags', () => {
|
||||
const profileBadges: ProfileBadges = {
|
||||
d: 'profile_badges',
|
||||
badges: [
|
||||
{
|
||||
a: 'badge-definition-address1',
|
||||
e: ['badge-award-event-id1', 'badge-award-event-id2'],
|
||||
},
|
||||
{
|
||||
a: 'badge-definition-address2',
|
||||
e: ['badge-award-event-id3'],
|
||||
},
|
||||
],
|
||||
}
|
||||
const eventTemplate = generateProfileBadgesEventTemplate(profileBadges)
|
||||
expect(eventTemplate.tags).toEqual([
|
||||
['d', 'profile_badges'],
|
||||
['a', 'badge-definition-address1'],
|
||||
['e', 'badge-award-event-id1', 'badge-award-event-id2'],
|
||||
['a', 'badge-definition-address2'],
|
||||
['e', 'badge-award-event-id3'],
|
||||
])
|
||||
})
|
||||
|
||||
test('validateProfileBadgesEvent returns true for valid ProfileBadges event', () => {
|
||||
const sk = generateSecretKey()
|
||||
const eventTemplate: EventTemplate = {
|
||||
content: '',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: ProfileBadgesKind,
|
||||
tags: [
|
||||
['d', 'profile_badges'],
|
||||
['a', 'badge-definition-address'],
|
||||
['e', 'badge-award-event-id'],
|
||||
],
|
||||
}
|
||||
const event = finalizeEvent(eventTemplate, sk)
|
||||
const isValid = validateProfileBadgesEvent(event)
|
||||
|
||||
expect(isValid).toBe(true)
|
||||
})
|
||||
|
||||
test('validateProfileBadgesEvent returns false for invalid ProfileBadges event', () => {
|
||||
const sk = generateSecretKey()
|
||||
const eventTemplate: EventTemplate = {
|
||||
content: '',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: ProfileBadgesKind,
|
||||
tags: [],
|
||||
}
|
||||
const event = finalizeEvent(eventTemplate, sk)
|
||||
const isValid = validateProfileBadgesEvent(event)
|
||||
|
||||
expect(isValid).toBe(false)
|
||||
})
|
||||
245
nip58.ts
Normal file
245
nip58.ts
Normal file
@@ -0,0 +1,245 @@
|
||||
import { Event, EventTemplate } from './core.ts'
|
||||
import {
|
||||
BadgeAward as BadgeAwardKind,
|
||||
BadgeDefinition as BadgeDefinitionKind,
|
||||
ProfileBadges as ProfileBadgesKind,
|
||||
} from './kinds.ts'
|
||||
|
||||
/**
|
||||
* Represents the structure for defining a badge within the Nostr network.
|
||||
* This structure is used to create templates for badge definition events,
|
||||
* facilitating the recognition and awarding of badges to users for various achievements.
|
||||
*/
|
||||
export type BadgeDefinition = {
|
||||
/**
|
||||
* A unique identifier for the badge. This is used to distinguish badges
|
||||
* from one another and should be unique across all badge definitions.
|
||||
* Typically, this could be a short, descriptive string.
|
||||
*/
|
||||
d: string
|
||||
|
||||
/**
|
||||
* An optional short name for the badge. This provides a human-readable
|
||||
* title for the badge, making it easier to recognize and refer to.
|
||||
*/
|
||||
name?: string
|
||||
|
||||
/**
|
||||
* An optional description for the badge. This field can be used to
|
||||
* provide more detailed information about the badge, such as the criteria
|
||||
* for its awarding or its significance.
|
||||
*/
|
||||
description?: string
|
||||
|
||||
/**
|
||||
* An optional image URL and dimensions for the badge. The first element
|
||||
* of the tuple is the URL pointing to a high-resolution image representing
|
||||
* the badge, and the second element specifies the image's dimensions in
|
||||
* the format "widthxheight". The recommended dimensions are 1024x1024 pixels.
|
||||
*/
|
||||
image?: [string, string]
|
||||
|
||||
/**
|
||||
* An optional list of thumbnail images for the badge. Each element in the
|
||||
* array is a tuple, where the first element is the URL pointing to a thumbnail
|
||||
* version of the badge image, and the second element specifies the thumbnail's
|
||||
* dimensions in the format "widthxheight". Multiple thumbnails can be provided
|
||||
* to support different display sizes.
|
||||
*/
|
||||
thumbs?: Array<[string, string]>
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the structure for awarding a badge to one or more recipients
|
||||
* within the Nostr network. This structure is used to create templates for
|
||||
* badge award events, which are immutable and signify the recognition of
|
||||
* individuals' achievements or contributions.
|
||||
*/
|
||||
export type BadgeAward = {
|
||||
/**
|
||||
* A reference to the Badge Definition event. This is typically composed
|
||||
* of the event ID of the badge definition. It establishes a clear linkage
|
||||
* between the badge being awarded and its original definition, ensuring
|
||||
* that recipients are awarded the correct badge.
|
||||
*/
|
||||
a: string
|
||||
|
||||
/**
|
||||
* An array of p tags, each containing a pubkey and its associated relays.
|
||||
*/
|
||||
p: string[][]
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents the collection of badges a user chooses to display on their profile.
|
||||
* This structure is crucial for applications that allow users to showcase achievements
|
||||
* or recognitions in the form of badges, following the specifications of NIP-58.
|
||||
*/
|
||||
export type ProfileBadges = {
|
||||
/**
|
||||
* A unique identifier for the profile badges collection. According to NIP-58,
|
||||
* this should be set to "profile_badges" to differentiate it from other event types.
|
||||
*/
|
||||
d: 'profile_badges'
|
||||
|
||||
/**
|
||||
* A list of badges that the user has elected to display on their profile. Each item
|
||||
* in the array represents a specific badge, including references to both its definition
|
||||
* and the award event.
|
||||
*/
|
||||
badges: Array<{
|
||||
/**
|
||||
* The event address of the badge definition. This is a reference to the specific badge
|
||||
* being displayed, linking back to the badge's original definition event. It allows
|
||||
* clients to fetch and display the badge's details, such as its name, description,
|
||||
* and image.
|
||||
*/
|
||||
a: string
|
||||
|
||||
/**
|
||||
* The event id of the badge award with corresponding relays. This references the event
|
||||
* in which the badge was awarded to the user. It is crucial for verifying the
|
||||
* authenticity of the badge display, ensuring that the user was indeed awarded the
|
||||
* badge they are choosing to display.
|
||||
*/
|
||||
e: string[]
|
||||
}>
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an EventTemplate based on the provided BadgeDefinition.
|
||||
*
|
||||
* @param {BadgeDefinition} badgeDefinition - The BadgeDefinition object.
|
||||
* @returns {EventTemplate} - The generated EventTemplate object.
|
||||
*/
|
||||
export function generateBadgeDefinitionEventTemplate({
|
||||
d,
|
||||
description,
|
||||
image,
|
||||
name,
|
||||
thumbs,
|
||||
}: BadgeDefinition): EventTemplate {
|
||||
// Mandatory tags
|
||||
const tags: string[][] = [['d', d]]
|
||||
|
||||
// Append optional tags
|
||||
name && tags.push(['name', name])
|
||||
description && tags.push(['description', description])
|
||||
image && tags.push(['image', ...image])
|
||||
if (thumbs) {
|
||||
for (const thumb of thumbs) {
|
||||
tags.push(['thumb', ...thumb])
|
||||
}
|
||||
}
|
||||
|
||||
// Construct the EventTemplate object
|
||||
const eventTemplate: EventTemplate = {
|
||||
content: '',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: BadgeDefinitionKind,
|
||||
tags,
|
||||
}
|
||||
|
||||
return eventTemplate
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a badge definition event.
|
||||
*
|
||||
* @param event - The event to validate.
|
||||
* @returns A boolean indicating whether the event is a valid badge definition event.
|
||||
*/
|
||||
export function validateBadgeDefinitionEvent(event: Event): boolean {
|
||||
if (event.kind !== BadgeDefinitionKind) return false
|
||||
|
||||
const requiredTags = ['d'] as const
|
||||
for (const tag of requiredTags) {
|
||||
if (!event.tags.find(([t]) => t == tag)) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an EventTemplate based on the provided BadgeAward.
|
||||
*
|
||||
* @param {BadgeAward} badgeAward - The BadgeAward object.
|
||||
* @returns {EventTemplate} - The generated EventTemplate object.
|
||||
*/
|
||||
export function generateBadgeAwardEventTemplate({ a, p }: BadgeAward): EventTemplate {
|
||||
// Mandatory tags
|
||||
const tags: string[][] = [['a', a]]
|
||||
for (const _p of p) {
|
||||
tags.push(['p', ..._p])
|
||||
}
|
||||
|
||||
// Construct the EventTemplate object
|
||||
const eventTemplate: EventTemplate = {
|
||||
content: '',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: BadgeAwardKind,
|
||||
tags,
|
||||
}
|
||||
|
||||
return eventTemplate
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a badge award event.
|
||||
*
|
||||
* @param event - The event to validate.
|
||||
* @returns A boolean indicating whether the event is a valid badge award event.
|
||||
*/
|
||||
export function validateBadgeAwardEvent(event: Event): boolean {
|
||||
if (event.kind !== BadgeAwardKind) return false
|
||||
|
||||
const requiredTags = ['a', 'p'] as const
|
||||
for (const tag of requiredTags) {
|
||||
if (!event.tags.find(([t]) => t == tag)) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
/**
|
||||
* Generates an EventTemplate based on the provided ProfileBadges.
|
||||
*
|
||||
* @param {ProfileBadges} profileBadges - The ProfileBadges object.
|
||||
* @returns {EventTemplate} - The generated EventTemplate object.
|
||||
*/
|
||||
export function generateProfileBadgesEventTemplate({ badges }: ProfileBadges): EventTemplate {
|
||||
// Mandatory tags
|
||||
const tags: string[][] = [['d', 'profile_badges']]
|
||||
|
||||
// Append optional tags
|
||||
for (const badge of badges) {
|
||||
tags.push(['a', badge.a], ['e', ...badge.e])
|
||||
}
|
||||
|
||||
// Construct the EventTemplate object
|
||||
const eventTemplate: EventTemplate = {
|
||||
content: '',
|
||||
created_at: Math.floor(Date.now() / 1000),
|
||||
kind: ProfileBadgesKind,
|
||||
tags,
|
||||
}
|
||||
|
||||
return eventTemplate
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a profile badges event.
|
||||
*
|
||||
* @param event - The event to validate.
|
||||
* @returns A boolean indicating whether the event is a valid profile badges event.
|
||||
*/
|
||||
export function validateProfileBadgesEvent(event: Event): boolean {
|
||||
if (event.kind !== ProfileBadgesKind) return false
|
||||
|
||||
const requiredTags = ['d'] as const
|
||||
for (const tag of requiredTags) {
|
||||
if (!event.tags.find(([t]) => t == tag)) return false
|
||||
}
|
||||
|
||||
return true
|
||||
}
|
||||
4
nip75.ts
4
nip75.ts
@@ -1,5 +1,5 @@
|
||||
import { Event, EventTemplate } from './core'
|
||||
import { ZapGoal } from './kinds'
|
||||
import { Event, EventTemplate } from './core.ts'
|
||||
import { ZapGoal } from './kinds.ts'
|
||||
|
||||
/**
|
||||
* Represents a fundraising goal in the Nostr network as defined by NIP-75.
|
||||
|
||||
2
nip94.ts
2
nip94.ts
@@ -1,4 +1,4 @@
|
||||
import { Event, EventTemplate } from './core'
|
||||
import { Event, EventTemplate } from './core.ts'
|
||||
import { FileMetadata as FileMetadataKind } from './kinds.ts'
|
||||
|
||||
/**
|
||||
|
||||
4
nip96.ts
4
nip96.ts
@@ -1,6 +1,6 @@
|
||||
import { sha256 } from '@noble/hashes/sha256'
|
||||
import { EventTemplate } from './core'
|
||||
import { FileServerPreference } from './kinds'
|
||||
import { EventTemplate } from './core.ts'
|
||||
import { FileServerPreference } from './kinds.ts'
|
||||
import { bytesToHex } from '@noble/hashes/utils'
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,9 +1,9 @@
|
||||
import { describe, expect, test } from 'bun:test'
|
||||
|
||||
import { Event } from './core'
|
||||
import { ClassifiedListing, DraftClassifiedListing } from './kinds'
|
||||
import { ClassifiedListingObject, generateEventTemplate, parseEvent, validateEvent } from './nip99'
|
||||
import { finalizeEvent, generateSecretKey } from './pure'
|
||||
import { Event } from './core.ts'
|
||||
import { ClassifiedListing, DraftClassifiedListing } from './kinds.ts'
|
||||
import { ClassifiedListingObject, generateEventTemplate, parseEvent, validateEvent } from './nip99.ts'
|
||||
import { finalizeEvent, generateSecretKey } from './pure.ts'
|
||||
|
||||
describe('validateEvent', () => {
|
||||
test('should return true for a valid classified listing event', () => {
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
{
|
||||
"type": "module",
|
||||
"name": "nostr-tools",
|
||||
"version": "2.3.1",
|
||||
"version": "2.4.0",
|
||||
"description": "Tools for making a Nostr client.",
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@@ -170,6 +170,11 @@
|
||||
"require": "./lib/cjs/nip57.js",
|
||||
"types": "./lib/types/nip57.d.ts"
|
||||
},
|
||||
"./nip58": {
|
||||
"import": "./lib/esm/nip58.js",
|
||||
"require": "./lib/cjs/nip58.js",
|
||||
"types": "./lib/types/nip58.d.ts"
|
||||
},
|
||||
"./nip75": {
|
||||
"import": "./lib/esm/nip75.js",
|
||||
"require": "./lib/cjs/nip75.js",
|
||||
|
||||
@@ -1,11 +1,11 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"module": "esnext",
|
||||
"module": "NodeNext",
|
||||
"target": "esnext",
|
||||
"lib": ["dom", "dom.iterable", "esnext"],
|
||||
"declaration": true,
|
||||
"strict": true,
|
||||
"moduleResolution": "node",
|
||||
"moduleResolution": "NodeNext",
|
||||
"skipLibCheck": true,
|
||||
"esModuleInterop": true,
|
||||
"emitDeclarationOnly": true,
|
||||
|
||||
4
wasm.ts
4
wasm.ts
@@ -1,6 +1,6 @@
|
||||
import { bytesToHex } from '@noble/hashes/utils'
|
||||
import { Nostr as NostrWasm } from 'nostr-wasm'
|
||||
import { EventTemplate, Event, Nostr, VerifiedEvent, verifiedSymbol } from './core'
|
||||
import { EventTemplate, Event, Nostr, VerifiedEvent, verifiedSymbol } from './core.ts'
|
||||
|
||||
let nw: NostrWasm
|
||||
|
||||
@@ -30,7 +30,7 @@ class Wasm implements Nostr {
|
||||
}
|
||||
}
|
||||
|
||||
const i = new Wasm()
|
||||
const i: Wasm = new Wasm()
|
||||
export const generateSecretKey = i.generateSecretKey
|
||||
export const getPublicKey = i.getPublicKey
|
||||
export const finalizeEvent = i.finalizeEvent
|
||||
|
||||
Reference in New Issue
Block a user