@elisym/merchant-node 0.2.0 → 0.3.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/README.md CHANGED
@@ -1,8 +1,8 @@
1
1
  # @elisym/merchant-node
2
2
 
3
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.
4
+ orders sent by the checkout widget, checks each payment on chain (USDC on Solana, or a
5
+ stablecoin on Tempo), and sends the buyer what they bought.
6
6
 
7
7
  You do not need an account or a server of ours. The node holds your store's keys and a small
8
8
  ledger on your own disk.
@@ -10,9 +10,10 @@ ledger on your own disk.
10
10
  ## What you need
11
11
 
12
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.
13
+ - A Solana wallet address to be paid to (USDC), a Tempo address, or both.
14
+ - For Solana payouts, a Solana RPC endpoint for the node itself. On mainnet, use your own
15
+ provider key (Helius, Triton, ...). A key restricted to a browser origin does not work from a
16
+ server. Tempo is read through its public endpoint unless you set your own.
16
17
  - What the buyer gets once paid: a link (a Blossom URL, a course page, a download) or text
17
18
  (a license key).
18
19
 
@@ -53,13 +54,15 @@ Gift wraps stay on the relays for two days, and payments are read back from the
53
54
 
54
55
  ## Commands
55
56
 
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 |
57
+ | Command | What it does |
58
+ | --------- | ----------------------------------------------------------------------------------- |
59
+ | `init` | Creates the home: a `config.json` template (never overwritten) and the store's keys |
60
+ | `setup` | Checks the inbox relays, publishes the store, and records the terms it offers |
61
+ | `run` | Takes orders, verifies payments and delivers |
62
+ | `orders` | Lists the orders: open, paid, delivered, and the buyer's email |
63
+ | `check` | Checks the inbox relays, the owner's payout list and the domain |
64
+ | `deliver` | Answers an unpaid order by hand with the configured delivery (node stopped) |
65
+ | `refund` | Answers an unpaid order by hand with a refund you already sent (node stopped) |
63
66
 
64
67
  Every command takes `--home <dir>`. Without it, the home is `$ELISYM_MERCHANT_HOME`, else
65
68
  `~/.elisym-merchant`.
@@ -83,21 +86,22 @@ it starts.
83
86
 
84
87
  ## The config
85
88
 
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 |
89
+ | Field | Meaning |
90
+ | ------------------------- | --------------------------------------------------------------------------------------------------- |
91
+ | `name` | The store's name, shown in the checkout |
92
+ | `nip05` | Optional. `_@your-domain.com` for level A (see below) |
93
+ | `network` | `devnet` or `mainnet` |
94
+ | `rpcUrl` | The node's Solana RPC (`https:`). Needed with a Solana payout |
95
+ | `tempo` | Optional. `{ "network": ... }` matching `network` (`moderato` on devnet), plus an optional `rpcUrl` |
96
+ | `inboxRelays` | 1 to 5 relays (`wss:`) where the store reads orders and replies |
97
+ | `product.d` | The product's id in the store (letters, digits, `.`, `-`, `_`) |
98
+ | `product.title` | Title |
99
+ | `product.description` | Description |
100
+ | `product.summary` | Optional short line |
101
+ | `product.priceUsd` | Price in USD, such as `"49"` or `"0.50"` |
102
+ | `product.delivery.method` | `access`, `download`, `license`, `api` or `webhook`: how the checkout labels it |
103
+ | `product.delivery.value` | The link or text the buyer gets (up to 1024 characters) |
104
+ | `payouts` | One `{ "caip19": ..., "address": ... }` per coin, see [Tempo payouts](#tempo-payouts) |
101
105
 
102
106
  The node refuses to start with a config it cannot use, and names every problem.
103
107
 
@@ -117,6 +121,26 @@ after the payment, one relay is enough. If a relay later drops a delivery, the n
117
121
  status again when it reads that order or a receipt for it again: at most every ten minutes per
118
122
  order, and a few at a time (the rest wait until the order is read again).
119
123
 
124
+ ### Tempo payouts
125
+
126
+ A store can also take a Tempo stablecoin, paid with an EIP-6963 wallet such as MetaMask. Add a
127
+ `tempo` block naming the Tempo network that matches the node's `network` (`mainnet` on mainnet,
128
+ `moderato` on devnet), and one payout per coin, with your Tempo address in
129
+ lowercase:
130
+
131
+ | Network | `tempo.network` | Coin | `caip19` |
132
+ | ------------------ | --------------- | ------- | --------------------------------------------------------------- |
133
+ | Tempo mainnet | `mainnet` | USDC.e | `eip155:4217/erc20:0x20c000000000000000000000b9537d11c60e8b50` |
134
+ | Tempo mainnet | `mainnet` | pathUSD | `eip155:4217/erc20:0x20c0000000000000000000000000000000000000` |
135
+ | Moderato (testnet) | `moderato` | pathUSD | `eip155:42431/erc20:0x20c0000000000000000000000000000000000000` |
136
+
137
+ A Tempo payout is refused without the `tempo` block, and a store with only Tempo payouts needs
138
+ no Solana `rpcUrl`, but it still names its network: `"network": "mainnet"` for Tempo mainnet
139
+ (`init` writes `devnet` unless told `--network mainnet`). A page shows the store's payouts on its own network, and the buyer picks
140
+ one. Upgrade
141
+ the node before the payout list names a Tempo address, and do not downgrade it afterwards: once
142
+ the node has seen a Tempo order, an older node refuses its ledger.
143
+
120
144
  ## Level A: your domain vouches for the store
121
145
 
122
146
  Without `nip05`, the store is level C. The checkout then shows the buyer that no domain vouches
@@ -157,10 +181,10 @@ To edit the config in the volume, mount a host directory instead, for example
157
181
  - `keys.json` holds the store key and the owner key. Whoever has them can publish a different
158
182
  payout address in your store's name. Keep the home private (the node creates it as `0700`)
159
183
  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
184
+ - The owner's payout list must name only payouts this node checks: on its networks, at the
185
+ addresses its ledger records. `run` refuses to start when the published list names another
186
+ one, for example a Tempo address added from elsewhere without a `tempo` block, or an address
187
+ `setup` did not record. A buyer could pay that address and never get a delivery. It refuses the same way
164
188
  when the published listing asks a price the ledger does not record (a `setup` killed
165
189
  between publishing and recording, for example by `docker stop`), and when the published
166
190
  inbox list names a relay the node does not read (the config changed without `setup`). Run
@@ -172,7 +196,9 @@ To edit the config in the volume, mount a host directory instead, for example
172
196
 
173
197
  ## Limits
174
198
 
175
- - One product per node, paid on Solana (USDC) on one network.
199
+ - One product per node, on one network: USDC on Solana and stablecoins on Tempo (Moderato on
200
+ devnet).
176
201
  - Delivery is the configured link or text. Uploading a file to Blossom is up to you; the link
177
202
  goes in `product.delivery.value`.
178
- - Refunds are made by hand from your wallet.
203
+ - Refunds are made by hand from your wallet. `refund` reports one to the buyer of an order the
204
+ node did not credit; a refund of a delivered order is between you and the buyer.
package/dist/cli.js CHANGED
@@ -406,6 +406,13 @@ var configSchema = z.object({
406
406
  }
407
407
  seen.add(caip19.id);
408
408
  });
409
+ if (config.tempo !== void 0 && tempoRegistryNetwork(config.tempo.network) !== config.network) {
410
+ context.addIssue({
411
+ code: "custom",
412
+ path: ["tempo", "network"],
413
+ message: `is ${config.tempo.network}, the node runs on ${config.network}: use network "mainnet" with Tempo "mainnet", or network "devnet" with Tempo "moderato"`
414
+ });
415
+ }
409
416
  if (solanaPayouts > 0 && config.rpcUrl === void 0) {
410
417
  context.addIssue({
411
418
  code: "custom",