You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
 
 
 
 

229 lines
6.3 KiB

import { describe, expect, it } from 'vitest'
import { ExtendedKind } from '@/constants'
import {
buildAttestedPaymentIdSet,
filterAttestedProfileWallSuperchats,
getPaymentNotificationInfo,
getSuperchatPaytoType,
getSuperchatReferenceFetchId,
isProfileWallPaymentNotification,
partitionAttestedSuperchats
} from '@/lib/superchat'
import { parsePaytoTagType } from '@/lib/payto'
import { kinds, type Event } from 'nostr-tools'
const RECIPIENT = 'a'.repeat(64)
const SENDER = 'b'.repeat(64)
const ZAP_ID = 'c'.repeat(64)
const PAYMENT_ID = 'd'.repeat(64)
function fakeEvent(partial: Partial<Event> & Pick<Event, 'kind' | 'tags'>): Event {
return {
id: partial.id ?? 'e'.repeat(64),
pubkey: partial.pubkey ?? SENDER,
created_at: partial.created_at ?? 1_700_000_000,
kind: partial.kind,
tags: partial.tags,
content: partial.content ?? '',
sig: partial.sig ?? ''
}
}
describe('buildAttestedPaymentIdSet', () => {
it('collects attested zap and payment notification ids from recipient', () => {
const attestations = [
fakeEvent({
kind: ExtendedKind.PAYMENT_ATTESTATION,
pubkey: RECIPIENT,
tags: [
['e', ZAP_ID],
['k', '9735']
]
}),
fakeEvent({
kind: ExtendedKind.PAYMENT_ATTESTATION,
pubkey: RECIPIENT,
tags: [
['e', PAYMENT_ID],
['k', '9740']
]
}),
fakeEvent({
kind: ExtendedKind.PAYMENT_ATTESTATION,
pubkey: SENDER,
tags: [
['e', ZAP_ID],
['k', '9735']
]
})
]
const ids = buildAttestedPaymentIdSet(attestations, RECIPIENT)
expect(ids.has(ZAP_ID)).toBe(true)
expect(ids.has(PAYMENT_ID)).toBe(true)
expect(ids.size).toBe(2)
})
})
describe('partitionAttestedSuperchats', () => {
it('drops unattested zaps and keeps attested zaps and payment notifications', () => {
const attested = new Set([ZAP_ID, PAYMENT_ID])
const zapAttested = fakeEvent({
id: ZAP_ID,
kind: kinds.Zap,
tags: [
['P', SENDER],
['p', RECIPIENT],
['bolt11', 'lnbc210n1p0fake'],
[
'description',
JSON.stringify({
pubkey: SENDER,
content: 'Zap!',
tags: [['p', RECIPIENT], ['amount', '21000']]
})
]
]
})
const zapUnattested = fakeEvent({
id: 'f'.repeat(64),
kind: kinds.Zap,
tags: [
['P', SENDER],
['p', RECIPIENT],
['amount', '42000'],
['bolt11', 'lnbc2']
]
})
const payment = fakeEvent({
id: PAYMENT_ID,
kind: ExtendedKind.PAYMENT_NOTIFICATION,
content: 'Thanks!',
tags: [
['p', RECIPIENT],
['amount', '100000']
]
})
const comment = fakeEvent({
id: '1'.repeat(64),
kind: ExtendedKind.COMMENT,
tags: [['e', '2'.repeat(64)]]
})
const { superchats, rest } = partitionAttestedSuperchats(
[zapAttested, zapUnattested, payment, comment],
attested,
1
)
expect(superchats.map((e) => e.id)).toEqual([payment.id, zapAttested.id])
expect(rest).toEqual([comment])
})
})
describe('getPaymentNotificationInfo', () => {
it('uses only the first p, e, and a tags', () => {
const evt = fakeEvent({
kind: ExtendedKind.PAYMENT_NOTIFICATION,
tags: [
['p', RECIPIENT],
['p', SENDER],
['e', '1'.repeat(64)],
['e', '2'.repeat(64)],
['a', '30023:' + RECIPIENT + ':'],
['a', '30023:' + SENDER + ':'],
['payto', 'monero/primary'],
['payto', 'lightning/user'],
['amount', '100000'],
['amount', '200000']
]
})
const info = getPaymentNotificationInfo(evt)
expect(info?.recipientPubkey).toBe(RECIPIENT)
expect(info?.referencedEventId).toBe('1'.repeat(64))
expect(info?.referencedCoordinate).toBe('30023:' + RECIPIENT + ':')
expect(info?.payto).toBe('monero/primary')
expect(info?.amountSats).toBe(100)
})
})
describe('getSuperchatPaytoType', () => {
it('returns lightning for zap receipts', () => {
const zap = fakeEvent({ kind: kinds.Zap, tags: [['p', RECIPIENT]] })
expect(getSuperchatPaytoType(zap)).toBe('lightning')
})
it('parses payto type from payment notification', () => {
const evt = fakeEvent({
kind: ExtendedKind.PAYMENT_NOTIFICATION,
tags: [
['p', RECIPIENT],
['payto', 'geyser/project123']
]
})
expect(getSuperchatPaytoType(evt)).toBe('geyser')
expect(parsePaytoTagType('lightning/user%40example.com')).toBe('lightning')
})
})
describe('getSuperchatReferenceFetchId', () => {
it('prefers event id over coordinate', () => {
const info = {
senderPubkey: SENDER,
recipientPubkey: RECIPIENT,
amountSats: 0,
referencedEventId: '1'.repeat(64),
referencedCoordinate: `30023:${RECIPIENT}:article`
}
expect(getSuperchatReferenceFetchId(info)).toBe('1'.repeat(64))
})
it('returns naddr for replaceable coordinate when no e tag', () => {
const info = {
senderPubkey: SENDER,
recipientPubkey: RECIPIENT,
amountSats: 0,
referencedCoordinate: `30023:${RECIPIENT}:my-article`
}
const id = getSuperchatReferenceFetchId(info)
expect(id).toBeTruthy()
expect(id).toMatch(/^naddr1/)
})
})
describe('profile wall payment notifications', () => {
it('accepts profile-only 9740 without thread reference', () => {
const evt = fakeEvent({
kind: ExtendedKind.PAYMENT_NOTIFICATION,
tags: [
['p', RECIPIENT],
['amount', '50000']
]
})
expect(isProfileWallPaymentNotification(evt, RECIPIENT)).toBe(true)
expect(getPaymentNotificationInfo(evt)?.amountSats).toBe(50)
})
it('filters to attested profile wall superchats', () => {
const paymentId = PAYMENT_ID
const payment = fakeEvent({
id: paymentId,
kind: ExtendedKind.PAYMENT_NOTIFICATION,
content: 'Wall tip',
tags: [
['p', RECIPIENT],
['amount', '21000']
]
})
const attestation = fakeEvent({
kind: ExtendedKind.PAYMENT_ATTESTATION,
pubkey: RECIPIENT,
tags: [
['e', paymentId],
['k', '9740']
]
})
const out = filterAttestedProfileWallSuperchats([payment], [attestation], RECIPIENT)
expect(out).toHaveLength(1)
expect(out[0]!.id).toBe(paymentId)
})
})