@twzrd1111/clawrouter-mpp 0.1.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 +172 -15
- package/dist/index.d.ts +204 -27
- package/dist/index.js +966 -112
- package/package.json +16 -5
package/README.md
CHANGED
|
@@ -3,45 +3,202 @@
|
|
|
3
3
|
MPP (Machine Payments Protocol) payment rails for [ClawRouter](https://github.com/BlockRunAI/ClawRouter):
|
|
4
4
|
pay an MPP seller over **Tempo** or **Solana** instead of x402, under ClawRouter's own spend limits.
|
|
5
5
|
|
|
6
|
+
## Upgrading from 0.2.0
|
|
7
|
+
|
|
8
|
+
- Fulfillment rows now use `twzrd.mpp.fulfillment.v2`. Readers must accept the new schema to retain
|
|
9
|
+
`response_bytes` and `body_sha256`; these fields are null when the recorder does not read a complete
|
|
10
|
+
body within its 1 MiB cap.
|
|
11
|
+
- Credit rows carry an optional `reference` matching the error and spend record. It is a local
|
|
12
|
+
signed-payload hash, not a settlement signature.
|
|
13
|
+
- `payFetch.flush({ timeoutMs })` bounds waiting for both records and ledger persistence. A timeout
|
|
14
|
+
leaves the paid response and background writes intact; an omitted timeout keeps the previous
|
|
15
|
+
unbounded wait. Values above 2,147,483,647 ms are rejected.
|
|
16
|
+
|
|
6
17
|
```ts
|
|
7
18
|
import { startProxy, SpendControl, FileSpendControlStorage } from "@twzrd1111/clawrouter";
|
|
8
19
|
import { createMppFetch, createSolanaMppFetch } from "@twzrd1111/clawrouter-mpp";
|
|
20
|
+
import { createTwzrdMppIntegration, createLocalDecisionSigner } from "twzrd-x402-gate";
|
|
9
21
|
|
|
10
22
|
const spend = new SpendControl({ storage: new FileSpendControlStorage() });
|
|
23
|
+
const twzrdMpp = createTwzrdMppIntegration({
|
|
24
|
+
signer: createLocalDecisionSigner(),
|
|
25
|
+
policy: { maxAmountUsd: "1.00" },
|
|
26
|
+
});
|
|
11
27
|
|
|
12
|
-
// Tempo (needs `mppx`)
|
|
13
|
-
const payFetch = createMppFetch(account, fetch, { spend });
|
|
14
|
-
|
|
15
|
-
// const payFetch = await createSolanaMppFetch({ signer, rpcUrl, network: "devnet", spend });
|
|
28
|
+
// Tempo (needs `mppx`); alternatively use createSolanaMppFetch with the same hooks.
|
|
29
|
+
const payFetch = createMppFetch(account, fetch, { spend, integration: twzrdMpp });
|
|
30
|
+
const proxy = await startProxy({ spendControl: spend, payFetch /* ... */ });
|
|
16
31
|
|
|
17
|
-
|
|
32
|
+
// proxy.close() closes the listener, then drains the rail's pending ledger writes.
|
|
33
|
+
await proxy.close();
|
|
18
34
|
```
|
|
19
35
|
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
36
|
+
The supported setup uses the integration helper returned by `createTwzrdMppIntegration`.
|
|
37
|
+
The `twzrdMppIntegration` marker is a wiring check only; it is forgeable and does not authenticate
|
|
38
|
+
the origin of the hooks. Its gate receives the full MPP charge challenge before credential
|
|
39
|
+
creation and returns
|
|
40
|
+
`{ disposition: "allow" | "warn" | "block" }`; `block` and callback errors refuse payment before
|
|
41
|
+
signing, while `warn` proceeds. The MPP rail rejects integrations without the marker or required
|
|
42
|
+
hooks before loading payment SDKs.
|
|
43
|
+
|
|
44
|
+
The integration writes the pre-sign decision durably to `.twzrd/decisions.jsonl` before allowing
|
|
45
|
+
credential creation, then appends the projected MPP receipt to that same file. Fulfillment writes
|
|
46
|
+
run asynchronously so they do not hold the response stream. `payFetch.flush?.()` waits for pending
|
|
47
|
+
records and fsyncs the local ledger during orderly shutdown. `startProxy` calls the rail's flush hook
|
|
48
|
+
after closing its listener, and direct rail users must call `payFetch.flush()` themselves. It waits for
|
|
49
|
+
active SSE responses too, so drain or cancel them before flushing; an open stream can keep the flush
|
|
50
|
+
pending. `payFetch.flush({ timeoutMs })` bounds the entire wait for records and ledger persistence
|
|
51
|
+
(0 through 2,147,483,647 ms). At the deadline it still attempts the ledger flush in the background,
|
|
52
|
+
then rejects with an `AggregateError` holding an `MppFlushTimeoutError`. Its `pending` field counts
|
|
53
|
+
unfinished records; `ledgerPending` indicates whether ledger flushing is still in progress.
|
|
54
|
+
A late ledger failure is reported once: by a flush still waiting on that ledger, otherwise by the next explicit flush. (Ownership is tracked per ledger promise; an integration that returns a fresh wrapper promise for the same failure to each caller can still see it twice.) A timeout does not confirm durability.
|
|
55
|
+
It never cancels or reads a response body, so a late reader still gets the whole paid response,
|
|
56
|
+
and its record lands when that body is drained. Network contribution remains off unless separately enabled. It blocks unscored
|
|
57
|
+
networks by default, including Tempo mainnet and Moderato, until TWZRD has a reputation corpus for
|
|
58
|
+
them. The default provider also fails closed on outages; the ledger sink itself makes no network
|
|
59
|
+
call. An explicit `allowUnscoredNetwork: true` requires `policy.maxAmountUsd` and returns
|
|
60
|
+
`reputation_unchecked` as a warning; warnings proceed unless `treatWarnAsBlock` is set. The
|
|
61
|
+
fulfillment row omits prompts, responses, credentials, and raw receipt headers; its seller model
|
|
62
|
+
field is explicitly a seller claim. Network contribution remains a separate opt-in.
|
|
63
|
+
|
|
64
|
+
This integration requires `twzrd-x402-gate@0.17.0`, which is not published yet. npm's current
|
|
65
|
+
`0.16.2` does not include Tempo charge evaluation or the shared-ledger adapter.
|
|
66
|
+
|
|
67
|
+
Receipt-safe payment metadata is available through `readPaidCallEvidence(response)` for a paid
|
|
68
|
+
response from this rail. The
|
|
69
|
+
fulfillment hook records the decoded receipt reference, never the raw base64 receipt or payment
|
|
70
|
+
credential. Tempo session receipts emitted in an SSE `payment-receipt` event are captured when the
|
|
71
|
+
response stream completes; the stream bytes are forwarded unchanged, including the seller-reported
|
|
72
|
+
model label from its first JSON data event. A `valid_format` receipt means its structure, method, and
|
|
73
|
+
intent match the challenge; it is not an on-chain verification. `seller_reported_model_matches_request`
|
|
74
|
+
compares labels only and does not verify model identity. A failed local write does not change an
|
|
75
|
+
already-paid response; `payFetch.flush()` surfaces persistence failures to the shutdown caller.
|
|
76
|
+
|
|
77
|
+
Rows use schema `twzrd.mpp.fulfillment.v2`. `response_bytes` and `body_sha256` (lowercase hex
|
|
78
|
+
SHA-256) describe the complete response body as the recorder read it: a JSON body read through its
|
|
79
|
+
clone, or an SSE body tapped to its end. Both are `null` when the recorder did not read the body,
|
|
80
|
+
it exceeded 1 MiB, or the stream was cancelled or errored before its end; a partial body is never
|
|
81
|
+
hashed. The digest is of bytes the seller sent and is not a seller attestation.
|
|
82
|
+
|
|
83
|
+
This candidate requires `@twzrd1111/clawrouter >=0.12.309`, including deferred streaming headers.
|
|
84
|
+
The fork is the release target. Upstream PRs #545 and #546 are independent of this release.
|
|
85
|
+
|
|
86
|
+
For Tempo install the optional peer `mppx@~0.17.0`. For Solana install `@solana/mpp@~0.7.0`,
|
|
87
|
+
`mppx@~0.17.0`, and `@solana/kit@~6.10.0`. Solana now uses the official charge method
|
|
88
|
+
directly, replacing the pay-kit client wrapper. That wrapper's progress events omit the
|
|
89
|
+
challenge ID; the direct method lets policy checks and credits use the full challenge
|
|
90
|
+
before signing. `@solana/pay-kit` is used only by development tests as a fixture seller.
|
|
25
91
|
|
|
26
92
|
## What it guarantees
|
|
27
93
|
|
|
28
94
|
- **Spend limits apply.** The amount, network, asset and payee are checked, and budget reserved, _before_
|
|
29
|
-
anything is signed.
|
|
95
|
+
anything is signed. Solana checks the complete challenge before transaction construction
|
|
96
|
+
and pins the configured network even when no spend control is supplied.
|
|
30
97
|
The proxy refuses to start a rail that does not set `enforcesSpend` while amount limits are configured.
|
|
31
98
|
- **A signed payment is never paid twice.** If a call fails after a credential or transaction was signed, it
|
|
32
99
|
ends in `MppPaidNoCompletionError`: the proxy answers 502 `payment_settled_no_completion` and does not
|
|
33
100
|
retry or fall back. The spend ledger keeps the spend with a note, and the error carries a payment
|
|
34
101
|
reference (`mpp-<16 hex>`, a hash of the signed payload) for disputes.
|
|
35
|
-
- **
|
|
36
|
-
|
|
102
|
+
- **Streaming receipts.** Both factories set `deferStreamingHeaders`. The proxy holds final
|
|
103
|
+
headers while paying, sends informational HTTP 102 responses, and forwards the seller's
|
|
104
|
+
exact `Payment-Receipt` on the final streaming response. It does not fabricate a missing receipt.
|
|
105
|
+
Both also set `supportsNativeStreaming`: streaming chat requests retain `stream: true`
|
|
106
|
+
upstream and request usage chunks. Native SSE flows incrementally, including structured
|
|
107
|
+
tool calls, with reasoning filtered and token/cost accounting retained.
|
|
108
|
+
Ambiguous text-encoded calls wait for the choice to finish so recovery matches the existing
|
|
109
|
+
whole-message parser; structured tool deltas and ordinary text flow incrementally.
|
|
110
|
+
JSON-only replies use the existing simulated stream. In-flight coalesced callers receive live chunks from
|
|
111
|
+
one paid attempt; completed cache replays do not claim a new receipt.
|
|
112
|
+
HTTP 102 does not extend OpenClaw's deadline. Real content/tool deltas reset client activity
|
|
113
|
+
timers as the model streams. Payment/header wait and stalled output still have deadlines.
|
|
114
|
+
- **Optional peers.** Importing this package does not load payment SDKs. Tempo loads `mppx` on
|
|
115
|
+
first use; the asynchronous Solana factory loads `@solana/mpp`. Missing peers produce an
|
|
116
|
+
install instruction.
|
|
117
|
+
|
|
118
|
+
"Signed" is not "settled": on Solana the seller submits the transaction. Seller-side refunds
|
|
119
|
+
remain outside this package.
|
|
120
|
+
|
|
121
|
+
## Replay protection at a seller
|
|
122
|
+
|
|
123
|
+
```ts
|
|
124
|
+
import { SpentProofLedger, fulfillPaidProof, ReplayRejectedError } from "@twzrd1111/clawrouter-mpp";
|
|
125
|
+
|
|
126
|
+
const proofs = new SpentProofLedger(); // share across requests in this process
|
|
127
|
+
|
|
128
|
+
// First authenticate the challenge and independently verify its payment on chain.
|
|
129
|
+
// Pass a canonical network ID plus verified transaction and challenge IDs,
|
|
130
|
+
// never untrusted body fields. Example IDs: "solana:mainnet-beta", "eip155:4217".
|
|
131
|
+
const completion = await fulfillPaidProof({
|
|
132
|
+
ledger: proofs,
|
|
133
|
+
networkId: verifiedNetworkId,
|
|
134
|
+
txSignature: verifiedSignature,
|
|
135
|
+
challengeId: verifiedChallengeId,
|
|
136
|
+
upstream: () => runModel(),
|
|
137
|
+
});
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
A reused transaction or challenge on the same network throws `ReplayRejectedError` with
|
|
141
|
+
`status: 409` and `code: "proof_replayed"` before invoking `upstream`. Network scope allows
|
|
142
|
+
the same signature text or challenge ID to exist independently on another chain. The
|
|
143
|
+
synchronous claim gives concurrent duplicates one winner. The claim remains spent if the
|
|
144
|
+
model fails. This helper does not verify a payment itself,
|
|
145
|
+
and installing a buyer rail cannot add replay protection to a remote seller.
|
|
146
|
+
|
|
147
|
+
`SpentProofLedger` keeps claims in memory, so a restart or a second process forgets spent
|
|
148
|
+
proofs, and a proof can be redeemed again while its challenge is still valid. A seller with
|
|
149
|
+
more than one process, or one that restarts, needs a durable, atomic uniqueness check shared by
|
|
150
|
+
every worker (for example a unique key on network plus transaction signature, as an
|
|
151
|
+
authoritative settlement table provides). Rejecting expired challenges narrows the window but
|
|
152
|
+
does not close it on its own.
|
|
153
|
+
|
|
154
|
+
## Credits for signed calls with no completion
|
|
155
|
+
|
|
156
|
+
```ts
|
|
157
|
+
import { MppPaidNoCompletionError, readCredits } from "@twzrd1111/clawrouter-mpp";
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
await payFetch(url, init);
|
|
161
|
+
} catch (error) {
|
|
162
|
+
if (error instanceof MppPaidNoCompletionError) {
|
|
163
|
+
console.log(readCredits()); // immutable snapshots; do not log credentials
|
|
164
|
+
}
|
|
165
|
+
throw error;
|
|
166
|
+
}
|
|
167
|
+
```
|
|
168
|
+
|
|
169
|
+
Both rails record `{ payer, amount, challengeId, reason }` on a signed call's HTTP error or
|
|
170
|
+
transport failure. Payer comes from the supplied account/signer, never the seller's recipient.
|
|
171
|
+
Amount is the original integer base-unit string. Repeating a payer/challenge records one row.
|
|
172
|
+
|
|
173
|
+
When the proxy detects a provider error hidden inside HTTP 200, it calls the rail's
|
|
174
|
+
`onNoCompletion(response, reason)` hook. For a signed response this records the credit and
|
|
175
|
+
throws the same terminal error before any fallback model is paid. Unsigned responses retain
|
|
176
|
+
the ordinary provider fallback behavior. A custom host using these rails can invoke that
|
|
177
|
+
hook after its own completion validation; the response must be the one returned by the rail.
|
|
178
|
+
|
|
179
|
+
For a broken or unfinished native body the proxy invokes that same hook, records one credit,
|
|
180
|
+
and ends the existing SSE response with `payment_settled_no_completion`. Its HTTP 200 and
|
|
181
|
+
receipt have already been sent, so this is an in-stream error rather than a new HTTP 502.
|
|
182
|
+
The proxy never retries a model or pays a fallback after committing that stream. Disconnects
|
|
183
|
+
and request deadlines cancel the upstream reader and clean up coalesced callers.
|
|
184
|
+
|
|
185
|
+
`CreditLedger` and `creditPaidNoCompletion(ledger, error, fields)` are also public for callers
|
|
186
|
+
with their own ledger; `readCredits(ledger)` reads that ledger. Retrying the credit operation
|
|
187
|
+
returns the same immutable row and invokes no signer. It does not retry a model request.
|
|
188
|
+
Each row carries the payment `reference` from the error (or from `fields.reference`) when one
|
|
189
|
+
is known, so a credit can be joined to the error and the spend record. The reference is a hash
|
|
190
|
+
of the signed payload, not a chain signature; an unknown reference is left off the row.
|
|
37
191
|
|
|
38
|
-
|
|
39
|
-
|
|
192
|
+
Both ledgers are in memory, scoped to one process, and reset on restart. Credits record
|
|
193
|
+
signed attempts, not confirmed settlement or redeemable balances. There is no refund, chain
|
|
194
|
+
write, cross-process exclusion, persistence, or automatic reconciliation in these helpers.
|
|
40
195
|
|
|
41
196
|
## Develop
|
|
42
197
|
|
|
43
198
|
```
|
|
44
199
|
npm install # from the ClawRouter repo root
|
|
200
|
+
# Before the candidate core is published, install rails development dependencies:
|
|
201
|
+
npm install --legacy-peer-deps # in packages/clawrouter-mpp
|
|
45
202
|
npm run typecheck && npm test && npm run build # in packages/clawrouter-mpp
|
|
46
203
|
```
|
|
47
204
|
|
package/dist/index.d.ts
CHANGED
|
@@ -1,14 +1,119 @@
|
|
|
1
1
|
import { Account } from 'viem';
|
|
2
|
-
import { SpendControl,
|
|
2
|
+
import { PayFetch, SpendControl, SpendRecord } from '@twzrd1111/clawrouter';
|
|
3
3
|
export { PayFetch } from '@twzrd1111/clawrouter';
|
|
4
|
-
import { SolanaNetwork } from '@solana/pay-kit/client';
|
|
5
4
|
import { KeyPairSigner } from '@solana/kit';
|
|
6
5
|
|
|
6
|
+
declare const MPP_FULFILLMENT_RECORD_SCHEMA = "twzrd.mpp.fulfillment.v2";
|
|
7
|
+
declare const MPP_FULFILLMENT_MAX_BODY_BYTES: number;
|
|
8
|
+
type MppPaymentFacts = {
|
|
9
|
+
challengeId: string;
|
|
10
|
+
method: string;
|
|
11
|
+
intent: string;
|
|
12
|
+
payTo: string | null;
|
|
13
|
+
network: string | null;
|
|
14
|
+
asset: string | null;
|
|
15
|
+
amountAtomic: string | null;
|
|
16
|
+
};
|
|
17
|
+
type MppFulfillmentRecord = {
|
|
18
|
+
schema: typeof MPP_FULFILLMENT_RECORD_SCHEMA;
|
|
19
|
+
at_unix_ms: number;
|
|
20
|
+
resource_origin: string | null;
|
|
21
|
+
http_status: number;
|
|
22
|
+
payment_method: string;
|
|
23
|
+
payment_intent: string;
|
|
24
|
+
challenge_id: string;
|
|
25
|
+
paid_to: string | null;
|
|
26
|
+
network: string | null;
|
|
27
|
+
asset: string | null;
|
|
28
|
+
amount_atomic: string | null;
|
|
29
|
+
receipt_state: "valid_format" | "missing" | "invalid_format" | "unavailable";
|
|
30
|
+
receipt_status: string | null;
|
|
31
|
+
receipt_method: string | null;
|
|
32
|
+
/** Seller-supplied MPP receipt reference (EVM hash or Solana signature); not chain-verified here. */
|
|
33
|
+
receipt_reference: string | null;
|
|
34
|
+
receipt_timestamp: string | null;
|
|
35
|
+
requested_model: string | null;
|
|
36
|
+
/** Model label reported by the seller. This is not an attestation of model identity. */
|
|
37
|
+
seller_reported_model: string | null;
|
|
38
|
+
/** Equality of seller-reported and requested labels only; this does not verify the served model. */
|
|
39
|
+
seller_reported_model_matches_request: boolean | null;
|
|
40
|
+
gate: MppGateDecision | null;
|
|
41
|
+
/**
|
|
42
|
+
* Byte length of the complete response body as received. null when the recorder did not read
|
|
43
|
+
* the body, it exceeded MPP_FULFILLMENT_MAX_BODY_BYTES, or it was cancelled or errored first.
|
|
44
|
+
*/
|
|
45
|
+
response_bytes: number | null;
|
|
46
|
+
/** Lowercase hex SHA-256 of the same complete body; null under the same conditions. */
|
|
47
|
+
body_sha256: string | null;
|
|
48
|
+
};
|
|
49
|
+
/** Size and digest of a complete response body, or null fields when it was not fully read. */
|
|
50
|
+
type MppBodyDigest = {
|
|
51
|
+
response_bytes: number | null;
|
|
52
|
+
body_sha256: string | null;
|
|
53
|
+
};
|
|
54
|
+
type MppFulfillmentSink = (record: MppFulfillmentRecord) => void | Promise<void>;
|
|
55
|
+
|
|
56
|
+
/** Optional buyer-side policy hook evaluated on the exact MPP challenge before signing. */
|
|
57
|
+
type MppGateChallenge = {
|
|
58
|
+
id: string;
|
|
59
|
+
realm?: string;
|
|
60
|
+
method: string;
|
|
61
|
+
intent: string;
|
|
62
|
+
request: Record<string, unknown>;
|
|
63
|
+
expires?: string;
|
|
64
|
+
description?: string;
|
|
65
|
+
};
|
|
66
|
+
/** Safe decision metadata retained with the paid response for receipt verification. */
|
|
67
|
+
type MppGateDecision = {
|
|
68
|
+
disposition: "allow" | "warn" | "block";
|
|
69
|
+
decisionId?: string;
|
|
70
|
+
intentHash?: string;
|
|
71
|
+
reasonCodes?: readonly string[];
|
|
72
|
+
};
|
|
73
|
+
/** Return the TWZRD gate's result. Throwing or returning an invalid result fails closed. */
|
|
74
|
+
type MppGateEvaluator = (challenge: MppGateChallenge) => MppGateDecision | Promise<MppGateDecision>;
|
|
75
|
+
/** Integration contract accepted by the MPP factories. The marker is not an authenticity proof. */
|
|
76
|
+
type MppIntegration = Readonly<{
|
|
77
|
+
twzrdMppIntegration: true;
|
|
78
|
+
gate: MppGateEvaluator;
|
|
79
|
+
onFulfillment: MppFulfillmentSink;
|
|
80
|
+
flush: () => Promise<void>;
|
|
81
|
+
}>;
|
|
82
|
+
declare class MppGateError extends Error {
|
|
83
|
+
readonly code: "MPP_GATE_BLOCKED" | "MPP_GATE_INVALID_RESULT";
|
|
84
|
+
readonly challengeId: string;
|
|
85
|
+
readonly decisionId?: string;
|
|
86
|
+
readonly reasonCodes?: readonly string[];
|
|
87
|
+
constructor(code: MppGateError["code"], challengeId: string, message: string, decision?: MppGateDecision);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
type MppFlushOptions = {
|
|
91
|
+
/**
|
|
92
|
+
* Bound the entire wait for fulfillment records and ledger persistence. At the deadline,
|
|
93
|
+
* ledger flushing is still attempted in the background; neither writes nor response bodies
|
|
94
|
+
* are cancelled. Late ledger failures are retained for the next flush(). Omit to wait forever.
|
|
95
|
+
* Valid range: 0 through 2_147_483_647 milliseconds (Node's maximum timer delay).
|
|
96
|
+
*/
|
|
97
|
+
timeoutMs?: number;
|
|
98
|
+
};
|
|
99
|
+
/** A rail fetch whose flush() accepts an optional deadline. Assignable to PayFetch. */
|
|
100
|
+
type MppPayFetch = PayFetch & {
|
|
101
|
+
flush: (options?: MppFlushOptions) => Promise<void>;
|
|
102
|
+
};
|
|
103
|
+
/** The deadline elapsed; persistence may still be running and response bodies remain caller-owned. */
|
|
104
|
+
declare class MppFlushTimeoutError extends Error {
|
|
105
|
+
readonly pending: number;
|
|
106
|
+
readonly timeoutMs: number;
|
|
107
|
+
readonly ledgerPending: boolean;
|
|
108
|
+
constructor(pending: number, timeoutMs: number, ledgerPending?: boolean);
|
|
109
|
+
}
|
|
110
|
+
|
|
7
111
|
/**
|
|
8
112
|
* Tempo TIP-20 USD stablecoins priced here, lower-case, all 6 decimals (the challenge carries no
|
|
9
113
|
* `decimals`). Any other asset cannot be priced, so it is refused while amount limits are set.
|
|
10
114
|
* - 0x20c0…0000 pathUSD (Moderato testnet 42431, also on mainnet 4217)
|
|
11
|
-
* - 0x20c0…b9537d11c60e8b50
|
|
115
|
+
* - 0x20c0…b9537d11c60e8b50 fee token a Tempo-run MPP proxy quoted on mainnet 4217 (2026-11-03)
|
|
116
|
+
* - 0x20c0…f37de3740adec032 charge currency on that same OpenRouter challenge (6 decimals, 750 = 0.00075 USD)
|
|
12
117
|
*/
|
|
13
118
|
declare const DEFAULT_TEMPO_USD_ASSETS: ReadonlySet<string>;
|
|
14
119
|
type MppFetchOptions = {
|
|
@@ -16,10 +121,17 @@ type MppFetchOptions = {
|
|
|
16
121
|
spend?: SpendControl;
|
|
17
122
|
/** Override the assets treated as 6-decimal USD (lower-case addresses). */
|
|
18
123
|
usdAssets?: ReadonlySet<string>;
|
|
124
|
+
/** Required branded object from createTwzrdMppIntegration. */
|
|
125
|
+
integration: MppIntegration;
|
|
19
126
|
};
|
|
20
127
|
type ChallengeLike = {
|
|
21
128
|
id: string;
|
|
22
|
-
|
|
129
|
+
realm?: string;
|
|
130
|
+
method: string;
|
|
131
|
+
intent: string;
|
|
132
|
+
request: Record<string, unknown>;
|
|
133
|
+
expires?: string;
|
|
134
|
+
description?: string;
|
|
23
135
|
};
|
|
24
136
|
/** The quote as the spend-control layer understands it. `amount` stays undefined if unpriceable. */
|
|
25
137
|
declare function quoteFromChallenge(challenge: ChallengeLike, usdAssets?: ReadonlySet<string>): {
|
|
@@ -29,36 +141,41 @@ declare function quoteFromChallenge(challenge: ChallengeLike, usdAssets?: Readon
|
|
|
29
141
|
amount: string | undefined;
|
|
30
142
|
priceable: boolean;
|
|
31
143
|
};
|
|
32
|
-
|
|
33
|
-
createCredential: (options: any) => Promise<string>;
|
|
34
|
-
};
|
|
35
|
-
/**
|
|
36
|
-
* Put the spend policy in the signing path of an mppx method.
|
|
37
|
-
*
|
|
38
|
-
* mppx's `challenge.received` event is an observer: it runs each handler inside try/catch and goes on to
|
|
39
|
-
* sign, so a handler cannot veto a payment by throwing (verified against mppx 0.17.0 and live, 2026-11-03).
|
|
40
|
-
* `createCredential` is the one place a throw really aborts: reserve (fail-closed) -> sign -> settle,
|
|
41
|
-
* and release the reservation if signing fails. A signed credential counts as spend even if the request
|
|
42
|
-
* then fails, because MPP settles before the upstream responds.
|
|
43
|
-
*/
|
|
44
|
-
declare function guardMethod<M extends MethodLike>(method: M, control: SpendControl | undefined, usdAssets?: ReadonlySet<string>): M;
|
|
45
|
-
declare function createMppFetch(account: Account, base?: typeof fetch, opts?: MppFetchOptions): PayFetch;
|
|
144
|
+
declare function createMppFetch(account: Account, base: typeof fetch | undefined, opts: MppFetchOptions): MppPayFetch;
|
|
46
145
|
|
|
47
|
-
/**
|
|
48
|
-
* 6-decimal USD stablecoin mints priced here (from pay-kit 0.13.1's own tables). Any other mint cannot be
|
|
49
|
-
* priced, so it is refused while amount limits are set. Pass `usdMints` to add e.g. a localnet test mint.
|
|
50
|
-
*/
|
|
51
146
|
declare const DEFAULT_SOLANA_USD_MINTS: ReadonlySet<string>;
|
|
147
|
+
type SolanaNetwork = "mainnet" | "devnet" | "localnet";
|
|
52
148
|
type SolanaMppFetchOptions = {
|
|
53
149
|
signer: KeyPairSigner;
|
|
54
150
|
rpcUrl: string;
|
|
55
151
|
network: SolanaNetwork;
|
|
56
|
-
/** When set, every payment is checked against these limits before it is signed. */
|
|
57
152
|
spend?: SpendControl;
|
|
58
|
-
/** Extra mints to treat as 6-decimal USD (e.g. a localnet test mint). */
|
|
59
153
|
usdMints?: Iterable<string>;
|
|
154
|
+
/** Required branded object from createTwzrdMppIntegration. */
|
|
155
|
+
integration: MppIntegration;
|
|
156
|
+
/** Injectable transport; defaults to fetch. The SDK's RPC transport is separate. */
|
|
157
|
+
fetch?: typeof fetch;
|
|
60
158
|
};
|
|
61
|
-
declare function createSolanaMppFetch(opts: SolanaMppFetchOptions): Promise<
|
|
159
|
+
declare function createSolanaMppFetch(opts: SolanaMppFetchOptions): Promise<MppPayFetch>;
|
|
160
|
+
|
|
161
|
+
/** In-memory credit row. No chain write and no signer. */
|
|
162
|
+
type CreditEntry = Readonly<{
|
|
163
|
+
payer: string;
|
|
164
|
+
amount: string;
|
|
165
|
+
challengeId: string;
|
|
166
|
+
reason: string;
|
|
167
|
+
/**
|
|
168
|
+
* Payment reference from MppPaidNoCompletionError, when one is known: `mpp-` plus a short hash of
|
|
169
|
+
* the signed payload. Joins the row to the error and the spend record. Not a chain tx signature.
|
|
170
|
+
*/
|
|
171
|
+
reference?: string;
|
|
172
|
+
}>;
|
|
173
|
+
declare class CreditLedger {
|
|
174
|
+
private readonly rows;
|
|
175
|
+
/** One row per payer and challenge. A retry returns the first row. */
|
|
176
|
+
recordOnce(entry: CreditEntry): CreditEntry;
|
|
177
|
+
read(): readonly CreditEntry[];
|
|
178
|
+
}
|
|
62
179
|
|
|
63
180
|
declare class MppPaidNoCompletionError extends Error {
|
|
64
181
|
readonly reference: string;
|
|
@@ -67,6 +184,66 @@ declare class MppPaidNoCompletionError extends Error {
|
|
|
67
184
|
cause?: unknown;
|
|
68
185
|
});
|
|
69
186
|
}
|
|
187
|
+
/**
|
|
188
|
+
* Record one credit for a paid call that returned no completion.
|
|
189
|
+
* A retry of the same payer and challenge returns the first row.
|
|
190
|
+
* The row carries `fields.reference`, else the error's reference when one is known.
|
|
191
|
+
* `sign`, when passed, is never called.
|
|
192
|
+
*/
|
|
193
|
+
declare function creditPaidNoCompletion(ledger: CreditLedger, err: MppPaidNoCompletionError, fields: CreditEntry, hooks?: {
|
|
194
|
+
sign?: () => Promise<unknown>;
|
|
195
|
+
}): CreditEntry;
|
|
196
|
+
type PaidCall = {
|
|
197
|
+
/** The paying account supplied to the rail, never the challenge recipient. */
|
|
198
|
+
payer: string;
|
|
199
|
+
/** A credential or transaction was produced for this call. */
|
|
200
|
+
signed: boolean;
|
|
201
|
+
/** Stable, non-secret handle for the payment (hash of what was sent), for disputes. */
|
|
202
|
+
reference?: string;
|
|
203
|
+
/** The ledger entry written when the payment was signed, if limits made one. */
|
|
204
|
+
record?: SpendRecord;
|
|
205
|
+
/** Payer, amount, and challenge known before the model runs. Reason is filled on failure. */
|
|
206
|
+
credit?: Pick<CreditEntry, "payer" | "amount" | "challengeId">;
|
|
207
|
+
/** Optional pre-sign policy decision; no raw challenge or signed token is retained. */
|
|
208
|
+
gate?: MppGateDecision;
|
|
209
|
+
payment?: MppPaymentFacts;
|
|
210
|
+
};
|
|
211
|
+
/** Rows written by `fail` when a signed call returns no completion. */
|
|
212
|
+
declare function readCredits(ledger?: CreditLedger): readonly CreditEntry[];
|
|
213
|
+
/** Safe payment facts associated with this exact response, if it was paid by this rail. */
|
|
214
|
+
declare function readPaidCallEvidence(response: Response): Pick<PaidCall, "payer" | "reference" | "credit" | "gate" | "payment"> | undefined;
|
|
215
|
+
|
|
216
|
+
/**
|
|
217
|
+
* Seller-side spent-proof ledger. In memory only.
|
|
218
|
+
*
|
|
219
|
+
* A proof is one payment transaction signature plus one challenge id.
|
|
220
|
+
* The buyer's 402 followed by the authorized retry is one proof: the 402
|
|
221
|
+
* carries no transaction signature, so it never claims a slot.
|
|
222
|
+
*/
|
|
223
|
+
declare class ReplayRejectedError extends Error {
|
|
224
|
+
readonly networkId: string;
|
|
225
|
+
readonly txSignature: string;
|
|
226
|
+
readonly challengeId: string;
|
|
227
|
+
readonly status = 409;
|
|
228
|
+
readonly code = "proof_replayed";
|
|
229
|
+
constructor(networkId: string, txSignature: string, challengeId: string);
|
|
230
|
+
}
|
|
231
|
+
declare class SpentProofLedger {
|
|
232
|
+
private readonly transactions;
|
|
233
|
+
private readonly challenges;
|
|
234
|
+
claim(networkId: string, txSignature: string, challengeId: string): boolean;
|
|
235
|
+
}
|
|
236
|
+
/**
|
|
237
|
+
* Accept one paid proof and call the model at most once.
|
|
238
|
+
* The claim is synchronous, so two overlapping calls yield one winner.
|
|
239
|
+
*/
|
|
240
|
+
declare function fulfillPaidProof<T>(opts: {
|
|
241
|
+
ledger: SpentProofLedger;
|
|
242
|
+
networkId: string;
|
|
243
|
+
txSignature: string;
|
|
244
|
+
challengeId: string;
|
|
245
|
+
upstream: () => Promise<T>;
|
|
246
|
+
}): Promise<T>;
|
|
70
247
|
|
|
71
|
-
export { DEFAULT_SOLANA_USD_MINTS, DEFAULT_TEMPO_USD_ASSETS, MppPaidNoCompletionError, createMppFetch, createSolanaMppFetch,
|
|
72
|
-
export type { MppFetchOptions, SolanaMppFetchOptions };
|
|
248
|
+
export { CreditLedger, DEFAULT_SOLANA_USD_MINTS, DEFAULT_TEMPO_USD_ASSETS, MPP_FULFILLMENT_MAX_BODY_BYTES, MPP_FULFILLMENT_RECORD_SCHEMA, MppFlushTimeoutError, MppGateError, MppPaidNoCompletionError, ReplayRejectedError, SpentProofLedger, createMppFetch, createSolanaMppFetch, creditPaidNoCompletion, fulfillPaidProof, quoteFromChallenge, readCredits, readPaidCallEvidence };
|
|
249
|
+
export type { CreditEntry, MppBodyDigest, MppFetchOptions, MppFlushOptions, MppFulfillmentRecord, MppFulfillmentSink, MppGateChallenge, MppGateDecision, MppGateEvaluator, MppIntegration, MppPayFetch, MppPaymentFacts, SolanaMppFetchOptions };
|