Decentralized advertising on top of nostr. draft nip-ADS.
The checked-in Gradle catalog pins the official nostr4j snapshot containing the
asynchronous NostrPool.publish API, together with its matching NGE platform API.
The stable nostr4j release 0.3.1 has the earlier synchronous
publication signature and cannot replace that snapshot. Snapshot artifacts are
pinned by timestamp and checked by Gradle dependency verification. Common/JVM and
TeaVM platform artifacts share a publication timestamp but have different build
numbers, so their catalog versions are kept separately. The JavaScript bindings
use the matching official NGE TeaVM compiler fork, pinned in gradle.properties;
upstream TeaVM 0.13 cannot compile the platform’s Cleaner-based resource handling.
Keep these dependencies aligned when refreshing the central catalog. Publishing this NostrAds update is required before downstream applications can consume its new binary API compatibility.
Open /console/ to manage campaigns, search and filter signed ads, inspect their
creative and publish revisions. The dashboard distinguishes campaigns returned
by the selected relays from copies saved on this device; relay absence alone
does not prove that a campaign has stopped everywhere.
Use Log in to choose a browser extension (NIP-07), a remote signer (NIP-46,
bunker:// or nostrconnect://), an nsec key, or an encrypted ncryptsec key
(NIP-49). Encrypted keys require their password and support scrypt parameters up
to a 256 MiB memory limit. The console remembers your account in local storage
and restores access when you reopen it. For key login, this stores the signing
key, including the unlocked key after an encrypted-key login; passwords are not
saved. Remote signers retain their connection key, while browser extensions keep
control of the signing key. Log out removes the saved account and signs out
other open console tabs. Wallet connections are not saved. Public signed campaigns
and account-specific network preferences are saved locally.
This is the client library that you can use to connect a webapp to the network. It uses a subset of the protocol but should be sufficient for the most common use cases.
# Install the exact, audited dependency lockfiles
(cd nostrads-bindings4js && npm ci)
(cd nostrads-js && npm ci)
# Compile the js library
./gradlew --no-daemon :nostrads-js:packJsClient
you should see two new files being created in nostrads-js/nostrads-client.js and nostrads-js/nostrads-worker.js.
These together with the optional css nostrads-js/nostr-ads.css, are all you need to include in your webapp.
Add this to your pages, make sure to replace <your_path> with the path to the files you just built.
<script src="<your_path>/nostrads-client.js"></script>
<script>
NostrAds({
appKey: "<npub>",
// relays = [...],
worker: "<your_path>/nostrads-worker.js",
// auth: "<npriv>",
// priceSlot: "BTC1_000",
// category: [],
// languages: [],
// advertisersWhitelist: []
// mimeTypes: [],
// Exact trusted Blossom/CDN origins allowed to serve image ads:
allowedImageOrigins: ["https://blossom.primal.net", "https://blossom.band"]
});
</script>Image ads are same-origin by default. Add only Blossom/CDN origins you trust to
allowedImageOrigins; arbitrary HTTPS image hosts are intentionally rejected to
avoid browser-side requests to attacker-selected or internal hosts.
In this snippet:
the npub you want to receive payouts for interacting with the ads. This could be a npub you set up for your app, or even the npub of the user if you want the payout to be routed directly to them (eg. as a reward system). In either case, the npub should have an associated kind 0 event with a lud16 or lud06 address to send the payouts to.
a list of relays to use, leave unset for the default list
is the path to the worker script that you just built.
is a private key that will be used to derivate the public key that identify the user in the advertising network. This identity should not be connected in anyway to the real identity (eg. social identity) of the user, and should be rotated often. The purpose of this identity is to allow and advertiser to reach out again to a specific user, eg. to promote a new product they might be interested in, but it should not be used in a way that makes long-term profiling of the user possible. By default leaving this unset will generate a new random private key for the user session, this is generally sufficient.
the minimum price that the ad must pay to be displayed in the ad spaces. Must be chosen from this list: Price Slot.
This filters out low-paying ads. By default it is set to BTC1_000.
a list of category ids to allow in the ad space. Must be chosen from this list: Nostr Ads Taxonomy note: ids are set as strings. Leave unset to allow all categories.
a list of language codes to allow in the ad space (eg ["en","it"]). Must be chosen from this list: ISO 639-1. Leave unset to allow all languages.
list of npubs of advertisers that are allowed to display ads, this can be used to effectively implement adblocking logic. Leave unset to allow all advertisers.
a list of mime types to allow in the ad space, Must be chosen from this list: Mime Types. Leave unset to allow all mime types.
Tip
The library runs in a dedicated worker for each page. Private session material is not shared across tabs.
Once you have the library set up, it will automatically start looking for ad space to fill in the page. To define an ad space you can use this html snippet:
<div class="nostr-ddspace" style="width: 250px; height: 250px;"></div>Tip
class="nostr-ddspace" is very important, since it is used by the library to identify the ad space.
Note: ddspace is not a typo
width: 250px; height: 250px; are valid dimensions for the ad chosen from this list: Ad size and Aspect ratio.
Tip
It is not mandatory to set the dimension in the style like this, the library will use whatever actual display size the element has to find the best ad to fill it, so as long as the space ends up with a size that resembles one of the valid ad sizes, it should work. However, specifying the dimensions manually makes things easier and more predictable.
You can configure the ad space with attributes on the element:
- appKey
- priceSlot
- mimeTypes
- category
- languages
- advertisersWhitelist
They are the same attributes set in the Initialization phase and they will override the values for a specific ad space. The only difference is that arrays are set as comma-separated strings, eg. languages="en,it".
Tip
You can add adspaces dynamically to the page, the library will automatically detect them and fill them with ads.
If you are an advertiser that wants to self-host, or you want to offer a service to advertisers, you can run a delegate service
The published delegate is a JVM application. Its native safe allocator requires glibc 2.34 or newer; the supported container uses Eclipse Temurin 21 on Ubuntu Noble and runs as an unprivileged user.
docker run -d --name nostrads-delegate \
--read-only --cap-drop=ALL --security-opt=no-new-privileges \
--tmpfs /tmp:rw,noexec,nosuid,size=16m \
-v /srv/nostrads-delegate:/data \
ghcr.io/nostrgameengine/nostrads/nostrads-delegate:latestthis will automatically generate a new pubkey that you can see with
docker logs nostrads-delegateTip
In the snipped above, /srv/nostrads-delegate is the path where all persistent data will be stored, including the private key of the delegate service. Change it to a path that suits your needs.
Important
The container runs as the unprivileged user 10001:10001, so the host directory mounted on /data must be writable by that UID:
mkdir -p /srv/nostrads-delegate
chown -R 10001:10001 /srv/nostrads-delegateThe service will start with sane defaults, and no fees. You can configure it by appending arguments to the command line, for example, this will list the available options:
docker run --name nostrads-delegate \
ghcr.io/nostrgameengine/nostrads/nostrads-delegate:latest --helpTo collect a fee for each successful negotiation, we can use:
docker run -d --name nostrads-delegate \
-v/srv/nostrads-delegate:/data \
ghcr.io/nostrgameengine/nostrads/nostrads-delegate:latest \
--fee 1000:0.02:21000:zap@rblb.it
this will collect a fee of 2% with a minimum of 1 sats and a maximum of 21 sats per negotiation, and will send the fee to zap@rblb.it.