mtok-bridge 0.3.3 → 0.3.5
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 +9 -1
- package/package.json +1 -1
- package/src/serve-core.mjs +26 -21
package/README.md
CHANGED
|
@@ -41,10 +41,18 @@ tool, one layer on top.
|
|
|
41
41
|
(and the workers-ai house seller) run: validate request => verify the on-chain DrawPaid
|
|
42
42
|
(request-hash bound) => bound both legs against the payment => claim => upstream => complete,
|
|
43
43
|
fail-closed. The redemption store is a pluggable interface (`{ state, get, claim, complete,
|
|
44
|
-
retentionMs }`, each method sync or async: the core awaits every call)
|
|
44
|
+
retentionMs }`, each method sync or async: the core awaits every call). The store must make
|
|
45
|
+
claims atomic across every host serving that offer. Independent local files and eventually
|
|
46
|
+
consistent KV read/write pairs do not provide that guarantee. If you
|
|
45
47
|
are just serving a model for a key, you never need it; it is here so every mtok seller host
|
|
46
48
|
shares one money path.
|
|
47
49
|
|
|
50
|
+
`state(key, { claimKey })` and `get(key, { claimKey })` receive the canonical commitment
|
|
51
|
+
identity even when `key` names a legacy record. `claim(key, markerKey, { paidAtMs })` receives
|
|
52
|
+
the verifier's payment timestamp, so a store migrating old claims can refuse ambiguous
|
|
53
|
+
pre-cutover payments. Existing stores may ignore these additional arguments. The core still
|
|
54
|
+
verifies the payment before reading a completion, and known claims never run upstream again.
|
|
55
|
+
|
|
48
56
|
|
|
49
57
|
---
|
|
50
58
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "mtok-bridge",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.5",
|
|
4
4
|
"description": "Serve any model as an OpenAI-compatible API with a key. No payment, no market, runs anywhere node runs. The transport core behind mtok.market's seller relay.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
package/src/serve-core.mjs
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
// payer-screen POLICY stay in the host.
|
|
9
9
|
//
|
|
10
10
|
// The redemption dependency is an INTERFACE the caller passes:
|
|
11
|
-
// { state(key), get(key), claim(key, markerKey), complete(key, payload), retentionMs }
|
|
11
|
+
// { state(key, { claimKey }), get(key, { claimKey }), claim(key, markerKey, { paidAtMs }), complete(key, payload), retentionMs }
|
|
12
12
|
// state(key) returns 'pending' | 'complete' | null. claim(key, markerKey) returns false when the
|
|
13
13
|
// draw is already claimed and THROWS when it cannot claim durably (the core then refuses before
|
|
14
14
|
// upstream spend). complete(key, payload) throws when the payload cannot be persisted; the claim
|
|
@@ -243,11 +243,6 @@ export function createServeCore({
|
|
|
243
243
|
maxOutputTokens,
|
|
244
244
|
}) {
|
|
245
245
|
const serve = async (body) => {
|
|
246
|
-
// #651: feeBps/feeRecipient may be getters so a long-running relay tracks a
|
|
247
|
-
// platform fee change instead of pinning the boot rate (a fee DECREASE with a
|
|
248
|
-
// stale higher rate would refuse an already-paid draw as fee_amount_too_low).
|
|
249
|
-
// Resolve once per serve and use the resolved values everywhere below.
|
|
250
|
-
const currentFeeBps = typeof feeBps === 'function' ? feeBps() : feeBps;
|
|
251
246
|
const currentFeeRecipient = typeof feeRecipient === 'function' ? feeRecipient() : feeRecipient;
|
|
252
247
|
const { bookingId, n, buyerId, request, requestNonce, drawPaidTxHash } = body;
|
|
253
248
|
const hasRequestNonce = Object.hasOwn(body, 'requestNonce');
|
|
@@ -273,6 +268,7 @@ export function createServeCore({
|
|
|
273
268
|
// Legacy redemption remains only to honor draws already paid by old SDKs.
|
|
274
269
|
const cacheKey = `${requestHashScheme}:${bookingId}:${n}:${requestHash}`;
|
|
275
270
|
const oldLegacyKey = hasRequestNonce ? null : `${bookingId}:${n}:${requestHash}`;
|
|
271
|
+
const redemptionContext = { claimKey: cacheKey };
|
|
276
272
|
|
|
277
273
|
// Contract mode is the ONLY mode (#487): the legacy direct-transfer FUND
|
|
278
274
|
// lane and its /api/bookings/:id balance read are gone. If the platform is
|
|
@@ -286,18 +282,18 @@ export function createServeCore({
|
|
|
286
282
|
// The redemption interface may be sync (fs store) or async (a Workers KV
|
|
287
283
|
// store): await normalizes both, and identity-awaits cost nothing under the
|
|
288
284
|
// host's per-booking lock.
|
|
289
|
-
let redemptionState = await redemption.state(storedKey);
|
|
285
|
+
let redemptionState = await redemption.state(storedKey, redemptionContext);
|
|
290
286
|
// Preserve an upgrade's pre-scheme redemption log; missing this alias
|
|
291
287
|
// would let a legacy paid draw run upstream again after relay upgrade.
|
|
292
288
|
if (!redemptionState && oldLegacyKey) {
|
|
293
|
-
const oldLegacyState = await redemption.state(oldLegacyKey);
|
|
289
|
+
const oldLegacyState = await redemption.state(oldLegacyKey, redemptionContext);
|
|
294
290
|
if (oldLegacyState) {
|
|
295
291
|
storedKey = oldLegacyKey;
|
|
296
292
|
redemptionState = oldLegacyState;
|
|
297
293
|
} else {
|
|
298
294
|
// Checking the legacy marker may have refreshed a prefixed record
|
|
299
295
|
// appended by another upgraded process.
|
|
300
|
-
redemptionState = await redemption.state(cacheKey);
|
|
296
|
+
redemptionState = await redemption.state(cacheKey, redemptionContext);
|
|
301
297
|
}
|
|
302
298
|
}
|
|
303
299
|
let paid;
|
|
@@ -313,9 +309,9 @@ export function createServeCore({
|
|
|
313
309
|
requestHash,
|
|
314
310
|
sellerWallet,
|
|
315
311
|
feeRecipient: currentFeeRecipient,
|
|
316
|
-
// A known
|
|
312
|
+
// A known claim spends no new inference. New claims need a verified
|
|
317
313
|
// age because both payload and claim markers expire after retention.
|
|
318
|
-
maxPaidAgeMs: redemptionState === 'complete' ? undefined : redemption.retentionMs,
|
|
314
|
+
maxPaidAgeMs: redemptionState === 'complete' || redemptionState === 'pending' ? undefined : redemption.retentionMs,
|
|
319
315
|
});
|
|
320
316
|
} catch (e) {
|
|
321
317
|
if (e.name === 'TimeoutError') return { status: 503, body: { error: 'relay_timeout', _bookingId: bookingId } };
|
|
@@ -323,14 +319,6 @@ export function createServeCore({
|
|
|
323
319
|
}
|
|
324
320
|
if (paid?.reason === 'payment_age_unavailable') return { status: 503, body: { error: 'payment_age_unavailable', detail: 'payment age could not be verified; retry this same paid draw', _bookingId: bookingId } };
|
|
325
321
|
if (!paid?.ok) return { status: 402, body: { error: 'payment_unverified', detail: paid?.reason || 'unknown' } };
|
|
326
|
-
const expectedFee = configuredFeeAtomic({
|
|
327
|
-
sellerUsdAtomic: paid.event.sellerUsdAtomic,
|
|
328
|
-
feeAddress: currentFeeRecipient,
|
|
329
|
-
feeBps: currentFeeBps,
|
|
330
|
-
});
|
|
331
|
-
if (BigInt(paid.event.feeUsdAtomic || 0) < expectedFee) {
|
|
332
|
-
return { status: 402, body: { error: 'payment_unverified', detail: 'fee_amount_too_low' } };
|
|
333
|
-
}
|
|
334
322
|
// Screen the verified payer before spending upstream capacity. The
|
|
335
323
|
// payment already settled on-chain (that money is the buyer's loss);
|
|
336
324
|
// this refuses the SERVICE, which is the only refusal an edge can
|
|
@@ -345,10 +333,27 @@ export function createServeCore({
|
|
|
345
333
|
return { status: 403, body: { error: 'payer_denied', detail: 'payer screening failed: ' + e.message } };
|
|
346
334
|
}
|
|
347
335
|
}
|
|
348
|
-
if (redemptionState === 'complete')
|
|
336
|
+
if (redemptionState === 'complete') {
|
|
337
|
+
const payload = await redemption.get(storedKey, redemptionContext);
|
|
338
|
+
if (payload == null) return { status: 503, body: { error: 'redemption_unavailable', detail: 'saved completion is no longer readable; retry this same paid draw', _bookingId: bookingId } };
|
|
339
|
+
return { status: 200, body: payload };
|
|
340
|
+
}
|
|
349
341
|
if (redemptionState === 'pending') {
|
|
350
342
|
return { status: 409, body: { error: 'draw_pending', detail: 'this paid draw was already claimed; refusing to run upstream again', _bookingId: bookingId } };
|
|
351
343
|
}
|
|
344
|
+
// Completed/pending claims already crossed the fee gate and cannot spend
|
|
345
|
+
// again. New claims use the policy at the verified payment time.
|
|
346
|
+
let currentFeeBps;
|
|
347
|
+
try {
|
|
348
|
+
currentFeeBps = typeof feeBps === 'function' ? await feeBps(paid) : feeBps ?? 0;
|
|
349
|
+
if (!Number.isSafeInteger(currentFeeBps) || currentFeeBps < 0 || currentFeeBps > 10_000) throw new TypeError('invalid fee rate');
|
|
350
|
+
} catch {
|
|
351
|
+
return { status: 503, body: { error: 'fee_policy_unavailable', detail: 'fee policy could not be verified; retry this same paid draw', _bookingId: bookingId } };
|
|
352
|
+
}
|
|
353
|
+
const expectedFee = configuredFeeAtomic({ sellerUsdAtomic: paid.event.sellerUsdAtomic, feeAddress: currentFeeRecipient, feeBps: currentFeeBps });
|
|
354
|
+
if (BigInt(paid.event.feeUsdAtomic || 0) < expectedFee) {
|
|
355
|
+
return { status: 402, body: { error: 'payment_unverified', detail: 'fee_amount_too_low' } };
|
|
356
|
+
}
|
|
352
357
|
const paidEvent = paid.event;
|
|
353
358
|
const remainingUsd = Number(paid.event.sellerUsdAtomic || 0) / 1e6;
|
|
354
359
|
if (remainingUsd < BALANCE_EPSILON) {
|
|
@@ -376,7 +381,7 @@ export function createServeCore({
|
|
|
376
381
|
try {
|
|
377
382
|
// Legacy uses its old unprefixed identity only for the atomic marker so
|
|
378
383
|
// parallel upgraded stores and an existing log converge on one claim.
|
|
379
|
-
if (!(await redemption.claim(cacheKey, oldLegacyKey ?? cacheKey))) {
|
|
384
|
+
if (!(await redemption.claim(cacheKey, oldLegacyKey ?? cacheKey, { paidAtMs: paid.paidAtMs }))) {
|
|
380
385
|
return { status: 409, body: { error: 'draw_pending', detail: 'this paid draw was already claimed; refusing to run upstream again', _bookingId: bookingId } };
|
|
381
386
|
}
|
|
382
387
|
} catch (e) {
|