metered-protocol 1.0.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/LICENSE +21 -0
- package/README.md +103 -0
- package/contracts/metered.ag +135 -0
- package/contracts/metered_ag.tests.json +1226 -0
- package/contracts/metered_session.sil +237 -0
- package/contracts/metered_session.tests.json +1113 -0
- package/dist/contracts/metered.ag +135 -0
- package/dist/contracts/metered_ag.tests.json +1226 -0
- package/dist/contracts/metered_session.sil +237 -0
- package/dist/contracts/metered_session.tests.json +1113 -0
- package/dist/src/bias.d.ts +48 -0
- package/dist/src/bias.d.ts.map +1 -0
- package/dist/src/bias.js +43 -0
- package/dist/src/bias.js.map +1 -0
- package/dist/src/checkpoint.d.ts +57 -0
- package/dist/src/checkpoint.d.ts.map +1 -0
- package/dist/src/checkpoint.js +84 -0
- package/dist/src/checkpoint.js.map +1 -0
- package/dist/src/deadline.d.ts +71 -0
- package/dist/src/deadline.d.ts.map +1 -0
- package/dist/src/deadline.js +77 -0
- package/dist/src/deadline.js.map +1 -0
- package/dist/src/encoding.d.ts +72 -0
- package/dist/src/encoding.d.ts.map +1 -0
- package/dist/src/encoding.js +187 -0
- package/dist/src/encoding.js.map +1 -0
- package/dist/src/history.d.ts +33 -0
- package/dist/src/history.d.ts.map +1 -0
- package/dist/src/history.js +38 -0
- package/dist/src/history.js.map +1 -0
- package/dist/src/http/buyer.d.ts +57 -0
- package/dist/src/http/buyer.d.ts.map +1 -0
- package/dist/src/http/buyer.js +151 -0
- package/dist/src/http/buyer.js.map +1 -0
- package/dist/src/http/client.d.ts +55 -0
- package/dist/src/http/client.d.ts.map +1 -0
- package/dist/src/http/client.js +83 -0
- package/dist/src/http/client.js.map +1 -0
- package/dist/src/http/harness.d.ts +18 -0
- package/dist/src/http/harness.d.ts.map +1 -0
- package/dist/src/http/harness.js +16 -0
- package/dist/src/http/harness.js.map +1 -0
- package/dist/src/http/protocol.d.ts +84 -0
- package/dist/src/http/protocol.d.ts.map +1 -0
- package/dist/src/http/protocol.js +22 -0
- package/dist/src/http/protocol.js.map +1 -0
- package/dist/src/http/provider.d.ts +123 -0
- package/dist/src/http/provider.d.ts.map +1 -0
- package/dist/src/http/provider.js +197 -0
- package/dist/src/http/provider.js.map +1 -0
- package/dist/src/http/serve.d.ts +34 -0
- package/dist/src/http/serve.d.ts.map +1 -0
- package/dist/src/http/serve.js +171 -0
- package/dist/src/http/serve.js.map +1 -0
- package/dist/src/http/service.d.ts +86 -0
- package/dist/src/http/service.d.ts.map +1 -0
- package/dist/src/http/service.js +161 -0
- package/dist/src/http/service.js.map +1 -0
- package/dist/src/index.d.ts +68 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +62 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/meter.d.ts +39 -0
- package/dist/src/meter.d.ts.map +1 -0
- package/dist/src/meter.js +100 -0
- package/dist/src/meter.js.map +1 -0
- package/dist/src/offer.d.ts +6 -0
- package/dist/src/offer.d.ts.map +1 -0
- package/dist/src/offer.js +92 -0
- package/dist/src/offer.js.map +1 -0
- package/dist/src/reconcile.d.ts +18 -0
- package/dist/src/reconcile.d.ts.map +1 -0
- package/dist/src/reconcile.js +101 -0
- package/dist/src/reconcile.js.map +1 -0
- package/dist/src/reservation.d.ts +51 -0
- package/dist/src/reservation.d.ts.map +1 -0
- package/dist/src/reservation.js +124 -0
- package/dist/src/reservation.js.map +1 -0
- package/dist/src/session.d.ts +40 -0
- package/dist/src/session.d.ts.map +1 -0
- package/dist/src/session.js +92 -0
- package/dist/src/session.js.map +1 -0
- package/dist/src/signer.d.ts +31 -0
- package/dist/src/signer.d.ts.map +1 -0
- package/dist/src/signer.js +57 -0
- package/dist/src/signer.js.map +1 -0
- package/dist/src/store.d.ts +30 -0
- package/dist/src/store.d.ts.map +1 -0
- package/dist/src/store.js +120 -0
- package/dist/src/store.js.map +1 -0
- package/dist/src/types.d.ts +80 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +9 -0
- package/dist/src/types.js.map +1 -0
- package/dist/tools/anchor-cli.d.ts +2 -0
- package/dist/tools/anchor-cli.d.ts.map +1 -0
- package/dist/tools/anchor-cli.js +101 -0
- package/dist/tools/anchor-cli.js.map +1 -0
- package/dist/tools/anchor.d.ts +68 -0
- package/dist/tools/anchor.d.ts.map +1 -0
- package/dist/tools/anchor.js +136 -0
- package/dist/tools/anchor.js.map +1 -0
- package/dist/tools/chain.d.ts +18 -0
- package/dist/tools/chain.d.ts.map +1 -0
- package/dist/tools/chain.js +18 -0
- package/dist/tools/chain.js.map +1 -0
- package/dist/tools/conformance.d.ts +2 -0
- package/dist/tools/conformance.d.ts.map +1 -0
- package/dist/tools/conformance.js +220 -0
- package/dist/tools/conformance.js.map +1 -0
- package/dist/tools/contracts.d.ts +2 -0
- package/dist/tools/contracts.d.ts.map +1 -0
- package/dist/tools/contracts.js +68 -0
- package/dist/tools/contracts.js.map +1 -0
- package/dist/tools/covenant-binding.d.ts +40 -0
- package/dist/tools/covenant-binding.d.ts.map +1 -0
- package/dist/tools/covenant-binding.js +60 -0
- package/dist/tools/covenant-binding.js.map +1 -0
- package/dist/tools/covenant-profile.d.ts +41 -0
- package/dist/tools/covenant-profile.d.ts.map +1 -0
- package/dist/tools/covenant-profile.js +74 -0
- package/dist/tools/covenant-profile.js.map +1 -0
- package/dist/tools/covenant.d.ts +30 -0
- package/dist/tools/covenant.d.ts.map +1 -0
- package/dist/tools/covenant.js +54 -0
- package/dist/tools/covenant.js.map +1 -0
- package/dist/tools/demo-report.d.ts +24 -0
- package/dist/tools/demo-report.d.ts.map +1 -0
- package/dist/tools/demo-report.js +40 -0
- package/dist/tools/demo-report.js.map +1 -0
- package/dist/tools/demo.d.ts +2 -0
- package/dist/tools/demo.d.ts.map +1 -0
- package/dist/tools/demo.js +204 -0
- package/dist/tools/demo.js.map +1 -0
- package/dist/tools/dust-map.d.ts +2 -0
- package/dist/tools/dust-map.d.ts.map +1 -0
- package/dist/tools/dust-map.js +63 -0
- package/dist/tools/dust-map.js.map +1 -0
- package/dist/tools/expire-cases.d.ts +4 -0
- package/dist/tools/expire-cases.d.ts.map +1 -0
- package/dist/tools/expire-cases.js +211 -0
- package/dist/tools/expire-cases.js.map +1 -0
- package/dist/tools/fee-check.d.ts +2 -0
- package/dist/tools/fee-check.d.ts.map +1 -0
- package/dist/tools/fee-check.js +127 -0
- package/dist/tools/fee-check.js.map +1 -0
- package/dist/tools/fixtures.d.ts +39 -0
- package/dist/tools/fixtures.d.ts.map +1 -0
- package/dist/tools/fixtures.js +31 -0
- package/dist/tools/fixtures.js.map +1 -0
- package/dist/tools/gen-contract-tests.d.ts +2 -0
- package/dist/tools/gen-contract-tests.d.ts.map +1 -0
- package/dist/tools/gen-contract-tests.js +170 -0
- package/dist/tools/gen-contract-tests.js.map +1 -0
- package/dist/tools/kaspa.d.ts +13 -0
- package/dist/tools/kaspa.d.ts.map +1 -0
- package/dist/tools/kaspa.js +76 -0
- package/dist/tools/kaspa.js.map +1 -0
- package/dist/tools/live-deadline.d.ts +2 -0
- package/dist/tools/live-deadline.d.ts.map +1 -0
- package/dist/tools/live-deadline.js +122 -0
- package/dist/tools/live-deadline.js.map +1 -0
- package/dist/tools/live-expire.d.ts +2 -0
- package/dist/tools/live-expire.d.ts.map +1 -0
- package/dist/tools/live-expire.js +127 -0
- package/dist/tools/live-expire.js.map +1 -0
- package/dist/tools/live-settle.d.ts +2 -0
- package/dist/tools/live-settle.d.ts.map +1 -0
- package/dist/tools/live-settle.js +226 -0
- package/dist/tools/live-settle.js.map +1 -0
- package/dist/tools/live-steps.d.ts +63 -0
- package/dist/tools/live-steps.d.ts.map +1 -0
- package/dist/tools/live-steps.js +155 -0
- package/dist/tools/live-steps.js.map +1 -0
- package/dist/tools/model.d.ts +32 -0
- package/dist/tools/model.d.ts.map +1 -0
- package/dist/tools/model.js +99 -0
- package/dist/tools/model.js.map +1 -0
- package/dist/tools/send.d.ts +2 -0
- package/dist/tools/send.d.ts.map +1 -0
- package/dist/tools/send.js +41 -0
- package/dist/tools/send.js.map +1 -0
- package/dist/tools/session-chain.d.ts +69 -0
- package/dist/tools/session-chain.d.ts.map +1 -0
- package/dist/tools/session-chain.js +113 -0
- package/dist/tools/session-chain.js.map +1 -0
- package/dist/tools/sighash.d.ts +9 -0
- package/dist/tools/sighash.d.ts.map +1 -0
- package/dist/tools/sighash.js +68 -0
- package/dist/tools/sighash.js.map +1 -0
- package/dist/tools/sigscript-check.d.ts +2 -0
- package/dist/tools/sigscript-check.d.ts.map +1 -0
- package/dist/tools/sigscript-check.js +113 -0
- package/dist/tools/sigscript-check.js.map +1 -0
- package/dist/tools/sigscript.d.ts +61 -0
- package/dist/tools/sigscript.d.ts.map +1 -0
- package/dist/tools/sigscript.js +70 -0
- package/dist/tools/sigscript.js.map +1 -0
- package/dist/tools/state-layout.d.ts +2 -0
- package/dist/tools/state-layout.d.ts.map +1 -0
- package/dist/tools/state-layout.js +57 -0
- package/dist/tools/state-layout.js.map +1 -0
- package/package.json +59 -0
- package/spec/CONFORMANCE.md +80 -0
- package/spec/SPEC.md +821 -0
- package/spec/conformance-vectors.json +677 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.js","sourceRoot":"","sources":["../../../src/http/client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* One metered server, started and stopped around a test.
|
|
3
|
+
*
|
|
4
|
+
* Shared rather than copied. Two files needed the same three lines -- start a service, listen on
|
|
5
|
+
* an ephemeral port, close it afterwards -- and a copy of a fixture drifts exactly as readily as
|
|
6
|
+
* a copy of anything else: the moment one file's terms change and the other's do not, two tests
|
|
7
|
+
* claiming to exercise "the server" are exercising different servers.
|
|
8
|
+
*/
|
|
9
|
+
import type { Server } from 'node:http';
|
|
10
|
+
import { MeteredService, type ServiceOptions } from './service.js';
|
|
11
|
+
export interface Harness {
|
|
12
|
+
base: string;
|
|
13
|
+
service: MeteredService;
|
|
14
|
+
server: Server;
|
|
15
|
+
}
|
|
16
|
+
/** Start a server around `opts`, run `body`, and close it however `body` ends. */
|
|
17
|
+
export declare function withMeteredServer(opts: ServiceOptions, body: (h: Harness) => Promise<void>): Promise<void>;
|
|
18
|
+
//# sourceMappingURL=harness.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.d.ts","sourceRoot":"","sources":["../../../src/http/harness.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAGxC,OAAO,EAAE,cAAc,EAAE,KAAK,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnE,MAAM,WAAW,OAAO;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,cAAc,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,kFAAkF;AAClF,wBAAsB,iBAAiB,CACrC,IAAI,EAAE,cAAc,EACpB,IAAI,EAAE,CAAC,CAAC,EAAE,OAAO,KAAK,OAAO,CAAC,IAAI,CAAC,GAClC,OAAO,CAAC,IAAI,CAAC,CAUf"}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { serveMetered } from './serve.js';
|
|
2
|
+
import { MeteredService } from './service.js';
|
|
3
|
+
/** Start a server around `opts`, run `body`, and close it however `body` ends. */
|
|
4
|
+
export async function withMeteredServer(opts, body) {
|
|
5
|
+
const service = new MeteredService(opts);
|
|
6
|
+
const server = serveMetered({ service });
|
|
7
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
8
|
+
const base = `http://127.0.0.1:${server.address().port}`;
|
|
9
|
+
try {
|
|
10
|
+
await body({ base, service, server });
|
|
11
|
+
}
|
|
12
|
+
finally {
|
|
13
|
+
await new Promise((resolve) => server.close(() => resolve()));
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
//# sourceMappingURL=harness.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"harness.js","sourceRoot":"","sources":["../../../src/http/harness.ts"],"names":[],"mappings":"AAUA,OAAO,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAC1C,OAAO,EAAE,cAAc,EAAuB,MAAM,cAAc,CAAC;AAQnE,kFAAkF;AAClF,MAAM,CAAC,KAAK,UAAU,iBAAiB,CACrC,IAAoB,EACpB,IAAmC;IAEnC,MAAM,OAAO,GAAG,IAAI,cAAc,CAAC,IAAI,CAAC,CAAC;IACzC,MAAM,MAAM,GAAG,YAAY,CAAC,EAAE,OAAO,EAAE,CAAC,CAAC;IACzC,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,MAAM,CAAC,CAAC,EAAE,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC;IAC7E,MAAM,IAAI,GAAG,oBAAqB,MAAM,CAAC,OAAO,EAAkB,CAAC,IAAI,EAAE,CAAC;IAC1E,IAAI,CAAC;QACH,MAAM,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,MAAM,EAAE,CAAC,CAAC;IACxC,CAAC;YAAS,CAAC;QACT,MAAM,IAAI,OAAO,CAAO,CAAC,OAAO,EAAE,EAAE,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,CAAC,CAAC;IACtE,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* `metered` as an x402 scheme over HTTP.
|
|
3
|
+
*
|
|
4
|
+
* x402 ships `exact` and `upto`, and the `upto` specification lists multi-settlement, streaming
|
|
5
|
+
* and pay-per-chunk as out of scope. This is the transport for the scheme that fills that gap, so
|
|
6
|
+
* it deliberately follows x402's v2 envelope rather than inventing one: an unpaid request gets a
|
|
7
|
+
* 402 whose body carries `x402Version`, a `resource` block and an `accepts` array. A client that
|
|
8
|
+
* already speaks x402 can read the shape even if it does not know this scheme, and will see
|
|
9
|
+
* `scheme: "metered"` and decline rather than misread it.
|
|
10
|
+
*
|
|
11
|
+
* A CHUNK IS TWO ROUND TRIPS, and that is not an accident of design. The buyer cannot measure
|
|
12
|
+
* content it has not received, so it cannot sign a Measurement in the same request that asks for
|
|
13
|
+
* the chunk. Collapsing them would mean the buyer signing a count it took on trust, which is
|
|
14
|
+
* exactly the one-sidedness SPEC.md 0 exists to remove.
|
|
15
|
+
*
|
|
16
|
+
* POST /metered/open -> 402 + Offer, or 200 once the covenant is funded
|
|
17
|
+
* POST /metered/babel -> content + the PROVIDER's Measurement
|
|
18
|
+
* POST /metered/state -> reconciles both Measurements, returns the State + provider signature
|
|
19
|
+
*/
|
|
20
|
+
import type { Measurement, Offer, Reservation, State } from '../types.js';
|
|
21
|
+
export declare const X402_VERSION = 2;
|
|
22
|
+
/** The x402 v2 body an unpaid request gets back. */
|
|
23
|
+
export interface PaymentRequiredBody {
|
|
24
|
+
x402Version: number;
|
|
25
|
+
error: string;
|
|
26
|
+
resource: {
|
|
27
|
+
url: string;
|
|
28
|
+
description?: string;
|
|
29
|
+
mimeType?: string;
|
|
30
|
+
};
|
|
31
|
+
accepts: Offer[];
|
|
32
|
+
}
|
|
33
|
+
export declare function toPaymentRequired(offer: Offer, resource: string, error: string): PaymentRequiredBody;
|
|
34
|
+
/** `POST /metered/babel` -- the buyer authorises exactly one chunk and asks for it. */
|
|
35
|
+
export interface ChunkRequest {
|
|
36
|
+
reservation: Reservation;
|
|
37
|
+
prompt: string;
|
|
38
|
+
}
|
|
39
|
+
/**
|
|
40
|
+
* What comes back. `contentB64` is the delivered bytes; `measurement` is the PROVIDER's count of
|
|
41
|
+
* them. The buyer counts the same bytes itself and does not have to believe this.
|
|
42
|
+
*
|
|
43
|
+
* BASE64 IS TRANSIT ONLY, and the field is named so that nothing can forget it. JSON has no way
|
|
44
|
+
* to carry arbitrary bytes, so they are encoded to cross the wire and decoded on arrival -- but
|
|
45
|
+
* the digest and the unit count are taken over the DECODED bytes at both ends. Metering the
|
|
46
|
+
* encoded form would bill the buyer for roughly a third more than it asked for, and would agree
|
|
47
|
+
* a digest over the encoding rather than over the content.
|
|
48
|
+
*/
|
|
49
|
+
export interface BabelResponse {
|
|
50
|
+
contentB64: string;
|
|
51
|
+
measurement: Measurement;
|
|
52
|
+
}
|
|
53
|
+
/** Bytes -> the wire. */
|
|
54
|
+
export declare const toBase64: (bytes: Uint8Array) => string;
|
|
55
|
+
/**
|
|
56
|
+
* The wire -> bytes.
|
|
57
|
+
*
|
|
58
|
+
* Node's base64 decoder is LENIENT: it discards anything outside the alphabet rather than
|
|
59
|
+
* refusing, so a corrupted field decodes to plausible-looking bytes instead of an error. That is
|
|
60
|
+
* survivable here only because it cannot be silent -- different bytes produce a different digest,
|
|
61
|
+
* and SPEC.md 5 rule 3 halts the session on a digest mismatch before any count is consulted.
|
|
62
|
+
*/
|
|
63
|
+
export declare const fromBase64: (b64: string) => Uint8Array;
|
|
64
|
+
/** `POST /metered/state` -- the buyer's count, after it has seen the content. */
|
|
65
|
+
export interface StateRequest {
|
|
66
|
+
measurement: Measurement;
|
|
67
|
+
}
|
|
68
|
+
/**
|
|
69
|
+
* The reconciled State and the provider's signature over it. The buyer verifies and countersigns;
|
|
70
|
+
* a State with one signature is not a State (SPEC.md 3.4).
|
|
71
|
+
*/
|
|
72
|
+
export interface StateResponse {
|
|
73
|
+
state: State;
|
|
74
|
+
providerSig: string;
|
|
75
|
+
billedUnits: number;
|
|
76
|
+
}
|
|
77
|
+
/** Every failure this transport can report, as an x402-shaped error body. */
|
|
78
|
+
export interface ErrorBody {
|
|
79
|
+
x402Version: number;
|
|
80
|
+
error: string;
|
|
81
|
+
reason?: string | undefined;
|
|
82
|
+
}
|
|
83
|
+
export declare const errorBody: (error: string, reason?: string) => ErrorBody;
|
|
84
|
+
//# sourceMappingURL=protocol.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.d.ts","sourceRoot":"","sources":["../../../src/http/protocol.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;GAkBG;AACH,OAAO,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,aAAa,CAAC;AAE1E,eAAO,MAAM,YAAY,IAAI,CAAC;AAE9B,oDAAoD;AACpD,MAAM,WAAW,mBAAmB;IAClC,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,QAAQ,EAAE;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,WAAW,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACnE,OAAO,EAAE,KAAK,EAAE,CAAC;CAClB;AAED,wBAAgB,iBAAiB,CAAC,KAAK,EAAE,KAAK,EAAE,QAAQ,EAAE,MAAM,EAAE,KAAK,EAAE,MAAM,GAAG,mBAAmB,CAOpG;AAED,uFAAuF;AACvF,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,WAAW,CAAC;IACzB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,aAAa;IAC5B,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED,yBAAyB;AACzB,eAAO,MAAM,QAAQ,GAAI,OAAO,UAAU,KAAG,MAA+C,CAAC;AAE7F;;;;;;;GAOG;AACH,eAAO,MAAM,UAAU,GAAI,KAAK,MAAM,KAAG,UAAwD,CAAC;AAElG,iFAAiF;AACjF,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,WAAW,CAAC;CAC1B;AAED;;;GAGG;AACH,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,KAAK,CAAC;IACb,WAAW,EAAE,MAAM,CAAC;IACpB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,6EAA6E;AAC7E,MAAM,WAAW,SAAS;IACxB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,GAAG,SAAS,CAAC;CAC7B;AAED,eAAO,MAAM,SAAS,GAAI,OAAO,MAAM,EAAE,SAAS,MAAM,KAAG,SACX,CAAC"}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
export const X402_VERSION = 2;
|
|
2
|
+
export function toPaymentRequired(offer, resource, error) {
|
|
3
|
+
return {
|
|
4
|
+
x402Version: X402_VERSION,
|
|
5
|
+
error,
|
|
6
|
+
resource: { url: resource, description: `metered session, ${offer.babelUnits} ${offer.unit} per chunk` },
|
|
7
|
+
accepts: [offer],
|
|
8
|
+
};
|
|
9
|
+
}
|
|
10
|
+
/** Bytes -> the wire. */
|
|
11
|
+
export const toBase64 = (bytes) => Buffer.from(bytes).toString('base64');
|
|
12
|
+
/**
|
|
13
|
+
* The wire -> bytes.
|
|
14
|
+
*
|
|
15
|
+
* Node's base64 decoder is LENIENT: it discards anything outside the alphabet rather than
|
|
16
|
+
* refusing, so a corrupted field decodes to plausible-looking bytes instead of an error. That is
|
|
17
|
+
* survivable here only because it cannot be silent -- different bytes produce a different digest,
|
|
18
|
+
* and SPEC.md 5 rule 3 halts the session on a digest mismatch before any count is consulted.
|
|
19
|
+
*/
|
|
20
|
+
export const fromBase64 = (b64) => new Uint8Array(Buffer.from(b64, 'base64'));
|
|
21
|
+
export const errorBody = (error, reason) => ({ x402Version: X402_VERSION, error, reason });
|
|
22
|
+
//# sourceMappingURL=protocol.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"protocol.js","sourceRoot":"","sources":["../../../src/http/protocol.ts"],"names":[],"mappings":"AAqBA,MAAM,CAAC,MAAM,YAAY,GAAG,CAAC,CAAC;AAU9B,MAAM,UAAU,iBAAiB,CAAC,KAAY,EAAE,QAAgB,EAAE,KAAa;IAC7E,OAAO;QACL,WAAW,EAAE,YAAY;QACzB,KAAK;QACL,QAAQ,EAAE,EAAE,GAAG,EAAE,QAAQ,EAAE,WAAW,EAAE,oBAAoB,KAAK,CAAC,UAAU,IAAI,KAAK,CAAC,IAAI,YAAY,EAAE;QACxG,OAAO,EAAE,CAAC,KAAK,CAAC;KACjB,CAAC;AACJ,CAAC;AAuBD,yBAAyB;AACzB,MAAM,CAAC,MAAM,QAAQ,GAAG,CAAC,KAAiB,EAAU,EAAE,CAAC,MAAM,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,CAAC;AAE7F;;;;;;;GAOG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,GAAW,EAAc,EAAE,CAAC,IAAI,UAAU,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,EAAE,QAAQ,CAAC,CAAC,CAAC;AAwBlG,MAAM,CAAC,MAAM,SAAS,GAAG,CAAC,KAAa,EAAE,MAAe,EAAa,EAAE,CACrE,CAAC,EAAE,WAAW,EAAE,YAAY,EAAE,KAAK,EAAE,MAAM,EAAE,CAAC,CAAC"}
|
|
@@ -0,0 +1,123 @@
|
|
|
1
|
+
import { type SignerStore } from '../signer.js';
|
|
2
|
+
import type { Checkpointer } from '../checkpoint.js';
|
|
3
|
+
import { type BabelCursor } from '../reservation.js';
|
|
4
|
+
import { type BiasState } from '../bias.js';
|
|
5
|
+
import type { Halt, Measurement, Offer, Reservation, State } from '../types.js';
|
|
6
|
+
/** Counts the units in delivered content. Injected, because SPEC.md 6 makes it the Offer's choice. */
|
|
7
|
+
export type Meter = (content: Uint8Array) => number;
|
|
8
|
+
/**
|
|
9
|
+
* Produces the content for a chunk. This is the thing actually being sold.
|
|
10
|
+
*
|
|
11
|
+
* BYTES, NOT TEXT. Everything the protocol does to delivered content -- digest it, count it,
|
|
12
|
+
* agree on it -- is defined over bytes, and this signature used to say `string`, which quietly
|
|
13
|
+
* restricted the whole scheme to things expressible as text. A file is not, so a unit called
|
|
14
|
+
* `net.bytes_delivered.v1` could be declared and never actually served.
|
|
15
|
+
*/
|
|
16
|
+
export type Deliver = (prompt: string, maxUnits: number) => Uint8Array;
|
|
17
|
+
export declare class SessionRejected extends Error {
|
|
18
|
+
readonly halt?: Halt | undefined;
|
|
19
|
+
constructor(halt?: Halt | undefined);
|
|
20
|
+
}
|
|
21
|
+
interface Pending {
|
|
22
|
+
seq: number;
|
|
23
|
+
content: Uint8Array;
|
|
24
|
+
units: number;
|
|
25
|
+
contentDigest: string;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Everything a session needs to be rebuilt after a restart.
|
|
29
|
+
*
|
|
30
|
+
* NOTE WHAT IS ABSENT: the delivered CONTENT. `settle` works from the babel's digest, its unit
|
|
31
|
+
* count and its seq, and never reads the text again -- so a provider that persists sessions is
|
|
32
|
+
* not thereby keeping a copy of everything it has ever said to anyone. That is a property of the
|
|
33
|
+
* protocol rather than a precaution taken here: the digest is what the parties agreed about.
|
|
34
|
+
*/
|
|
35
|
+
export interface SessionSnapshot {
|
|
36
|
+
offer: Offer;
|
|
37
|
+
cursor: BabelCursor | null;
|
|
38
|
+
pending: Omit<Pending, 'content'> | null;
|
|
39
|
+
lastStateDigest: string | null;
|
|
40
|
+
bias: BiasState;
|
|
41
|
+
settled: [string, {
|
|
42
|
+
state: State;
|
|
43
|
+
providerSig: string;
|
|
44
|
+
billedUnits: number;
|
|
45
|
+
}][];
|
|
46
|
+
}
|
|
47
|
+
export declare class ProviderSession {
|
|
48
|
+
readonly offer: Offer;
|
|
49
|
+
private readonly providerSk;
|
|
50
|
+
private readonly meter;
|
|
51
|
+
private readonly deliver;
|
|
52
|
+
/** SPEC.md 8. Optional: a session with `checkpointEvery: 0` anchors nothing. */
|
|
53
|
+
private readonly checkpointer?;
|
|
54
|
+
/**
|
|
55
|
+
* SPEC.md 4. Defaults to a store that forgets on restart, which is fine for a test and is
|
|
56
|
+
* NOT fine for a provider holding money: 4.4's restart rule is only as good as the store.
|
|
57
|
+
* src/store.ts has one that survives a power cut.
|
|
58
|
+
*/
|
|
59
|
+
private readonly store;
|
|
60
|
+
private cursor;
|
|
61
|
+
private bias;
|
|
62
|
+
private pending;
|
|
63
|
+
private lastStateDigest;
|
|
64
|
+
private lastState;
|
|
65
|
+
/**
|
|
66
|
+
* Settled results by the buyer's `measurementId`, so SPEC.md 3.3's idempotency rule holds.
|
|
67
|
+
* Bounded by `maxBabels`, which the Offer fixes, so it cannot grow without limit.
|
|
68
|
+
*/
|
|
69
|
+
private readonly settled;
|
|
70
|
+
constructor(offer: Offer, providerSk: string, meter: Meter, deliver: Deliver,
|
|
71
|
+
/** SPEC.md 8. Optional: a session with `checkpointEvery: 0` anchors nothing. */
|
|
72
|
+
checkpointer?: Checkpointer | undefined,
|
|
73
|
+
/**
|
|
74
|
+
* SPEC.md 4. Defaults to a store that forgets on restart, which is fine for a test and is
|
|
75
|
+
* NOT fine for a provider holding money: 4.4's restart rule is only as good as the store.
|
|
76
|
+
* src/store.ts has one that survives a power cut.
|
|
77
|
+
*/
|
|
78
|
+
store?: SignerStore);
|
|
79
|
+
/** State that must outlive the process. See SessionSnapshot. */
|
|
80
|
+
snapshot(): SessionSnapshot;
|
|
81
|
+
/** Rebuild a session from a snapshot. The counterparty cannot tell this from a long-lived one. */
|
|
82
|
+
static restore(snap: SessionSnapshot, providerSk: string, meter: Meter, deliver: Deliver, checkpointer?: Checkpointer, store?: SignerStore): ProviderSession;
|
|
83
|
+
get pendingSeq(): number;
|
|
84
|
+
/**
|
|
85
|
+
* Accept a Reservation and deliver one chunk, with the provider's own count of it.
|
|
86
|
+
*
|
|
87
|
+
* The Reservation is checked BEFORE anything is delivered -- units above `babelUnits`, a seq
|
|
88
|
+
* that does not advance by exactly one, or totals that do not recompute all mean the buyer has
|
|
89
|
+
* not authorised what it is asking for, and delivering first would be giving work away.
|
|
90
|
+
*/
|
|
91
|
+
chunk(reservation: Reservation, prompt: string): {
|
|
92
|
+
content: Uint8Array;
|
|
93
|
+
measurement: Measurement;
|
|
94
|
+
};
|
|
95
|
+
/**
|
|
96
|
+
* Reconcile the buyer's count against the provider's and produce the State to be countersigned.
|
|
97
|
+
*
|
|
98
|
+
* The residual is fed to the bias detector BEFORE the State is signed, so a chunk that trips the
|
|
99
|
+
* alarm is never settled -- signing first and halting after would hand the buyer a State it
|
|
100
|
+
* could close on (SPEC.md 5.1.1).
|
|
101
|
+
*/
|
|
102
|
+
settle(buyerMeasurement: Measurement): {
|
|
103
|
+
state: State;
|
|
104
|
+
providerSig: string;
|
|
105
|
+
billedUnits: number;
|
|
106
|
+
};
|
|
107
|
+
/**
|
|
108
|
+
* Record the digest of a State once both parties have signed it, so the chain can continue --
|
|
109
|
+
* and checkpoint it if this chunk calls for one.
|
|
110
|
+
*
|
|
111
|
+
* THE CHECKPOINT HAPPENS HERE, not in `settle`, and the difference is the whole point of a
|
|
112
|
+
* checkpoint. SPEC.md 8 anchors `digest(State)` as EVIDENCE that a State existed before a given
|
|
113
|
+
* block. A State with one signature is not a State (SPEC.md 3.4), so anchoring at `settle` would
|
|
114
|
+
* be publishing a claim the buyer had not yet agreed to -- evidence of nothing.
|
|
115
|
+
*
|
|
116
|
+
* It does not await. SPEC.md 8 requires checkpointing to be NON-BLOCKING because Study B
|
|
117
|
+
* measured p90 confirmation at 1,879 ms, and a session that stalled two seconds every few chunks
|
|
118
|
+
* would be unusable for a streamed response.
|
|
119
|
+
*/
|
|
120
|
+
chainTo(digest: string): void;
|
|
121
|
+
}
|
|
122
|
+
export {};
|
|
123
|
+
//# sourceMappingURL=provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.d.ts","sourceRoot":"","sources":["../../../src/http/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,197 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The provider's half of a metered session, as a transport-agnostic state machine.
|
|
3
|
+
*
|
|
4
|
+
* No HTTP in here on purpose. The rules are the interesting part and they are testable without a
|
|
5
|
+
* socket; `serve.ts` is the thin layer that maps them onto requests. That split is the same one
|
|
6
|
+
* the rest of this project uses -- the part with rules is tested against fixtures, the part with
|
|
7
|
+
* I/O is proven live.
|
|
8
|
+
*
|
|
9
|
+
* WHAT THE PROVIDER IS TRUSTED FOR: nothing (SPEC.md 1). It counts its own delivery and says so,
|
|
10
|
+
* and the buyer's count is an equal input to what settles. This class never sees the buyer's key
|
|
11
|
+
* and cannot produce a State on its own.
|
|
12
|
+
*/
|
|
13
|
+
import { blake3Hex, signEnvelope } from '../encoding.js';
|
|
14
|
+
import { signStateWithObligations, memoryStore } from '../signer.js';
|
|
15
|
+
import { acceptReservation } from '../reservation.js';
|
|
16
|
+
import { reconcileBabel } from '../reconcile.js';
|
|
17
|
+
import { newBiasState, observeResidual, biasAlarm } from '../bias.js';
|
|
18
|
+
/** The stand-in for content a restored session deliberately no longer holds. */
|
|
19
|
+
const EMPTY = new Uint8Array(0);
|
|
20
|
+
export class SessionRejected extends Error {
|
|
21
|
+
halt;
|
|
22
|
+
constructor(halt) {
|
|
23
|
+
super(halt ? `${halt.reason}: ${halt.detail}` : 'session rejected');
|
|
24
|
+
this.halt = halt;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
export class ProviderSession {
|
|
28
|
+
offer;
|
|
29
|
+
providerSk;
|
|
30
|
+
meter;
|
|
31
|
+
deliver;
|
|
32
|
+
checkpointer;
|
|
33
|
+
store;
|
|
34
|
+
cursor = null;
|
|
35
|
+
bias = newBiasState();
|
|
36
|
+
pending = null;
|
|
37
|
+
lastStateDigest = null;
|
|
38
|
+
lastState = null;
|
|
39
|
+
/**
|
|
40
|
+
* Settled results by the buyer's `measurementId`, so SPEC.md 3.3's idempotency rule holds.
|
|
41
|
+
* Bounded by `maxBabels`, which the Offer fixes, so it cannot grow without limit.
|
|
42
|
+
*/
|
|
43
|
+
settled = new Map();
|
|
44
|
+
constructor(offer, providerSk, meter, deliver,
|
|
45
|
+
/** SPEC.md 8. Optional: a session with `checkpointEvery: 0` anchors nothing. */
|
|
46
|
+
checkpointer,
|
|
47
|
+
/**
|
|
48
|
+
* SPEC.md 4. Defaults to a store that forgets on restart, which is fine for a test and is
|
|
49
|
+
* NOT fine for a provider holding money: 4.4's restart rule is only as good as the store.
|
|
50
|
+
* src/store.ts has one that survives a power cut.
|
|
51
|
+
*/
|
|
52
|
+
store = memoryStore()) {
|
|
53
|
+
this.offer = offer;
|
|
54
|
+
this.providerSk = providerSk;
|
|
55
|
+
this.meter = meter;
|
|
56
|
+
this.deliver = deliver;
|
|
57
|
+
this.checkpointer = checkpointer;
|
|
58
|
+
this.store = store;
|
|
59
|
+
}
|
|
60
|
+
/** State that must outlive the process. See SessionSnapshot. */
|
|
61
|
+
snapshot() {
|
|
62
|
+
const { content, ...pending } = this.pending ?? { content: EMPTY, seq: -1, units: 0, contentDigest: '' };
|
|
63
|
+
void content;
|
|
64
|
+
return {
|
|
65
|
+
offer: this.offer,
|
|
66
|
+
cursor: this.cursor,
|
|
67
|
+
pending: this.pending ? pending : null,
|
|
68
|
+
lastStateDigest: this.lastStateDigest,
|
|
69
|
+
bias: this.bias,
|
|
70
|
+
settled: [...this.settled.entries()],
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
/** Rebuild a session from a snapshot. The counterparty cannot tell this from a long-lived one. */
|
|
74
|
+
static restore(snap, providerSk, meter, deliver, checkpointer, store) {
|
|
75
|
+
const session = new ProviderSession(snap.offer, providerSk, meter, deliver, checkpointer, store);
|
|
76
|
+
session.cursor = snap.cursor;
|
|
77
|
+
session.lastStateDigest = snap.lastStateDigest;
|
|
78
|
+
session.bias = snap.bias;
|
|
79
|
+
session.pending = snap.pending ? { ...snap.pending, content: EMPTY } : null;
|
|
80
|
+
for (const [id, result] of snap.settled)
|
|
81
|
+
session.settled.set(id, result);
|
|
82
|
+
return session;
|
|
83
|
+
}
|
|
84
|
+
get pendingSeq() {
|
|
85
|
+
return this.cursor?.seq ?? -1;
|
|
86
|
+
}
|
|
87
|
+
/**
|
|
88
|
+
* Accept a Reservation and deliver one chunk, with the provider's own count of it.
|
|
89
|
+
*
|
|
90
|
+
* The Reservation is checked BEFORE anything is delivered -- units above `babelUnits`, a seq
|
|
91
|
+
* that does not advance by exactly one, or totals that do not recompute all mean the buyer has
|
|
92
|
+
* not authorised what it is asking for, and delivering first would be giving work away.
|
|
93
|
+
*/
|
|
94
|
+
chunk(reservation, prompt) {
|
|
95
|
+
acceptReservation(this.offer, reservation, this.cursor);
|
|
96
|
+
const content = this.deliver(prompt, reservation.units);
|
|
97
|
+
const units = this.meter(content);
|
|
98
|
+
const contentDigest = blake3Hex(content);
|
|
99
|
+
this.pending = { seq: reservation.seq, content, units, contentDigest };
|
|
100
|
+
const measurement = {
|
|
101
|
+
v: 1,
|
|
102
|
+
sessionId: this.offer.sessionId,
|
|
103
|
+
seq: reservation.seq,
|
|
104
|
+
by: 'provider',
|
|
105
|
+
units,
|
|
106
|
+
cumulativeUnits: (this.cursor?.cumulativeUnits ?? 0) + units,
|
|
107
|
+
contentDigest,
|
|
108
|
+
measurementId: blake3Hex(`${this.offer.sessionId}/${reservation.seq}/provider`).slice(0, 32),
|
|
109
|
+
};
|
|
110
|
+
return { content, measurement: signEnvelope(measurement, this.providerSk) };
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Reconcile the buyer's count against the provider's and produce the State to be countersigned.
|
|
114
|
+
*
|
|
115
|
+
* The residual is fed to the bias detector BEFORE the State is signed, so a chunk that trips the
|
|
116
|
+
* alarm is never settled -- signing first and halting after would hand the buyer a State it
|
|
117
|
+
* could close on (SPEC.md 5.1.1).
|
|
118
|
+
*/
|
|
119
|
+
settle(buyerMeasurement) {
|
|
120
|
+
// SPEC.md 3.3: `measurementId` is an idempotency key, and "a retransmission MUST be a no-op".
|
|
121
|
+
// Without this a client that times out and retries -- which is ordinary HTTP behaviour, not
|
|
122
|
+
// an attack -- finds `pending` already consumed, gets a SessionRejected, and kills its own
|
|
123
|
+
// session. The settled result is returned again instead, unchanged.
|
|
124
|
+
const replay = this.settled.get(buyerMeasurement.measurementId);
|
|
125
|
+
if (replay)
|
|
126
|
+
return replay;
|
|
127
|
+
const pending = this.pending;
|
|
128
|
+
if (!pending)
|
|
129
|
+
throw new SessionRejected();
|
|
130
|
+
const providerMeasurement = {
|
|
131
|
+
v: 1,
|
|
132
|
+
sessionId: this.offer.sessionId,
|
|
133
|
+
seq: pending.seq,
|
|
134
|
+
by: 'provider',
|
|
135
|
+
units: pending.units,
|
|
136
|
+
cumulativeUnits: (this.cursor?.cumulativeUnits ?? 0) + pending.units,
|
|
137
|
+
contentDigest: pending.contentDigest,
|
|
138
|
+
measurementId: blake3Hex(`${this.offer.sessionId}/${pending.seq}/provider`).slice(0, 32),
|
|
139
|
+
};
|
|
140
|
+
const signedProvider = signEnvelope(providerMeasurement, this.providerSk);
|
|
141
|
+
const outcome = reconcileBabel(this.offer, buyerMeasurement, signedProvider, pending.seq);
|
|
142
|
+
if (!outcome.ok)
|
|
143
|
+
throw new SessionRejected(outcome);
|
|
144
|
+
this.bias = observeResidual(this.bias, outcome.residual);
|
|
145
|
+
if (biasAlarm(this.bias)) {
|
|
146
|
+
throw new SessionRejected({
|
|
147
|
+
ok: false,
|
|
148
|
+
reason: 'bias',
|
|
149
|
+
detail: `CUSUM reached ${this.bias.sum} after ${this.bias.babels} chunks`,
|
|
150
|
+
});
|
|
151
|
+
}
|
|
152
|
+
const state = {
|
|
153
|
+
v: 1,
|
|
154
|
+
sessionId: this.offer.sessionId,
|
|
155
|
+
seq: pending.seq,
|
|
156
|
+
cumulativeUnits: outcome.billedCumulativeUnits,
|
|
157
|
+
cumulativeSompi: outcome.billedCumulativeUnits * this.offer.unitPriceSompi,
|
|
158
|
+
prevState: this.lastStateDigest,
|
|
159
|
+
};
|
|
160
|
+
this.cursor = {
|
|
161
|
+
seq: state.seq,
|
|
162
|
+
cumulativeUnits: state.cumulativeUnits,
|
|
163
|
+
cumulativeSompi: state.cumulativeSompi,
|
|
164
|
+
stateDigest: null,
|
|
165
|
+
};
|
|
166
|
+
this.pending = null;
|
|
167
|
+
this.lastState = state;
|
|
168
|
+
// SPEC.md 4, THROUGH THE STORE. Never `signState` directly: 4.1 (one State per seq), 4.3 (the
|
|
169
|
+
// chain is unbroken) and 4.2 (record BEFORE the signature escapes) are the rules that lose
|
|
170
|
+
// money without any message being malformed, and none of them is checkable by a counterparty.
|
|
171
|
+
const providerSig = signStateWithObligations(this.store, state, this.providerSk);
|
|
172
|
+
const result = { state, providerSig, billedUnits: outcome.billedUnits };
|
|
173
|
+
this.settled.set(buyerMeasurement.measurementId, result);
|
|
174
|
+
return result;
|
|
175
|
+
}
|
|
176
|
+
/**
|
|
177
|
+
* Record the digest of a State once both parties have signed it, so the chain can continue --
|
|
178
|
+
* and checkpoint it if this chunk calls for one.
|
|
179
|
+
*
|
|
180
|
+
* THE CHECKPOINT HAPPENS HERE, not in `settle`, and the difference is the whole point of a
|
|
181
|
+
* checkpoint. SPEC.md 8 anchors `digest(State)` as EVIDENCE that a State existed before a given
|
|
182
|
+
* block. A State with one signature is not a State (SPEC.md 3.4), so anchoring at `settle` would
|
|
183
|
+
* be publishing a claim the buyer had not yet agreed to -- evidence of nothing.
|
|
184
|
+
*
|
|
185
|
+
* It does not await. SPEC.md 8 requires checkpointing to be NON-BLOCKING because Study B
|
|
186
|
+
* measured p90 confirmation at 1,879 ms, and a session that stalled two seconds every few chunks
|
|
187
|
+
* would be unusable for a streamed response.
|
|
188
|
+
*/
|
|
189
|
+
chainTo(digest) {
|
|
190
|
+
this.lastStateDigest = digest;
|
|
191
|
+
if (this.cursor)
|
|
192
|
+
this.cursor = { ...this.cursor, stateDigest: digest };
|
|
193
|
+
if (this.checkpointer && this.lastState)
|
|
194
|
+
this.checkpointer.record(this.offer, this.lastState);
|
|
195
|
+
}
|
|
196
|
+
}
|
|
197
|
+
//# sourceMappingURL=provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"provider.js","sourceRoot":"","sources":["../../../src/http/provider.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The HTTP glue. Thin on purpose: every rule lives in provider.ts and service.ts, and this only
|
|
3
|
+
* maps them onto requests and status codes.
|
|
4
|
+
*
|
|
5
|
+
* ROUTING IS BY sessionId, AND IT COSTS NOTHING EXTRA. Every message in SPEC.md 3 already carries
|
|
6
|
+
* one, because the specification needed it to stop a Reservation or Measurement being lifted from
|
|
7
|
+
* one session into another -- threat X1. A field that exists for a security reason turns
|
|
8
|
+
* out to be exactly the routing key a multi-session server needs, so there is no session cookie,
|
|
9
|
+
* no header, and no server-side handle to leak or confuse.
|
|
10
|
+
*
|
|
11
|
+
* An unpaid request gets x402's 402 with the signed Offer in `accepts`. A client that speaks x402
|
|
12
|
+
* but not this scheme reads the envelope, sees `scheme: "metered"` and declines cleanly.
|
|
13
|
+
*/
|
|
14
|
+
import { type IncomingMessage, type ServerResponse, type Server } from 'node:http';
|
|
15
|
+
import { MeteredService } from './service.js';
|
|
16
|
+
/**
|
|
17
|
+
* The largest request this server will read. A Reservation, a Measurement and a State are all a
|
|
18
|
+
* few hundred bytes; a prompt is the only field with any size to it.
|
|
19
|
+
*
|
|
20
|
+
* BOUNDED ON PURPOSE. Reading an unbounded body means an unauthenticated client can hold the
|
|
21
|
+
* process open and grow a buffer until it dies -- which costs the attacker nothing and does not
|
|
22
|
+
* require knowing any session. This is the cheapest denial of service there is, and the only
|
|
23
|
+
* defence is to refuse to read past a limit.
|
|
24
|
+
*/
|
|
25
|
+
export declare const MAX_BODY_BYTES: number;
|
|
26
|
+
export declare class BodyTooLarge extends Error {
|
|
27
|
+
}
|
|
28
|
+
export interface ServeOptions {
|
|
29
|
+
service: MeteredService;
|
|
30
|
+
resource?: string;
|
|
31
|
+
}
|
|
32
|
+
export declare function meteredHandler(opts: ServeOptions): (req: IncomingMessage, res: ServerResponse) => Promise<void>;
|
|
33
|
+
export declare function serveMetered(opts: ServeOptions): Server;
|
|
34
|
+
//# sourceMappingURL=serve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../../src/http/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AACH,OAAO,EAAgB,KAAK,eAAe,EAAE,KAAK,cAAc,EAAE,KAAK,MAAM,EAAE,MAAM,WAAW,CAAC;AAIjG,OAAO,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAK9C;;;;;;;;GAQG;AACH,eAAO,MAAM,cAAc,QAAa,CAAC;AAEzC,qBAAa,YAAa,SAAQ,KAAK;CAAG;AA6B1C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,cAAc,CAAC;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAmFD,wBAAgB,cAAc,CAAC,IAAI,EAAE,YAAY,IAGjC,KAAK,eAAe,EAAE,KAAK,cAAc,KAAG,OAAO,CAAC,IAAI,CAAC,CAoBxE;AAED,wBAAgB,YAAY,CAAC,IAAI,EAAE,YAAY,GAAG,MAAM,CAKvD"}
|