Skip to content

Commit 933f114

Browse files
committed
add nip SDAN draft
1 parent d64fc0e commit 933f114

File tree

6 files changed

+1068
-1
lines changed

6 files changed

+1068
-1
lines changed

.gitignore

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
11
venv
22
node_modules
3-
site
3+
site
4+
docs/nip-drafts/*-draft.md

docs/nip-drafts/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,2 +1,3 @@
11
- [NIP-RTC](nip-RTC.md)
22
- [NIP-GAMERTAG](nip-GAMERTAG.md)
3+
- [NIP-SDAN](nip-SDAN.md)

docs/nip-drafts/nip-SDAN.md

Lines changed: 337 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,337 @@
1+
## NOSTR Simple Decentralized Advertising Network (NOSTR‑SDAN)
2+
3+
`draft` `optional` `author:riccardobl`
4+
5+
---
6+
7+
## Rationale
8+
9+
Most digital products rely on data‑driven advertising. NOSTR‑SDAN brings this model to NOSTR apps, enabling decentralized ad auctions and payouts. By standardizing bidding, offers, and payments via NOSTR events, applications can:
10+
11+
* Monetize through ad space auctions
12+
* Distribute ads across multiple offerers
13+
* Maintain transparency and decentralization
14+
15+
---
16+
17+
# High Level Overview
18+
19+
1. **Advertiser** publishes a **Bidding Event** (`kind:30100`) with ad description, bid parameters, and targeting.
20+
2. **Offerers** filter these events, then respond with an **Offer ActionEvent** (`kind:30101`, `type=offer`) containing a payment invoice.
21+
3. **Advertiser** reviews offers and sends **Accept** or **Reject** **ActionEvents** (`type=accept_offer` or `type=reject_offer`).
22+
4. Upon acceptance, **Offerer** displays the ad; once the user action completes, sends a **Payment Request** (`type=payment_request`).
23+
5. **Advertiser** pays and emits either a **Payout** (`type=payout`) or **Payout Error** (`type=payout_error`).
24+
6. Parties may impose NIP‑13 PoW challenges (`difficulty` field) to deter cheating.
25+
26+
---
27+
28+
## Nomenclature
29+
30+
* **Advertiser**: Entity promoting a product or service, paying per user action.
31+
* **Offerer**: Entity offering ad space in their application.
32+
* **Bid**: Advertiser’s proposed msat amount per action.
33+
* **Action**: User interaction that triggers payment (e.g., view, click).
34+
* **PoW Difficulty**: Optional proof‑of‑work requirement to penalize misbehavior.
35+
36+
---
37+
38+
## Bidding Event
39+
40+
A replaceable event (`kind:30100`) where an advertiser bids for ad placement.
41+
42+
```json
43+
{
44+
"kind": 30100,
45+
"content": json({
46+
"description": "<product description>",
47+
"context": "<product context in natural language for semantic search>",
48+
"payload": "<link or text for the ad>",
49+
"size": [<width>, <height>],
50+
"link": "<link to open when the ad is interacted with>",
51+
"call_to_action": "<text for the call to action>",
52+
"bid": "<amount in msats to pay per action>",
53+
"hold_time": "<time in seconds>",
54+
}),
55+
"tags": [
56+
["k", "<action type>"],
57+
["m", "<mime/type>"],
58+
["h", "<tier1 category>"],
59+
["H", "<tier2 category>"],
60+
["T", "<tier3 category>"],
61+
["t", "<tier4 category>"],
62+
["l", "<language>"],
63+
["p", "<target>", "<target>", ...],
64+
["d", "<ad id>"]
65+
["f", "<slot bid>"],
66+
["s", "<slot size>"],
67+
["expiration", "<timestamp>"]
68+
]
69+
}
70+
```
71+
72+
### Content Structure
73+
74+
* **description** (`required`): User‑facing product/service description.
75+
* **context** (`optional`): Natural‑language context for optional semantic matching (not displayed).
76+
* **payload** (`required`): Ad payload (text or image URL).
77+
* **size** (`required`): `[width, height]` in pixels.
78+
* **link** (`optional`): URL opened on interaction.
79+
* **call\_to\_action** (`optional`): Button/text prompt (e.g., “Buy now”). The offerer may choose to display this prompt as part of the ad, or omit it entirely. If not set, the offerer may apply a default call-to-action of their own choosing.
80+
* **bid** (`required`): The amount in msats the advertiser is willing to pay for a successful action.
81+
* **hold\_time** (`optional`): Seconds the advertiser will reserve funds after the offer has been accepted, while waiting for the action to be completed.
82+
83+
### Tags
84+
85+
#### Action Type (`k`) `required`
86+
87+
Specifies the user action that triggers payment. Available types:
88+
89+
* `link`: Paid when a user clicks the ad and successfully opens the target URL.
90+
* `view`: Paid when the ad is rendered and visible to the user.
91+
* `conversion`: Paid when a user completes a defined goal (e.g., newsletter signup, purchase) after interacting with the ad.
92+
* `attention`: Paid when the user demonstrates focused engagement (e.g., hovering, dwell time) with the ad content.
93+
94+
#### Mime Type (`m`) `required`
95+
96+
Specifies the content format of the ad payload. Supported types:
97+
98+
* `image/png`: PNG image URL
99+
* `image/jpeg`: JPEG image URL
100+
* `image/gif`: GIF image URL
101+
* `text/plain`: Plain text content
102+
* `text/markdown`: Markdown-formatted text content
103+
104+
#### Categories (`h`,`H`,`T`,`t`) `optional` `recommended`
105+
106+
Organize ads into a hierarchical taxonomy for targeted filtering. Tiers:
107+
108+
* `h`: Tier 1 category
109+
* `H`: Tier 2 category
110+
* `T`: Tier 3 category
111+
* `t`: Tier 4 category
112+
113+
Refer to the [Nostr Content Taxonomy](../nostr-content-taxonomy) [\[csv\]](nostr-content-taxonomy.csv) (adapted from IAB Content Taxonomy) for full list of categories.
114+
115+
116+
#### Language (`l`) `optional`
117+
118+
Specifies the language of the ad content. Offerers may choose to display ads only to users matching this two‑letter ISO 639‑1 code (e.g., `en`, `es`).
119+
120+
#### Targets (`p`) `optional`
121+
122+
Specifies one or more offerer pubkeys that are authorized to offer to this bid. If an offer originates from a pubkey not listed here, the advertiser may automatically reject it.
123+
124+
This can be used to target specific apps.
125+
126+
#### Ad ID (`d`) `required`
127+
128+
* Unique identifier in advertiser’s namespace.
129+
130+
#### Slots `required`
131+
132+
Bids can fill predefined slots that the offerer can use to filter out ads they cannot or do not wish to display. This filtering occurs at the relay level before more detailed ranking takes place.
133+
134+
**f tag**
135+
136+
Format: `BTC<amount>`
137+
Indicates the minimum millisatoshi (payment) the advertiser is willing to pay per action.
138+
Accepted values are:
139+
140+
* `BTC10_000` (10 satoshis)
141+
* `BTC100_000` (100 satoshis)
142+
* `BTC1_000_000` (1 000 satoshis)
143+
* `BTC2_000_000` (2 000 satoshis)
144+
* `BTC5_000_000` (5 000 satoshis)
145+
* `BTC10_000_000` (10 000 satoshis)
146+
* `BTC50_000_000` (50 000 satoshis)
147+
148+
Offerers can filter out bids below their minimum acceptable satoshi amount.
149+
150+
**s tag**
151+
152+
Format: `<width>x<height>`
153+
Specifies the maximum container size in pixels that can contain the ad
154+
Accepted values are:
155+
156+
- 120x90
157+
- 180x150
158+
- 728x90
159+
- 300x250
160+
- 720x300
161+
- 240x400
162+
- 250x250
163+
- 300x600
164+
- 160x600
165+
- 336x280
166+
167+
The ad does not have to fill the entire container, but it must fit within the specified dimensions.
168+
This allows offerers to pre-filter bids based on their rough ad space capacity.
169+
170+
#### expiration tag `optional`
171+
172+
* Unix timestamp (seconds) when bid expires.
173+
174+
---
175+
176+
## Cancellation Event
177+
178+
Advertiser cancels a bid by publishing a NOSTR **Deletion** (`kind:5`) referencing the bidding event ID.
179+
180+
---
181+
182+
## Action Events
183+
184+
Replaceable NIP‑44 encrypted events (`kind:30101`) used for offer, acceptance, payment requests, and payouts.
185+
186+
```yaml
187+
{
188+
"kind": 30101,
189+
"content": nip44(json({
190+
"type": "<action type>",
191+
"message": "<status message>",
192+
"difficulty": <optional pow difficulty to respond to the action>,
193+
// ... other fields depending on the action type ...
194+
})),
195+
"tags": [
196+
["e","<bidding_event_id>"],
197+
["p","<counterparty_pubkey>"]
198+
]
199+
}
200+
```
201+
202+
### Common Fields
203+
204+
* **type** (`required`): Subtype (`offer`, `accept_offer`, etc.).
205+
* **message** (`required`): Human‑readable status or error.
206+
* **difficulty** (`optional`): A number defining the number of leading zero bits required in the event ID of the response ActionEvent, as defined in NIP‑13. This proof-of-work mechanism is used to penalize a counterparty that fails to follow the protocol rules. See the [Punishments and Due Diligence](#punishments-and-due-diligence) section for more details.
207+
208+
### Common Tags
209+
* **e** (`required`): The ID of the original bidding event this ActionEvent is related to.
210+
* **p** (`required`): The pubkey of the counterparty (offerer or advertiser) this ActionEvent is directed to.
211+
212+
213+
### Offer ActionEvent (offerer → advertiser)
214+
215+
This Action Event is used by the offerer to propose its ad space for a given bid. The `content` field (encrypted via NIP‑44) must include:
216+
217+
* **type**: `offer`
218+
* **invoice** (`required`): A BOLT11 invoice, BOLT12 offer, or LNURL address for payment.
219+
220+
The tags must include:
221+
* **e**: The ID of the original bidding event this offer is responding to.
222+
* **p**: The pubkey of the advertiser who made the bid.
223+
224+
Upon receiving this event, the advertiser may accept or reject using the corresponding ActionEvents.
225+
226+
### Accept Offer ActionEvent (advertiser → offerer)
227+
228+
This Action Event is used by the advertiser to accept a specific offer. The encrypted `content` field (NIP‑44) must include:
229+
230+
* **type**: `accept_offer`
231+
232+
The tags must include:
233+
* **e**: The ID of the original bidding event this offer is responding to.
234+
* **p**: The pubkey of the offerer who made the offer.
235+
236+
Upon sending this event, the advertiser must reserve (hold) the full `bid` amount from their funds for up to the specified `hold_time`. This reservation guarantees payment to the offerer once the user action completes and the offerer submits a Payment Request.
237+
238+
### Reject Offer ActionEvent (advertiser → offerer)
239+
240+
This Action Event is used by the advertiser to decline a specific offer. The encrypted `content` field (NIP‑44) must include:
241+
242+
* **type**: `reject_offer`
243+
* **message** (`required`): A string explaining the reason for rejection.
244+
245+
The tags must include:
246+
* **e**: The ID of the original bidding event this offer is responding to.
247+
* **p**: The pubkey of the offerer who made the offer.
248+
249+
Advertisers may reject offers for reasons such as:
250+
251+
* **out\_of\_budget**: No remaining budget for ads.
252+
* **payment\_method\_not\_supported**: Unsupported invoice or payment method.
253+
* **invoice\_expired\_or\_invalid**: Invoice does not match the bid or has expired.
254+
* **expired**: The bid or ad has expired and can no longer be accepted.
255+
256+
Implementers can define additional custom reason strings as needed.
257+
258+
### Payment Request ActionEvent (offerer → advertiser)
259+
260+
Upon receiving an **Accept Offer** ActionEvent, the offerer’s application must begin delivering the ad or user interaction flow. Some actions (e.g., `conversion`) may require user input or external processes and could fail or timeout.
261+
262+
When the offerer has reason to believe the requested action has been successfully completed, it MUST publish a **Payment Request** ActionEvent (`type=payment_request`) with the following encrypted NIP‑44 content:
263+
264+
* **type**: `payment_request`
265+
* **message** (`required`): A human‑readable explanation of why the action is considered complete (e.g., “Ad displayed to user”, “User clicked link”, “Purchase confirmed”). This explanation can be evaluated by a human or an automated reviewer to determine whether the action was successfully completed.
266+
267+
The tags must include:
268+
* **e**: The ID of the original bidding event this payment request is related to.
269+
* **p**: The pubkey of the advertiser who made the bid.
270+
271+
This event serves as a trigger for the advertiser to execute the payout.
272+
273+
### Payout ActionEvent (advertiser → offerer)
274+
275+
Triggered when the advertiser processes a **Payment Request**. Upon receiving a `payment_request` ActionEvent, the advertiser may (optionally) verify the requested user action. The advertiser can choose to skip verification and proceed directly to payment if desired.
276+
277+
* If the action is (or is assumed) successful, the advertiser must pay the agreed `bid` amount to the invoice provided in the original **Offer** event, then publish a **Payout** ActionEvent:
278+
279+
* **type**: `payout`
280+
* **message** (`required`): A natural-language description of the payment execution (e.g., “Paid to BOLT11 invoice”, “Paid via LNURL address”). This message can be evaluated by a human or automated reviewer.
281+
282+
* If verification fails or payment cannot be executed, the advertiser should instead publish a **Payout Error** ActionEvent.
283+
284+
The tags must include:
285+
* **e**: The ID of the original bidding event this payout is related to.
286+
* **p**: The pubkey of the offerer who made the offer.
287+
288+
### Payout Error (advertiser → offerer)
289+
290+
Sent when the advertiser is unable to complete the payout. The encrypted `content` must include:
291+
292+
* **type**: `payout_error`
293+
* **message** (`required`): An error message explaining why the payout could not be completed. This can be any string, but the following default values are recommended:
294+
295+
* `no_route`: The advertiser cannot find a payment route to the offerer's node.
296+
* `invoice_expired_or_invalid`: The invoice provided is expired or invalid.
297+
* `expired`: The bid time has expired or the ad is no longer valid.
298+
* `action_incomplete`: The advertiser did not detect successful completion of the user action on their end.
299+
300+
301+
The tags must include:
302+
* **e**: The ID of the original bidding event this payout error is related to.
303+
* **p**: The pubkey of the offerer who made the offer.
304+
305+
Upon receiving a `payout_error`, or if no payout is ever initiated, the offerer may, at their discretion, impose penalties or blacklist the advertiser (see [Punishments and Due Diligence](#punishments-and-due-diligence)).
306+
307+
An advertiser may choose to proceed with payment even after the original bid or ad expiration, ensuring flexibility for late fulfillment.
308+
309+
310+
## Punishments and Due Diligence
311+
312+
### When the app cheats
313+
314+
An application may falsely claim completion of user actions (e.g., reporting an ad view that never occurred). Advertisers should perform due diligence by:
315+
316+
* Restricting bids to trusted offerers via the `p` tag.
317+
* Applying heuristic checks or third‑party audits to detect inconsistencies.
318+
319+
If an advertiser detects cheating, it may:
320+
321+
* Ignore all future offers from the offending pubkey.
322+
* Impose a PoW challenge by raising the `difficulty` in outgoing ActionEvents, requiring the offerer to commit computational work to continue interactions.
323+
324+
### When the advertiser cheats
325+
326+
An advertiser may fail to honor payments, claim `hold_time` has expired prematurely, or otherwise violate the protocol.
327+
328+
If an offerer detects misbehavior, it may:
329+
330+
* Reject bids from that advertiser pubkey permanently.
331+
* Impose a PoW challenge by raising the `difficulty` in outgoing ActionEvents, requiring the advertiser to commit computational work to continue interactions.
332+
333+
---
334+
335+
Implementations should maintain local blacklists of repeat offenders and may share reputational data off‑protocol to enhance ecosystem trustworthiness.
336+
337+
THe Pow penalty PoW deters further misconduct while allowing honest parties to redeem themselves for issues that may have been caused by bugs or misconfigurations.

0 commit comments

Comments
 (0)