feat: respect existing zap tags in source content when creating highlights

- Updated addZapTags function to check for existing zap tags in source event
- When source has zap tags (author group), split proportionally:
  - Highlighter gets their configured percentage
  - Remaining percentage distributed among existing authors proportionally
- Example: 50/50 split with 2 source authors = 50% highlighter, 25% each author
- Falls back to simple two-way split if no existing zap tags
- Prevents duplicate entries if highlighter is already in author group
This commit is contained in:
Gigi
2025-10-08 06:53:53 +01:00
parent a3edb64e4c
commit 29edd159e7
2 changed files with 69 additions and 13 deletions

View File

@@ -0,0 +1,20 @@
---
description: Specification for zaps and zaps splits
alwaysApply: false
---
When we create highlights, we want to add `zap` tags to the event, to allow for value splits between the highlighter/curator and the author (or authors).
`zap` tags are defined in Appendix G of NIP-57:
- https://github.com/nostr-protocol/nips/blob/master/57.md
More on `zap` tags here:
- https://nostrbook.dev/tags/zap
Note that nostr-native content might have `zap` tags already, which can be seen as the "author group" of e.g. the long-form article (writer, editor, designer, etc). We should respect these `zap` tags and include them into our "zap splits" appropriately.
Example: if our zap-split setting is 50/50, and the nostr-native blog post has two authors, our zap splits should be as follows:
- Highlighter: 50%
- Author1: 25%
- Author2: 25%

View File

@@ -77,7 +77,7 @@ export async function createHighlight(
// Add zap tags for nostr-native content (NIP-57 Appendix G) // Add zap tags for nostr-native content (NIP-57 Appendix G)
if (typeof source === 'object' && 'kind' in source) { if (typeof source === 'object' && 'kind' in source) {
const zapSplitPercentage = settings?.zapSplitPercentage ?? 50 const zapSplitPercentage = settings?.zapSplitPercentage ?? 50
addZapTags(highlightEvent, account.pubkey, source.pubkey, zapSplitPercentage) addZapTags(highlightEvent, account.pubkey, source, zapSplitPercentage)
} }
// Sign the event // Sign the event
@@ -186,31 +186,67 @@ function extractContext(selectedText: string, articleContent: string): string |
/** /**
* Adds zap tags to a highlight event for split payments (NIP-57 Appendix G) * Adds zap tags to a highlight event for split payments (NIP-57 Appendix G)
* Respects existing zap tags in the source event (author group)
* @param event The highlight event to add zap tags to * @param event The highlight event to add zap tags to
* @param highlighterPubkey The pubkey of the user creating the highlight * @param highlighterPubkey The pubkey of the user creating the highlight
* @param authorPubkey The pubkey of the original article author * @param sourceEvent The source event (may contain existing zap tags)
* @param highlighterPercentage Percentage (0-100) to give to the highlighter (default 50) * @param highlighterPercentage Percentage (0-100) to give to the highlighter (default 50)
*/ */
function addZapTags( function addZapTags(
event: NostrEvent, event: NostrEvent,
highlighterPubkey: string, highlighterPubkey: string,
authorPubkey: string, sourceEvent: NostrEvent,
highlighterPercentage: number = 50 highlighterPercentage: number = 50
): void { ): void {
// Calculate weights based on percentage
// Using simple integer weights where highlighterPercentage:authorPercentage ratio is maintained
const highlighterWeight = Math.round(highlighterPercentage)
const authorWeight = Math.round(100 - highlighterPercentage)
// Use a reliable relay for zap metadata lookup (first non-local relay) // Use a reliable relay for zap metadata lookup (first non-local relay)
const zapRelay = RELAYS.find(r => !r.includes('localhost')) || RELAYS[0] const zapRelay = RELAYS.find(r => !r.includes('localhost')) || RELAYS[0]
// Add zap tag for the highlighter // Extract existing zap tags from source event (the "author group")
event.tags.push(['zap', highlighterPubkey, zapRelay, highlighterWeight.toString()]) const existingZapTags = sourceEvent.tags.filter(tag => tag[0] === 'zap')
// Add zap tag for the original author (only if different from highlighter) if (existingZapTags.length > 0) {
if (authorPubkey !== highlighterPubkey) { // Calculate total weight from existing zap tags
event.tags.push(['zap', authorPubkey, zapRelay, authorWeight.toString()]) const totalExistingWeight = existingZapTags.reduce((sum, tag) => {
const weight = parseInt(tag[3] || '1', 10)
return sum + weight
}, 0)
// Calculate author group percentage (what remains after highlighter's share)
const authorGroupPercentage = 100 - highlighterPercentage
// Add zap tag for the highlighter
event.tags.push(['zap', highlighterPubkey, zapRelay, highlighterPercentage.toString()])
// Add proportionally adjusted zap tags for each existing author
// Don't add the highlighter again if they're already in the author group
for (const zapTag of existingZapTags) {
const authorPubkey = zapTag[1]
// Skip if this is the highlighter (they already have their share)
if (authorPubkey === highlighterPubkey) continue
const originalWeight = parseInt(zapTag[3] || '1', 10)
const originalRelay = zapTag[2] || zapRelay
// Calculate proportional weight: (original weight / total weight) * author group percentage
const adjustedWeight = Math.round((originalWeight / totalExistingWeight) * authorGroupPercentage)
// Only add if weight is greater than 0
if (adjustedWeight > 0) {
event.tags.push(['zap', authorPubkey, originalRelay, adjustedWeight.toString()])
}
}
} else {
// No existing zap tags, use simple two-way split between highlighter and source author
const authorWeight = Math.round(100 - highlighterPercentage)
// Add zap tag for the highlighter
event.tags.push(['zap', highlighterPubkey, zapRelay, highlighterPercentage.toString()])
// Add zap tag for the original author (only if different from highlighter)
if (sourceEvent.pubkey !== highlighterPubkey && authorWeight > 0) {
event.tags.push(['zap', sourceEvent.pubkey, zapRelay, authorWeight.toString()])
}
} }
} }