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.
- package/README.md +167 -2
- package/dist/mtok-relay.mjs +2517 -174
- 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).
|
|
3
|
+
Reference paid seller relay for [mtok.market](https://mtok.market).
|
|
4
4
|
|
|
5
|
-
|
|
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
|
---
|