mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-08 16:28:49 +00:00
Compare commits
17 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5c7e9c8f36 | ||
|
|
1d7620a057 | ||
|
|
e5cda3509c | ||
|
|
02da1dc036 | ||
|
|
7aed747bb2 | ||
|
|
747a7944d7 | ||
|
|
9f8b7274b3 | ||
|
|
ee565db7f5 | ||
|
|
e9ee8258e7 | ||
|
|
ad07d260ab | ||
|
|
632184afb8 | ||
|
|
d7d5d30f41 | ||
|
|
387ce2c335 | ||
|
|
b62b8f88af | ||
|
|
6b43533f2e | ||
|
|
e30e08d8e2 | ||
|
|
59426d9f35 |
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 --allow-slow-types
|
|
||||||
@@ -25,6 +25,15 @@ let sk = generateSecretKey() // `sk` is a Uint8Array
|
|||||||
let pk = getPublicKey(sk) // `pk` is a hex string
|
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
|
### Creating, signing and verifying events
|
||||||
|
|
||||||
```js
|
```js
|
||||||
|
|||||||
@@ -24,6 +24,9 @@ export class AbstractRelay {
|
|||||||
public onclose: (() => void) | null = null
|
public onclose: (() => void) | null = null
|
||||||
public onnotice: (msg: string) => void = msg => console.debug(`NOTICE from ${this.url}: ${msg}`)
|
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 baseEoseTimeout: number = 4400
|
||||||
public connectionTimeout: number = 4400
|
public connectionTimeout: number = 4400
|
||||||
public openSubs: Map<string, Subscription> = new Map()
|
public openSubs: Map<string, Subscription> = new Map()
|
||||||
@@ -215,6 +218,7 @@ export class AbstractRelay {
|
|||||||
return
|
return
|
||||||
case 'AUTH': {
|
case 'AUTH': {
|
||||||
this.challenge = data[1] as string
|
this.challenge = data[1] as string
|
||||||
|
this._onauth?.(data[1] as string)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
import { run, bench, group, baseline } from 'mitata'
|
import { run, bench, group, baseline } from 'mitata'
|
||||||
import { initNostrWasm } from 'nostr-wasm'
|
import { initNostrWasm } from 'nostr-wasm'
|
||||||
import { NostrEvent } from './core'
|
import { NostrEvent } from './core.ts'
|
||||||
import { finalizeEvent, generateSecretKey } from './pure'
|
import { finalizeEvent, generateSecretKey } from './pure.ts'
|
||||||
import { setNostrWasm, verifyEvent } from './wasm'
|
import { setNostrWasm, verifyEvent } from './wasm.ts'
|
||||||
import { AbstractRelay } from './abstract-relay.ts'
|
import { AbstractRelay } from './abstract-relay.ts'
|
||||||
import { Relay as PureRelay } from './relay.ts'
|
import { Relay as PureRelay } from './relay.ts'
|
||||||
import { alwaysTrue } from './helpers.ts'
|
import { alwaysTrue } from './helpers.ts'
|
||||||
|
|||||||
2
jsr.json
2
jsr.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nostr/tools",
|
"name": "@nostr/tools",
|
||||||
"version": "2.3.1",
|
"version": "2.3.2",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts",
|
".": "./index.ts",
|
||||||
"./core": "./core.ts",
|
"./core": "./core.ts",
|
||||||
|
|||||||
@@ -1,17 +1,16 @@
|
|||||||
import { describe, test, expect } from 'bun:test'
|
import { describe, test, expect } from 'bun:test'
|
||||||
import fetch from 'node-fetch'
|
import fetch from 'node-fetch'
|
||||||
import { useFetchImplementation, fetchRelayInformation } from './nip11'
|
import { useFetchImplementation, fetchRelayInformation } from './nip11.ts'
|
||||||
|
|
||||||
// TODO: replace with a mock
|
// TODO: replace with a mock
|
||||||
describe('requesting relay as for NIP11', () => {
|
describe('requesting relay as for NIP11', () => {
|
||||||
useFetchImplementation(fetch)
|
useFetchImplementation(fetch)
|
||||||
|
|
||||||
test('testing a relay', async () => {
|
test('testing a relay', async () => {
|
||||||
const info = await fetchRelayInformation('wss://atlas.nostr.land')
|
const info = await fetchRelayInformation('wss://nos.lol')
|
||||||
expect(info.name).toEqual('nostr.land')
|
expect(info.name).toEqual('nos.lol')
|
||||||
expect(info.description).toContain('nostr.land family')
|
expect(info.description).toContain('Generally accepts notes, except spammy ones.')
|
||||||
expect(info.fees).toBeTruthy()
|
|
||||||
expect(info.supported_nips).toEqual([1, 2, 4, 9, 11, 12, 16, 20, 22, 28, 33, 40])
|
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 { AbstractSimplePool } from './abstract-pool.ts'
|
||||||
import { Subscription } from './abstract-relay'
|
import { Subscription } from './abstract-relay.ts'
|
||||||
import { decode } from './nip19'
|
import { decode } from './nip19.ts'
|
||||||
import type { Event } from './core'
|
import type { Event } from './core.ts'
|
||||||
import { fetchRelayInformation } from './nip11'
|
import { fetchRelayInformation } from './nip11.ts'
|
||||||
import { normalizeURL } from './utils'
|
import { normalizeURL } from './utils.ts'
|
||||||
import { AddressPointer } from './nip19'
|
import { AddressPointer } from './nip19.ts'
|
||||||
|
|
||||||
export function subscribeRelayGroups(
|
export function subscribeRelayGroups(
|
||||||
pool: AbstractSimplePool,
|
pool: AbstractSimplePool,
|
||||||
|
|||||||
2
nip46.ts
2
nip46.ts
@@ -193,7 +193,7 @@ export class BunkerSigner {
|
|||||||
* Calls the "connect" method on the bunker.
|
* Calls the "connect" method on the bunker.
|
||||||
*/
|
*/
|
||||||
async connect(): Promise<void> {
|
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 { describe, test, expect } from 'bun:test'
|
||||||
import { hexToBytes } from '@noble/hashes/utils'
|
import { hexToBytes } from '@noble/hashes/utils'
|
||||||
import { makeNwcRequestEvent, parseConnectionString } from './nip47'
|
import { makeNwcRequestEvent, parseConnectionString } from './nip47.ts'
|
||||||
import { decrypt } from './nip04.ts'
|
import { decrypt } from './nip04.ts'
|
||||||
import { NWCWalletRequest } from './kinds.ts'
|
import { NWCWalletRequest } from './kinds.ts'
|
||||||
|
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
import { test, expect } from 'bun:test'
|
import { test, expect } from 'bun:test'
|
||||||
import { decrypt, encrypt } from './nip49'
|
import { decrypt, encrypt } from './nip49.ts'
|
||||||
import { hexToBytes } from '@noble/hashes/utils'
|
import { hexToBytes } from '@noble/hashes/utils'
|
||||||
|
|
||||||
test('encrypt and decrypt', () => {
|
test('encrypt and decrypt', () => {
|
||||||
|
|||||||
2
nip49.ts
2
nip49.ts
@@ -1,7 +1,7 @@
|
|||||||
import { scrypt } from '@noble/hashes/scrypt'
|
import { scrypt } from '@noble/hashes/scrypt'
|
||||||
import { xchacha20poly1305 } from '@noble/ciphers/chacha'
|
import { xchacha20poly1305 } from '@noble/ciphers/chacha'
|
||||||
import { concatBytes, randomBytes } from '@noble/hashes/utils'
|
import { concatBytes, randomBytes } from '@noble/hashes/utils'
|
||||||
import { Bech32MaxSize, encodeBytes } from './nip19'
|
import { Bech32MaxSize, encodeBytes } from './nip19.ts'
|
||||||
import { bech32 } from '@scure/base'
|
import { bech32 } from '@scure/base'
|
||||||
|
|
||||||
export function encrypt(sec: Uint8Array, password: string, logn: number = 16, ksb: 0x00 | 0x01 | 0x02 = 0x02): string {
|
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 { Event, EventTemplate } from './core.ts'
|
||||||
import { ZapGoal } from './kinds'
|
import { ZapGoal } from './kinds.ts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a fundraising goal in the Nostr network as defined by NIP-75.
|
* 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'
|
import { FileMetadata as FileMetadataKind } from './kinds.ts'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
4
nip96.ts
4
nip96.ts
@@ -1,6 +1,6 @@
|
|||||||
import { sha256 } from '@noble/hashes/sha256'
|
import { sha256 } from '@noble/hashes/sha256'
|
||||||
import { EventTemplate } from './core'
|
import { EventTemplate } from './core.ts'
|
||||||
import { FileServerPreference } from './kinds'
|
import { FileServerPreference } from './kinds.ts'
|
||||||
import { bytesToHex } from '@noble/hashes/utils'
|
import { bytesToHex } from '@noble/hashes/utils'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -1,9 +1,9 @@
|
|||||||
import { describe, expect, test } from 'bun:test'
|
import { describe, expect, test } from 'bun:test'
|
||||||
|
|
||||||
import { Event } from './core'
|
import { Event } from './core.ts'
|
||||||
import { ClassifiedListing, DraftClassifiedListing } from './kinds'
|
import { ClassifiedListing, DraftClassifiedListing } from './kinds.ts'
|
||||||
import { ClassifiedListingObject, generateEventTemplate, parseEvent, validateEvent } from './nip99'
|
import { ClassifiedListingObject, generateEventTemplate, parseEvent, validateEvent } from './nip99.ts'
|
||||||
import { finalizeEvent, generateSecretKey } from './pure'
|
import { finalizeEvent, generateSecretKey } from './pure.ts'
|
||||||
|
|
||||||
describe('validateEvent', () => {
|
describe('validateEvent', () => {
|
||||||
test('should return true for a valid classified listing event', () => {
|
test('should return true for a valid classified listing event', () => {
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "nostr-tools",
|
"name": "nostr-tools",
|
||||||
"version": "2.3.2",
|
"version": "2.4.0",
|
||||||
"description": "Tools for making a Nostr client.",
|
"description": "Tools for making a Nostr client.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
@@ -170,6 +170,11 @@
|
|||||||
"require": "./lib/cjs/nip57.js",
|
"require": "./lib/cjs/nip57.js",
|
||||||
"types": "./lib/types/nip57.d.ts"
|
"types": "./lib/types/nip57.d.ts"
|
||||||
},
|
},
|
||||||
|
"./nip58": {
|
||||||
|
"import": "./lib/esm/nip58.js",
|
||||||
|
"require": "./lib/cjs/nip58.js",
|
||||||
|
"types": "./lib/types/nip58.d.ts"
|
||||||
|
},
|
||||||
"./nip75": {
|
"./nip75": {
|
||||||
"import": "./lib/esm/nip75.js",
|
"import": "./lib/esm/nip75.js",
|
||||||
"require": "./lib/cjs/nip75.js",
|
"require": "./lib/cjs/nip75.js",
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"compilerOptions": {
|
"compilerOptions": {
|
||||||
"module": "esnext",
|
"module": "NodeNext",
|
||||||
"target": "esnext",
|
"target": "esnext",
|
||||||
"lib": ["dom", "dom.iterable", "esnext"],
|
"lib": ["dom", "dom.iterable", "esnext"],
|
||||||
"declaration": true,
|
"declaration": true,
|
||||||
"strict": true,
|
"strict": true,
|
||||||
"moduleResolution": "Bundler",
|
"moduleResolution": "NodeNext",
|
||||||
"skipLibCheck": true,
|
"skipLibCheck": true,
|
||||||
"esModuleInterop": true,
|
"esModuleInterop": true,
|
||||||
"emitDeclarationOnly": true,
|
"emitDeclarationOnly": true,
|
||||||
|
|||||||
4
wasm.ts
4
wasm.ts
@@ -1,6 +1,6 @@
|
|||||||
import { bytesToHex } from '@noble/hashes/utils'
|
import { bytesToHex } from '@noble/hashes/utils'
|
||||||
import { Nostr as NostrWasm } from 'nostr-wasm'
|
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
|
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 generateSecretKey = i.generateSecretKey
|
||||||
export const getPublicKey = i.getPublicKey
|
export const getPublicKey = i.getPublicKey
|
||||||
export const finalizeEvent = i.finalizeEvent
|
export const finalizeEvent = i.finalizeEvent
|
||||||
|
|||||||
Reference in New Issue
Block a user