@tabai/sdk 0.2.3 → 0.2.4
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 +8 -7
- package/dist/_shared/abi.d.ts.map +1 -1
- package/dist/_shared/abi.js +6 -3
- package/dist/_shared/abi.js.map +1 -1
- package/dist/_shared/chains.d.ts +4 -6
- package/dist/_shared/chains.d.ts.map +1 -1
- package/dist/_shared/chains.js +1 -2
- package/dist/_shared/chains.js.map +1 -1
- package/dist/_shared/keccak256.d.ts +6 -6
- package/dist/_shared/keccak256.js +6 -6
- package/dist/_shared/keccak256.js.map +1 -1
- package/dist/_shared/result.d.ts +5 -6
- package/dist/_shared/result.d.ts.map +1 -1
- package/dist/_shared/result.js +5 -6
- package/dist/_shared/result.js.map +1 -1
- package/dist/cli/client-config.d.ts +4 -5
- package/dist/cli/client-config.d.ts.map +1 -1
- package/dist/cli/client-config.js +4 -5
- package/dist/cli/client-config.js.map +1 -1
- package/dist/cli/connect.d.ts +1 -3
- package/dist/cli/connect.d.ts.map +1 -1
- package/dist/cli/connect.js +1 -3
- package/dist/cli/connect.js.map +1 -1
- package/dist/cli/doctor.d.ts +1 -3
- package/dist/cli/doctor.d.ts.map +1 -1
- package/dist/cli/doctor.js +1 -3
- package/dist/cli/doctor.js.map +1 -1
- package/dist/cli/index.d.ts +1 -1
- package/dist/cli/index.js +1 -1
- package/dist/cli/index.js.map +1 -1
- package/dist/cli/main.d.ts +1 -3
- package/dist/cli/main.d.ts.map +1 -1
- package/dist/cli/main.js +2 -4
- package/dist/cli/main.js.map +1 -1
- package/dist/errors.d.ts +2 -4
- package/dist/errors.d.ts.map +1 -1
- package/dist/errors.js +2 -4
- package/dist/errors.js.map +1 -1
- package/dist/http/client-402.d.ts +11 -13
- package/dist/http/client-402.d.ts.map +1 -1
- package/dist/http/client-402.js +12 -14
- package/dist/http/client-402.js.map +1 -1
- package/dist/http/headers.d.ts +9 -11
- package/dist/http/headers.d.ts.map +1 -1
- package/dist/http/headers.js +9 -11
- package/dist/http/headers.js.map +1 -1
- package/dist/http/index.d.ts +0 -2
- package/dist/http/index.d.ts.map +1 -1
- package/dist/http/index.js +0 -2
- package/dist/http/index.js.map +1 -1
- package/dist/http/metering-claim.d.ts +0 -2
- package/dist/http/metering-claim.d.ts.map +1 -1
- package/dist/http/metering-claim.js +0 -2
- package/dist/http/metering-claim.js.map +1 -1
- package/dist/index.d.ts +15 -12
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +15 -12
- package/dist/index.js.map +1 -1
- package/dist/logger.d.ts +1 -4
- package/dist/logger.d.ts.map +1 -1
- package/dist/logger.js +1 -4
- package/dist/logger.js.map +1 -1
- package/dist/mcp/index.d.ts +1 -1
- package/dist/mcp/index.js +1 -1
- package/dist/mcp/index.js.map +1 -1
- package/dist/mcp/json-schema.d.ts +1 -3
- package/dist/mcp/json-schema.d.ts.map +1 -1
- package/dist/mcp/json-schema.js +1 -3
- package/dist/mcp/json-schema.js.map +1 -1
- package/dist/mcp/json.d.ts +0 -2
- package/dist/mcp/json.d.ts.map +1 -1
- package/dist/mcp/json.js +0 -2
- package/dist/mcp/json.js.map +1 -1
- package/dist/mcp/registry-client.d.ts +0 -2
- package/dist/mcp/registry-client.d.ts.map +1 -1
- package/dist/mcp/registry-client.js +3 -4
- package/dist/mcp/registry-client.js.map +1 -1
- package/dist/mcp/schemas.d.ts +10 -8
- package/dist/mcp/schemas.d.ts.map +1 -1
- package/dist/mcp/schemas.js +5 -7
- package/dist/mcp/schemas.js.map +1 -1
- package/dist/mcp/server.d.ts +0 -2
- package/dist/mcp/server.d.ts.map +1 -1
- package/dist/mcp/server.js +4 -7
- package/dist/mcp/server.js.map +1 -1
- package/dist/mcp/settings.d.ts +0 -2
- package/dist/mcp/settings.d.ts.map +1 -1
- package/dist/mcp/settings.js +0 -2
- package/dist/mcp/settings.js.map +1 -1
- package/dist/mcp/toolset.d.ts +2 -5
- package/dist/mcp/toolset.d.ts.map +1 -1
- package/dist/mcp/toolset.js +3 -6
- package/dist/mcp/toolset.js.map +1 -1
- package/dist/payments/config.d.ts +6 -8
- package/dist/payments/config.d.ts.map +1 -1
- package/dist/payments/config.js +5 -7
- package/dist/payments/config.js.map +1 -1
- package/dist/payments/index.d.ts +0 -2
- package/dist/payments/index.d.ts.map +1 -1
- package/dist/payments/index.js +0 -2
- package/dist/payments/index.js.map +1 -1
- package/dist/payments/registry.d.ts +11 -12
- package/dist/payments/registry.d.ts.map +1 -1
- package/dist/payments/registry.js +11 -12
- package/dist/payments/registry.js.map +1 -1
- package/dist/payments/strategy.d.ts +9 -5
- package/dist/payments/strategy.d.ts.map +1 -1
- package/dist/payments/strategy.js +9 -5
- package/dist/payments/strategy.js.map +1 -1
- package/dist/proxy/hooks.d.ts +3 -5
- package/dist/proxy/hooks.d.ts.map +1 -1
- package/dist/proxy/hooks.js +2 -4
- package/dist/proxy/hooks.js.map +1 -1
- package/dist/proxy/index.d.ts +2 -2
- package/dist/proxy/index.js +2 -2
- package/dist/proxy/index.js.map +1 -1
- package/dist/proxy/proxy.d.ts +3 -5
- package/dist/proxy/proxy.d.ts.map +1 -1
- package/dist/proxy/proxy.js +2 -4
- package/dist/proxy/proxy.js.map +1 -1
- package/dist/server/adapters/express.d.ts +0 -2
- package/dist/server/adapters/express.d.ts.map +1 -1
- package/dist/server/adapters/express.js +0 -2
- package/dist/server/adapters/express.js.map +1 -1
- package/dist/server/adapters/hono.d.ts +0 -2
- package/dist/server/adapters/hono.d.ts.map +1 -1
- package/dist/server/adapters/hono.js +0 -2
- package/dist/server/adapters/hono.js.map +1 -1
- package/dist/server/adapters/next.d.ts +0 -2
- package/dist/server/adapters/next.d.ts.map +1 -1
- package/dist/server/adapters/next.js +0 -2
- package/dist/server/adapters/next.js.map +1 -1
- package/dist/server/index.d.ts +5 -5
- package/dist/server/index.js +5 -5
- package/dist/server/index.js.map +1 -1
- package/dist/server/metering.d.ts +9 -11
- package/dist/server/metering.d.ts.map +1 -1
- package/dist/server/metering.js +6 -8
- package/dist/server/metering.js.map +1 -1
- package/dist/server/post-paid.d.ts +8 -10
- package/dist/server/post-paid.d.ts.map +1 -1
- package/dist/server/post-paid.js +7 -9
- package/dist/server/post-paid.js.map +1 -1
- package/dist/x402/client.d.ts +5 -6
- package/dist/x402/client.d.ts.map +1 -1
- package/dist/x402/client.js +6 -7
- package/dist/x402/client.js.map +1 -1
- package/dist/x402/server.d.ts +5 -5
- package/dist/x402/server.d.ts.map +1 -1
- package/dist/x402/server.js +2 -2
- package/dist/x402/server.js.map +1 -1
- package/dist/x402/wire.d.ts +1 -1
- package/dist/x402/wire.js +1 -1
- package/dist/x402/wire.js.map +1 -1
- package/package.json +1 -1
package/dist/server/metering.js
CHANGED
|
@@ -20,8 +20,8 @@
|
|
|
20
20
|
* the authorisation (`AuthorisationMissing`, `AuthorisationExpired`,
|
|
21
21
|
* `AuthorisationExceeded`), and the headroom check (`LimitExceeded`, plus the
|
|
22
22
|
* witness-validation errors the Credit Limit computation raises). Every name in
|
|
23
|
-
* {@link RECORD_DELIVERY_REVERTS} is one of those
|
|
24
|
-
*
|
|
23
|
+
* {@link RECORD_DELIVERY_REVERTS} is one of those, read off the deployed
|
|
24
|
+
* contract's errors.
|
|
25
25
|
*
|
|
26
26
|
* ## Whose fault it is decides who sees it
|
|
27
27
|
*
|
|
@@ -46,8 +46,6 @@
|
|
|
46
46
|
*
|
|
47
47
|
* Every amount here is an integer count of Asset base units as a `bigint`. USDC
|
|
48
48
|
* is 6 decimals, and nothing in this module converts, scales, or rounds.
|
|
49
|
-
*
|
|
50
|
-
* Requirements: 23.3, 12.1, 12.2, 12.3, 21.5
|
|
51
49
|
*/
|
|
52
50
|
import { httpStatusOf, } from "../_shared/index.js";
|
|
53
51
|
import { tabError } from "../errors.js";
|
|
@@ -66,8 +64,8 @@ export const TAB_BOOK_RECORD_DELIVERY_ABI = [
|
|
|
66
64
|
* Every revert `recordDelivery` can produce, and what each one becomes.
|
|
67
65
|
*
|
|
68
66
|
* The status each row maps to is not stored: it is `httpStatusOf(category)`, so
|
|
69
|
-
* `LIMIT` is 402 and nothing else is, which is the whole
|
|
70
|
-
*
|
|
67
|
+
* `LIMIT` is 402 and nothing else is, which is the whole rule expressed as a
|
|
68
|
+
* lookup rather than as a comment.
|
|
71
69
|
*/
|
|
72
70
|
export const RECORD_DELIVERY_REVERTS = {
|
|
73
71
|
// --- the Agent's to fix: these replace the response -----------------------
|
|
@@ -108,8 +106,8 @@ export const RECORD_DELIVERY_REVERTS = {
|
|
|
108
106
|
* Past the Settlement Window, so the tab is delinquent and the Agent's Credit
|
|
109
107
|
* Limit for the Asset is zero.
|
|
110
108
|
*
|
|
111
|
-
* A 409 rather than a 402, deliberately.
|
|
112
|
-
* `LimitExceeded` alone
|
|
109
|
+
* A 409 rather than a 402, deliberately. On this surface 402 means
|
|
110
|
+
* `LimitExceeded` alone, and delinquency is a different fact
|
|
113
111
|
* from a full tab: the state of the tab conflicts with metering at all, and no
|
|
114
112
|
* headroom figure would make this request succeed. The Agent settles the named
|
|
115
113
|
* tab and the state clears.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"metering.js","sourceRoot":"","sources":["../../src/server/metering.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The metering seam: what the post-paid plugin calls to record one Metered\n * Delivery, and how a `recordDelivery` revert becomes a `TabError`.\n *\n * Two things live here and they are deliberately separate. {@link TabBookClient}\n * is the interface the plugin depends on, so the plugin has no opinion about\n * `ethers`, an RPC endpoint, a signer, or a witness. {@link RECORD_DELIVERY_REVERTS}\n * is the mapping table from `TabBook.recordDelivery`'s revert set to the\n * `TabError` each one becomes, so every implementation of that interface, the\n * gateway or a Service operator's own client, classifies a refusal the same way\n * rather than each inventing its own status codes.\n *\n * ## The revert set is the real one, read off the contract\n *\n * `recordDelivery(agent, serviceId, asset, tool, units, expectedUnitPrice,\n * witness)` runs six steps in a fixed order, and each step has its own refusals.\n * In call order: the registry read (`UnknownService`), the operator check\n * (`NotServiceOperator`), the delinquency check (`TabIsDelinquent`), pricing\n * (`ZeroUnits`, `UnknownTool`, `PriceListChangedMidCall`, `AmountOutOfRange`),\n * the authorisation (`AuthorisationMissing`, `AuthorisationExpired`,\n * `AuthorisationExceeded`), and the headroom check (`LimitExceeded`, plus the\n * witness-validation errors the Credit Limit computation raises). Every name in\n * {@link RECORD_DELIVERY_REVERTS} is one of those; nothing in the table was\n * guessed from the design.\n *\n * ## Whose fault it is decides who sees it\n *\n * Each row carries a {@link RefusalDisposition}, and that field is the whole\n * policy of this module:\n *\n * - `refuse-request`, the refusal is the Agent's to fix. Its Credit Limit has no\n * headroom, its spending authorisation is missing, lapsed, or spent, or its tab\n * went past its Settlement Window. The Agent is told, with the figures it needs\n * to act on.\n * - `deliver-anyway`, the refusal is the Service's to fix, or ours. A wrong\n * operator key, a tool that is not in the applied price list, a price that\n * moved under the call, a witness the Service assembled wrongly. The Agent did\n * nothing, so the Agent's response is delivered and the Service is told through\n * its own failure channel. A Service whose billing is broken eats the cost of\n * the delivery; it does not hand the cost to the caller.\n *\n * `refuse-request` is the smaller set on purpose. The plugin exists to accrue, not\n * to gate.\n *\n * ## Amounts\n *\n * Every amount here is an integer count of Asset base units as a `bigint`. USDC\n * is 6 decimals, and nothing in this module converts, scales, or rounds.\n *\n * Requirements: 23.3, 12.1, 12.2, 12.3, 21.5\n */\n\nimport {\n httpStatusOf,\n type Address,\n type Bytes32,\n type ErrorCategory,\n type Hex,\n type Result,\n type TabError,\n} from \"../_shared/index.js\";\nimport { tabError } from \"../errors.js\";\nimport type { AssetRef } from \"../payments/strategy.js\";\n\n/**\n * The one delivery a Service is charging for.\n *\n * The fields are `recordDelivery`'s own parameters minus the witness, because the\n * witness is not the plugin's to build: it is the full ordered\n * Settlement history plus one Bond entry per counterparty, validated on chain\n * against the rolling commitment, and assembling it needs chain reads the plugin\n * does not make. A {@link TabBookClient} owns that.\n */\nexport interface MeteredDelivery {\n /** The Agent's Monad address. Identified from the request, never from a session. */\n readonly agent: Address;\n readonly serviceId: Bytes32;\n readonly asset: AssetRef;\n /** The named priced unit, as the 32-byte word the applied price list is keyed by. */\n readonly tool: Bytes32;\n /** Count of priced units. A `uint32` on chain, and zero reverts `ZeroUnits`. */\n readonly units: number;\n /**\n * The unit price the Service quoted, in Asset base units.\n *\n * Compared for exact equality against the applied price list, so a price that\n * moved between the quote and the charge reverts `PriceListChangedMidCall`\n * rather than charging a figure the Agent never saw. Per unit rather than\n * total, so the count cannot be adjusted to match a total already committed to.\n */\n readonly expectedUnitPrice: bigint;\n}\n\n/** What `recordDelivery` returned, in the order the contract returns it. */\nexport interface DeliveryReceipt {\n /** `units * expectedUnitPrice`, in Asset base units. */\n readonly charged: bigint;\n /** The Open Tab for this Agent, Service, and Asset after the charge. */\n readonly openAfter: bigint;\n /** Headroom left for this Agent across every Service in this Asset. */\n readonly headroomAfter: bigint;\n /** The Monad transaction the delivery was recorded in, when the client knows it. */\n readonly monadTxHash?: Hex;\n /** Milliseconds since the epoch, on the recording client's clock. */\n readonly recordedAt: number;\n}\n\n/**\n * The two calls the post-paid plugin makes.\n *\n * Narrow on purpose: this is what lets a Service meter against a real signer in\n * production and against a stub in a test without the plugin knowing the\n * difference. Both methods return a `Result` and neither throws (design section\n * 13.1). On a revert, `recordDelivery` returns the `TabError`\n * {@link classifyRecordDeliveryRevert} produces, so the plugin reads the\n * disposition off `details.disposition` rather than re-decoding contract data.\n */\nexport interface TabBookClient {\n recordDelivery(delivery: MeteredDelivery): Promise<Result<DeliveryReceipt>>;\n /**\n * The Open Tab for this Agent, Service, and Asset, read without metering.\n *\n * A second method rather than one, because the `402` needs a figure a revert\n * does not carry. `LimitExceeded` reverts with the requested amount and the\n * headroom and says nothing about the Open Tab, and the charge block on the wire\n * is all six headers or none of them, so a 402 that could not name the Open Tab\n * would reach the 402 client as a malformed charge block instead of as the credit\n * decision it is. The call is a view, no witness, no signature, and the figure\n * is truthful precisely because the metering transaction reverted and moved\n * nothing.\n *\n * Called on the refusal path alone. It is never on the path of a successful\n * delivery, which reads its figures off `recordDelivery`'s own return.\n */\n openTabOf(delivery: MeteredDelivery): Promise<Result<bigint>>;\n}\n\n/**\n * The human-readable ABI fragment of the real entrypoint, field for field.\n *\n * Carried here so a {@link TabBookClient} implementation encodes against the\n * deployed signature rather than a remembered one. The witness is one tuple of\n * two arrays: `LimitLib.SettlementRecord[]` and `LimitLib.BondEntry[]`, in that\n * order.\n */\nexport const TAB_BOOK_RECORD_DELIVERY_ABI = [\n \"function recordDelivery(address agent, bytes32 serviceId, address asset, bytes32 tool, uint32 units, uint256 expectedUnitPrice, ((bytes32 serviceId, address asset, uint128 amount, uint64 settledAt, uint64 firstDeliveryAt, bool curated, bool bonded)[] history, (bytes32 serviceId, address asset, uint128 amount)[] bonds) witness) returns (uint256 charged, uint128 openAfter, uint256 headroomAfter)\",\n] as const;\n\n/**\n * Whether a refusal changes what the caller receives.\n *\n * `refuse-request` replaces the handler's response. `deliver-anyway` does not:\n * the response goes out, and the Service learns about the refusal through\n * `onMeteringFailed`.\n */\nexport type RefusalDisposition = \"refuse-request\" | \"deliver-anyway\";\n\n/** One row of the revert mapping table. */\nexport interface RevertMapping {\n /** The `TabError` category, which is also what fixes the HTTP status (design section 13.1). */\n readonly category: ErrorCategory;\n /** Stable and machine-readable, for example `LIMIT_EXCEEDED`. */\n readonly code: string;\n readonly disposition: RefusalDisposition;\n readonly retryable: boolean;\n /**\n * The revert's argument names, positionally. Decoded arguments are attached to\n * `TabError.details` under these names, so the 402 body can carry the required\n * amount and the headroom without this module knowing which row it is on.\n */\n readonly args: readonly string[];\n /** What the Agent or the Service operator is supposed to do about it. */\n readonly action: string;\n}\n\n/**\n * Every revert `recordDelivery` can produce, and what each one becomes.\n *\n * The status each row maps to is not stored: it is `httpStatusOf(category)`, so\n * `LIMIT` is 402 and nothing else is, which is the whole of design section 9.5's\n * rule expressed as a lookup rather than as a comment.\n */\nexport const RECORD_DELIVERY_REVERTS: Readonly<Record<string, RevertMapping>> = {\n // --- the Agent's to fix: these replace the response -----------------------\n\n /** The one 402. The Agent's Open Tab for the Asset has no room for this charge. */\n LimitExceeded: {\n category: \"LIMIT\",\n code: \"LIMIT_EXCEEDED\",\n disposition: \"refuse-request\",\n retryable: false,\n args: [\"agent\", \"asset\", \"requested\", \"headroom\"],\n action:\n \"settle the Open Tab, or raise the Credit Limit by growing proven settlement history, then repeat the request\",\n },\n AuthorisationMissing: {\n category: \"AUTHORISATION\",\n code: \"AUTHORISATION_MISSING\",\n disposition: \"refuse-request\",\n retryable: false,\n args: [\"agent\", \"serviceId\", \"asset\"],\n action: \"the Agent must call TabBook.authorise for this Service and Asset before any delivery is metered\",\n },\n AuthorisationExpired: {\n category: \"AUTHORISATION\",\n code: \"AUTHORISATION_EXPIRED\",\n disposition: \"refuse-request\",\n retryable: false,\n args: [\"expiry\", \"nowTs\"],\n action: \"the Agent must call TabBook.authorise again with a later expiry\",\n },\n AuthorisationExceeded: {\n category: \"AUTHORISATION\",\n code: \"AUTHORISATION_EXCEEDED\",\n disposition: \"refuse-request\",\n retryable: false,\n args: [\"maxCumulative\", \"spent\", \"requested\"],\n action: \"the Agent must raise maxCumulative on its authorisation\",\n },\n /**\n * Past the Settlement Window, so the tab is delinquent and the Agent's Credit\n * Limit for the Asset is zero.\n *\n * A 409 rather than a 402, deliberately. Design section 9.5 fixes 402 to\n * `LimitExceeded` alone on this surface, and delinquency is a different fact\n * from a full tab: the state of the tab conflicts with metering at all, and no\n * headroom figure would make this request succeed. The Agent settles the named\n * tab and the state clears.\n */\n TabIsDelinquent: {\n category: \"CONFLICT\",\n code: \"TAB_DELINQUENT\",\n disposition: \"refuse-request\",\n retryable: false,\n args: [\"tabId\"],\n action: \"the Agent must settle the named tab; its Credit Limit for this Asset is zero until it does\",\n },\n\n // --- the Service's to fix, or ours: the response is delivered anyway ------\n\n /**\n * The applied price moved between the Service's quote and the charge.\n *\n * The Service's problem, not the Agent's: the Agent was quoted a price and the\n * work was delivered at it. Retryable once with the price the registry now\n * serves.\n */\n PriceListChangedMidCall: {\n category: \"CONFLICT\",\n code: \"PRICE_LIST_CHANGED_MID_CALL\",\n disposition: \"deliver-anyway\",\n retryable: true,\n args: [\"serviceId\", \"asset\", \"tool\", \"quoted\", \"applied\"],\n action: \"re-quote from the applied price list and record the delivery again\",\n },\n UnknownTool: {\n category: \"NOT_FOUND\",\n code: \"UNKNOWN_TOOL\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"serviceId\", \"asset\", \"tool\"],\n action: \"register the tool in the Service's price list; a priced unit that is not listed cannot be charged\",\n },\n UnknownService: {\n category: \"NOT_FOUND\",\n code: \"UNKNOWN_SERVICE\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"serviceId\"],\n action: \"register the Service before metering into it\",\n },\n NotServiceOperator: {\n category: \"AUTHORISATION\",\n code: \"NOT_SERVICE_OPERATOR\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"serviceId\", \"caller\"],\n action: \"meter with the Service operator's own Monad key\",\n },\n ZeroUnits: {\n category: \"VALIDATION\",\n code: \"ZERO_UNITS\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [],\n action: \"price the delivery at one unit or more, or treat it as unbillable and record nothing\",\n },\n AmountOutOfRange: {\n category: \"VALIDATION\",\n code: \"AMOUNT_OUT_OF_RANGE\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"amount\"],\n action: \"reduce the unit count; the Open Tab is kept in uint128 base units\",\n },\n HistoryCommitmentMismatch: {\n category: \"CONFLICT\",\n code: \"HISTORY_COMMITMENT_MISMATCH\",\n disposition: \"deliver-anyway\",\n retryable: true,\n args: [\"expected\", \"provided\"],\n action: \"refresh the witness from chain state and record the delivery again\",\n },\n HistoryLengthMismatch: {\n category: \"CONFLICT\",\n code: \"HISTORY_LENGTH_MISMATCH\",\n disposition: \"deliver-anyway\",\n retryable: true,\n args: [\"expected\", \"provided\"],\n action: \"refresh the witness from chain state and record the delivery again\",\n },\n DuplicateBondEntry: {\n category: \"INTERNAL\",\n code: \"DUPLICATE_BOND_ENTRY\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"serviceId\"],\n action: \"fix the witness builder: one Bond entry per counterparty Service\",\n },\n IneligibleBondEntry: {\n category: \"INTERNAL\",\n code: \"INELIGIBLE_BOND_ENTRY\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"serviceId\", \"asset\"],\n action: \"fix the witness builder: every Bond entry must name a counterparty from the history\",\n },\n TooManyBondEntries: {\n category: \"INTERNAL\",\n code: \"TOO_MANY_BOND_ENTRIES\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"count\", \"maximum\"],\n action: \"compact the history; this is the documented scaling limit of the pure computation\",\n },\n HistoryTooLong: {\n category: \"INTERNAL\",\n code: \"HISTORY_TOO_LONG\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"count\", \"maximum\"],\n action: \"compact the history; this is the documented scaling limit of the pure computation\",\n },\n TooManyCounterparties: {\n category: \"INTERNAL\",\n code: \"TOO_MANY_COUNTERPARTIES\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"count\", \"maximum\"],\n action: \"compact the history; this is the documented scaling limit of the pure computation\",\n },\n};\n\n/** The revert name and decoded arguments, as `ethers` v6 reports them. */\nexport interface DecodedRevert {\n readonly name: string;\n readonly args?: readonly unknown[];\n}\n\n/**\n * Pulls a decoded revert out of whatever a client caught.\n *\n * `ethers` v6 attaches `error.revert` as `{ name, signature, args }` when it can\n * match the returndata against the contract's ABI; older shapes carry\n * `errorName` and `errorArgs`; and a plain object with a `name` is what a test\n * or a non-`ethers` client hands over. All three are read, and anything else\n * yields undefined rather than a guess.\n */\nexport function decodeRevert(thrown: unknown): DecodedRevert | undefined {\n if (typeof thrown !== \"object\" || thrown === null) return undefined;\n const candidate = thrown as {\n revert?: { name?: unknown; args?: unknown } | null;\n errorName?: unknown;\n errorArgs?: unknown;\n name?: unknown;\n args?: unknown;\n };\n\n const revert = candidate.revert;\n if (revert !== undefined && revert !== null && typeof revert.name === \"string\") {\n return { name: revert.name, ...(Array.isArray(revert.args) ? { args: revert.args } : {}) };\n }\n if (typeof candidate.errorName === \"string\") {\n return {\n name: candidate.errorName,\n ...(Array.isArray(candidate.errorArgs) ? { args: candidate.errorArgs } : {}),\n };\n }\n if (typeof candidate.name === \"string\" && RECORD_DELIVERY_REVERTS[candidate.name] !== undefined) {\n return { name: candidate.name, ...(Array.isArray(candidate.args) ? { args: candidate.args } : {}) };\n }\n return undefined;\n}\n\n/** The mapping row for one revert name, or undefined when the name is not one of them. */\nexport const revertMappingFor = (name: string): RevertMapping | undefined =>\n RECORD_DELIVERY_REVERTS[name];\n\n/**\n * Turns a `recordDelivery` revert into the `TabError` the plugin acts on.\n *\n * `details` always carries `revert` and `disposition`, and carries each decoded\n * argument under the name the row gives it. That is how the plugin builds a 402\n * body naming the required amount and the current headroom without a special\n * case for `LimitExceeded`: it reads `details.requested` and `details.headroom`,\n * which the table put there.\n *\n * A revert this table does not name is `CHAIN`/`RECORD_DELIVERY_REVERTED` with\n * `deliver-anyway`, because an unclassified refusal is not something to charge an\n * Agent for and not something to refuse a delivered response over either.\n */\nexport function classifyRecordDeliveryRevert(thrown: unknown): TabError {\n const decoded = decodeRevert(thrown);\n const mapping = decoded === undefined ? undefined : revertMappingFor(decoded.name);\n const cause = causeFrom(thrown);\n const carriedCause = cause === undefined ? {} : { cause };\n\n if (decoded === undefined || mapping === undefined) {\n return tabError(\n \"CHAIN\",\n \"RECORD_DELIVERY_REVERTED\",\n decoded === undefined\n ? \"recordDelivery failed and the returndata matched no known revert\"\n : `recordDelivery reverted \\`${decoded.name}\\`, which is not a revert this SDK version classifies`,\n {\n retryable: false,\n details: {\n disposition: \"deliver-anyway\",\n ...(decoded === undefined ? {} : { revert: decoded.name }),\n },\n ...carriedCause,\n },\n );\n }\n\n return tabError(\n mapping.category,\n mapping.code,\n `recordDelivery reverted \\`${decoded.name}\\`: ${mapping.action}`,\n {\n retryable: mapping.retryable,\n details: {\n revert: decoded.name,\n disposition: mapping.disposition,\n ...namedArgs(mapping.args, decoded.args),\n },\n ...carriedCause,\n },\n );\n}\n\n/** The disposition carried on a classified error, defaulting to the safe one. */\nexport const dispositionOf = (error: TabError): RefusalDisposition =>\n error.details?.[\"disposition\"] === \"refuse-request\" ? \"refuse-request\" : \"deliver-anyway\";\n\n/** The HTTP status a refusal is served with, taken from its category alone. */\nexport const refusalStatusOf = (error: TabError): number => httpStatusOf(error);\n\n/**\n * Reads one decoded argument off a classified error as a `bigint`.\n *\n * Arguments land in `details` as decimal strings, because `TabError.details`\n * holds no `bigint`. This reads one back, and returns undefined rather than\n * `0n` when it is absent, a headroom nobody reported is not a headroom of zero.\n */\nexport function detailAmount(error: TabError, name: string): bigint | undefined {\n const raw = error.details?.[name];\n if (typeof raw === \"bigint\") return raw;\n if (typeof raw === \"number\" && Number.isSafeInteger(raw)) return BigInt(raw);\n if (typeof raw !== \"string\" || !/^\\d+$/.test(raw)) return undefined;\n return BigInt(raw);\n}\n\n/** Names the positional revert arguments and renders each into `details`. */\nfunction namedArgs(\n names: readonly string[],\n args: readonly unknown[] | undefined,\n): Record<string, string | number | boolean> {\n if (args === undefined) return {};\n const named: Record<string, string | number | boolean> = {};\n for (const [index, name] of names.entries()) {\n if (index >= args.length) break;\n const value = args[index];\n if (typeof value === \"bigint\") named[name] = value.toString(10);\n else if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n named[name] = value;\n } else if (value !== undefined && value !== null) named[name] = String(value);\n }\n return named;\n}\n\n/** Narrows a caught value into the `cause` shape a `TabError` accepts. */\nfunction causeFrom(thrown: unknown): { code: string; message: string } | undefined {\n if (thrown instanceof Error) return { code: thrown.name, message: thrown.message };\n if (typeof thrown === \"object\" && thrown !== null) {\n const candidate = thrown as { code?: unknown; shortMessage?: unknown; message?: unknown };\n const message =\n typeof candidate.shortMessage === \"string\"\n ? candidate.shortMessage\n : typeof candidate.message === \"string\"\n ? candidate.message\n : undefined;\n if (message === undefined) return undefined;\n return { code: typeof candidate.code === \"string\" ? candidate.code : \"UNKNOWN\", message };\n }\n return undefined;\n}\n"]}
|
|
1
|
+
{"version":3,"file":"metering.js","sourceRoot":"","sources":["../../src/server/metering.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The metering seam: what the post-paid plugin calls to record one Metered\n * Delivery, and how a `recordDelivery` revert becomes a `TabError`.\n *\n * Two things live here and they are deliberately separate. {@link TabBookClient}\n * is the interface the plugin depends on, so the plugin has no opinion about\n * `ethers`, an RPC endpoint, a signer, or a witness. {@link RECORD_DELIVERY_REVERTS}\n * is the mapping table from `TabBook.recordDelivery`'s revert set to the\n * `TabError` each one becomes, so every implementation of that interface, the\n * gateway or a Service operator's own client, classifies a refusal the same way\n * rather than each inventing its own status codes.\n *\n * ## The revert set is the real one, read off the contract\n *\n * `recordDelivery(agent, serviceId, asset, tool, units, expectedUnitPrice,\n * witness)` runs six steps in a fixed order, and each step has its own refusals.\n * In call order: the registry read (`UnknownService`), the operator check\n * (`NotServiceOperator`), the delinquency check (`TabIsDelinquent`), pricing\n * (`ZeroUnits`, `UnknownTool`, `PriceListChangedMidCall`, `AmountOutOfRange`),\n * the authorisation (`AuthorisationMissing`, `AuthorisationExpired`,\n * `AuthorisationExceeded`), and the headroom check (`LimitExceeded`, plus the\n * witness-validation errors the Credit Limit computation raises). Every name in\n * {@link RECORD_DELIVERY_REVERTS} is one of those, read off the deployed\n * contract's errors.\n *\n * ## Whose fault it is decides who sees it\n *\n * Each row carries a {@link RefusalDisposition}, and that field is the whole\n * policy of this module:\n *\n * - `refuse-request`, the refusal is the Agent's to fix. Its Credit Limit has no\n * headroom, its spending authorisation is missing, lapsed, or spent, or its tab\n * went past its Settlement Window. The Agent is told, with the figures it needs\n * to act on.\n * - `deliver-anyway`, the refusal is the Service's to fix, or ours. A wrong\n * operator key, a tool that is not in the applied price list, a price that\n * moved under the call, a witness the Service assembled wrongly. The Agent did\n * nothing, so the Agent's response is delivered and the Service is told through\n * its own failure channel. A Service whose billing is broken eats the cost of\n * the delivery; it does not hand the cost to the caller.\n *\n * `refuse-request` is the smaller set on purpose. The plugin exists to accrue, not\n * to gate.\n *\n * ## Amounts\n *\n * Every amount here is an integer count of Asset base units as a `bigint`. USDC\n * is 6 decimals, and nothing in this module converts, scales, or rounds.\n */\n\nimport {\n httpStatusOf,\n type Address,\n type Bytes32,\n type ErrorCategory,\n type Hex,\n type Result,\n type TabError,\n} from \"../_shared/index.js\";\nimport { tabError } from \"../errors.js\";\nimport type { AssetRef } from \"../payments/strategy.js\";\n\n/**\n * The one delivery a Service is charging for.\n *\n * The fields are `recordDelivery`'s own parameters minus the witness, because the\n * witness is not the plugin's to build: it is the full ordered\n * Settlement history plus one Bond entry per counterparty, validated on chain\n * against the rolling commitment, and assembling it needs chain reads the plugin\n * does not make. A {@link TabBookClient} owns that.\n */\nexport interface MeteredDelivery {\n /** The Agent's Monad address. Identified from the request, never from a session. */\n readonly agent: Address;\n readonly serviceId: Bytes32;\n readonly asset: AssetRef;\n /** The named priced unit, as the 32-byte word the applied price list is keyed by. */\n readonly tool: Bytes32;\n /** Count of priced units. A `uint32` on chain, and zero reverts `ZeroUnits`. */\n readonly units: number;\n /**\n * The unit price the Service quoted, in Asset base units.\n *\n * Compared for exact equality against the applied price list, so a price that\n * moved between the quote and the charge reverts `PriceListChangedMidCall`\n * rather than charging a figure the Agent never saw. Per unit rather than\n * total, so the count cannot be adjusted to match a total already committed to.\n */\n readonly expectedUnitPrice: bigint;\n}\n\n/** What `recordDelivery` returned, in the order the contract returns it. */\nexport interface DeliveryReceipt {\n /** `units * expectedUnitPrice`, in Asset base units. */\n readonly charged: bigint;\n /** The Open Tab for this Agent, Service, and Asset after the charge. */\n readonly openAfter: bigint;\n /** Headroom left for this Agent across every Service in this Asset. */\n readonly headroomAfter: bigint;\n /** The Monad transaction the delivery was recorded in, when the client knows it. */\n readonly monadTxHash?: Hex;\n /** Milliseconds since the epoch, on the recording client's clock. */\n readonly recordedAt: number;\n}\n\n/**\n * The two calls the post-paid plugin makes.\n *\n * Narrow on purpose: this is what lets a Service meter against a real signer in\n * production and against a stub in a test without the plugin knowing the\n * difference. Both methods return a `Result` and neither throws. On a revert,\n * `recordDelivery` returns the `TabError` {@link classifyRecordDeliveryRevert}\n * produces, so the plugin reads the disposition off `details.disposition` rather\n * than re-decoding contract data.\n */\nexport interface TabBookClient {\n recordDelivery(delivery: MeteredDelivery): Promise<Result<DeliveryReceipt>>;\n /**\n * The Open Tab for this Agent, Service, and Asset, read without metering.\n *\n * A second method rather than one, because the `402` needs a figure a revert\n * does not carry. `LimitExceeded` reverts with the requested amount and the\n * headroom and says nothing about the Open Tab, and the charge block on the wire\n * is all six headers or none of them, so a 402 that could not name the Open Tab\n * would reach the 402 client as a malformed charge block instead of as the credit\n * decision it is. The call is a view, no witness, no signature, and the figure\n * is truthful precisely because the metering transaction reverted and moved\n * nothing.\n *\n * Called on the refusal path alone. It is never on the path of a successful\n * delivery, which reads its figures off `recordDelivery`'s own return.\n */\n openTabOf(delivery: MeteredDelivery): Promise<Result<bigint>>;\n}\n\n/**\n * The human-readable ABI fragment of the real entrypoint, field for field.\n *\n * Carried here so a {@link TabBookClient} implementation encodes against the\n * deployed signature rather than a remembered one. The witness is one tuple of\n * two arrays: `LimitLib.SettlementRecord[]` and `LimitLib.BondEntry[]`, in that\n * order.\n */\nexport const TAB_BOOK_RECORD_DELIVERY_ABI = [\n \"function recordDelivery(address agent, bytes32 serviceId, address asset, bytes32 tool, uint32 units, uint256 expectedUnitPrice, ((bytes32 serviceId, address asset, uint128 amount, uint64 settledAt, uint64 firstDeliveryAt, bool curated, bool bonded)[] history, (bytes32 serviceId, address asset, uint128 amount)[] bonds) witness) returns (uint256 charged, uint128 openAfter, uint256 headroomAfter)\",\n] as const;\n\n/**\n * Whether a refusal changes what the caller receives.\n *\n * `refuse-request` replaces the handler's response. `deliver-anyway` does not:\n * the response goes out, and the Service learns about the refusal through\n * `onMeteringFailed`.\n */\nexport type RefusalDisposition = \"refuse-request\" | \"deliver-anyway\";\n\n/** One row of the revert mapping table. */\nexport interface RevertMapping {\n /** The `TabError` category, which is also what fixes the HTTP status. */\n readonly category: ErrorCategory;\n /** Stable and machine-readable, for example `LIMIT_EXCEEDED`. */\n readonly code: string;\n readonly disposition: RefusalDisposition;\n readonly retryable: boolean;\n /**\n * The revert's argument names, positionally. Decoded arguments are attached to\n * `TabError.details` under these names, so the 402 body can carry the required\n * amount and the headroom without this module knowing which row it is on.\n */\n readonly args: readonly string[];\n /** What the Agent or the Service operator is supposed to do about it. */\n readonly action: string;\n}\n\n/**\n * Every revert `recordDelivery` can produce, and what each one becomes.\n *\n * The status each row maps to is not stored: it is `httpStatusOf(category)`, so\n * `LIMIT` is 402 and nothing else is, which is the whole rule expressed as a\n * lookup rather than as a comment.\n */\nexport const RECORD_DELIVERY_REVERTS: Readonly<Record<string, RevertMapping>> = {\n // --- the Agent's to fix: these replace the response -----------------------\n\n /** The one 402. The Agent's Open Tab for the Asset has no room for this charge. */\n LimitExceeded: {\n category: \"LIMIT\",\n code: \"LIMIT_EXCEEDED\",\n disposition: \"refuse-request\",\n retryable: false,\n args: [\"agent\", \"asset\", \"requested\", \"headroom\"],\n action:\n \"settle the Open Tab, or raise the Credit Limit by growing proven settlement history, then repeat the request\",\n },\n AuthorisationMissing: {\n category: \"AUTHORISATION\",\n code: \"AUTHORISATION_MISSING\",\n disposition: \"refuse-request\",\n retryable: false,\n args: [\"agent\", \"serviceId\", \"asset\"],\n action: \"the Agent must call TabBook.authorise for this Service and Asset before any delivery is metered\",\n },\n AuthorisationExpired: {\n category: \"AUTHORISATION\",\n code: \"AUTHORISATION_EXPIRED\",\n disposition: \"refuse-request\",\n retryable: false,\n args: [\"expiry\", \"nowTs\"],\n action: \"the Agent must call TabBook.authorise again with a later expiry\",\n },\n AuthorisationExceeded: {\n category: \"AUTHORISATION\",\n code: \"AUTHORISATION_EXCEEDED\",\n disposition: \"refuse-request\",\n retryable: false,\n args: [\"maxCumulative\", \"spent\", \"requested\"],\n action: \"the Agent must raise maxCumulative on its authorisation\",\n },\n /**\n * Past the Settlement Window, so the tab is delinquent and the Agent's Credit\n * Limit for the Asset is zero.\n *\n * A 409 rather than a 402, deliberately. On this surface 402 means\n * `LimitExceeded` alone, and delinquency is a different fact\n * from a full tab: the state of the tab conflicts with metering at all, and no\n * headroom figure would make this request succeed. The Agent settles the named\n * tab and the state clears.\n */\n TabIsDelinquent: {\n category: \"CONFLICT\",\n code: \"TAB_DELINQUENT\",\n disposition: \"refuse-request\",\n retryable: false,\n args: [\"tabId\"],\n action: \"the Agent must settle the named tab; its Credit Limit for this Asset is zero until it does\",\n },\n\n // --- the Service's to fix, or ours: the response is delivered anyway ------\n\n /**\n * The applied price moved between the Service's quote and the charge.\n *\n * The Service's problem, not the Agent's: the Agent was quoted a price and the\n * work was delivered at it. Retryable once with the price the registry now\n * serves.\n */\n PriceListChangedMidCall: {\n category: \"CONFLICT\",\n code: \"PRICE_LIST_CHANGED_MID_CALL\",\n disposition: \"deliver-anyway\",\n retryable: true,\n args: [\"serviceId\", \"asset\", \"tool\", \"quoted\", \"applied\"],\n action: \"re-quote from the applied price list and record the delivery again\",\n },\n UnknownTool: {\n category: \"NOT_FOUND\",\n code: \"UNKNOWN_TOOL\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"serviceId\", \"asset\", \"tool\"],\n action: \"register the tool in the Service's price list; a priced unit that is not listed cannot be charged\",\n },\n UnknownService: {\n category: \"NOT_FOUND\",\n code: \"UNKNOWN_SERVICE\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"serviceId\"],\n action: \"register the Service before metering into it\",\n },\n NotServiceOperator: {\n category: \"AUTHORISATION\",\n code: \"NOT_SERVICE_OPERATOR\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"serviceId\", \"caller\"],\n action: \"meter with the Service operator's own Monad key\",\n },\n ZeroUnits: {\n category: \"VALIDATION\",\n code: \"ZERO_UNITS\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [],\n action: \"price the delivery at one unit or more, or treat it as unbillable and record nothing\",\n },\n AmountOutOfRange: {\n category: \"VALIDATION\",\n code: \"AMOUNT_OUT_OF_RANGE\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"amount\"],\n action: \"reduce the unit count; the Open Tab is kept in uint128 base units\",\n },\n HistoryCommitmentMismatch: {\n category: \"CONFLICT\",\n code: \"HISTORY_COMMITMENT_MISMATCH\",\n disposition: \"deliver-anyway\",\n retryable: true,\n args: [\"expected\", \"provided\"],\n action: \"refresh the witness from chain state and record the delivery again\",\n },\n HistoryLengthMismatch: {\n category: \"CONFLICT\",\n code: \"HISTORY_LENGTH_MISMATCH\",\n disposition: \"deliver-anyway\",\n retryable: true,\n args: [\"expected\", \"provided\"],\n action: \"refresh the witness from chain state and record the delivery again\",\n },\n DuplicateBondEntry: {\n category: \"INTERNAL\",\n code: \"DUPLICATE_BOND_ENTRY\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"serviceId\"],\n action: \"fix the witness builder: one Bond entry per counterparty Service\",\n },\n IneligibleBondEntry: {\n category: \"INTERNAL\",\n code: \"INELIGIBLE_BOND_ENTRY\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"serviceId\", \"asset\"],\n action: \"fix the witness builder: every Bond entry must name a counterparty from the history\",\n },\n TooManyBondEntries: {\n category: \"INTERNAL\",\n code: \"TOO_MANY_BOND_ENTRIES\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"count\", \"maximum\"],\n action: \"compact the history; this is the documented scaling limit of the pure computation\",\n },\n HistoryTooLong: {\n category: \"INTERNAL\",\n code: \"HISTORY_TOO_LONG\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"count\", \"maximum\"],\n action: \"compact the history; this is the documented scaling limit of the pure computation\",\n },\n TooManyCounterparties: {\n category: \"INTERNAL\",\n code: \"TOO_MANY_COUNTERPARTIES\",\n disposition: \"deliver-anyway\",\n retryable: false,\n args: [\"count\", \"maximum\"],\n action: \"compact the history; this is the documented scaling limit of the pure computation\",\n },\n};\n\n/** The revert name and decoded arguments, as `ethers` v6 reports them. */\nexport interface DecodedRevert {\n readonly name: string;\n readonly args?: readonly unknown[];\n}\n\n/**\n * Pulls a decoded revert out of whatever a client caught.\n *\n * `ethers` v6 attaches `error.revert` as `{ name, signature, args }` when it can\n * match the returndata against the contract's ABI; older shapes carry\n * `errorName` and `errorArgs`; and a plain object with a `name` is what a test\n * or a non-`ethers` client hands over. All three are read, and anything else\n * yields undefined rather than a guess.\n */\nexport function decodeRevert(thrown: unknown): DecodedRevert | undefined {\n if (typeof thrown !== \"object\" || thrown === null) return undefined;\n const candidate = thrown as {\n revert?: { name?: unknown; args?: unknown } | null;\n errorName?: unknown;\n errorArgs?: unknown;\n name?: unknown;\n args?: unknown;\n };\n\n const revert = candidate.revert;\n if (revert !== undefined && revert !== null && typeof revert.name === \"string\") {\n return { name: revert.name, ...(Array.isArray(revert.args) ? { args: revert.args } : {}) };\n }\n if (typeof candidate.errorName === \"string\") {\n return {\n name: candidate.errorName,\n ...(Array.isArray(candidate.errorArgs) ? { args: candidate.errorArgs } : {}),\n };\n }\n if (typeof candidate.name === \"string\" && RECORD_DELIVERY_REVERTS[candidate.name] !== undefined) {\n return { name: candidate.name, ...(Array.isArray(candidate.args) ? { args: candidate.args } : {}) };\n }\n return undefined;\n}\n\n/** The mapping row for one revert name, or undefined when the name is not one of them. */\nexport const revertMappingFor = (name: string): RevertMapping | undefined =>\n RECORD_DELIVERY_REVERTS[name];\n\n/**\n * Turns a `recordDelivery` revert into the `TabError` the plugin acts on.\n *\n * `details` always carries `revert` and `disposition`, and carries each decoded\n * argument under the name the row gives it. That is how the plugin builds a 402\n * body naming the required amount and the current headroom without a special\n * case for `LimitExceeded`: it reads `details.requested` and `details.headroom`,\n * which the table put there.\n *\n * A revert this table does not name is `CHAIN`/`RECORD_DELIVERY_REVERTED` with\n * `deliver-anyway`, because an unclassified refusal is not something to charge an\n * Agent for and not something to refuse a delivered response over either.\n */\nexport function classifyRecordDeliveryRevert(thrown: unknown): TabError {\n const decoded = decodeRevert(thrown);\n const mapping = decoded === undefined ? undefined : revertMappingFor(decoded.name);\n const cause = causeFrom(thrown);\n const carriedCause = cause === undefined ? {} : { cause };\n\n if (decoded === undefined || mapping === undefined) {\n return tabError(\n \"CHAIN\",\n \"RECORD_DELIVERY_REVERTED\",\n decoded === undefined\n ? \"recordDelivery failed and the returndata matched no known revert\"\n : `recordDelivery reverted \\`${decoded.name}\\`, which is not a revert this SDK version classifies`,\n {\n retryable: false,\n details: {\n disposition: \"deliver-anyway\",\n ...(decoded === undefined ? {} : { revert: decoded.name }),\n },\n ...carriedCause,\n },\n );\n }\n\n return tabError(\n mapping.category,\n mapping.code,\n `recordDelivery reverted \\`${decoded.name}\\`: ${mapping.action}`,\n {\n retryable: mapping.retryable,\n details: {\n revert: decoded.name,\n disposition: mapping.disposition,\n ...namedArgs(mapping.args, decoded.args),\n },\n ...carriedCause,\n },\n );\n}\n\n/** The disposition carried on a classified error, defaulting to the safe one. */\nexport const dispositionOf = (error: TabError): RefusalDisposition =>\n error.details?.[\"disposition\"] === \"refuse-request\" ? \"refuse-request\" : \"deliver-anyway\";\n\n/** The HTTP status a refusal is served with, taken from its category alone. */\nexport const refusalStatusOf = (error: TabError): number => httpStatusOf(error);\n\n/**\n * Reads one decoded argument off a classified error as a `bigint`.\n *\n * Arguments land in `details` as decimal strings, because `TabError.details`\n * holds no `bigint`. This reads one back, and returns undefined rather than\n * `0n` when it is absent, a headroom nobody reported is not a headroom of zero.\n */\nexport function detailAmount(error: TabError, name: string): bigint | undefined {\n const raw = error.details?.[name];\n if (typeof raw === \"bigint\") return raw;\n if (typeof raw === \"number\" && Number.isSafeInteger(raw)) return BigInt(raw);\n if (typeof raw !== \"string\" || !/^\\d+$/.test(raw)) return undefined;\n return BigInt(raw);\n}\n\n/** Names the positional revert arguments and renders each into `details`. */\nfunction namedArgs(\n names: readonly string[],\n args: readonly unknown[] | undefined,\n): Record<string, string | number | boolean> {\n if (args === undefined) return {};\n const named: Record<string, string | number | boolean> = {};\n for (const [index, name] of names.entries()) {\n if (index >= args.length) break;\n const value = args[index];\n if (typeof value === \"bigint\") named[name] = value.toString(10);\n else if (typeof value === \"string\" || typeof value === \"number\" || typeof value === \"boolean\") {\n named[name] = value;\n } else if (value !== undefined && value !== null) named[name] = String(value);\n }\n return named;\n}\n\n/** Narrows a caught value into the `cause` shape a `TabError` accepts. */\nfunction causeFrom(thrown: unknown): { code: string; message: string } | undefined {\n if (thrown instanceof Error) return { code: thrown.name, message: thrown.message };\n if (typeof thrown === \"object\" && thrown !== null) {\n const candidate = thrown as { code?: unknown; shortMessage?: unknown; message?: unknown };\n const message =\n typeof candidate.shortMessage === \"string\"\n ? candidate.shortMessage\n : typeof candidate.message === \"string\"\n ? candidate.message\n : undefined;\n if (message === undefined) return undefined;\n return { code: typeof candidate.code === \"string\" ? candidate.code : \"UNKNOWN\", message };\n }\n return undefined;\n}\n"]}
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Only then is the delivered work metered into the Open Tab. Nothing in this file
|
|
8
8
|
* awaits a payment, a Settlement, or a signature before a response is
|
|
9
9
|
* released, because a plugin that did would be the prepay model this rail exists
|
|
10
|
-
* to replace. Metering is a record of work already delivered
|
|
10
|
+
* to replace. Metering is a record of work already delivered.
|
|
11
11
|
*
|
|
12
12
|
* Two orderings ship, and the difference is exactly one `await`:
|
|
13
13
|
*
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* | `before-metering` | handler resolves, the response is released, the delivery is recorded behind it | no, the response has already gone |
|
|
18
18
|
*
|
|
19
19
|
* `after-metering` is the default because a `LimitExceeded` refusal can only reach
|
|
20
|
-
* the caller on the request that caused it, and
|
|
21
|
-
*
|
|
20
|
+
* the caller on the request that caused it, and the Agent needs that refusal to
|
|
21
|
+
* know it should settle. What it holds the response for is a single credit record, not a
|
|
22
22
|
* payment, and it holds it for nothing else: if metering fails for any reason
|
|
23
23
|
* that is not the Agent's to fix, the handler's own response is delivered
|
|
24
24
|
* unchanged. A Service whose billing is broken pays for the delivery. It does not
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
* ## 402 is the exception, not the path
|
|
32
32
|
*
|
|
33
33
|
* The normal path is 200, delivery recorded, charge headers attached. `402`
|
|
34
|
-
* happens on `LimitExceeded` alone
|
|
35
|
-
* headroom for this charge, which is a credit decision and not a prepayment
|
|
34
|
+
* happens on `LimitExceeded` alone (the Agent's Open Tab for the Asset has no
|
|
35
|
+
* headroom for this charge), which is a credit decision and not a prepayment
|
|
36
36
|
* demand. When it happens the body carries the required amount and the current
|
|
37
|
-
* headroom, and the same figures go out as headers, so the 402 client
|
|
38
|
-
*
|
|
37
|
+
* headroom, and the same figures go out as headers, so the 402 client in
|
|
38
|
+
* `http/client-402.ts` reads a refusal with the identical parser it uses on a success.
|
|
39
39
|
*
|
|
40
40
|
* Two other refusals also reach the Agent, because only the Agent can clear them:
|
|
41
41
|
* a missing, lapsed, or spent spending authorisation (403), and a tab that went
|
|
@@ -77,8 +77,6 @@
|
|
|
77
77
|
* against, available on the charge for logging and carrying no authority. Metering
|
|
78
78
|
* is bounded by the on-chain authorisation the Agent set, and a Service that needs
|
|
79
79
|
* the `Tab-Agent` claim authenticated does that in `agentOf`.
|
|
80
|
-
*
|
|
81
|
-
* Requirements: 23.3, 12.1, 12.2, 12.3, 21.5
|
|
82
80
|
*/
|
|
83
81
|
import { type Address, type Bytes32, type Hex, type TabError } from "../_shared/index.js";
|
|
84
82
|
import { type Logger } from "../logger.js";
|
|
@@ -110,7 +108,7 @@ export interface DeliveredResponse {
|
|
|
110
108
|
/**
|
|
111
109
|
* What the Service charges for this call.
|
|
112
110
|
*
|
|
113
|
-
* A deliberate superset of
|
|
111
|
+
* A deliberate superset of a bare `{ tool, units }`.
|
|
114
112
|
* `recordDelivery` takes an `expectedUnitPrice` and reverts
|
|
115
113
|
* `PriceListChangedMidCall` when it disagrees with the applied price list, so a
|
|
116
114
|
* plugin that cannot state the price it quoted cannot call the contract at all.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-paid.d.ts","sourceRoot":"","sources":["../../src/server/post-paid.ts"],"names":[],"mappings":"AAAA
|
|
1
|
+
{"version":3,"file":"post-paid.d.ts","sourceRoot":"","sources":["../../src/server/post-paid.ts"],"names":[],"mappings":"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"}
|
package/dist/server/post-paid.js
CHANGED
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
* Only then is the delivered work metered into the Open Tab. Nothing in this file
|
|
8
8
|
* awaits a payment, a Settlement, or a signature before a response is
|
|
9
9
|
* released, because a plugin that did would be the prepay model this rail exists
|
|
10
|
-
* to replace. Metering is a record of work already delivered
|
|
10
|
+
* to replace. Metering is a record of work already delivered.
|
|
11
11
|
*
|
|
12
12
|
* Two orderings ship, and the difference is exactly one `await`:
|
|
13
13
|
*
|
|
@@ -17,8 +17,8 @@
|
|
|
17
17
|
* | `before-metering` | handler resolves, the response is released, the delivery is recorded behind it | no, the response has already gone |
|
|
18
18
|
*
|
|
19
19
|
* `after-metering` is the default because a `LimitExceeded` refusal can only reach
|
|
20
|
-
* the caller on the request that caused it, and
|
|
21
|
-
*
|
|
20
|
+
* the caller on the request that caused it, and the Agent needs that refusal to
|
|
21
|
+
* know it should settle. What it holds the response for is a single credit record, not a
|
|
22
22
|
* payment, and it holds it for nothing else: if metering fails for any reason
|
|
23
23
|
* that is not the Agent's to fix, the handler's own response is delivered
|
|
24
24
|
* unchanged. A Service whose billing is broken pays for the delivery. It does not
|
|
@@ -31,11 +31,11 @@
|
|
|
31
31
|
* ## 402 is the exception, not the path
|
|
32
32
|
*
|
|
33
33
|
* The normal path is 200, delivery recorded, charge headers attached. `402`
|
|
34
|
-
* happens on `LimitExceeded` alone
|
|
35
|
-
* headroom for this charge, which is a credit decision and not a prepayment
|
|
34
|
+
* happens on `LimitExceeded` alone (the Agent's Open Tab for the Asset has no
|
|
35
|
+
* headroom for this charge), which is a credit decision and not a prepayment
|
|
36
36
|
* demand. When it happens the body carries the required amount and the current
|
|
37
|
-
* headroom, and the same figures go out as headers, so the 402 client
|
|
38
|
-
*
|
|
37
|
+
* headroom, and the same figures go out as headers, so the 402 client in
|
|
38
|
+
* `http/client-402.ts` reads a refusal with the identical parser it uses on a success.
|
|
39
39
|
*
|
|
40
40
|
* Two other refusals also reach the Agent, because only the Agent can clear them:
|
|
41
41
|
* a missing, lapsed, or spent spending authorisation (403), and a tab that went
|
|
@@ -77,8 +77,6 @@
|
|
|
77
77
|
* against, available on the charge for logging and carrying no authority. Metering
|
|
78
78
|
* is bounded by the on-chain authorisation the Agent set, and a Service that needs
|
|
79
79
|
* the `Tab-Agent` claim authenticated does that in `agentOf`.
|
|
80
|
-
*
|
|
81
|
-
* Requirements: 23.3, 12.1, 12.2, 12.3, 21.5
|
|
82
80
|
*/
|
|
83
81
|
import { isAddress, isBytes32 } from "../_shared/index.js";
|
|
84
82
|
import { defaultLogger } from "../logger.js";
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"post-paid.js","sourceRoot":"","sources":["../../src/server/post-paid.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The server-side post-paid plugin: it accrues, and it never gates.\n *\n * ## The inversion, stated once\n *\n * The handler runs. The handler produces its response. The response goes out.\n * Only then is the delivered work metered into the Open Tab. Nothing in this file\n * awaits a payment, a Settlement, or a signature before a response is\n * released, because a plugin that did would be the prepay model this rail exists\n * to replace. Metering is a record of work already delivered (R23.3, R12.1).\n *\n * Two orderings ship, and the difference is exactly one `await`:\n *\n * | `release` | what happens | can this request see a 402? |\n * | --- | --- | --- |\n * | `after-metering` (default) | handler resolves, the delivery is recorded, then the response is released carrying its charge headers | yes |\n * | `before-metering` | handler resolves, the response is released, the delivery is recorded behind it | no, the response has already gone |\n *\n * `after-metering` is the default because a `LimitExceeded` refusal can only reach\n * the caller on the request that caused it, and design section 9.5 asks for that\n * refusal. What it holds the response for is a single credit record, not a\n * payment, and it holds it for nothing else: if metering fails for any reason\n * that is not the Agent's to fix, the handler's own response is delivered\n * unchanged. A Service whose billing is broken pays for the delivery. It does not\n * hand the cost to the caller.\n *\n * `before-metering` is there for a Service that will not accept even that much\n * latency, and it is the shape that makes the guarantee observable: the response\n * is returned while the metering promise is still pending.\n *\n * ## 402 is the exception, not the path\n *\n * The normal path is 200, delivery recorded, charge headers attached. `402`\n * happens on `LimitExceeded` alone, the Agent's Open Tab for the Asset has no\n * headroom for this charge, which is a credit decision and not a prepayment\n * demand. When it happens the body carries the required amount and the current\n * headroom, and the same figures go out as headers, so the 402 client of design\n * section 9.4 reads a refusal with the identical parser it uses on a success.\n *\n * Two other refusals also reach the Agent, because only the Agent can clear them:\n * a missing, lapsed, or spent spending authorisation (403), and a tab that went\n * past its Settlement Window (409). Neither is a 402. Every other refusal in\n * `metering.ts`'s table is the Service's to fix and never changes what the caller\n * receives.\n *\n * ## Header contract\n *\n * The wire format is defined once, in `src/http/headers.ts`, and this plugin is\n * its emitting half: every header it writes goes through `formatChargeHeaders`,\n * which is the same module the 402 client parses with. So there is one definition\n * of `Tab-Charge-Amount`, one of `Tab-Charge-Asset`, and no second string-building\n * routine here to drift away from it. Three consequences are this side's to state:\n *\n * **The six charge headers are one all-or-nothing block.** `Tab-Charge-Amount`,\n * `Tab-Charge-Asset`, `Tab-Charge-Service`, `Tab-Charge-Tool`, `Tab-Open-Tab`, and\n * `Tab-Headroom` go out together or not at all, because a client cannot complete a\n * partial block by guessing and a missing header is not a zero. A response with\n * none of them is simply not a metered response.\n *\n * **A `402` carries the whole block, which is why the metering seam has a second\n * method.** `LimitExceeded` reverts with the requested amount and the headroom and\n * says nothing about the Open Tab, so the plugin reads it through\n * `TabBookClient.openTabOf`, a view call, truthful precisely because the metering\n * transaction reverted and moved nothing. Where that read fails, the refusal goes\n * out with no charge headers rather than with an invented figure, and its body\n * still names the required amount and the headroom.\n *\n * **A 403 or 409 refusal carries no charge block.** `AuthorisationMissing`,\n * `AuthorisationExpired`, `AuthorisationExceeded`, and `TabIsDelinquent` revert\n * with no headroom figure, so there is no complete block to send. The body carries\n * the required amount, the reason, and the action.\n *\n * `Tab-Agent` and `Tab-Authorisation` are read from the request. Both are claims,\n * and neither is an authentication. `TabBook` derives the authKey itself from\n * `(agent, serviceId, asset)`, so `Tab-Authorisation` cannot redirect a charge: it\n * is the Agent's statement about which authorisation it expects to be metered\n * against, available on the charge for logging and carrying no authority. Metering\n * is bounded by the on-chain authorisation the Agent set, and a Service that needs\n * the `Tab-Agent` claim authenticated does that in `agentOf`.\n *\n * Requirements: 23.3, 12.1, 12.2, 12.3, 21.5\n */\n\nimport { isAddress, isBytes32, type Address, type Bytes32, type Hex, type TabError } from \"../_shared/index.js\";\nimport { defaultLogger, type Logger } from \"../logger.js\";\nimport { tabError } from \"../errors.js\";\nimport type { AssetRef } from \"../payments/strategy.js\";\nimport {\n TAB_HEADER,\n chargedAssetKey,\n formatChargeHeaders,\n type ChargeBlock,\n type ChargedAsset,\n type HeaderReader,\n} from \"../http/headers.js\";\nimport {\n classifyRecordDeliveryRevert,\n detailAmount,\n dispositionOf,\n refusalStatusOf,\n type DeliveryReceipt,\n type MeteredDelivery,\n type TabBookClient,\n} from \"./metering.js\";\n\n/**\n * The request, reduced to what pricing and Agent identification need.\n *\n * Structural, and the body is not part of it. A web-standard `Request` satisfies\n * this as it stands, and the Express adapter builds one from `req` without\n * touching the stream, because buffering a request body to price it would take\n * the body away from the framework that owns it.\n */\nexport interface MeteredRequest {\n readonly method: string;\n readonly url: string;\n readonly headers: HeaderReader;\n}\n\n/**\n * The response, reduced to what the billability decision needs.\n *\n * A web-standard `Response` satisfies it, and so does an Express `res`, which is\n * why the metering core never mentions `Response` at all.\n */\nexport interface DeliveredResponse {\n readonly status: number;\n}\n\n/**\n * What the Service charges for this call.\n *\n * A deliberate superset of design section 9.5's `{ tool, units }`.\n * `recordDelivery` takes an `expectedUnitPrice` and reverts\n * `PriceListChangedMidCall` when it disagrees with the applied price list, so a\n * plugin that cannot state the price it quoted cannot call the contract at all.\n * The field is required rather than optional for the same reason: there is no\n * sensible default, and a zero would be read as a free tool.\n */\nexport interface PriceQuote {\n /** The named priced unit, as the 32-byte word the applied price list is keyed by. */\n readonly tool: Bytes32;\n /** Count of priced units. A positive integer inside `uint32`. */\n readonly units: number;\n /** Base units per unit of `tool`, exactly as quoted to the Agent. */\n readonly unitPrice: bigint;\n}\n\n/** One recorded charge, as the Service's own logging and hooks see it. */\nexport interface MeteredCharge {\n readonly agent: Address;\n readonly serviceId: Bytes32;\n readonly asset: AssetRef;\n /**\n * The 32-byte word the applied price list is keyed by, and what\n * `Tab-Charge-Tool` carries. The word rather than a decoded name, because it is\n * what round-trips back into a contract call unchanged.\n */\n readonly tool: Bytes32;\n readonly units: number;\n readonly unitPrice: bigint;\n /** `units * unitPrice`, as the contract computed it. */\n readonly amount: bigint;\n readonly openTabAfter: bigint;\n readonly headroomAfter: bigint;\n /** Whatever the Agent claimed in `Tab-Authorisation`, when it claimed anything. */\n readonly authorisationClaim?: string;\n readonly monadTxHash?: Hex;\n readonly recordedAt: number;\n}\n\n/** Why a request was not metered. Never an error: none of these is a failure. */\nexport type NotMeteredReason =\n /** No `Tab-Agent` header and no `agentOf`, so there is nobody to charge. */\n | \"no-agent\"\n /** `Tab-Agent` was present and was not a 20-byte address. */\n | \"agent-malformed\"\n /** `priceOf` returned nothing: the Service does not charge for this request. */\n | \"not-priced\"\n /** `priceOf` returned a quote that cannot be metered, such as zero units. */\n | \"price-invalid\"\n /** `billable` said this response is not a delivery, by default, any status at or above 400. */\n | \"not-billable\"\n /** The handler threw, so no delivery happened and nothing is charged for. */\n | \"handler-failed\";\n\nexport interface ChargedOutcome {\n readonly kind: \"charged\";\n readonly charge: MeteredCharge;\n readonly receipt: DeliveryReceipt;\n /** The `Tab-*` headers this charge puts on the response. */\n readonly headers: Readonly<Record<string, string>>;\n}\n\nexport interface NotMeteredOutcome {\n readonly kind: \"not-metered\";\n readonly reason: NotMeteredReason;\n readonly detail: string;\n}\n\n/** The body of a refusal. Amounts are decimal integer base-unit strings. */\nexport interface RefusalBody {\n readonly ok: false;\n readonly error: {\n readonly category: string;\n readonly code: string;\n readonly message: string;\n readonly retryable: boolean;\n };\n readonly agent: Address;\n readonly serviceId: Bytes32;\n readonly asset: string;\n readonly tool: string;\n /** Base units this call needed. Present on every refusal: it is the charge that was refused. */\n readonly requiredBaseUnits: string;\n /** Base units of headroom left. Present when the refusal reported one. */\n readonly headroomBaseUnits?: string;\n /** What the Agent has to do about it. */\n readonly action: string;\n}\n\n/** A refusal the Agent has to clear. The response is replaced by it. */\nexport interface RefusedOutcome {\n readonly kind: \"refused\";\n readonly error: TabError;\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body: RefusalBody;\n readonly context: RefusalContext;\n}\n\n/** Metering failed and the response is delivered anyway. */\nexport interface MeteringFailedOutcome {\n readonly kind: \"failed\";\n readonly error: TabError;\n}\n\nexport type MeteringOutcome =\n | ChargedOutcome\n | NotMeteredOutcome\n | RefusedOutcome\n | MeteringFailedOutcome;\n\n/** Everything a refusal handler needs to write its own response. */\nexport interface RefusalContext {\n readonly request: MeteredRequest;\n readonly agent: Address;\n readonly serviceId: Bytes32;\n readonly asset: AssetRef;\n readonly quote: PriceQuote;\n /** Base units this call needed: `units * unitPrice`. */\n readonly requiredBaseUnits: bigint;\n /** Base units of headroom the refusal reported, when it reported one. */\n readonly headroomBaseUnits?: bigint;\n readonly error: TabError;\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body: RefusalBody;\n}\n\n/** A `LimitExceeded` refusal, where the headroom is always known. */\nexport interface LimitExceededContext extends RefusalContext {\n readonly headroomBaseUnits: bigint;\n}\n\n/** A metering failure the caller never sees. The Service does. */\nexport interface MeteringFailureContext {\n readonly request: MeteredRequest;\n readonly error: TabError;\n readonly agent?: Address;\n readonly quote?: PriceQuote;\n}\n\n/** When the delivered response is released relative to the metering call. */\nexport type ReleaseOrder = \"after-metering\" | \"before-metering\";\n\nexport interface TabPostPaidOptions {\n readonly serviceId: Bytes32;\n readonly asset: AssetRef;\n /**\n * What this request costs, or nothing when the Service does not charge for it.\n *\n * Called once, after the handler has produced its response, with the method,\n * URL, and headers of the request. Consumer code, so a throw is caught and read\n * as \"not priced\" rather than allowed to take down a delivered response.\n */\n readonly priceOf: (request: MeteredRequest) => PriceQuote | undefined | null;\n readonly tabBook: TabBookClient;\n /** Defaults to `after-metering`, the only ordering in which a 402 can reach this request. */\n readonly release?: ReleaseOrder;\n /**\n * Identifies the Agent. Defaults to the `Tab-Agent` request header.\n *\n * Supply this to authenticate the claim, a signature, a session, an API key\n * mapped to an address. The plugin does not authenticate it, and a Service\n * that needs it authenticated does it here.\n */\n readonly agentOf?: (request: MeteredRequest) => string | undefined | null;\n /**\n * Whether a response counts as a delivery worth charging for. Defaults to\n * `status < 400`.\n *\n * The policy this default states: an Agent is not charged for a response that\n * failed. A 5xx is the Service's fault and a 4xx delivered nothing the Agent\n * asked for. A Service that meters attempts rather than deliveries, a rate\n * limiter, say, overrides it.\n */\n readonly billable?: (response: DeliveredResponse, request: MeteredRequest) => boolean;\n readonly onCharge?: (charge: MeteredCharge) => void;\n /** Replaces the default 402. Called only for `LimitExceeded`. */\n readonly onLimitExceeded?: (context: LimitExceededContext) => Response;\n /** Replaces the default response for any other refusal the Agent must clear. */\n readonly onRefused?: (context: RefusalContext) => Response | undefined;\n /** Where a metering failure the caller never sees is reported. */\n readonly onMeteringFailed?: (context: MeteringFailureContext) => void;\n readonly logger?: Logger;\n /** Injectable clock, so `recordedAt` is testable. Defaults to `Date.now`. */\n readonly now?: () => number;\n}\n\n/**\n * What `execute` did.\n *\n * `delivered` is the ordinary outcome and covers every case in which the\n * handler's own response is what goes out: charged, not metered, and metering\n * failed. `metering` resolves when the recording finished, which under\n * `before-metering` is after the response was already returned.\n */\nexport type PostPaidExecution =\n | {\n readonly kind: \"delivered\";\n readonly response: Response;\n readonly metering: Promise<MeteringOutcome>;\n }\n | {\n readonly kind: \"refused\";\n readonly response: Response;\n readonly outcome: RefusedOutcome;\n }\n | {\n readonly kind: \"handler-failed\";\n readonly thrown: unknown;\n readonly error: TabError;\n };\n\n/** The framework-free plugin the three adapters are thin wrappers over. */\nexport interface PostPaidPlugin {\n readonly serviceId: Bytes32;\n readonly asset: AssetRef;\n readonly release: ReleaseOrder;\n /**\n * Records the delivery for a response the caller already holds.\n *\n * Never throws and never rejects, whatever a consumer callback or a\n * {@link TabBookClient} does.\n */\n meter(request: MeteredRequest, response: DeliveredResponse): Promise<MeteringOutcome>;\n /**\n * Runs the handler, then meters. The handler is awaited to completion before\n * any metering call is made, always.\n */\n execute(request: MeteredRequest, handler: () => Response | Promise<Response>): Promise<PostPaidExecution>;\n /** The `Tab-*` headers an outcome puts on a response. Empty when there are none. */\n headersFor(outcome: MeteringOutcome): Readonly<Record<string, string>>;\n /** The response a refusal is served with, through the consumer's override when there is one. */\n refusalResponseFor(outcome: RefusedOutcome): Response;\n}\n\n/**\n * Builds the plugin.\n *\n * Construction is total: it cannot fail and returns a {@link PostPaidPlugin}\n * rather than a `Result`. Everything fallible belongs to the call that meters,\n * because that is the only place a caller can do anything about it.\n */\nexport function tabPostPaid(options: TabPostPaidOptions): PostPaidPlugin {\n const logger = options.logger ?? defaultLogger;\n const now = options.now ?? (() => Date.now());\n const release = options.release ?? \"after-metering\";\n const { serviceId, asset, tabBook } = options;\n const billable = options.billable ?? ((response: DeliveredResponse) => response.status < 400);\n\n const notMetered = (reason: NotMeteredReason, detail: string): NotMeteredOutcome => ({\n kind: \"not-metered\",\n reason,\n detail,\n });\n\n const failed = (error: TabError, context: Omit<MeteringFailureContext, \"error\">): MeteringFailedOutcome => {\n logger.warn(\"post-paid metering failed and the response was delivered anyway\", {\n serviceId,\n code: error.code,\n category: error.category,\n message: error.message,\n });\n call(() => options.onMeteringFailed?.({ ...context, error }), \"onMeteringFailed\", logger);\n return { kind: \"failed\", error };\n };\n\n const meter = async (\n request: MeteredRequest,\n response: DeliveredResponse,\n ): Promise<MeteringOutcome> => {\n const deliverable = call(() => billable(response, request), \"billable\", logger);\n if (!deliverable.ok) {\n return failed(\n tabError(\"INTERNAL\", \"BILLABLE_THREW\", \"the billable predicate threw, so nothing was metered\", {\n cause: deliverable.cause,\n }),\n { request },\n );\n }\n if (deliverable.value !== true) {\n return notMetered(\n \"not-billable\",\n `status ${response.status} is not a delivery this Service charges for`,\n );\n }\n\n const agent = resolveAgent(request, options.agentOf, logger);\n if (agent.kind !== \"ok\") return notMetered(agent.kind, agent.detail);\n\n const quote = resolveQuote(request, options.priceOf, logger);\n if (quote.kind !== \"ok\") return notMetered(quote.kind, quote.detail);\n\n const required = BigInt(quote.value.units) * quote.value.unitPrice;\n const delivery: MeteredDelivery = {\n agent: agent.value,\n serviceId,\n asset,\n tool: quote.value.tool,\n units: quote.value.units,\n expectedUnitPrice: quote.value.unitPrice,\n };\n const recorded = await callAsync(\n () => tabBook.recordDelivery(delivery),\n \"tabBook.recordDelivery\",\n logger,\n );\n\n if (!recorded.ok) {\n // A `TabBookClient` is not supposed to throw. One that does is treated as a\n // revert of unknown shape, which lands on `deliver-anyway`.\n return failed(classifyRecordDeliveryRevert(recorded.thrown), {\n request,\n agent: agent.value,\n quote: quote.value,\n });\n }\n\n if (!recorded.value.ok) {\n const error = recorded.value.error;\n if (dispositionOf(error) !== \"refuse-request\") {\n return failed(error, { request, agent: agent.value, quote: quote.value });\n }\n return refuse({\n request,\n agent: agent.value,\n asset,\n serviceId,\n quote: quote.value,\n required,\n error,\n // Read only where the refusal reports a headroom, because the charge block\n // needs both figures or neither. `LimitExceeded` is that case.\n openTab:\n detailAmount(error, \"headroom\") === undefined\n ? undefined\n : await openTabFor(delivery),\n logger,\n });\n }\n\n const receipt = recorded.value.value;\n const claim = claimOf(request);\n const charge: MeteredCharge = {\n agent: agent.value,\n serviceId,\n asset,\n tool: quote.value.tool,\n units: quote.value.units,\n unitPrice: quote.value.unitPrice,\n amount: receipt.charged,\n openTabAfter: receipt.openAfter,\n headroomAfter: receipt.headroomAfter,\n ...(claim === undefined ? {} : { authorisationClaim: claim }),\n ...(receipt.monadTxHash === undefined ? {} : { monadTxHash: receipt.monadTxHash }),\n recordedAt: receipt.recordedAt,\n };\n\n call(() => options.onCharge?.(charge), \"onCharge\", logger);\n\n return { kind: \"charged\", charge, receipt, headers: chargeHeaders(charge, logger) };\n };\n\n /**\n * The Open Tab the refusal reports, or undefined when it could not be read.\n *\n * A failed read costs the refusal its charge headers and nothing else: the body\n * still names the required amount and the headroom, and no figure is invented to\n * fill the block.\n */\n const openTabFor = async (delivery: MeteredDelivery): Promise<bigint | undefined> => {\n const figure = await callAsync(() => tabBook.openTabOf(delivery), \"tabBook.openTabOf\", logger);\n if (!figure.ok) return undefined;\n if (!figure.value.ok) {\n logger.warn(\"post-paid could not read the Open Tab, so the refusal carries no charge headers\", {\n serviceId,\n code: figure.value.error.code,\n });\n return undefined;\n }\n return figure.value.value;\n };\n\n /**\n * The response a refusal is served with.\n *\n * The `?? 0n` below is unreachable in practice: `LimitExceeded` reverts with a\n * headroom, which is what put the figure on the context. It exists so\n * {@link LimitExceededContext} can promise a `bigint` rather than making every\n * consumer of the hook handle an absence that the revert set does not produce.\n */\n const refusalResponseFor = (outcome: RefusedOutcome): Response => {\n const override =\n outcome.error.code === \"LIMIT_EXCEEDED\" && options.onLimitExceeded !== undefined\n ? call(\n () =>\n options.onLimitExceeded?.({\n ...outcome.context,\n headroomBaseUnits: outcome.context.headroomBaseUnits ?? 0n,\n }),\n \"onLimitExceeded\",\n logger,\n )\n : options.onRefused !== undefined\n ? call(() => options.onRefused?.(outcome.context), \"onRefused\", logger)\n : { ok: true as const, value: undefined };\n\n if (override.ok && override.value !== undefined) return override.value;\n return jsonResponse(outcome.status, outcome.headers, outcome.body);\n };\n\n const plugin: PostPaidPlugin = {\n serviceId,\n asset,\n release,\n\n meter,\n\n async execute(request, handler) {\n // The handler runs first and runs to completion. Nothing above this line\n // touches the chain, and nothing below it runs until the response exists.\n let response: Response;\n try {\n response = await handler();\n } catch (thrown) {\n // No response means no delivery, and a delivery that did not happen is\n // not charged for. Deliberate, and not configurable.\n logger.debug(\"post-paid metered nothing because the handler failed\", { serviceId });\n return {\n kind: \"handler-failed\",\n thrown,\n error: tabError(\"INTERNAL\", \"HANDLER_FAILED\", \"the handler failed, so no delivery was metered\", {\n details: { metered: false },\n cause: causeOf(thrown),\n }),\n };\n }\n\n if (release === \"before-metering\") {\n // The response is handed back now. The metering promise is still pending,\n // and a caller with a host lifetime hook, `waitUntil`, `after`, passes\n // it there so the runtime does not tear down underneath it.\n return { kind: \"delivered\", response, metering: meter(request, response) };\n }\n\n const outcome = await meter(request, response);\n if (outcome.kind === \"refused\") {\n return { kind: \"refused\", response: refusalResponseFor(outcome), outcome };\n }\n return {\n kind: \"delivered\",\n response: attachHeaders(response, plugin.headersFor(outcome), logger),\n metering: Promise.resolve(outcome),\n };\n },\n\n headersFor(outcome) {\n if (outcome.kind === \"charged\") return outcome.headers;\n if (outcome.kind === \"refused\") return outcome.headers;\n return {};\n },\n\n refusalResponseFor,\n };\n\n return plugin;\n}\n\n/**\n * The six charge headers a recorded charge puts on the response.\n *\n * Formatted by `src/http/headers.ts`, which is also what the 402 client parses\n * with, so the two halves of the wire format cannot disagree. A block that will\n * not format is dropped rather than half-written, the client treats a partial\n * block as an error, and correctly.\n */\nexport function chargeHeaders(\n charge: MeteredCharge,\n logger: Logger = defaultLogger,\n): Readonly<Record<string, string>> {\n return blockHeaders(\n {\n amount: charge.amount,\n asset: chargedAssetOf(charge.asset),\n serviceId: charge.serviceId,\n tool: charge.tool,\n openTab: charge.openTabAfter,\n headroom: charge.headroomAfter,\n },\n logger,\n );\n}\n\n/** The Asset as the header contract carries it: a chain id and an address. */\nconst chargedAssetOf = (asset: AssetRef): ChargedAsset => ({\n chainId: asset.chainId,\n address: asset.address,\n});\n\n/** Formats one charge block, or nothing at all if it would be malformed. */\nfunction blockHeaders(block: ChargeBlock, logger: Logger): Readonly<Record<string, string>> {\n const formatted = formatChargeHeaders(block);\n if (formatted.ok) return formatted.value;\n logger.error(\"post-paid built a charge block that will not format, so no charge headers were sent\", {\n code: formatted.error.code,\n message: formatted.error.message,\n });\n return {};\n}\n\n/**\n * Builds the refusal: its status, its headers, and its body.\n *\n * The status comes from the error's category and from nothing else, which is why\n * `LIMIT` is the only category that can produce a 402 here.\n *\n * The charge headers go out as the complete six-header block or not at all. That\n * needs both a headroom, which only `LimitExceeded` reverts with, and an Open Tab,\n * which no revert carries and which the caller read through `openTabOf`. Where\n * either is missing the block is dropped, and the body, which always names the\n * required amount, and names the headroom whenever the refusal reported one -\n * carries the refusal on its own.\n */\nfunction refuse(input: {\n readonly request: MeteredRequest;\n readonly agent: Address;\n readonly serviceId: Bytes32;\n readonly asset: AssetRef;\n readonly quote: PriceQuote;\n readonly required: bigint;\n readonly error: TabError;\n /** The Open Tab as read on the refusal path, absent when it could not be read. */\n readonly openTab: bigint | undefined;\n readonly logger: Logger;\n}): RefusedOutcome {\n const { error, quote, required, openTab } = input;\n const status = refusalStatusOf(error);\n const headroom = detailAmount(error, \"headroom\");\n\n const headers =\n headroom === undefined || openTab === undefined\n ? {}\n : blockHeaders(\n {\n amount: required,\n asset: chargedAssetOf(input.asset),\n serviceId: input.serviceId,\n tool: quote.tool,\n openTab,\n headroom,\n },\n input.logger,\n );\n\n const body: RefusalBody = {\n ok: false,\n error: {\n category: error.category,\n code: error.code,\n message: error.message,\n retryable: error.retryable,\n },\n agent: input.agent,\n serviceId: input.serviceId,\n asset: chargedAssetKey(chargedAssetOf(input.asset)),\n tool: quote.tool,\n requiredBaseUnits: required.toString(10),\n ...(headroom === undefined ? {} : { headroomBaseUnits: headroom.toString(10) }),\n action: actionOf(error),\n };\n\n const context: RefusalContext = {\n request: input.request,\n agent: input.agent,\n serviceId: input.serviceId,\n asset: input.asset,\n quote,\n requiredBaseUnits: required,\n ...(headroom === undefined ? {} : { headroomBaseUnits: headroom }),\n error,\n status,\n headers,\n body,\n };\n\n return { kind: \"refused\", error, status, headers, body, context };\n}\n\n/** A JSON response carrying the refusal, with its `Tab-*` headers. */\nexport function jsonResponse(\n status: number,\n headers: Readonly<Record<string, string>>,\n body: unknown,\n): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { ...headers, \"Content-Type\": \"application/json; charset=utf-8\" },\n });\n}\n\n/**\n * Puts the charge headers on the delivered response.\n *\n * Mutation first, because that keeps the handler's own response object, and a\n * test asserting the delivered response is the handler's response is asserting\n * something worth keeping true. A `Response` that came back from `fetch` guards\n * its headers as immutable, so the fallback rebuilds it around the same body\n * stream rather than reading the body into memory.\n */\nexport function attachHeaders(\n response: Response,\n headers: Readonly<Record<string, string>>,\n logger: Logger = defaultLogger,\n): Response {\n const entries = Object.entries(headers);\n if (entries.length === 0) return response;\n try {\n for (const [name, value] of entries) response.headers.set(name, value);\n return response;\n } catch {\n logger.debug(\"response headers were immutable, so the charge headers went onto a copy\");\n const merged = new Headers(response.headers);\n for (const [name, value] of entries) merged.set(name, value);\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: merged,\n });\n }\n}\n\n/** The action text a refusal body carries, taken from the classified message. */\nfunction actionOf(error: TabError): string {\n const separator = error.message.indexOf(\": \");\n return separator === -1 ? error.message : error.message.slice(separator + 2);\n}\n\n/** The `Tab-Authorisation` claim, when the Agent made one. */\nconst claimOf = (request: MeteredRequest): string | undefined => {\n const raw = read(request.headers, TAB_HEADER.authorisation);\n return raw === undefined || raw.length === 0 ? undefined : raw;\n};\n\n/** Reads one header, tolerating a reader that throws or returns null. */\nfunction read(headers: HeaderReader, name: string): string | undefined {\n try {\n const value = headers.get(name);\n return typeof value === \"string\" ? value.trim() : undefined;\n } catch {\n return undefined;\n }\n}\n\ntype Resolved<T> = { kind: \"ok\"; value: T } | { kind: NotMeteredReason; detail: string };\n\n/** The Agent to charge, from `agentOf` or from `Tab-Agent`. */\nfunction resolveAgent(\n request: MeteredRequest,\n agentOf: TabPostPaidOptions[\"agentOf\"],\n logger: Logger,\n): Resolved<Address> {\n let raw: string | undefined;\n if (agentOf !== undefined) {\n const supplied = call(() => agentOf(request), \"agentOf\", logger);\n if (!supplied.ok) return { kind: \"no-agent\", detail: \"agentOf threw\" };\n raw = supplied.value === null || supplied.value === undefined ? undefined : supplied.value.trim();\n } else {\n raw = read(request.headers, TAB_HEADER.agent);\n }\n\n if (raw === undefined || raw.length === 0) {\n return {\n kind: \"no-agent\",\n detail: `no ${TAB_HEADER.agent} header, so there is no Agent to charge`,\n };\n }\n if (!isAddress(raw)) {\n return {\n kind: \"agent-malformed\",\n detail: `${TAB_HEADER.agent} carried \\`${raw}\\`, which is not a 20-byte 0x address`,\n };\n }\n return { kind: \"ok\", value: raw };\n}\n\n/** The price of this call, validated into something `recordDelivery` accepts. */\nfunction resolveQuote(\n request: MeteredRequest,\n priceOf: TabPostPaidOptions[\"priceOf\"],\n logger: Logger,\n): Resolved<PriceQuote> {\n const quoted = call(() => priceOf(request), \"priceOf\", logger);\n if (!quoted.ok) return { kind: \"not-priced\", detail: \"priceOf threw\" };\n const quote = quoted.value;\n if (quote === undefined || quote === null) {\n return { kind: \"not-priced\", detail: \"priceOf returned nothing, so this request is not charged for\" };\n }\n if (!isBytes32(quote.tool)) {\n return { kind: \"price-invalid\", detail: \"quote.tool must be a 32-byte 0x word\" };\n }\n if (!Number.isInteger(quote.units) || quote.units <= 0 || quote.units > 4_294_967_295) {\n return {\n kind: \"price-invalid\",\n detail: `quote.units must be a positive integer inside uint32, received ${String(quote.units)}`,\n };\n }\n if (typeof quote.unitPrice !== \"bigint\" || quote.unitPrice <= 0n) {\n return {\n kind: \"price-invalid\",\n detail: \"quote.unitPrice must be a positive bigint count of Asset base units, never a number\",\n };\n }\n return { kind: \"ok\", value: quote };\n}\n\ntype Called<T> = { ok: true; value: T } | { ok: false; cause: { code: string; message: string } };\n\n/** Runs consumer code without letting it throw into the metering path. */\nfunction call<T>(fn: () => T, what: string, logger: Logger): Called<T> {\n try {\n return { ok: true, value: fn() };\n } catch (thrown) {\n const cause = causeOf(thrown);\n logger.error(`post-paid ${what} threw and was ignored`, cause);\n return { ok: false, cause };\n }\n}\n\ntype CalledAsync<T> = { ok: true; value: T } | { ok: false; thrown: unknown };\n\n/** The asynchronous sibling, keeping a rejected promise out of the metering path. */\nasync function callAsync<T>(\n fn: () => Promise<T>,\n what: string,\n logger: Logger,\n): Promise<CalledAsync<T>> {\n try {\n return { ok: true, value: await fn() };\n } catch (thrown) {\n logger.error(`post-paid ${what} threw and was treated as a refusal of unknown shape`, causeOf(thrown));\n return { ok: false, thrown };\n }\n}\n\n/** Narrows a caught value into the `cause` shape a `TabError` accepts. */\nfunction causeOf(thrown: unknown): { code: string; message: string } {\n if (thrown instanceof Error) return { code: thrown.name, message: thrown.message };\n return { code: \"UNKNOWN\", message: String(thrown) };\n}\n"]}
|
|
1
|
+
{"version":3,"file":"post-paid.js","sourceRoot":"","sources":["../../src/server/post-paid.ts"],"names":[],"mappings":"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","sourcesContent":["/**\n * The server-side post-paid plugin: it accrues, and it never gates.\n *\n * ## The inversion, stated once\n *\n * The handler runs. The handler produces its response. The response goes out.\n * Only then is the delivered work metered into the Open Tab. Nothing in this file\n * awaits a payment, a Settlement, or a signature before a response is\n * released, because a plugin that did would be the prepay model this rail exists\n * to replace. Metering is a record of work already delivered.\n *\n * Two orderings ship, and the difference is exactly one `await`:\n *\n * | `release` | what happens | can this request see a 402? |\n * | --- | --- | --- |\n * | `after-metering` (default) | handler resolves, the delivery is recorded, then the response is released carrying its charge headers | yes |\n * | `before-metering` | handler resolves, the response is released, the delivery is recorded behind it | no, the response has already gone |\n *\n * `after-metering` is the default because a `LimitExceeded` refusal can only reach\n * the caller on the request that caused it, and the Agent needs that refusal to\n * know it should settle. What it holds the response for is a single credit record, not a\n * payment, and it holds it for nothing else: if metering fails for any reason\n * that is not the Agent's to fix, the handler's own response is delivered\n * unchanged. A Service whose billing is broken pays for the delivery. It does not\n * hand the cost to the caller.\n *\n * `before-metering` is there for a Service that will not accept even that much\n * latency, and it is the shape that makes the guarantee observable: the response\n * is returned while the metering promise is still pending.\n *\n * ## 402 is the exception, not the path\n *\n * The normal path is 200, delivery recorded, charge headers attached. `402`\n * happens on `LimitExceeded` alone (the Agent's Open Tab for the Asset has no\n * headroom for this charge), which is a credit decision and not a prepayment\n * demand. When it happens the body carries the required amount and the current\n * headroom, and the same figures go out as headers, so the 402 client in\n * `http/client-402.ts` reads a refusal with the identical parser it uses on a success.\n *\n * Two other refusals also reach the Agent, because only the Agent can clear them:\n * a missing, lapsed, or spent spending authorisation (403), and a tab that went\n * past its Settlement Window (409). Neither is a 402. Every other refusal in\n * `metering.ts`'s table is the Service's to fix and never changes what the caller\n * receives.\n *\n * ## Header contract\n *\n * The wire format is defined once, in `src/http/headers.ts`, and this plugin is\n * its emitting half: every header it writes goes through `formatChargeHeaders`,\n * which is the same module the 402 client parses with. So there is one definition\n * of `Tab-Charge-Amount`, one of `Tab-Charge-Asset`, and no second string-building\n * routine here to drift away from it. Three consequences are this side's to state:\n *\n * **The six charge headers are one all-or-nothing block.** `Tab-Charge-Amount`,\n * `Tab-Charge-Asset`, `Tab-Charge-Service`, `Tab-Charge-Tool`, `Tab-Open-Tab`, and\n * `Tab-Headroom` go out together or not at all, because a client cannot complete a\n * partial block by guessing and a missing header is not a zero. A response with\n * none of them is simply not a metered response.\n *\n * **A `402` carries the whole block, which is why the metering seam has a second\n * method.** `LimitExceeded` reverts with the requested amount and the headroom and\n * says nothing about the Open Tab, so the plugin reads it through\n * `TabBookClient.openTabOf`, a view call, truthful precisely because the metering\n * transaction reverted and moved nothing. Where that read fails, the refusal goes\n * out with no charge headers rather than with an invented figure, and its body\n * still names the required amount and the headroom.\n *\n * **A 403 or 409 refusal carries no charge block.** `AuthorisationMissing`,\n * `AuthorisationExpired`, `AuthorisationExceeded`, and `TabIsDelinquent` revert\n * with no headroom figure, so there is no complete block to send. The body carries\n * the required amount, the reason, and the action.\n *\n * `Tab-Agent` and `Tab-Authorisation` are read from the request. Both are claims,\n * and neither is an authentication. `TabBook` derives the authKey itself from\n * `(agent, serviceId, asset)`, so `Tab-Authorisation` cannot redirect a charge: it\n * is the Agent's statement about which authorisation it expects to be metered\n * against, available on the charge for logging and carrying no authority. Metering\n * is bounded by the on-chain authorisation the Agent set, and a Service that needs\n * the `Tab-Agent` claim authenticated does that in `agentOf`.\n */\n\nimport { isAddress, isBytes32, type Address, type Bytes32, type Hex, type TabError } from \"../_shared/index.js\";\nimport { defaultLogger, type Logger } from \"../logger.js\";\nimport { tabError } from \"../errors.js\";\nimport type { AssetRef } from \"../payments/strategy.js\";\nimport {\n TAB_HEADER,\n chargedAssetKey,\n formatChargeHeaders,\n type ChargeBlock,\n type ChargedAsset,\n type HeaderReader,\n} from \"../http/headers.js\";\nimport {\n classifyRecordDeliveryRevert,\n detailAmount,\n dispositionOf,\n refusalStatusOf,\n type DeliveryReceipt,\n type MeteredDelivery,\n type TabBookClient,\n} from \"./metering.js\";\n\n/**\n * The request, reduced to what pricing and Agent identification need.\n *\n * Structural, and the body is not part of it. A web-standard `Request` satisfies\n * this as it stands, and the Express adapter builds one from `req` without\n * touching the stream, because buffering a request body to price it would take\n * the body away from the framework that owns it.\n */\nexport interface MeteredRequest {\n readonly method: string;\n readonly url: string;\n readonly headers: HeaderReader;\n}\n\n/**\n * The response, reduced to what the billability decision needs.\n *\n * A web-standard `Response` satisfies it, and so does an Express `res`, which is\n * why the metering core never mentions `Response` at all.\n */\nexport interface DeliveredResponse {\n readonly status: number;\n}\n\n/**\n * What the Service charges for this call.\n *\n * A deliberate superset of a bare `{ tool, units }`.\n * `recordDelivery` takes an `expectedUnitPrice` and reverts\n * `PriceListChangedMidCall` when it disagrees with the applied price list, so a\n * plugin that cannot state the price it quoted cannot call the contract at all.\n * The field is required rather than optional for the same reason: there is no\n * sensible default, and a zero would be read as a free tool.\n */\nexport interface PriceQuote {\n /** The named priced unit, as the 32-byte word the applied price list is keyed by. */\n readonly tool: Bytes32;\n /** Count of priced units. A positive integer inside `uint32`. */\n readonly units: number;\n /** Base units per unit of `tool`, exactly as quoted to the Agent. */\n readonly unitPrice: bigint;\n}\n\n/** One recorded charge, as the Service's own logging and hooks see it. */\nexport interface MeteredCharge {\n readonly agent: Address;\n readonly serviceId: Bytes32;\n readonly asset: AssetRef;\n /**\n * The 32-byte word the applied price list is keyed by, and what\n * `Tab-Charge-Tool` carries. The word rather than a decoded name, because it is\n * what round-trips back into a contract call unchanged.\n */\n readonly tool: Bytes32;\n readonly units: number;\n readonly unitPrice: bigint;\n /** `units * unitPrice`, as the contract computed it. */\n readonly amount: bigint;\n readonly openTabAfter: bigint;\n readonly headroomAfter: bigint;\n /** Whatever the Agent claimed in `Tab-Authorisation`, when it claimed anything. */\n readonly authorisationClaim?: string;\n readonly monadTxHash?: Hex;\n readonly recordedAt: number;\n}\n\n/** Why a request was not metered. Never an error: none of these is a failure. */\nexport type NotMeteredReason =\n /** No `Tab-Agent` header and no `agentOf`, so there is nobody to charge. */\n | \"no-agent\"\n /** `Tab-Agent` was present and was not a 20-byte address. */\n | \"agent-malformed\"\n /** `priceOf` returned nothing: the Service does not charge for this request. */\n | \"not-priced\"\n /** `priceOf` returned a quote that cannot be metered, such as zero units. */\n | \"price-invalid\"\n /** `billable` said this response is not a delivery, by default, any status at or above 400. */\n | \"not-billable\"\n /** The handler threw, so no delivery happened and nothing is charged for. */\n | \"handler-failed\";\n\nexport interface ChargedOutcome {\n readonly kind: \"charged\";\n readonly charge: MeteredCharge;\n readonly receipt: DeliveryReceipt;\n /** The `Tab-*` headers this charge puts on the response. */\n readonly headers: Readonly<Record<string, string>>;\n}\n\nexport interface NotMeteredOutcome {\n readonly kind: \"not-metered\";\n readonly reason: NotMeteredReason;\n readonly detail: string;\n}\n\n/** The body of a refusal. Amounts are decimal integer base-unit strings. */\nexport interface RefusalBody {\n readonly ok: false;\n readonly error: {\n readonly category: string;\n readonly code: string;\n readonly message: string;\n readonly retryable: boolean;\n };\n readonly agent: Address;\n readonly serviceId: Bytes32;\n readonly asset: string;\n readonly tool: string;\n /** Base units this call needed. Present on every refusal: it is the charge that was refused. */\n readonly requiredBaseUnits: string;\n /** Base units of headroom left. Present when the refusal reported one. */\n readonly headroomBaseUnits?: string;\n /** What the Agent has to do about it. */\n readonly action: string;\n}\n\n/** A refusal the Agent has to clear. The response is replaced by it. */\nexport interface RefusedOutcome {\n readonly kind: \"refused\";\n readonly error: TabError;\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body: RefusalBody;\n readonly context: RefusalContext;\n}\n\n/** Metering failed and the response is delivered anyway. */\nexport interface MeteringFailedOutcome {\n readonly kind: \"failed\";\n readonly error: TabError;\n}\n\nexport type MeteringOutcome =\n | ChargedOutcome\n | NotMeteredOutcome\n | RefusedOutcome\n | MeteringFailedOutcome;\n\n/** Everything a refusal handler needs to write its own response. */\nexport interface RefusalContext {\n readonly request: MeteredRequest;\n readonly agent: Address;\n readonly serviceId: Bytes32;\n readonly asset: AssetRef;\n readonly quote: PriceQuote;\n /** Base units this call needed: `units * unitPrice`. */\n readonly requiredBaseUnits: bigint;\n /** Base units of headroom the refusal reported, when it reported one. */\n readonly headroomBaseUnits?: bigint;\n readonly error: TabError;\n readonly status: number;\n readonly headers: Readonly<Record<string, string>>;\n readonly body: RefusalBody;\n}\n\n/** A `LimitExceeded` refusal, where the headroom is always known. */\nexport interface LimitExceededContext extends RefusalContext {\n readonly headroomBaseUnits: bigint;\n}\n\n/** A metering failure the caller never sees. The Service does. */\nexport interface MeteringFailureContext {\n readonly request: MeteredRequest;\n readonly error: TabError;\n readonly agent?: Address;\n readonly quote?: PriceQuote;\n}\n\n/** When the delivered response is released relative to the metering call. */\nexport type ReleaseOrder = \"after-metering\" | \"before-metering\";\n\nexport interface TabPostPaidOptions {\n readonly serviceId: Bytes32;\n readonly asset: AssetRef;\n /**\n * What this request costs, or nothing when the Service does not charge for it.\n *\n * Called once, after the handler has produced its response, with the method,\n * URL, and headers of the request. Consumer code, so a throw is caught and read\n * as \"not priced\" rather than allowed to take down a delivered response.\n */\n readonly priceOf: (request: MeteredRequest) => PriceQuote | undefined | null;\n readonly tabBook: TabBookClient;\n /** Defaults to `after-metering`, the only ordering in which a 402 can reach this request. */\n readonly release?: ReleaseOrder;\n /**\n * Identifies the Agent. Defaults to the `Tab-Agent` request header.\n *\n * Supply this to authenticate the claim, a signature, a session, an API key\n * mapped to an address. The plugin does not authenticate it, and a Service\n * that needs it authenticated does it here.\n */\n readonly agentOf?: (request: MeteredRequest) => string | undefined | null;\n /**\n * Whether a response counts as a delivery worth charging for. Defaults to\n * `status < 400`.\n *\n * The policy this default states: an Agent is not charged for a response that\n * failed. A 5xx is the Service's fault and a 4xx delivered nothing the Agent\n * asked for. A Service that meters attempts rather than deliveries, a rate\n * limiter, say, overrides it.\n */\n readonly billable?: (response: DeliveredResponse, request: MeteredRequest) => boolean;\n readonly onCharge?: (charge: MeteredCharge) => void;\n /** Replaces the default 402. Called only for `LimitExceeded`. */\n readonly onLimitExceeded?: (context: LimitExceededContext) => Response;\n /** Replaces the default response for any other refusal the Agent must clear. */\n readonly onRefused?: (context: RefusalContext) => Response | undefined;\n /** Where a metering failure the caller never sees is reported. */\n readonly onMeteringFailed?: (context: MeteringFailureContext) => void;\n readonly logger?: Logger;\n /** Injectable clock, so `recordedAt` is testable. Defaults to `Date.now`. */\n readonly now?: () => number;\n}\n\n/**\n * What `execute` did.\n *\n * `delivered` is the ordinary outcome and covers every case in which the\n * handler's own response is what goes out: charged, not metered, and metering\n * failed. `metering` resolves when the recording finished, which under\n * `before-metering` is after the response was already returned.\n */\nexport type PostPaidExecution =\n | {\n readonly kind: \"delivered\";\n readonly response: Response;\n readonly metering: Promise<MeteringOutcome>;\n }\n | {\n readonly kind: \"refused\";\n readonly response: Response;\n readonly outcome: RefusedOutcome;\n }\n | {\n readonly kind: \"handler-failed\";\n readonly thrown: unknown;\n readonly error: TabError;\n };\n\n/** The framework-free plugin the three adapters are thin wrappers over. */\nexport interface PostPaidPlugin {\n readonly serviceId: Bytes32;\n readonly asset: AssetRef;\n readonly release: ReleaseOrder;\n /**\n * Records the delivery for a response the caller already holds.\n *\n * Never throws and never rejects, whatever a consumer callback or a\n * {@link TabBookClient} does.\n */\n meter(request: MeteredRequest, response: DeliveredResponse): Promise<MeteringOutcome>;\n /**\n * Runs the handler, then meters. The handler is awaited to completion before\n * any metering call is made, always.\n */\n execute(request: MeteredRequest, handler: () => Response | Promise<Response>): Promise<PostPaidExecution>;\n /** The `Tab-*` headers an outcome puts on a response. Empty when there are none. */\n headersFor(outcome: MeteringOutcome): Readonly<Record<string, string>>;\n /** The response a refusal is served with, through the consumer's override when there is one. */\n refusalResponseFor(outcome: RefusedOutcome): Response;\n}\n\n/**\n * Builds the plugin.\n *\n * Construction is total: it cannot fail and returns a {@link PostPaidPlugin}\n * rather than a `Result`. Everything fallible belongs to the call that meters,\n * because that is the only place a caller can do anything about it.\n */\nexport function tabPostPaid(options: TabPostPaidOptions): PostPaidPlugin {\n const logger = options.logger ?? defaultLogger;\n const now = options.now ?? (() => Date.now());\n const release = options.release ?? \"after-metering\";\n const { serviceId, asset, tabBook } = options;\n const billable = options.billable ?? ((response: DeliveredResponse) => response.status < 400);\n\n const notMetered = (reason: NotMeteredReason, detail: string): NotMeteredOutcome => ({\n kind: \"not-metered\",\n reason,\n detail,\n });\n\n const failed = (error: TabError, context: Omit<MeteringFailureContext, \"error\">): MeteringFailedOutcome => {\n logger.warn(\"post-paid metering failed and the response was delivered anyway\", {\n serviceId,\n code: error.code,\n category: error.category,\n message: error.message,\n });\n call(() => options.onMeteringFailed?.({ ...context, error }), \"onMeteringFailed\", logger);\n return { kind: \"failed\", error };\n };\n\n const meter = async (\n request: MeteredRequest,\n response: DeliveredResponse,\n ): Promise<MeteringOutcome> => {\n const deliverable = call(() => billable(response, request), \"billable\", logger);\n if (!deliverable.ok) {\n return failed(\n tabError(\"INTERNAL\", \"BILLABLE_THREW\", \"the billable predicate threw, so nothing was metered\", {\n cause: deliverable.cause,\n }),\n { request },\n );\n }\n if (deliverable.value !== true) {\n return notMetered(\n \"not-billable\",\n `status ${response.status} is not a delivery this Service charges for`,\n );\n }\n\n const agent = resolveAgent(request, options.agentOf, logger);\n if (agent.kind !== \"ok\") return notMetered(agent.kind, agent.detail);\n\n const quote = resolveQuote(request, options.priceOf, logger);\n if (quote.kind !== \"ok\") return notMetered(quote.kind, quote.detail);\n\n const required = BigInt(quote.value.units) * quote.value.unitPrice;\n const delivery: MeteredDelivery = {\n agent: agent.value,\n serviceId,\n asset,\n tool: quote.value.tool,\n units: quote.value.units,\n expectedUnitPrice: quote.value.unitPrice,\n };\n const recorded = await callAsync(\n () => tabBook.recordDelivery(delivery),\n \"tabBook.recordDelivery\",\n logger,\n );\n\n if (!recorded.ok) {\n // A `TabBookClient` is not supposed to throw. One that does is treated as a\n // revert of unknown shape, which lands on `deliver-anyway`.\n return failed(classifyRecordDeliveryRevert(recorded.thrown), {\n request,\n agent: agent.value,\n quote: quote.value,\n });\n }\n\n if (!recorded.value.ok) {\n const error = recorded.value.error;\n if (dispositionOf(error) !== \"refuse-request\") {\n return failed(error, { request, agent: agent.value, quote: quote.value });\n }\n return refuse({\n request,\n agent: agent.value,\n asset,\n serviceId,\n quote: quote.value,\n required,\n error,\n // Read only where the refusal reports a headroom, because the charge block\n // needs both figures or neither. `LimitExceeded` is that case.\n openTab:\n detailAmount(error, \"headroom\") === undefined\n ? undefined\n : await openTabFor(delivery),\n logger,\n });\n }\n\n const receipt = recorded.value.value;\n const claim = claimOf(request);\n const charge: MeteredCharge = {\n agent: agent.value,\n serviceId,\n asset,\n tool: quote.value.tool,\n units: quote.value.units,\n unitPrice: quote.value.unitPrice,\n amount: receipt.charged,\n openTabAfter: receipt.openAfter,\n headroomAfter: receipt.headroomAfter,\n ...(claim === undefined ? {} : { authorisationClaim: claim }),\n ...(receipt.monadTxHash === undefined ? {} : { monadTxHash: receipt.monadTxHash }),\n recordedAt: receipt.recordedAt,\n };\n\n call(() => options.onCharge?.(charge), \"onCharge\", logger);\n\n return { kind: \"charged\", charge, receipt, headers: chargeHeaders(charge, logger) };\n };\n\n /**\n * The Open Tab the refusal reports, or undefined when it could not be read.\n *\n * A failed read costs the refusal its charge headers and nothing else: the body\n * still names the required amount and the headroom, and no figure is invented to\n * fill the block.\n */\n const openTabFor = async (delivery: MeteredDelivery): Promise<bigint | undefined> => {\n const figure = await callAsync(() => tabBook.openTabOf(delivery), \"tabBook.openTabOf\", logger);\n if (!figure.ok) return undefined;\n if (!figure.value.ok) {\n logger.warn(\"post-paid could not read the Open Tab, so the refusal carries no charge headers\", {\n serviceId,\n code: figure.value.error.code,\n });\n return undefined;\n }\n return figure.value.value;\n };\n\n /**\n * The response a refusal is served with.\n *\n * The `?? 0n` below is unreachable in practice: `LimitExceeded` reverts with a\n * headroom, which is what put the figure on the context. It exists so\n * {@link LimitExceededContext} can promise a `bigint` rather than making every\n * consumer of the hook handle an absence that the revert set does not produce.\n */\n const refusalResponseFor = (outcome: RefusedOutcome): Response => {\n const override =\n outcome.error.code === \"LIMIT_EXCEEDED\" && options.onLimitExceeded !== undefined\n ? call(\n () =>\n options.onLimitExceeded?.({\n ...outcome.context,\n headroomBaseUnits: outcome.context.headroomBaseUnits ?? 0n,\n }),\n \"onLimitExceeded\",\n logger,\n )\n : options.onRefused !== undefined\n ? call(() => options.onRefused?.(outcome.context), \"onRefused\", logger)\n : { ok: true as const, value: undefined };\n\n if (override.ok && override.value !== undefined) return override.value;\n return jsonResponse(outcome.status, outcome.headers, outcome.body);\n };\n\n const plugin: PostPaidPlugin = {\n serviceId,\n asset,\n release,\n\n meter,\n\n async execute(request, handler) {\n // The handler runs first and runs to completion. Nothing above this line\n // touches the chain, and nothing below it runs until the response exists.\n let response: Response;\n try {\n response = await handler();\n } catch (thrown) {\n // No response means no delivery, and a delivery that did not happen is\n // not charged for. Deliberate, and not configurable.\n logger.debug(\"post-paid metered nothing because the handler failed\", { serviceId });\n return {\n kind: \"handler-failed\",\n thrown,\n error: tabError(\"INTERNAL\", \"HANDLER_FAILED\", \"the handler failed, so no delivery was metered\", {\n details: { metered: false },\n cause: causeOf(thrown),\n }),\n };\n }\n\n if (release === \"before-metering\") {\n // The response is handed back now. The metering promise is still pending,\n // and a caller with a host lifetime hook, `waitUntil`, `after`, passes\n // it there so the runtime does not tear down underneath it.\n return { kind: \"delivered\", response, metering: meter(request, response) };\n }\n\n const outcome = await meter(request, response);\n if (outcome.kind === \"refused\") {\n return { kind: \"refused\", response: refusalResponseFor(outcome), outcome };\n }\n return {\n kind: \"delivered\",\n response: attachHeaders(response, plugin.headersFor(outcome), logger),\n metering: Promise.resolve(outcome),\n };\n },\n\n headersFor(outcome) {\n if (outcome.kind === \"charged\") return outcome.headers;\n if (outcome.kind === \"refused\") return outcome.headers;\n return {};\n },\n\n refusalResponseFor,\n };\n\n return plugin;\n}\n\n/**\n * The six charge headers a recorded charge puts on the response.\n *\n * Formatted by `src/http/headers.ts`, which is also what the 402 client parses\n * with, so the two halves of the wire format cannot disagree. A block that will\n * not format is dropped rather than half-written, the client treats a partial\n * block as an error, and correctly.\n */\nexport function chargeHeaders(\n charge: MeteredCharge,\n logger: Logger = defaultLogger,\n): Readonly<Record<string, string>> {\n return blockHeaders(\n {\n amount: charge.amount,\n asset: chargedAssetOf(charge.asset),\n serviceId: charge.serviceId,\n tool: charge.tool,\n openTab: charge.openTabAfter,\n headroom: charge.headroomAfter,\n },\n logger,\n );\n}\n\n/** The Asset as the header contract carries it: a chain id and an address. */\nconst chargedAssetOf = (asset: AssetRef): ChargedAsset => ({\n chainId: asset.chainId,\n address: asset.address,\n});\n\n/** Formats one charge block, or nothing at all if it would be malformed. */\nfunction blockHeaders(block: ChargeBlock, logger: Logger): Readonly<Record<string, string>> {\n const formatted = formatChargeHeaders(block);\n if (formatted.ok) return formatted.value;\n logger.error(\"post-paid built a charge block that will not format, so no charge headers were sent\", {\n code: formatted.error.code,\n message: formatted.error.message,\n });\n return {};\n}\n\n/**\n * Builds the refusal: its status, its headers, and its body.\n *\n * The status comes from the error's category and from nothing else, which is why\n * `LIMIT` is the only category that can produce a 402 here.\n *\n * The charge headers go out as the complete six-header block or not at all. That\n * needs both a headroom, which only `LimitExceeded` reverts with, and an Open Tab,\n * which no revert carries and which the caller read through `openTabOf`. Where\n * either is missing the block is dropped, and the body, which always names the\n * required amount, and names the headroom whenever the refusal reported one -\n * carries the refusal on its own.\n */\nfunction refuse(input: {\n readonly request: MeteredRequest;\n readonly agent: Address;\n readonly serviceId: Bytes32;\n readonly asset: AssetRef;\n readonly quote: PriceQuote;\n readonly required: bigint;\n readonly error: TabError;\n /** The Open Tab as read on the refusal path, absent when it could not be read. */\n readonly openTab: bigint | undefined;\n readonly logger: Logger;\n}): RefusedOutcome {\n const { error, quote, required, openTab } = input;\n const status = refusalStatusOf(error);\n const headroom = detailAmount(error, \"headroom\");\n\n const headers =\n headroom === undefined || openTab === undefined\n ? {}\n : blockHeaders(\n {\n amount: required,\n asset: chargedAssetOf(input.asset),\n serviceId: input.serviceId,\n tool: quote.tool,\n openTab,\n headroom,\n },\n input.logger,\n );\n\n const body: RefusalBody = {\n ok: false,\n error: {\n category: error.category,\n code: error.code,\n message: error.message,\n retryable: error.retryable,\n },\n agent: input.agent,\n serviceId: input.serviceId,\n asset: chargedAssetKey(chargedAssetOf(input.asset)),\n tool: quote.tool,\n requiredBaseUnits: required.toString(10),\n ...(headroom === undefined ? {} : { headroomBaseUnits: headroom.toString(10) }),\n action: actionOf(error),\n };\n\n const context: RefusalContext = {\n request: input.request,\n agent: input.agent,\n serviceId: input.serviceId,\n asset: input.asset,\n quote,\n requiredBaseUnits: required,\n ...(headroom === undefined ? {} : { headroomBaseUnits: headroom }),\n error,\n status,\n headers,\n body,\n };\n\n return { kind: \"refused\", error, status, headers, body, context };\n}\n\n/** A JSON response carrying the refusal, with its `Tab-*` headers. */\nexport function jsonResponse(\n status: number,\n headers: Readonly<Record<string, string>>,\n body: unknown,\n): Response {\n return new Response(JSON.stringify(body), {\n status,\n headers: { ...headers, \"Content-Type\": \"application/json; charset=utf-8\" },\n });\n}\n\n/**\n * Puts the charge headers on the delivered response.\n *\n * Mutation first, because that keeps the handler's own response object, and a\n * test asserting the delivered response is the handler's response is asserting\n * something worth keeping true. A `Response` that came back from `fetch` guards\n * its headers as immutable, so the fallback rebuilds it around the same body\n * stream rather than reading the body into memory.\n */\nexport function attachHeaders(\n response: Response,\n headers: Readonly<Record<string, string>>,\n logger: Logger = defaultLogger,\n): Response {\n const entries = Object.entries(headers);\n if (entries.length === 0) return response;\n try {\n for (const [name, value] of entries) response.headers.set(name, value);\n return response;\n } catch {\n logger.debug(\"response headers were immutable, so the charge headers went onto a copy\");\n const merged = new Headers(response.headers);\n for (const [name, value] of entries) merged.set(name, value);\n return new Response(response.body, {\n status: response.status,\n statusText: response.statusText,\n headers: merged,\n });\n }\n}\n\n/** The action text a refusal body carries, taken from the classified message. */\nfunction actionOf(error: TabError): string {\n const separator = error.message.indexOf(\": \");\n return separator === -1 ? error.message : error.message.slice(separator + 2);\n}\n\n/** The `Tab-Authorisation` claim, when the Agent made one. */\nconst claimOf = (request: MeteredRequest): string | undefined => {\n const raw = read(request.headers, TAB_HEADER.authorisation);\n return raw === undefined || raw.length === 0 ? undefined : raw;\n};\n\n/** Reads one header, tolerating a reader that throws or returns null. */\nfunction read(headers: HeaderReader, name: string): string | undefined {\n try {\n const value = headers.get(name);\n return typeof value === \"string\" ? value.trim() : undefined;\n } catch {\n return undefined;\n }\n}\n\ntype Resolved<T> = { kind: \"ok\"; value: T } | { kind: NotMeteredReason; detail: string };\n\n/** The Agent to charge, from `agentOf` or from `Tab-Agent`. */\nfunction resolveAgent(\n request: MeteredRequest,\n agentOf: TabPostPaidOptions[\"agentOf\"],\n logger: Logger,\n): Resolved<Address> {\n let raw: string | undefined;\n if (agentOf !== undefined) {\n const supplied = call(() => agentOf(request), \"agentOf\", logger);\n if (!supplied.ok) return { kind: \"no-agent\", detail: \"agentOf threw\" };\n raw = supplied.value === null || supplied.value === undefined ? undefined : supplied.value.trim();\n } else {\n raw = read(request.headers, TAB_HEADER.agent);\n }\n\n if (raw === undefined || raw.length === 0) {\n return {\n kind: \"no-agent\",\n detail: `no ${TAB_HEADER.agent} header, so there is no Agent to charge`,\n };\n }\n if (!isAddress(raw)) {\n return {\n kind: \"agent-malformed\",\n detail: `${TAB_HEADER.agent} carried \\`${raw}\\`, which is not a 20-byte 0x address`,\n };\n }\n return { kind: \"ok\", value: raw };\n}\n\n/** The price of this call, validated into something `recordDelivery` accepts. */\nfunction resolveQuote(\n request: MeteredRequest,\n priceOf: TabPostPaidOptions[\"priceOf\"],\n logger: Logger,\n): Resolved<PriceQuote> {\n const quoted = call(() => priceOf(request), \"priceOf\", logger);\n if (!quoted.ok) return { kind: \"not-priced\", detail: \"priceOf threw\" };\n const quote = quoted.value;\n if (quote === undefined || quote === null) {\n return { kind: \"not-priced\", detail: \"priceOf returned nothing, so this request is not charged for\" };\n }\n if (!isBytes32(quote.tool)) {\n return { kind: \"price-invalid\", detail: \"quote.tool must be a 32-byte 0x word\" };\n }\n if (!Number.isInteger(quote.units) || quote.units <= 0 || quote.units > 4_294_967_295) {\n return {\n kind: \"price-invalid\",\n detail: `quote.units must be a positive integer inside uint32, received ${String(quote.units)}`,\n };\n }\n if (typeof quote.unitPrice !== \"bigint\" || quote.unitPrice <= 0n) {\n return {\n kind: \"price-invalid\",\n detail: \"quote.unitPrice must be a positive bigint count of Asset base units, never a number\",\n };\n }\n return { kind: \"ok\", value: quote };\n}\n\ntype Called<T> = { ok: true; value: T } | { ok: false; cause: { code: string; message: string } };\n\n/** Runs consumer code without letting it throw into the metering path. */\nfunction call<T>(fn: () => T, what: string, logger: Logger): Called<T> {\n try {\n return { ok: true, value: fn() };\n } catch (thrown) {\n const cause = causeOf(thrown);\n logger.error(`post-paid ${what} threw and was ignored`, cause);\n return { ok: false, cause };\n }\n}\n\ntype CalledAsync<T> = { ok: true; value: T } | { ok: false; thrown: unknown };\n\n/** The asynchronous sibling, keeping a rejected promise out of the metering path. */\nasync function callAsync<T>(\n fn: () => Promise<T>,\n what: string,\n logger: Logger,\n): Promise<CalledAsync<T>> {\n try {\n return { ok: true, value: await fn() };\n } catch (thrown) {\n logger.error(`post-paid ${what} threw and was treated as a refusal of unknown shape`, causeOf(thrown));\n return { ok: false, thrown };\n }\n}\n\n/** Narrows a caught value into the `cause` shape a `TabError` accepts. */\nfunction causeOf(thrown: unknown): { code: string; message: string } {\n if (thrown instanceof Error) return { code: thrown.name, message: thrown.message };\n return { code: \"UNKNOWN\", message: String(thrown) };\n}\n"]}
|
package/dist/x402/client.d.ts
CHANGED
|
@@ -13,7 +13,7 @@
|
|
|
13
13
|
*
|
|
14
14
|
* ## The signing is hand-rolled, and here is the check on it
|
|
15
15
|
*
|
|
16
|
-
* `@x402/evm` is the
|
|
16
|
+
* `@x402/evm` is the official client for this scheme and it is built on viem.
|
|
17
17
|
* This package signs with ethers, so pulling it in would ship a second EVM
|
|
18
18
|
* library to sign one struct. The struct is small enough to state:
|
|
19
19
|
*
|
|
@@ -23,11 +23,10 @@
|
|
|
23
23
|
* - primary type `TransferWithAuthorization(address from, address to, uint256
|
|
24
24
|
* value, uint256 validAfter, uint256 validBefore, bytes32 nonce)` (EIP-3009);
|
|
25
25
|
* - `validAfter` `0`, `validBefore` now plus `maxTimeoutSeconds`, and a random
|
|
26
|
-
* 32-byte `nonce`, exactly as
|
|
26
|
+
* 32-byte `nonce`, exactly as `@x402/evm`'s `createEIP3009Payload` builds them.
|
|
27
27
|
*
|
|
28
|
-
* The test suite recovers the signer from every signature this file produces
|
|
29
|
-
* and
|
|
30
|
-
* written: the two signatures are byte for byte the same.
|
|
28
|
+
* The test suite recovers the signer from every signature this file produces
|
|
29
|
+
* against that domain and struct, so a drift from the scheme fails a test.
|
|
31
30
|
*
|
|
32
31
|
* Specification: scheme_exact_evm.md section 1, x402-specification-v2.md section 5.2.
|
|
33
32
|
*/
|
|
@@ -123,7 +122,7 @@ export interface SignedExactPayment {
|
|
|
123
122
|
/**
|
|
124
123
|
* Signs one `exact` requirement.
|
|
125
124
|
*
|
|
126
|
-
* The domain and the struct are exactly what
|
|
125
|
+
* The domain and the struct are exactly what `@x402/evm` signs, so
|
|
127
126
|
* the facilitator's recovery lands on `authorization.from`. The requirement is
|
|
128
127
|
* re-validated here rather than trusted, because it arrived over the network.
|
|
129
128
|
*/
|