@elisym/merchant-node 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Igor Peregudov
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,178 @@
1
+ # @elisym/merchant-node
2
+
3
+ The self-hosted store behind the elisym checkout. It publishes your product to Nostr, takes
4
+ orders sent by the checkout widget, checks each Solana payment on chain, and sends the
5
+ buyer what they bought.
6
+
7
+ You do not need an account or a server of ours. The node holds your store's keys and a small
8
+ ledger on your own disk.
9
+
10
+ ## What you need
11
+
12
+ - Node.js 22.4 or newer, or Docker.
13
+ - A Solana wallet address to be paid to (USDC).
14
+ - A Solana RPC endpoint for the node itself. On mainnet, use your own provider key
15
+ (Helius, Triton, ...). A key restricted to a browser origin does not work from a server.
16
+ - What the buyer gets once paid: a link (a Blossom URL, a course page, a download) or text
17
+ (a license key).
18
+
19
+ ## Quick start (devnet)
20
+
21
+ ```bash
22
+ npx @elisym/merchant-node init --network devnet
23
+ ```
24
+
25
+ This creates `~/.elisym-merchant/` with a `config.json` to edit and the store's keys. Edit
26
+ `config.json`:
27
+
28
+ - `name`, `product.title`, `product.description` and `product.priceUsd`: what the store
29
+ sells, at what price, in USD. It is paid 1:1 in USDC.
30
+ - `payouts[0].address`: your Solana wallet address.
31
+ - `product.delivery.value`: the link or text the buyer gets.
32
+
33
+ Then publish the store and start taking orders:
34
+
35
+ ```bash
36
+ npx @elisym/merchant-node setup
37
+ npx @elisym/merchant-node run
38
+ ```
39
+
40
+ `setup` prints the product's `naddr`. Put it in the checkout snippet on your page:
41
+
42
+ ```html
43
+ <elisym-buy product="naddr1..." network="devnet"></elisym-buy>
44
+ <script
45
+ src="https://pay.elisym.network/v1/embed.js"
46
+ integrity="<see the checkout docs>"
47
+ crossorigin="anonymous"
48
+ ></script>
49
+ ```
50
+
51
+ Keep `run` running. If it stops, a restart catches up on the orders and payments it missed.
52
+ Gift wraps stay on the relays for two days, and payments are read back from the chain.
53
+
54
+ ## Commands
55
+
56
+ | Command | What it does |
57
+ | -------- | ----------------------------------------------------------------------------------- |
58
+ | `init` | Creates the home: a `config.json` template (never overwritten) and the store's keys |
59
+ | `setup` | Checks the inbox relays, publishes the store, and records the terms it offers |
60
+ | `run` | Takes orders, verifies payments and delivers |
61
+ | `orders` | Lists the orders: open, paid, delivered, and the buyer's email |
62
+ | `check` | Checks the inbox relays, the owner's payout list and the domain |
63
+
64
+ Every command takes `--home <dir>`. Without it, the home is `$ELISYM_MERCHANT_HOME`, else
65
+ `~/.elisym-merchant`.
66
+
67
+ Run `setup` again after every change to `config.json`, except `product.d`: a store sells one
68
+ product, and `setup` refuses a new id (the old listing would stay payable with no one taking its
69
+ orders). Give a new product its own home. Stop `run` first: `setup` refuses to
70
+ run while a node holds the home. Run `run` again afterwards. If only part of a change reaches
71
+ the relays (for example the payout list but not the listing), `setup` records what buyers can
72
+ now see, says so and fails: run it again. Each of the listing, the payout list and the inbox
73
+ list must also reach at least one of the relays every checkout reads
74
+ (`wss://relay.elisym.network`, `wss://relay.damus.io`, `wss://nos.lol`,
75
+ `wss://relay.nostr.band`), whatever inbox relays the store uses; `setup` fails until one
76
+ takes it.
77
+
78
+ The home's lock (`run.lock`) keeps two processes from writing the ledger at once, wherever they
79
+ run (containers and hosts sharing the home included). Its holder refreshes it every 20 seconds.
80
+ A node that stopped without releasing it leaves it behind, and the lock frees itself 90 seconds
81
+ after its last refresh. A container restarted at once may therefore fail for that long before
82
+ it starts.
83
+
84
+ ## The config
85
+
86
+ | Field | Meaning |
87
+ | ------------------------- | ------------------------------------------------------------------------------- |
88
+ | `name` | The store's name, shown in the checkout |
89
+ | `nip05` | Optional. `_@your-domain.com` for level A (see below) |
90
+ | `network` | `devnet` or `mainnet` |
91
+ | `rpcUrl` | The node's Solana RPC (`https:`) |
92
+ | `inboxRelays` | 1 to 5 relays (`wss:`) where the store reads orders and replies |
93
+ | `product.d` | The product's id in the store (letters, digits, `.`, `-`, `_`) |
94
+ | `product.title` | Title |
95
+ | `product.description` | Description |
96
+ | `product.summary` | Optional short line |
97
+ | `product.priceUsd` | Price in USD, such as `"49"` or `"0.50"` |
98
+ | `product.delivery.method` | `access`, `download`, `license`, `api` or `webhook`: how the checkout labels it |
99
+ | `product.delivery.value` | The link or text the buyer gets (up to 1024 characters) |
100
+ | `payouts` | One `{ "caip19": ..., "address": ... }` per coin. Solana, on `network`, only |
101
+
102
+ The node refuses to start with a config it cannot use, and names every problem.
103
+
104
+ ### Inbox relays
105
+
106
+ The store replies to one-time buyer keys, which have no inbox of their own. Each inbox relay
107
+ must therefore accept a gift wrap (kind 1059) addressed to any key and serve it back to that
108
+ key. It must also keep gift wraps for at least two days past their date. `setup` and `check`
109
+ test the first two by writing a probe and reading it back. Every relay must pass, or `setup`
110
+ stops: buyers send orders to all of them. Retention cannot be tested, so pick relays that keep events. `wss://nos.lol`
111
+ and `wss://relay.elisym.network` passed when this was written. `wss://relay.damus.io` did
112
+ not: it accepts gift wraps but does not serve them back.
113
+
114
+ A delivery counts as done once two inbox relays accept it (or all of them, when only one is
115
+ configured). While one relay is down, the node retries only that relay, each minute. An hour
116
+ after the payment, one relay is enough. If a relay later drops a delivery, the node sends the
117
+ status again when it reads that order or a receipt for it again: at most every ten minutes per
118
+ order, and a few at a time (the rest wait until the order is read again).
119
+
120
+ ## Level A: your domain vouches for the store
121
+
122
+ Without `nip05`, the store is level C. The checkout then shows the buyer that no domain vouches
123
+ for it. For level A:
124
+
125
+ 1. Set `"nip05": "_@your-domain.com"` and run `setup`.
126
+ 2. Serve the `nostr.json` that `setup` writes to the home at
127
+ `https://your-domain.com/.well-known/nostr.json`, with the header
128
+ `Access-Control-Allow-Origin: *`.
129
+ 3. Run `check`: it fetches the file and says whether the domain now vouches for the store.
130
+
131
+ Only the domain-wide name `_` gives level A. A named address such as `shop@your-domain.com`
132
+ stays level C.
133
+
134
+ ## Docker
135
+
136
+ Build from the repository root:
137
+
138
+ ```bash
139
+ docker build -f packages/merchant-node/Dockerfile -t elisym-merchant .
140
+ ```
141
+
142
+ The home is the volume at `/data`. Its files belong to the image's user (uid 1000).
143
+
144
+ ```bash
145
+ docker run --rm -v elisym-merchant:/data elisym-merchant init --network devnet
146
+ # edit config.json in the volume, then:
147
+ docker run --rm -v elisym-merchant:/data elisym-merchant setup
148
+ docker run -d --name shop --restart unless-stopped -v elisym-merchant:/data elisym-merchant run
149
+ docker logs -f shop
150
+ ```
151
+
152
+ To edit the config in the volume, mount a host directory instead, for example
153
+ `-v "$PWD/shop:/data"`, and edit `shop/config.json`. The directory must be writable by uid 1000.
154
+
155
+ ## Keeping it safe
156
+
157
+ - `keys.json` holds the store key and the owner key. Whoever has them can publish a different
158
+ payout address in your store's name. Keep the home private (the node creates it as `0700`)
159
+ and back it up. A lost key means a new store.
160
+ - The owner's payout list must name only payouts this node checks: Solana, on its network,
161
+ at the addresses its ledger records. `run` refuses to start when the published list names
162
+ another one, for example a Tempo address added from elsewhere or an address `setup` did not
163
+ record. A buyer could pay that address and never get a delivery. It refuses the same way
164
+ when the published listing asks a price the ledger does not record (a `setup` killed
165
+ between publishing and recording, for example by `docker stop`), and when the published
166
+ inbox list names a relay the node does not read (the config changed without `setup`). Run
167
+ `setup` to publish what the config names, then `run`.
168
+ - The node never sends email. It records the buyer's email when the checkout asked for one
169
+ (`orders` lists it), and sending anything is up to you.
170
+ - `ledger.json` records which transaction paid which order. Do not edit it or restore an older
171
+ copy while orders are open: a payment could then be credited twice.
172
+
173
+ ## Limits
174
+
175
+ - One product per node, paid on Solana (USDC) on one network.
176
+ - Delivery is the configured link or text. Uploading a file to Blossom is up to you; the link
177
+ goes in `product.delivery.value`.
178
+ - Refunds are made by hand from your wallet.