mirror of
https://github.com/nbd-wtf/nostr-tools.git
synced 2025-12-10 17:18:51 +00:00
Compare commits
3 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
51b8f42529 | ||
|
|
24d885aaeb | ||
|
|
74c77a2e9f |
335
nip57.test.js
Normal file
335
nip57.test.js
Normal file
@@ -0,0 +1,335 @@
|
|||||||
|
const {bech32} = require('@scure/base')
|
||||||
|
const {
|
||||||
|
nip57,
|
||||||
|
generatePrivateKey,
|
||||||
|
getPublicKey,
|
||||||
|
finishEvent
|
||||||
|
} = require('./lib/nostr.cjs')
|
||||||
|
|
||||||
|
describe('getZapEndpoint', () => {
|
||||||
|
test('returns null if neither lud06 nor lud16 is present', async () => {
|
||||||
|
const metadata = {content: '{}'}
|
||||||
|
const result = await nip57.getZapEndpoint(metadata)
|
||||||
|
|
||||||
|
expect(result).toBeNull()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns null if fetch fails', async () => {
|
||||||
|
const fetchImplementation = jest.fn(() => Promise.reject(new Error()))
|
||||||
|
nip57.useFetchImplementation(fetchImplementation)
|
||||||
|
|
||||||
|
const metadata = {content: '{"lud16": "name@domain"}'}
|
||||||
|
const result = await nip57.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 = jest.fn(() =>
|
||||||
|
Promise.resolve({json: () => ({allowsNostr: false})})
|
||||||
|
)
|
||||||
|
nip57.useFetchImplementation(fetchImplementation)
|
||||||
|
|
||||||
|
const metadata = {content: '{"lud16": "name@domain"}'}
|
||||||
|
const result = await nip57.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 = jest.fn(() =>
|
||||||
|
Promise.resolve({
|
||||||
|
json: () => ({
|
||||||
|
allowsNostr: true,
|
||||||
|
nostrPubkey: 'pubkey',
|
||||||
|
callback: 'callback'
|
||||||
|
})
|
||||||
|
})
|
||||||
|
)
|
||||||
|
nip57.useFetchImplementation(fetchImplementation)
|
||||||
|
|
||||||
|
const metadata = {content: '{"lud16": "name@domain"}'}
|
||||||
|
const result = await nip57.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(() =>
|
||||||
|
nip57.makeZapRequest({
|
||||||
|
profile: 'profile',
|
||||||
|
event: null,
|
||||||
|
relays: [],
|
||||||
|
comment: ''
|
||||||
|
})
|
||||||
|
).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('throws an error if profile is not given', () => {
|
||||||
|
expect(() =>
|
||||||
|
nip57.makeZapRequest({
|
||||||
|
event: null,
|
||||||
|
amount: 100,
|
||||||
|
relays: [],
|
||||||
|
comment: ''
|
||||||
|
})
|
||||||
|
).toThrow()
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns a valid Zap request', () => {
|
||||||
|
const result = nip57.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']
|
||||||
|
])
|
||||||
|
)
|
||||||
|
expect(result.tags).toContainEqual(['e', 'event'])
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('validateZapRequest', () => {
|
||||||
|
test('returns an error message for invalid JSON', () => {
|
||||||
|
expect(nip57.validateZapRequest('invalid JSON')).toBe(
|
||||||
|
'Invalid zap request JSON.'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns an error message if the Zap request is not a valid Nostr event', () => {
|
||||||
|
const zapRequest = {
|
||||||
|
kind: 1234,
|
||||||
|
created_at: Date.now() / 1000,
|
||||||
|
content: 'content',
|
||||||
|
tags: [
|
||||||
|
['p', 'profile'],
|
||||||
|
['amount', '100'],
|
||||||
|
['relays', 'relay1', 'relay2']
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(nip57.validateZapRequest(JSON.stringify(zapRequest))).toBe(
|
||||||
|
'Zap request is not a valid Nostr event.'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns an error message if the signature on the Zap request is invalid', () => {
|
||||||
|
const privateKey = generatePrivateKey()
|
||||||
|
const publicKey = getPublicKey(privateKey)
|
||||||
|
|
||||||
|
const zapRequest = {
|
||||||
|
pubkey: publicKey,
|
||||||
|
kind: 9734,
|
||||||
|
created_at: Date.now() / 1000,
|
||||||
|
content: 'content',
|
||||||
|
tags: [
|
||||||
|
['p', publicKey],
|
||||||
|
['amount', '100'],
|
||||||
|
['relays', 'relay1', 'relay2']
|
||||||
|
]
|
||||||
|
}
|
||||||
|
|
||||||
|
expect(nip57.validateZapRequest(JSON.stringify(zapRequest))).toBe(
|
||||||
|
'Invalid signature on zap request.'
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns an error message if the Zap request does not have a "p" tag', () => {
|
||||||
|
const privateKey = generatePrivateKey()
|
||||||
|
|
||||||
|
const zapRequest = finishEvent(
|
||||||
|
{
|
||||||
|
kind: 9734,
|
||||||
|
created_at: Date.now() / 1000,
|
||||||
|
content: 'content',
|
||||||
|
tags: [
|
||||||
|
['amount', '100'],
|
||||||
|
['relays', 'relay1', 'relay2']
|
||||||
|
]
|
||||||
|
},
|
||||||
|
privateKey
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(nip57.validateZapRequest(JSON.stringify(zapRequest))).toBe(
|
||||||
|
"Zap request doesn't have a 'p' tag."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns an error message if the "p" tag on the Zap request is not valid hex', () => {
|
||||||
|
const privateKey = generatePrivateKey()
|
||||||
|
|
||||||
|
const zapRequest = finishEvent(
|
||||||
|
{
|
||||||
|
kind: 9734,
|
||||||
|
created_at: Date.now() / 1000,
|
||||||
|
content: 'content',
|
||||||
|
tags: [
|
||||||
|
['p', 'invalid hex'],
|
||||||
|
['amount', '100'],
|
||||||
|
['relays', 'relay1', 'relay2']
|
||||||
|
]
|
||||||
|
},
|
||||||
|
privateKey
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(nip57.validateZapRequest(JSON.stringify(zapRequest))).toBe(
|
||||||
|
"Zap request 'p' tag is not valid hex."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns an error message if the "e" tag on the Zap request is not valid hex', () => {
|
||||||
|
const privateKey = generatePrivateKey()
|
||||||
|
const publicKey = getPublicKey(privateKey)
|
||||||
|
|
||||||
|
const zapRequest = finishEvent(
|
||||||
|
{
|
||||||
|
kind: 9734,
|
||||||
|
created_at: Date.now() / 1000,
|
||||||
|
content: 'content',
|
||||||
|
tags: [
|
||||||
|
['p', publicKey],
|
||||||
|
['e', 'invalid hex'],
|
||||||
|
['amount', '100'],
|
||||||
|
['relays', 'relay1', 'relay2']
|
||||||
|
]
|
||||||
|
},
|
||||||
|
privateKey
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(nip57.validateZapRequest(JSON.stringify(zapRequest))).toBe(
|
||||||
|
"Zap request 'e' tag is not valid hex."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns an error message if the Zap request does not have a relays tag', () => {
|
||||||
|
const privateKey = generatePrivateKey()
|
||||||
|
const publicKey = getPublicKey(privateKey)
|
||||||
|
|
||||||
|
const zapRequest = finishEvent(
|
||||||
|
{
|
||||||
|
kind: 9734,
|
||||||
|
created_at: Date.now() / 1000,
|
||||||
|
content: 'content',
|
||||||
|
tags: [
|
||||||
|
['p', publicKey],
|
||||||
|
['amount', '100']
|
||||||
|
]
|
||||||
|
},
|
||||||
|
privateKey
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(nip57.validateZapRequest(JSON.stringify(zapRequest))).toBe(
|
||||||
|
"Zap request doesn't have a 'relays' tag."
|
||||||
|
)
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns null for a valid Zap request', () => {
|
||||||
|
const privateKey = generatePrivateKey()
|
||||||
|
const publicKey = getPublicKey(privateKey)
|
||||||
|
|
||||||
|
const zapRequest = finishEvent(
|
||||||
|
{
|
||||||
|
kind: 9734,
|
||||||
|
created_at: Date.now() / 1000,
|
||||||
|
content: 'content',
|
||||||
|
tags: [
|
||||||
|
['p', publicKey],
|
||||||
|
['amount', '100'],
|
||||||
|
['relays', 'relay1', 'relay2']
|
||||||
|
]
|
||||||
|
},
|
||||||
|
privateKey
|
||||||
|
)
|
||||||
|
|
||||||
|
expect(nip57.validateZapRequest(JSON.stringify(zapRequest))).toBeNull()
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
describe('makeZapReceipt', () => {
|
||||||
|
test('returns a valid Zap receipt with a preimage', () => {
|
||||||
|
const privateKey = generatePrivateKey()
|
||||||
|
const publicKey = getPublicKey(privateKey)
|
||||||
|
|
||||||
|
const zapRequest = JSON.stringify(
|
||||||
|
finishEvent(
|
||||||
|
{
|
||||||
|
kind: 9734,
|
||||||
|
created_at: Date.now() / 1000,
|
||||||
|
content: 'content',
|
||||||
|
tags: [
|
||||||
|
['p', publicKey],
|
||||||
|
['amount', '100'],
|
||||||
|
['relays', 'relay1', 'relay2']
|
||||||
|
]
|
||||||
|
},
|
||||||
|
privateKey
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const preimage = 'preimage'
|
||||||
|
const bolt11 = 'bolt11'
|
||||||
|
const paidAt = new Date()
|
||||||
|
|
||||||
|
const result = nip57.makeZapReceipt({zapRequest, preimage, bolt11, paidAt})
|
||||||
|
|
||||||
|
expect(result.kind).toBe(9735)
|
||||||
|
expect(result.created_at).toBeCloseTo(paidAt.getTime() / 1000, 0)
|
||||||
|
expect(result.content).toBe('')
|
||||||
|
expect(result.tags).toContainEqual(['bolt11', bolt11])
|
||||||
|
expect(result.tags).toContainEqual(['description', zapRequest])
|
||||||
|
expect(result.tags).toContainEqual(['p', publicKey])
|
||||||
|
expect(result.tags).toContainEqual(['preimage', preimage])
|
||||||
|
})
|
||||||
|
|
||||||
|
test('returns a valid Zap receipt without a preimage', () => {
|
||||||
|
const privateKey = generatePrivateKey()
|
||||||
|
const publicKey = getPublicKey(privateKey)
|
||||||
|
|
||||||
|
const zapRequest = JSON.stringify(
|
||||||
|
finishEvent(
|
||||||
|
{
|
||||||
|
kind: 9734,
|
||||||
|
created_at: Date.now() / 1000,
|
||||||
|
content: 'content',
|
||||||
|
tags: [
|
||||||
|
['p', publicKey],
|
||||||
|
['amount', '100'],
|
||||||
|
['relays', 'relay1', 'relay2']
|
||||||
|
]
|
||||||
|
},
|
||||||
|
privateKey
|
||||||
|
)
|
||||||
|
)
|
||||||
|
const bolt11 = 'bolt11'
|
||||||
|
const paidAt = new Date()
|
||||||
|
|
||||||
|
const result = nip57.makeZapReceipt({zapRequest, bolt11, paidAt})
|
||||||
|
|
||||||
|
expect(result.kind).toBe(9735)
|
||||||
|
expect(result.created_at).toBeCloseTo(paidAt.getTime() / 1000, 0)
|
||||||
|
expect(result.content).toBe('')
|
||||||
|
expect(result.tags).toContainEqual(['bolt11', bolt11])
|
||||||
|
expect(result.tags).toContainEqual(['description', zapRequest])
|
||||||
|
expect(result.tags).toContainEqual(['p', publicKey])
|
||||||
|
expect(result.tags).not.toContain('preimage')
|
||||||
|
})
|
||||||
|
})
|
||||||
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "nostr-tools",
|
"name": "nostr-tools",
|
||||||
"version": "1.8.0",
|
"version": "1.8.1",
|
||||||
"description": "Tools for making a Nostr client.",
|
"description": "Tools for making a Nostr client.",
|
||||||
"repository": {
|
"repository": {
|
||||||
"type": "git",
|
"type": "git",
|
||||||
|
|||||||
2
pool.ts
2
pool.ts
@@ -55,7 +55,6 @@ export class SimplePool {
|
|||||||
let set = this._seenOn[id] || new Set()
|
let set = this._seenOn[id] || new Set()
|
||||||
set.add(url)
|
set.add(url)
|
||||||
this._seenOn[id] = set
|
this._seenOn[id] = set
|
||||||
_knownIds.add(id)
|
|
||||||
return _knownIds.has(id)
|
return _knownIds.has(id)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -81,6 +80,7 @@ export class SimplePool {
|
|||||||
if (!r) return
|
if (!r) return
|
||||||
let s = r.sub(filters, modifiedOpts)
|
let s = r.sub(filters, modifiedOpts)
|
||||||
s.on('event', (event: Event) => {
|
s.on('event', (event: Event) => {
|
||||||
|
_knownIds.add(event.id as string)
|
||||||
for (let cb of eventListeners.values()) cb(event)
|
for (let cb of eventListeners.values()) cb(event)
|
||||||
})
|
})
|
||||||
s.on('eose', () => {
|
s.on('eose', () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user