mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-10 17:18:51 +00:00
Compare commits
2 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
929d62bbbb | ||
|
|
b575e47844 |
2
jsr.json
2
jsr.json
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "@nostr/tools",
|
"name": "@nostr/tools",
|
||||||
"version": "2.16.0",
|
"version": "2.16.1",
|
||||||
"exports": {
|
"exports": {
|
||||||
".": "./index.ts",
|
".": "./index.ts",
|
||||||
"./core": "./core.ts",
|
"./core": "./core.ts",
|
||||||
|
|||||||
109
nip57.test.ts
109
nip57.test.ts
@@ -1,112 +1,7 @@
|
|||||||
import { describe, test, expect, mock } from 'bun:test'
|
import { describe, test, expect } from 'bun:test'
|
||||||
import { finalizeEvent } from './pure.ts'
|
import { finalizeEvent } from './pure.ts'
|
||||||
import { getPublicKey, generateSecretKey } from './pure.ts'
|
import { getPublicKey, generateSecretKey } from './pure.ts'
|
||||||
import {
|
import { getSatoshisAmountFromBolt11, makeZapReceipt, validateZapRequest } from './nip57.ts'
|
||||||
getSatoshisAmountFromBolt11,
|
|
||||||
getZapEndpoint,
|
|
||||||
makeZapReceipt,
|
|
||||||
makeZapRequest,
|
|
||||||
useFetchImplementation,
|
|
||||||
validateZapRequest,
|
|
||||||
} from './nip57.ts'
|
|
||||||
import { buildEvent } from './test-helpers.ts'
|
|
||||||
|
|
||||||
describe('getZapEndpoint', () => {
|
|
||||||
test('returns null if neither lud06 nor lud16 is present', async () => {
|
|
||||||
const metadata = buildEvent({ kind: 0, content: '{}' })
|
|
||||||
const result = await getZapEndpoint(metadata)
|
|
||||||
|
|
||||||
expect(result).toBeNull()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('returns null if fetch fails', async () => {
|
|
||||||
const fetchImplementation = mock(() => Promise.reject(new Error()))
|
|
||||||
useFetchImplementation(fetchImplementation)
|
|
||||||
|
|
||||||
const metadata = buildEvent({ kind: 0, content: '{"lud16": "name@domain"}' })
|
|
||||||
const result = await getZapEndpoint(metadata)
|
|
||||||
|
|
||||||
expect(result).toBeNull()
|
|
||||||
expect(fetchImplementation).toHaveBeenCalledWith('https://domain/.well-known/lnurlp/name')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('returns null if the response does not allow Nostr payments', async () => {
|
|
||||||
const fetchImplementation = mock(() => Promise.resolve({ json: () => ({ allowsNostr: false }) }))
|
|
||||||
useFetchImplementation(fetchImplementation)
|
|
||||||
|
|
||||||
const metadata = buildEvent({ kind: 0, content: '{"lud16": "name@domain"}' })
|
|
||||||
const result = await getZapEndpoint(metadata)
|
|
||||||
|
|
||||||
expect(result).toBeNull()
|
|
||||||
expect(fetchImplementation).toHaveBeenCalledWith('https://domain/.well-known/lnurlp/name')
|
|
||||||
})
|
|
||||||
|
|
||||||
test('returns the callback URL if the response allows Nostr payments', async () => {
|
|
||||||
const fetchImplementation = mock(() =>
|
|
||||||
Promise.resolve({
|
|
||||||
json: () => ({
|
|
||||||
allowsNostr: true,
|
|
||||||
nostrPubkey: 'pubkey',
|
|
||||||
callback: 'callback',
|
|
||||||
}),
|
|
||||||
}),
|
|
||||||
)
|
|
||||||
useFetchImplementation(fetchImplementation)
|
|
||||||
|
|
||||||
const metadata = buildEvent({ kind: 0, content: '{"lud16": "name@domain"}' })
|
|
||||||
const result = await getZapEndpoint(metadata)
|
|
||||||
|
|
||||||
expect(result).toBe('callback')
|
|
||||||
expect(fetchImplementation).toHaveBeenCalledWith('https://domain/.well-known/lnurlp/name')
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('makeZapRequest', () => {
|
|
||||||
test('throws an error if amount is not given', () => {
|
|
||||||
expect(() =>
|
|
||||||
// @ts-expect-error
|
|
||||||
makeZapRequest({
|
|
||||||
profile: 'profile',
|
|
||||||
event: null,
|
|
||||||
relays: [],
|
|
||||||
comment: '',
|
|
||||||
}),
|
|
||||||
).toThrow()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('throws an error if profile is not given', () => {
|
|
||||||
expect(() =>
|
|
||||||
// @ts-expect-error
|
|
||||||
makeZapRequest({
|
|
||||||
event: null,
|
|
||||||
amount: 100,
|
|
||||||
relays: [],
|
|
||||||
comment: '',
|
|
||||||
}),
|
|
||||||
).toThrow()
|
|
||||||
})
|
|
||||||
|
|
||||||
test('returns a valid Zap request', () => {
|
|
||||||
const result = makeZapRequest({
|
|
||||||
profile: 'profile',
|
|
||||||
event: 'event',
|
|
||||||
amount: 100,
|
|
||||||
relays: ['relay1', 'relay2'],
|
|
||||||
comment: 'comment',
|
|
||||||
})
|
|
||||||
expect(result.kind).toBe(9734)
|
|
||||||
expect(result.created_at).toBeCloseTo(Date.now() / 1000, 0)
|
|
||||||
expect(result.content).toBe('comment')
|
|
||||||
expect(result.tags).toEqual(
|
|
||||||
expect.arrayContaining([
|
|
||||||
['p', 'profile'],
|
|
||||||
['amount', '100'],
|
|
||||||
['relays', 'relay1', 'relay2'],
|
|
||||||
['e', 'event'],
|
|
||||||
]),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
})
|
|
||||||
|
|
||||||
describe('validateZapRequest', () => {
|
describe('validateZapRequest', () => {
|
||||||
test('returns an error message for invalid JSON', () => {
|
test('returns an error message for invalid JSON', () => {
|
||||||
|
|||||||
53
nip57.ts
53
nip57.ts
@@ -1,6 +1,6 @@
|
|||||||
import { bech32 } from '@scure/base'
|
import { bech32 } from '@scure/base'
|
||||||
|
|
||||||
import { validateEvent, verifyEvent, type Event, type EventTemplate } from './pure.ts'
|
import { NostrEvent, validateEvent, verifyEvent, type Event, type EventTemplate } from './pure.ts'
|
||||||
import { utf8Decoder } from './utils.ts'
|
import { utf8Decoder } from './utils.ts'
|
||||||
import { isReplaceableKind, isAddressableKind } from './kinds.ts'
|
import { isReplaceableKind, isAddressableKind } from './kinds.ts'
|
||||||
|
|
||||||
@@ -42,48 +42,43 @@ export async function getZapEndpoint(metadata: Event): Promise<null | string> {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
export function makeZapRequest({
|
type ProfileZap = {
|
||||||
profile,
|
pubkey: string
|
||||||
event,
|
|
||||||
amount,
|
|
||||||
relays,
|
|
||||||
comment = '',
|
|
||||||
}: {
|
|
||||||
profile: string
|
|
||||||
event: string | Event | null
|
|
||||||
amount: number
|
amount: number
|
||||||
comment: string
|
comment?: string
|
||||||
relays: string[]
|
relays: string[]
|
||||||
}): EventTemplate {
|
}
|
||||||
if (!amount) throw new Error('amount not given')
|
|
||||||
if (!profile) throw new Error('profile not given')
|
|
||||||
|
|
||||||
|
type EventZap = {
|
||||||
|
event: NostrEvent
|
||||||
|
amount: number
|
||||||
|
comment?: string
|
||||||
|
relays: string[]
|
||||||
|
}
|
||||||
|
|
||||||
|
export function makeZapRequest(params: ProfileZap | EventZap): EventTemplate {
|
||||||
let zr: EventTemplate = {
|
let zr: EventTemplate = {
|
||||||
kind: 9734,
|
kind: 9734,
|
||||||
created_at: Math.round(Date.now() / 1000),
|
created_at: Math.round(Date.now() / 1000),
|
||||||
content: comment,
|
content: params.comment || '',
|
||||||
tags: [
|
tags: [
|
||||||
['p', profile],
|
['p', 'pubkey' in params ? params.pubkey : params.event.pubkey],
|
||||||
['amount', amount.toString()],
|
['amount', params.amount.toString()],
|
||||||
['relays', ...relays],
|
['relays', ...params.relays],
|
||||||
],
|
],
|
||||||
}
|
}
|
||||||
|
|
||||||
if (event && typeof event === 'string') {
|
if ('event' in params) {
|
||||||
zr.tags.push(['e', event])
|
if (isReplaceableKind(params.event.kind)) {
|
||||||
}
|
const a = ['a', `${params.event.kind}:${params.event.pubkey}:`]
|
||||||
if (event && typeof event === 'object') {
|
|
||||||
// replacable event
|
|
||||||
if (isReplaceableKind(event.kind)) {
|
|
||||||
const a = ['a', `${event.kind}:${event.pubkey}:`]
|
|
||||||
zr.tags.push(a)
|
zr.tags.push(a)
|
||||||
// addressable event
|
} else if (isAddressableKind(params.event.kind)) {
|
||||||
} else if (isAddressableKind(event.kind)) {
|
let d = params.event.tags.find(([t, v]) => t === 'd' && v)
|
||||||
let d = event.tags.find(([t, v]) => t === 'd' && v)
|
|
||||||
if (!d) throw new Error('d tag not found or is empty')
|
if (!d) throw new Error('d tag not found or is empty')
|
||||||
const a = ['a', `${event.kind}:${event.pubkey}:${d[1]}`]
|
const a = ['a', `${params.event.kind}:${params.event.pubkey}:${d[1]}`]
|
||||||
zr.tags.push(a)
|
zr.tags.push(a)
|
||||||
}
|
}
|
||||||
|
zr.tags.push(['k', params.event.kind.toString()])
|
||||||
}
|
}
|
||||||
|
|
||||||
return zr
|
return zr
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
{
|
{
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"name": "nostr-tools",
|
"name": "nostr-tools",
|
||||||
"version": "2.16.0",
|
"version": "2.16.1",
|
||||||
"description": "Tools for making a Nostr client.",
|
"description": "Tools for making a Nostr client.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
Reference in New Issue
Block a user