docs: publishing tiers + zaps + NWC + JPEG/PDF fallback

Adds a "Publishing & payments" group covering the three tiers, the
Lightning plumbing, and how to wire a wallet up:

- publishing.md            — Overview of Public / Private / Premium
                             with a comparison table; how tipping vs
                             unlocking differs; replaceability +
                             republish-vs-fork.
- publishing-public.md     — Default tier. Event shape, tag layout,
                             discoverability, edit-vs-fork.
- publishing-private.md    — End-to-end encrypted to self via NIP-44
                             / NIP-04. AES-256-GCM pipeline, what stays
                             visible (pubkey, kind, d-tag), multi-
                             device decrypt via "Mine" tab, caveats
                             (no multi-recipient envelopes in v1).
- publishing-premium.md    — Zap-gated soft-gate publishing. Soft-gate
                             key derivation, watermarked preview,
                             zap-split tags (creator + platform),
                             buyer unlock flow, My Purchases vault,
                             honest framing about soft-gate limits.
- zaps.md                  — NIP-57 lightning payments. Tipping vs
                             unlocking. How to receive zaps (set
                             lud16 on profile). Fallback flow without
                             NWC (BOLT11 + QR + external wallet).
- nwc.md                   — Nostr Wallet Connect (NIP-47) setup.
                             CoinOS quickstart, supported wallets,
                             multi-device sync via NIP-44 encrypted
                             event, test-connection button, security
                             model (treat URI like a password + set a
                             budget).

Plus a "Why JPEG sometimes routes through PDF" section added to
panel-print-save.md, explaining the three fallback cases (CORS-
blocked images, tainted canvas, unsupported mode) and the auto-
switch to the print → PDF path that the browser's print engine
can handle.

Co-Authored-By: Claude Opus 4.7 <[email protected]>
This commit is contained in:
thePR0M3TH3ANandClaude Opus 4.7 committed 2026-06-04 09:37:04 -04:00
1 parent ef03f731d1
commit 10a5022e9c
8 files changed
+541 -2

No files matched your search

+98
View File
@@ -0,0 +1,98 @@
# NWC — Nostr Wallet Connect setup
NWC (NIP-47) lets Artstr send Lightning payments through your
wallet without leaving the app. Required for one-click zapping +
unlocking premium designs.
## Quick start (CoinOS, recommended)
CoinOS is a free web-based wallet that supports NWC. Easiest path
if you don't already have a Lightning wallet:
1. Sign up at <https://coinos.io>.
2. Open **Settings → Connect** in the CoinOS app.
3. Create a new Nostr Wallet Connect link. Set a **budget** that
comfortably covers the zaps + unlocks you expect (a few
thousand sats is plenty to start).
4. Copy the connection string (starts with `nostr+walletconnect://`).
5. In Artstr, open **Settings → Lightning wallet**.
6. Paste the connection string + click **Connect**.
7. The status line confirms the connection + lists the wallet's
advertised capabilities (`pay_invoice` is the required one).
## Other supported wallets
Any NIP-47 wallet works:
- **Alby Hub** — self-hosted; nostr-native; great for power users.
- **Mutiny** — browser-based, open-source.
- **Phoenix** — mobile-first, advertises NWC.
- **Cashu.me** — ecash custodial.
- **LNbits** — self-hosted; the NWC extension exposes a
connection string per wallet.
In every case the pattern is the same: the wallet generates a
`nostr+walletconnect://...` URI that you paste into Artstr.
## Multi-device sync
The **Sync this wallet to my Nostr account** checkbox (appears
once connected) publishes your NWC URI as a kind-30078 event,
encrypted to your own pubkey via NIP-44. On any other device where
you sign in with the same Nostr key, Artstr finds the synced event,
decrypts it, and auto-reconnects without you pasting the URI
again.
Privacy: the synced event is NIP-44-encrypted to **only your own
pubkey**. Relays + other clients can't read the URI — only an
extension holding your nsec can decrypt it.
## Sending a zap with NWC connected
1. Click the **⚡ Zap** button on any design / page / chapter (or
the **Unlock with ⚡ zap** on a premium row).
2. Amount picker opens with presets.
3. Artstr builds a zap request, gets the BOLT11 invoice from the
recipient's LSP, and sends `pay_invoice` through your NWC
connection.
4. The wallet pays; the LSP issues the zap receipt; the UI
refreshes.
End-to-end usually completes in under 3 seconds.
## Test connection
The **Test connection** button (next to Disconnect) runs a
zero-amount pay_invoice round-trip to verify the wallet is
reachable + capable. Useful before assuming the integration is
broken.
## Capability warnings
If the wallet doesn't advertise `pay_invoice`, the UI shows an
⚠ banner — unlocks will fail. Reconnect via a different wallet's
URI or enable `pay_invoice` in the source wallet (most wallets
ship it on; some self-hosted setups disable it by default).
## Security model
- The connection string contains the wallet's relay URL + a
client-side secret key Artstr uses to sign NIP-04 messages to
the wallet. **Treat it like a password** — anyone with the URI
can send `pay_invoice` up to the configured budget.
- Set a **budget** on the wallet side that caps total spending.
Most NWC wallets let you renew the budget periodically.
- Setting a low per-payment maximum (e.g., 1 000 sats) is a good
default for unlocking designs.
- Revoking NWC is a one-click action on the wallet side — the URI
becomes inert immediately.
## Caveats
- NWC over relays adds latency (signed event → relay → wallet →
signed receipt → relay → client). Expect 1-3 seconds per zap on
a healthy connection.
- If the wallet's relay is down, zaps queue but won't complete.
The wallet usually has multiple relays as fallbacks.
- Mobile browsers sometimes throttle background WebSockets; if
zaps time out, foreground the Artstr tab + retry.
+27 -2
View File
@@ -33,8 +33,9 @@ publishing to Nostr lives in the top-right Publish flow, not here.
## Export
- **Export JPEG** — direct rasterised export at the artboard's
natural pixel size, JPEG-compressed (~0.92 quality). One file
per artboard.
natural pixel size, JPEG-compressed. The resolution dropdown
picks between **Low (96 DPI)**, **Standard (150 DPI)**, and
**Print (300 DPI)**.
- **Export PNG** — same but as PNG (lossless). Sometimes available
only for modes that support transparent backgrounds.
- Pixel-art mode has its own dedicated exports (PNG at multiple
@@ -43,6 +44,30 @@ publishing to Nostr lives in the top-right Publish flow, not here.
- Book mode has its own dedicated **Export PDF…** flow (page range,
bleed, crop marks, split output) from the Pages overview toolbar.
### Why JPEG sometimes routes through PDF
The direct-JPEG path renders every layer onto a hidden canvas, then
extracts the canvas as a JPEG. That fails in three cases:
1. **CORS-blocked images** — if any image layer's source server
doesn't send `Access-Control-Allow-Origin: *`, the browser
refuses to embed the pixels in the canvas. The "Render canvas"
path can't bypass this; the print engine can.
2. **Tainted canvas** — the canvas accepted the image but flagged it
"tainted" once it tried to read pixels. Same root cause as #1
but caught at a different stage.
3. **Unsupported modes** — pixel-art (uses its own dedicated
exporter) and standalone decks (which have a multi-slide
exporter elsewhere) don't go through this path.
In any of those cases, the JPEG button automatically opens the
**Print → PDF** flow: the browser's print engine has a separate
image pipeline that handles cross-origin embeds the canvas can't
touch. You'll see a toast explaining the switch ("N image(s)
couldn't be embedded directly — switching to the PDF route"). Save
the resulting PDF, then re-export it as JPEG from a PDF viewer if
you specifically need a flat JPEG.
## Designer tab
The Designer tab has a separate `designerPrintSaveFieldset` with
+101
View File
@@ -0,0 +1,101 @@
# Premium publishing (zap-gated)
Sell designs for sats. The published event ships a watermarked
preview anyone can browse + an encrypted payload only paying
viewers can decrypt. Discovery + browsing is public; the design
itself is gated.
## How it works
1. The full design payload is encrypted via the same AES-256-GCM
pipeline as a private publish, but with a special **softgate**
key derivation.
2. The key derives from public event material (event id, pubkey,
d-tag) plus an obfuscated per-epoch pepper baked into the
static Artstr client.
3. A watermarked JPEG preview (max 720 px long axis, ~50 KB
budget) ships in the unencrypted envelope so buyers can see
what they'd be unlocking.
4. The event publishes with:
- `['t', 'casewrap']`, `['t', 'casewrap-<mode>']` — discovery.
- `['title', …]`, `['category', …]` — public metadata.
- `['premium', 'true']`.
- `['zap-gate', 'true']`, `['zap-min', '<sats>']`, `['zap-split', 'required']`.
- `['zap', <creator-pubkey>, <relay>, '<weight>']` — creator share.
- `['zap', <platform-pubkey>, <relay>, '<weight>']` — platform fee.
5. When a buyer's zap receipt for this event lands on relays, the
client computes the soft-gate key + decrypts.
## Soft-gate is "soft"
Honest framing: this raises friction against drive-by scraping but
a determined reverse-engineer can recover the client pepper and
decrypt without paying. The mode is for low-trust commercial
distribution, not DRM-grade gating. Stronger DRM can swap in later
without changing the buyer side.
## What you need before publishing premium
- A **lightning address** (`lud16`) on your kind-0 Nostr profile.
Set this on any Nostr client that edits your profile metadata
(Damus, Amethyst, nos2x, etc). Without it the platform can't
route the buyer's payment to you.
- A non-empty title + category (same as any publish).
- A working artboard preview — the modal renders the watermarked
JPEG live + refuses to publish if rendering fails (CORS images
etc).
## Publishing flow
1. Set Title + Category.
2. Click **Publish to Nostr**.
3. In the confirm modal:
- Keep visibility on **Public**.
- Toggle **Zap-gated (premium)** on.
- Set the **Minimum zap** in sats (default 100).
- Read the split note (creator: 70 %, platform: 30 % by default;
see the per-event `zap` tags for the exact split that ships).
4. Click **Publish**.
## How buyers unlock
In the community browser's **Premium** tab, a locked design shows
the watermarked preview + an **Unlock with ⚡ zap** button. Clicking
it:
1. Opens the buyer's connected NWC wallet (see
[NWC setup](#/help/nwc)).
2. Builds a NIP-57 zap request for the minimum amount.
3. Sends `pay_invoice` through the NWC connection.
4. On a successful zap receipt, derives the soft-gate key + decrypts
the design in-place.
5. Caches the decrypted payload in the **My Purchases** vault so
later re-visits skip the re-zap.
## My Purchases vault
Your unlocks live in a **vault** encrypted to your own pubkey (so
they sync across devices the same way private publishes do).
Capped at a fixed size; if the vault overflows, the oldest items
get evicted with a visible failure rather than silently lost.
## Editing a premium event
The same NIP-33 replaceability applies — re-publishing updates the
event in place. Existing buyers' vault entries continue working
against the new event since the soft-gate key derives from the
d-tag, which stays stable.
## Caveats
- Decks ship a per-slide watermarked preview (up to 6 slides) so
buyers can flip through what they'd unlock; non-deck modes get a
single preview JPEG.
- The platform fee is currently a fixed 30/70 split on the event's
zap tags. Future updates may make this configurable.
- The buyer's NWC wallet must support `pay_invoice` (almost all
do).
## Deeper reference
- Spec: `docs/PREMIUM_DESIGNS_FEATURE.md` in the GitHub repo.
+71
View File
@@ -0,0 +1,71 @@
# Private publishing
Encrypted-to-self designs. Same Nostr event format as a public
design, but `content` is an encrypted envelope and the metadata
tags are stripped down so the design doesn't appear in any
community feed.
## How the encryption works
1. The full design payload is JSON-serialised.
2. Gzipped (when the browser supports `CompressionStream`).
3. Encrypted with a fresh AES-256-GCM key.
4. The AES key is wrapped to **your own Nostr pubkey** via either
NIP-44 (preferred) or NIP-04 (fallback).
5. The wrapped key + iv + ciphertext go into an `envelope` JSON
object that becomes the event's `content` field.
Only someone signing with your Nostr key can derive the AES key
and decrypt the payload. The event is technically broadcast to all
relays, but it's mathematically opaque to everyone else.
## What's still visible
- Your **pubkey** (every Nostr event is signed and the pubkey is
part of the signature).
- The event's `kind`, `created_at`, `d` tag, and `encrypted` tag.
- The fact that you published *something* at this time.
What's **not** visible: title, category, IDs, layers, dimensions,
images, or any other payload content.
## When to use it
- Working drafts you want backed up across devices but not shared.
- Personal projects you don't want indexed.
- Sensitive content where even the title shouldn't leak.
## Editing across devices
Open Artstr on another device, sign in with the same Nostr
extension, and click **Mine** in the community browser. Your
private events appear (decrypted lazily as you click them). The
**Private** tab specifically filters to your encrypted events.
## Caveats
- Requires a Nostr extension that supports NIP-44 or NIP-04
encryption. Most do; alby and nos2x both work.
- The d-tag for a private design is a random opaque string
(`artstr-private-<base36>`) so it can't be guessed. Don't lose
the d-tag if you intentionally want to overwrite the event
later — it's stored in your project's autosave under
`state.privateDTag`.
- **Sharing privately with one specific other person** is *not*
supported in v1. The current encryption only wraps the AES key
to your own pubkey. Multi-recipient envelopes are a future arc.
## Books
The book sidebar has its own **🔒 Private** chip. Flipping it
encrypts the manifest **and** every sidecar event (pages,
masters, cover, chapters) on subsequent publishes. See
[Book Designer](#/help/book) for the full sidecar flow.
A private book's chapter events are kind-30078 (encrypted), not
kind-30023 NIP-23 articles, so external NIP-23 readers won't see
them — which is the desired behaviour for a private book.
## Deeper reference
- Spec: `docs/PRIVATE_PUBLISHING_FEATURE.md` in the GitHub repo.
+79
View File
@@ -0,0 +1,79 @@
# Public publishing
The default tier. Plain-text event the relays distribute to anyone
who asks for it. Discoverable in the community browser, forkable,
embeddable.
## What the event looks like
- `kind: 30078` (or `30023` for book chapters).
- `tags`:
- `['d', '<mode>:<id-or-title>']` — the addressable identifier.
Replaceable per NIP-33: re-publishing under the same d-tag
replaces the event in place.
- `['t', 'casewrap']` + `['t', '<mode-specific>']` (e.g.
`casewrap-cover`, `casewrap-deck`, `casewrap-book`).
- `['title', '<your title>']`, `['category', '<…>']`,
`['language', '<…>']`.
- External ID tags when you've filled them in: `['i', 'imdb:…']`,
`['i', 'upc:…']`, `['i', 'tvdb:…']`, etc.
- `['client', 'Artstr Studio']`.
- For books / linked designs: `['p', '<contributor>', …,
'design-contributor']` + `['link', '<kind:pubkey:dTag>', …]`.
- `content`: JSON-serialised design payload. Every layer, every
mode-specific block, every reference.
## Discoverability
- Listed in the **community browser** under the New / Popular /
Zapped tabs, filterable by category + language.
- Searchable via the Search tab + Nostr search relays that index
`casewrap` content.
- Linked from share URLs: `<host>/#/share/<naddr>` resolves to a
read-only view + Fork button.
## Publishing flow
1. Set **Title** + **Category** in Project Metadata.
2. Click the **Publish to Nostr** button (top right).
3. Confirm modal opens with:
- A preview pane.
- Metadata list (Type / Title / Category / Author / etc).
- **Public / Private** radio (defaults to Public).
- **Zap-gated (premium)** toggle (defaults off → public free).
4. Click **Publish**.
5. The event signs through your Nostr extension + broadcasts to
the relay list in **Settings → Relays**.
## Editing a published design
Loading a public design from the community browser drops you in
**edit mode** — the publish button now says "Publish update" and
preserves the original d-tag. Republishing replaces the event in
place via NIP-33.
To **fork** instead of edit (so your version becomes a new event
under your pubkey), use the community browser's **Fork** action on
the design row — it clears the editing state so your next publish
mints a fresh d-tag.
## Forking + attribution
Linked-design layers from other authors auto-emit:
- `p` tags for each contributor (so they get notifications + can
be discovered as design contributors).
- `link` tags so external clients can resolve the sub-designs.
See the [Credits panel](#/help/panel-credits) for what gets surfaced
in-app + at publish time.
## Caveats
- Anything you upload as an image layer is embedded in the event
payload as a `data:` URL — Nostr relays may reject very large
events. For high-res photo layers, prefer publishing the photo
somewhere else and linking via an `https://` URL.
- Even with private toggles off, the metadata tags (title,
category) are public. The encryption only protects the `content`
field.
+66
View File
@@ -0,0 +1,66 @@
# Publishing — overview
Artstr Studio publishes designs to Nostr instead of a private
server. Every published design becomes a kind-30078 event (or
kind-30023 for book chapters) that anyone can fetch, fork, or
embed.
There are three visibility tiers:
| Tier | Who can open it | Use case |
|------|-----------------|----------|
| **[Public](#/help/publishing-public)** | Anyone with the naddr / nevent. | Default — discoverable in the community browser, forkable. |
| **[Private](#/help/publishing-private)** | Only you, via the same Nostr key (any device). | Working drafts, personal projects, archives. End-to-end encrypted. |
| **[Premium](#/help/publishing-premium)** | Anyone who pays a one-time zap (sats). | Paid templates / books. Watermarked preview + an unlock CTA. |
## Choosing a tier
The choice is made in the **Publish to Nostr** confirm modal:
- Top of the modal shows a **Public / Private** radio.
- A **Zap-gated (premium)** toggle below it (hidden when Private is
selected — encrypted designs can't be feed-listed, so gating them
for sale doesn't make sense).
- The book sidebar has its own **🔒 Private** chip that flips
visibility for the whole book + every sidecar event.
## How the publish event differs per tier
| | Public | Private | Premium |
|---|---|---|---|
| Event kind | 30078 | 30078 | 30078 |
| `t` tag | `casewrap` + mode-specific | `encrypted` | `casewrap` + `premium` |
| `content` field | JSON payload | encrypted envelope | encrypted envelope |
| Preview JPEG | none | none | watermarked, embedded |
| Discovery | community browser + naddr share | naddr share only | community browser (premium tab) |
| Lightning | `zap` tags for tipping | none | `zap` tags split between creator + platform |
## Tipping vs unlocking (NIP-57 zaps)
Zaps are NIP-57 lightning payments. Two flavours:
- **Tipping** — any user can zap any design / page / chapter. Goes
100% to the creator's lightning address (no platform share).
Works on public, private (for you only), and premium designs.
- **Unlocking** — a premium design's zap requirement. The buyer zaps
the required minimum; on receipt of a valid zap receipt, the
client decrypts the design. Split: creator share + platform fee.
See [Zaps](#/help/zaps) for the full mechanics + how to enable
zapping in your profile.
## Sending zaps
You need a **NIP-47 wallet** (Nostr Wallet Connect, or NWC) connected
to Artstr so it can sign + send invoice payments without leaving the
app. See [NWC setup](#/help/nwc).
## Editing + republishing
All published events are **replaceable** (NIP-33). Re-publishing under
the same d-tag updates the event in place — old `naddr` share links
keep working; `nevent` (snapshot) share links point at the old
version.
The community browser's **Mine** tab shows every event you've
published with a "Republish" + "Delete" action per row.
+93
View File
@@ -0,0 +1,93 @@
# Zaps — NIP-57 lightning payments
Artstr uses Nostr-native lightning zaps for both tipping creators
and unlocking premium designs. Same protocol either way; the
difference is who the zap goes to and what the client does on
receipt.
## How zaps work in two paragraphs
A zap is a kind-9735 receipt event proving that a Lightning
payment happened. The flow:
1. The user (you) signs a kind-9734 **zap request** event with the
amount + recipient + intent.
2. The recipient's lightning address (`lud16` on their profile)
resolves to a LNURL endpoint; the request becomes a BOLT11
invoice.
3. You pay the invoice from your wallet. The wallet's LSP issues
a **zap receipt** event back to the relays.
4. Clients that see the receipt update UI (zap-totals, unlock
gated content, etc).
## Tipping any design / page / chapter
Every design preview pane has a **⚡ Zap** button (visible when
the author has a lightning address on their profile). Clicking:
1. Opens an amount picker (default presets 21 / 100 / 1000 sats +
custom).
2. Builds + signs the zap request via your Nostr extension.
3. Pays through your connected **NWC wallet** (see
[NWC setup](#/help/nwc)). If you don't have NWC connected, it
falls back to copying the invoice for you to pay manually in
any Lightning wallet.
The zap goes 100% to the author. No platform share.
## Unlocking a premium design
Same plumbing, different outcome. The locked design row shows an
**Unlock with ⚡ zap** button. Clicking sends the minimum required
amount, split between the creator + the platform per the event's
`zap` tags (currently 70/30). On receipt:
1. The client verifies the receipt's amount + recipient match the
event's `zap-min` + `zap` tags.
2. Derives the soft-gate key from the event metadata.
3. Decrypts the payload + saves it to your **My Purchases**
vault.
See [Premium publishing](#/help/publishing-premium) for the publish
side.
## Receiving zaps on your designs
To get tipped or sold-to:
1. Sign up at any LN-address service. **CoinOS** (free, web-based)
is the easiest: <https://coinos.io>.
2. Edit your Nostr kind-0 profile to add `lud16` = your LN address
(e.g. `[email protected]`). Any Nostr client that edits profiles
works (Damus, Amethyst, primal.net, nos2x options page).
3. Wait a few minutes for your profile to propagate to the relays
you care about.
Once `lud16` is set, every published design of yours becomes
zap-able automatically — Artstr discovers the lightning address
from your profile.
## Sending zaps without an NWC wallet
If you don't have NWC connected, the zap modal will:
- Build the BOLT11 invoice.
- Show a QR code + a copy button.
- Wait for the invoice to be paid externally (you can pay from any
Lightning wallet — Phoenix, Wallet of Satoshi, etc).
- Verify the receipt on relays + update the UI.
This is slower than NWC (you switch apps to pay) but works
universally.
## Tips
- The **Zapped** tab in the community browser sorts designs by
total zap volume — a quick way to find what people are paying
for.
- Zaps are public on Nostr (the receipt event is signed by the
LSP and broadcast). If you want anonymous payment, you'll need a
separate Lightning identity.
- Failed zaps (invoice paid but receipt never arrives) can be
diagnosed via the wallet's transaction log. The LSP issued the
receipt; the relays may have dropped it.
+6
View File
@@ -12074,6 +12074,12 @@
{ slug: 'panel-print-save', title: 'Save / Print', group: 'Sidebar panels', file: 'help/panel-print-save.md' },
{ slug: 'panel-credits', title: 'Credits', group: 'Sidebar panels', file: 'help/panel-credits.md' },
{ slug: 'panel-tool-options', title: 'Tool Options pane', group: 'Sidebar panels', file: 'help/panel-tool-options.md' },
{ slug: 'publishing', title: 'Overview', group: 'Publishing & payments', file: 'help/publishing.md' },
{ slug: 'publishing-public', title: 'Public publishing', group: 'Publishing & payments', file: 'help/publishing-public.md' },
{ slug: 'publishing-private', title: 'Private publishing', group: 'Publishing & payments', file: 'help/publishing-private.md' },
{ slug: 'publishing-premium', title: 'Premium (zap-gated)', group: 'Publishing & payments', file: 'help/publishing-premium.md' },
{ slug: 'zaps', title: 'Zaps (NIP-57)', group: 'Publishing & payments', file: 'help/zaps.md' },
{ slug: 'nwc', title: 'NWC wallet setup', group: 'Publishing & payments', file: 'help/nwc.md' },
];
const _docsCache = new Map(); // slug → rendered HTML
let _docsActiveSlug = 'index';