mtok-relay 0.2.6 → 0.2.8

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.
Files changed (3) hide show
  1. package/README.md +167 -2
  2. package/dist/mtok-relay.mjs +2517 -174
  3. package/package.json +2 -1
package/README.md CHANGED
@@ -1,8 +1,173 @@
1
1
  # mtok-relay
2
2
 
3
- Reference seller relay for [mtok.market](https://mtok.market). Run with:
3
+ Reference paid seller relay for [mtok.market](https://mtok.market).
4
4
 
5
- npx mtok-relay --offer <id> --model <id> --upstream <url> --out-price <positive-usd-per-MTok>
5
+ `mtok-relay` sits in front of an OpenAI-compatible upstream you control. A buyer
6
+ pays one bounded draw on Base through MtokDripLedger, sends the confirmed
7
+ `drawPaidTxHash` to your `/chunk` endpoint, and the relay verifies the `DrawPaid`
8
+ event before it spends your upstream key. It caps the serve to the paid amount,
9
+ caches the completion for honest retries, and reports nothing back to the
10
+ platform. The market indexes the draw from Base events.
11
+
12
+ The relay is dual-stack for rolling upgrades. Offers signed with
13
+ `requestHashScheme: "nonce-v1"` carry a buyer-generated 16-byte `requestNonce`;
14
+ their on-chain `requestHash` commits to
15
+ `JSON.stringify({ request, requestNonce })`, preventing chain observers from
16
+ guessing common prompts and reading cached completions. Offers without the
17
+ marker use the legacy request-only hash. Set the marker only after every relay
18
+ instance behind the advertised endpoint runs this dual-stack version. Legacy
19
+ text requests remain redeemable, but old optional fields such as `top_p`,
20
+ `stop`, tools, streaming, or multi-completion controls are ignored; the relay
21
+ still pins one non-streaming text completion to the listed model.
22
+
23
+ ```sh
24
+ npx mtok-relay --offer <offerId> --model <id> --upstream <url> --out-price <usd/MTok>
25
+ ```
26
+
27
+ Required environment:
28
+
29
+ - `UPSTREAM_KEY`: bearer token for the upstream model server.
30
+ - `RELAY_WALLET_KEY`: seller EVM key, used only to derive the settlement
31
+ address, unless `--settlement-pubkey` is supplied.
32
+
33
+ Common flags:
34
+
35
+ - `--api <url>`: mtok market host, default `https://mtok.market`.
36
+ - `--port <n>`: relay port, default `8788`.
37
+ - `--rpc <url>`: Base RPC override.
38
+ - `--settlement-pubkey <0x...>`: seller payout wallet, if you do not want to
39
+ derive it from `RELAY_WALLET_KEY`.
40
+ - `--seller-agent <id>`: seller agent id that owns the offer.
41
+ - `--out-price <usd/MTok>`: required positive local output cost used to bound the
42
+ serve from the verified paid amount.
43
+ - `--in-price <usd/MTok>`: positive local input cost. Defaults to `--out-price`.
44
+ - `--redemption-file <path>`: durable at-most-one-upstream-attempt log for one host. The default is
45
+ `./.mtok-redemption.jsonl`. An empty path is rejected; an unwritable path makes
46
+ paid serves fail closed before upstream inference.
47
+ - `RELAY_REDEMPTION_DATABASE_URL`: PostgreSQL connection URL for replicas on
48
+ separate hosts. Every replica serving the same offers must use the same
49
+ database. Set it through your host's secret store. It cannot be combined with
50
+ an explicit redemption file.
51
+ - `--payer-denylist <a,b,c>`: optional payer wallet denylist checked after
52
+ `DrawPaid` verification.
53
+
54
+ Delivery is at-most-once by design. A paid draw is durably claimed before the
55
+ upstream call, and a failure after that claim (an upstream timeout, a model
56
+ echo mismatch) leaves the draw permanently unservable: every retry gets
57
+ `409 draw_pending`, and buyers should treat it as terminal. This protects the
58
+ seller's upstream from being re-run repeatedly against one payment; the cost is
59
+ that a transient upstream blip can consume a paid draw. That trade is
60
+ deliberate ([#601](https://github.com/mtok-market/mtok-market/issues/601)
61
+ tracks an optional bounded-retry mode).
62
+
63
+ ### Shared redemption and migration
64
+
65
+ The default file protects processes sharing that file and its `.claims`
66
+ directory. Separate local files do not coordinate replicas. For replicas with
67
+ separate disks, set `RELAY_REDEMPTION_DATABASE_URL` on every instance to one
68
+ PostgreSQL database. The relay creates `mtok_redemptions`, its key-alias table,
69
+ and an expiry index;
70
+ its database role needs table/index creation and read/write access there. Use
71
+ TLS certificate verification for remote connections (`sslmode=verify-full` in
72
+ the URL), retain database backups, and use a database deployment that preserves
73
+ acknowledged commits during failover. A lagging read replica is not a claim
74
+ authority. The relay requests synchronous commits and never falls back to a
75
+ local file if PostgreSQL is unavailable.
76
+
77
+ Before moving existing file-backed relays:
78
+
79
+ 1. Stop every old relay process and confirm its upstream requests have ended.
80
+ Stop traffic to every route serving those offers during the migration.
81
+ 2. Copy each process's JSONL file and adjacent `.claims` directory, preserving
82
+ both. Do not delete the originals or import while old writers are running.
83
+ 3. With the shared database URL supplied in the environment, run
84
+ `npx mtok-relay import-redemptions /copy/first.jsonl /copy/second.jsonl`.
85
+ Include every old writer, even if its log is empty: its marker directory may
86
+ contain an interrupted claim. Pre-marker relay versions need only their log.
87
+ Repeat the command safely after an interrupted import. A malformed record or
88
+ conflicting saved completion exits unsuccessfully; resolve it before
89
+ resuming traffic.
90
+ 4. Start every replacement with the same database URL, then restore traffic.
91
+ Keep that database when restarting, scaling or rolling back. Returning to
92
+ the old files would forget the claims made after migration.
93
+
94
+ One unique row admits the first claim. An unresolved claim stays pending; a
95
+ saved completion can be replayed by any replica after payment verification.
96
+ Legacy unprefixed and `legacy-v0` keys share a row, while `nonce-v1` remains
97
+ separate. Cleanup retains claims beyond the seven-day payment-age window and
98
+ its clock allowance. Database or import failures never authorize inference.
99
+
100
+ The relay admits at most 64 requests at once, including body readers and booking
101
+ waiters, and 1,200 requests per minute in total. Excess work gets `503 relay_busy`
102
+ or `429 rate_limited` before verification. Bodies have a 10-second deadline;
103
+ configuration and each chain RPC have a 5-second deadline. Upstream HTTP calls
104
+ abort after 120 seconds, including response-body reads. Set
105
+ `RELAY_MAX_CONCURRENT_REQUESTS` and `RELAY_UPSTREAM_TIMEOUT_MS` for your upstream's
106
+ capacity and generation time. A post-claim timeout keeps its durable claim.
107
+ The SDK leaves temporary failures `paid_unresolved`; it never pays again or
108
+ automatically disputes those responses. A completed retry can still replay its
109
+ stored result after receipt verification. An unverifiable payment age returns
110
+ `503 payment_age_unavailable` before any new claim.
111
+
112
+ New claims check the platform fee schedule at the verified payment block time.
113
+ `GET /api/config` advertises the current `feeBps` and `feeSchedule` activation
114
+ history (`effectiveAtMs`, `feeBps`). A rate quoted in the minute before payment
115
+ remains valid, so a transaction mining across an increase has a bounded grace.
116
+ An older payment keeps its historical rate across relay restarts. The relay
117
+ refreshes policy at least once per minute before new claims; an unavailable or
118
+ invalid refresh returns `503 fee_policy_unavailable` without claiming the draw.
119
+ Retry the same payment. Completed and pending claims still require payment proof
120
+ but do not depend on another fee-policy fetch. The fee recipient remains pinned
121
+ at startup; a recipient migration requires coordinated relay restarts.
122
+
123
+ Caller limits allow 120 requests per minute per IPv4 address or IPv6 /64. When
124
+ running behind a reverse proxy, set `RELAY_TRUSTED_PROXIES` to its exact peer IPs
125
+ (comma-separated). Only those peers may supply a single client address in
126
+ `CF-Connecting-IP`, or the header named by `RELAY_CLIENT_IP_HEADER`. Configure the
127
+ proxy to overwrite that header and prevent direct clients from impersonating the
128
+ proxy. Untrusted, missing, malformed, or multi-address headers fall back to the
129
+ socket peer; forwarded headers are never trusted by default.
130
+
131
+ The transport core is `mtok-bridge`: the relay composes the bridge's
132
+ OpenAI-compatible upstream forwarding with settlement verification and
133
+ redemption. If you want free private access with no market, account, or payment,
134
+ run `npx mtok-bridge` instead.
135
+
136
+ ## Release notes
137
+
138
+ 0.2.2:
139
+
140
+ - Input-estimate fix (#626). The serve bound estimated one token per UTF-8 byte,
141
+ a true worst case but roughly 4x pessimistic for real text. Because the bound
142
+ refuses AFTER the buyer has paid on chain, an honest ~4KB prompt on a budget
143
+ that covered it was refused and auto-disputed. The estimate is now bytes with
144
+ a ~25% margin (`BYTES_PER_TOKEN_EST`). A prompt that genuinely cannot be paid
145
+ for still refuses before any upstream spend.
146
+
147
+ 0.2.1:
148
+
149
+ - boot-time compaction of redemption claim markers (#600), and the serve state
150
+ machine now composes `createServeCore` from mtok-bridge (#603), behavior
151
+ unchanged.
152
+
153
+ 0.2.0 (breaking):
154
+
155
+ - `--out-price` is now required and must be positive; `--in-price` is validated
156
+ the same way (it still defaults to `--out-price`). A 0.1.x command line
157
+ without `--out-price` refuses to boot.
158
+ - An empty `--redemption-file` is rejected at startup, and an unwritable path
159
+ fails closed before upstream spend (0.1.x fell back to in-memory with a
160
+ warning).
161
+ - Paid draws are claimed durably before the upstream call; post-claim failures
162
+ are terminal (`409 draw_pending`). See the delivery note above.
163
+ - The input estimator now upper-bounds tokens by UTF-8 bytes instead of
164
+ chars/3.2, roughly 4x more conservative: a long prompt refuses earlier unless
165
+ the draw funds the input leg accordingly.
166
+ - Requests are validated and rebuilt from a whitelist before forwarding. The
167
+ model is pinned to the configured one, streaming and `n>1` are rejected, and
168
+ legacy optional fields (`top_p`, `stop`, tools) are dropped rather than
169
+ forwarded.
170
+ - `requestHashScheme: "nonce-v1"` support (see the dual-stack note above).
6
171
 
7
172
 
8
173
  ---