@tabai/sdk 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (204) hide show
  1. package/LICENSE +23 -0
  2. package/README.md +401 -0
  3. package/bin/tab.mjs +23 -0
  4. package/dist/_shared/abi.d.ts +150 -0
  5. package/dist/_shared/abi.d.ts.map +1 -0
  6. package/dist/_shared/abi.js +197 -0
  7. package/dist/_shared/abi.js.map +1 -0
  8. package/dist/_shared/chains.d.ts +118 -0
  9. package/dist/_shared/chains.d.ts.map +1 -0
  10. package/dist/_shared/chains.js +89 -0
  11. package/dist/_shared/chains.js.map +1 -0
  12. package/dist/_shared/hex.d.ts +35 -0
  13. package/dist/_shared/hex.d.ts.map +1 -0
  14. package/dist/_shared/hex.js +40 -0
  15. package/dist/_shared/hex.js.map +1 -0
  16. package/dist/_shared/index.d.ts +14 -0
  17. package/dist/_shared/index.d.ts.map +1 -0
  18. package/dist/_shared/index.js +14 -0
  19. package/dist/_shared/index.js.map +1 -0
  20. package/dist/_shared/keccak256.d.ts +29 -0
  21. package/dist/_shared/keccak256.d.ts.map +1 -0
  22. package/dist/_shared/keccak256.js +145 -0
  23. package/dist/_shared/keccak256.js.map +1 -0
  24. package/dist/_shared/result.d.ts +78 -0
  25. package/dist/_shared/result.d.ts.map +1 -0
  26. package/dist/_shared/result.js +61 -0
  27. package/dist/_shared/result.js.map +1 -0
  28. package/dist/cli/client-config.d.ts +155 -0
  29. package/dist/cli/client-config.d.ts.map +1 -0
  30. package/dist/cli/client-config.js +382 -0
  31. package/dist/cli/client-config.js.map +1 -0
  32. package/dist/cli/connect.d.ts +76 -0
  33. package/dist/cli/connect.d.ts.map +1 -0
  34. package/dist/cli/connect.js +158 -0
  35. package/dist/cli/connect.js.map +1 -0
  36. package/dist/cli/doctor.d.ts +57 -0
  37. package/dist/cli/doctor.d.ts.map +1 -0
  38. package/dist/cli/doctor.js +253 -0
  39. package/dist/cli/doctor.js.map +1 -0
  40. package/dist/cli/index.d.ts +13 -0
  41. package/dist/cli/index.d.ts.map +1 -0
  42. package/dist/cli/index.js +13 -0
  43. package/dist/cli/index.js.map +1 -0
  44. package/dist/cli/main.d.ts +45 -0
  45. package/dist/cli/main.d.ts.map +1 -0
  46. package/dist/cli/main.js +371 -0
  47. package/dist/cli/main.js.map +1 -0
  48. package/dist/errors.d.ts +29 -0
  49. package/dist/errors.d.ts.map +1 -0
  50. package/dist/errors.js +37 -0
  51. package/dist/errors.js.map +1 -0
  52. package/dist/http/client-402.d.ts +243 -0
  53. package/dist/http/client-402.d.ts.map +1 -0
  54. package/dist/http/client-402.js +515 -0
  55. package/dist/http/client-402.js.map +1 -0
  56. package/dist/http/headers.d.ts +173 -0
  57. package/dist/http/headers.d.ts.map +1 -0
  58. package/dist/http/headers.js +284 -0
  59. package/dist/http/headers.js.map +1 -0
  60. package/dist/http/index.d.ts +15 -0
  61. package/dist/http/index.d.ts.map +1 -0
  62. package/dist/http/index.js +15 -0
  63. package/dist/http/index.js.map +1 -0
  64. package/dist/http/metering-claim.d.ts +82 -0
  65. package/dist/http/metering-claim.d.ts.map +1 -0
  66. package/dist/http/metering-claim.js +99 -0
  67. package/dist/http/metering-claim.js.map +1 -0
  68. package/dist/index.d.ts +48 -0
  69. package/dist/index.d.ts.map +1 -0
  70. package/dist/index.js +51 -0
  71. package/dist/index.js.map +1 -0
  72. package/dist/logger.d.ts +40 -0
  73. package/dist/logger.d.ts.map +1 -0
  74. package/dist/logger.js +50 -0
  75. package/dist/logger.js.map +1 -0
  76. package/dist/mcp/assets.d.ts +31 -0
  77. package/dist/mcp/assets.d.ts.map +1 -0
  78. package/dist/mcp/assets.js +78 -0
  79. package/dist/mcp/assets.js.map +1 -0
  80. package/dist/mcp/index.d.ts +19 -0
  81. package/dist/mcp/index.d.ts.map +1 -0
  82. package/dist/mcp/index.js +19 -0
  83. package/dist/mcp/index.js.map +1 -0
  84. package/dist/mcp/json-schema.d.ts +86 -0
  85. package/dist/mcp/json-schema.d.ts.map +1 -0
  86. package/dist/mcp/json-schema.js +215 -0
  87. package/dist/mcp/json-schema.js.map +1 -0
  88. package/dist/mcp/json.d.ts +43 -0
  89. package/dist/mcp/json.d.ts.map +1 -0
  90. package/dist/mcp/json.js +69 -0
  91. package/dist/mcp/json.js.map +1 -0
  92. package/dist/mcp/registry-client.d.ts +88 -0
  93. package/dist/mcp/registry-client.d.ts.map +1 -0
  94. package/dist/mcp/registry-client.js +158 -0
  95. package/dist/mcp/registry-client.js.map +1 -0
  96. package/dist/mcp/schemas.d.ts +82 -0
  97. package/dist/mcp/schemas.d.ts.map +1 -0
  98. package/dist/mcp/schemas.js +493 -0
  99. package/dist/mcp/schemas.js.map +1 -0
  100. package/dist/mcp/server.d.ts +97 -0
  101. package/dist/mcp/server.d.ts.map +1 -0
  102. package/dist/mcp/server.js +285 -0
  103. package/dist/mcp/server.js.map +1 -0
  104. package/dist/mcp/settings.d.ts +90 -0
  105. package/dist/mcp/settings.d.ts.map +1 -0
  106. package/dist/mcp/settings.js +160 -0
  107. package/dist/mcp/settings.js.map +1 -0
  108. package/dist/mcp/toolset.d.ts +231 -0
  109. package/dist/mcp/toolset.d.ts.map +1 -0
  110. package/dist/mcp/toolset.js +760 -0
  111. package/dist/mcp/toolset.js.map +1 -0
  112. package/dist/payments/abi.d.ts +9 -0
  113. package/dist/payments/abi.d.ts.map +1 -0
  114. package/dist/payments/abi.js +17 -0
  115. package/dist/payments/abi.js.map +1 -0
  116. package/dist/payments/config.d.ts +199 -0
  117. package/dist/payments/config.d.ts.map +1 -0
  118. package/dist/payments/config.js +259 -0
  119. package/dist/payments/config.js.map +1 -0
  120. package/dist/payments/index.d.ts +13 -0
  121. package/dist/payments/index.d.ts.map +1 -0
  122. package/dist/payments/index.js +13 -0
  123. package/dist/payments/index.js.map +1 -0
  124. package/dist/payments/kuru.d.ts +191 -0
  125. package/dist/payments/kuru.d.ts.map +1 -0
  126. package/dist/payments/kuru.js +377 -0
  127. package/dist/payments/kuru.js.map +1 -0
  128. package/dist/payments/monad.d.ts +69 -0
  129. package/dist/payments/monad.d.ts.map +1 -0
  130. package/dist/payments/monad.js +306 -0
  131. package/dist/payments/monad.js.map +1 -0
  132. package/dist/payments/permit2.d.ts +118 -0
  133. package/dist/payments/permit2.d.ts.map +1 -0
  134. package/dist/payments/permit2.js +366 -0
  135. package/dist/payments/permit2.js.map +1 -0
  136. package/dist/payments/registry.d.ts +119 -0
  137. package/dist/payments/registry.d.ts.map +1 -0
  138. package/dist/payments/registry.js +199 -0
  139. package/dist/payments/registry.js.map +1 -0
  140. package/dist/payments/strategy.d.ts +80 -0
  141. package/dist/payments/strategy.d.ts.map +1 -0
  142. package/dist/payments/strategy.js +103 -0
  143. package/dist/payments/strategy.js.map +1 -0
  144. package/dist/proxy/hooks.d.ts +90 -0
  145. package/dist/proxy/hooks.d.ts.map +1 -0
  146. package/dist/proxy/hooks.js +35 -0
  147. package/dist/proxy/hooks.js.map +1 -0
  148. package/dist/proxy/index.d.ts +9 -0
  149. package/dist/proxy/index.d.ts.map +1 -0
  150. package/dist/proxy/index.js +9 -0
  151. package/dist/proxy/index.js.map +1 -0
  152. package/dist/proxy/proxy.d.ts +156 -0
  153. package/dist/proxy/proxy.d.ts.map +1 -0
  154. package/dist/proxy/proxy.js +366 -0
  155. package/dist/proxy/proxy.js.map +1 -0
  156. package/dist/server/adapters/express.d.ts +89 -0
  157. package/dist/server/adapters/express.d.ts.map +1 -0
  158. package/dist/server/adapters/express.js +215 -0
  159. package/dist/server/adapters/express.js.map +1 -0
  160. package/dist/server/adapters/hono.d.ts +52 -0
  161. package/dist/server/adapters/hono.d.ts.map +1 -0
  162. package/dist/server/adapters/hono.js +61 -0
  163. package/dist/server/adapters/hono.js.map +1 -0
  164. package/dist/server/adapters/next.d.ts +52 -0
  165. package/dist/server/adapters/next.d.ts.map +1 -0
  166. package/dist/server/adapters/next.js +56 -0
  167. package/dist/server/adapters/next.js.map +1 -0
  168. package/dist/server/index.d.ts +30 -0
  169. package/dist/server/index.d.ts.map +1 -0
  170. package/dist/server/index.js +30 -0
  171. package/dist/server/index.js.map +1 -0
  172. package/dist/server/metering.d.ts +209 -0
  173. package/dist/server/metering.d.ts.map +1 -0
  174. package/dist/server/metering.js +365 -0
  175. package/dist/server/metering.js.map +1 -0
  176. package/dist/server/post-paid.d.ts +355 -0
  177. package/dist/server/post-paid.d.ts.map +1 -0
  178. package/dist/server/post-paid.js +512 -0
  179. package/dist/server/post-paid.js.map +1 -0
  180. package/dist/x402/client.d.ts +203 -0
  181. package/dist/x402/client.d.ts.map +1 -0
  182. package/dist/x402/client.js +337 -0
  183. package/dist/x402/client.js.map +1 -0
  184. package/dist/x402/hub.d.ts +79 -0
  185. package/dist/x402/hub.d.ts.map +1 -0
  186. package/dist/x402/hub.js +164 -0
  187. package/dist/x402/hub.js.map +1 -0
  188. package/dist/x402/index.d.ts +27 -0
  189. package/dist/x402/index.d.ts.map +1 -0
  190. package/dist/x402/index.js +27 -0
  191. package/dist/x402/index.js.map +1 -0
  192. package/dist/x402/proxy.d.ts +162 -0
  193. package/dist/x402/proxy.d.ts.map +1 -0
  194. package/dist/x402/proxy.js +198 -0
  195. package/dist/x402/proxy.js.map +1 -0
  196. package/dist/x402/server.d.ts +162 -0
  197. package/dist/x402/server.d.ts.map +1 -0
  198. package/dist/x402/server.js +306 -0
  199. package/dist/x402/server.js.map +1 -0
  200. package/dist/x402/wire.d.ts +104 -0
  201. package/dist/x402/wire.d.ts.map +1 -0
  202. package/dist/x402/wire.js +265 -0
  203. package/dist/x402/wire.js.map +1 -0
  204. package/package.json +61 -0
@@ -0,0 +1,30 @@
1
+ /**
2
+ * The server-side surface: the post-paid plugin, the metering seam, and the three
3
+ * framework adapters.
4
+ *
5
+ * `tabPostPaid` is the whole product claim in one function. A Service installs it,
6
+ * keeps writing handlers the way it already does, and its Open Tab accrues behind
7
+ * each delivered response. No caller prepays, no response waits on a payment, and
8
+ * the one status this surface adds is a `402` on `LimitExceeded`, a credit
9
+ * decision, not a prepayment demand (R23.3, R12.1).
10
+ *
11
+ * The adapters are thin by design and carry no dependency on the framework they
12
+ * adapt: every framework type in here is structural, so a Service on Hono installs
13
+ * nothing for Express and nothing for Next.js.
14
+ *
15
+ * The wire format is not defined here. `src/http/headers.ts` owns it, this surface
16
+ * is its emitting half, and the 402 client of R23.2 is its reading half, so a
17
+ * disagreement about a header is a test failure in one file rather than a silent
18
+ * mis-parse between two.
19
+ *
20
+ * Nothing exported here throws, with one named exception: the Hono and Next.js
21
+ * adapters re-raise a handler's own thrown value, because a framework's contract
22
+ * for a failed handler is an exception and the adapter is the boundary where a
23
+ * `Result` becomes whatever the host expects.
24
+ */
25
+ export * from "./metering.js";
26
+ export * from "./post-paid.js";
27
+ export * from "./adapters/hono.js";
28
+ export * from "./adapters/express.js";
29
+ export * from "./adapters/next.js";
30
+ //# sourceMappingURL=index.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AAEH,cAAc,eAAe,CAAC;AAC9B,cAAc,gBAAgB,CAAC;AAC/B,cAAc,oBAAoB,CAAC;AACnC,cAAc,uBAAuB,CAAC;AACtC,cAAc,oBAAoB,CAAC","sourcesContent":["/**\n * The server-side surface: the post-paid plugin, the metering seam, and the three\n * framework adapters.\n *\n * `tabPostPaid` is the whole product claim in one function. A Service installs it,\n * keeps writing handlers the way it already does, and its Open Tab accrues behind\n * each delivered response. No caller prepays, no response waits on a payment, and\n * the one status this surface adds is a `402` on `LimitExceeded`, a credit\n * decision, not a prepayment demand (R23.3, R12.1).\n *\n * The adapters are thin by design and carry no dependency on the framework they\n * adapt: every framework type in here is structural, so a Service on Hono installs\n * nothing for Express and nothing for Next.js.\n *\n * The wire format is not defined here. `src/http/headers.ts` owns it, this surface\n * is its emitting half, and the 402 client of R23.2 is its reading half, so a\n * disagreement about a header is a test failure in one file rather than a silent\n * mis-parse between two.\n *\n * Nothing exported here throws, with one named exception: the Hono and Next.js\n * adapters re-raise a handler's own thrown value, because a framework's contract\n * for a failed handler is an exception and the adapter is the boundary where a\n * `Result` becomes whatever the host expects.\n */\n\nexport * from \"./metering.js\";\nexport * from \"./post-paid.js\";\nexport * from \"./adapters/hono.js\";\nexport * from \"./adapters/express.js\";\nexport * from \"./adapters/next.js\";\n"]}
@@ -0,0 +1,209 @@
1
+ /**
2
+ * The metering seam: what the post-paid plugin calls to record one Metered
3
+ * Delivery, and how a `recordDelivery` revert becomes a `TabError`.
4
+ *
5
+ * Two things live here and they are deliberately separate. {@link TabBookClient}
6
+ * is the interface the plugin depends on, so the plugin has no opinion about
7
+ * `ethers`, an RPC endpoint, a signer, or a witness. {@link RECORD_DELIVERY_REVERTS}
8
+ * is the mapping table from `TabBook.recordDelivery`'s revert set to the
9
+ * `TabError` each one becomes, so every implementation of that interface, the
10
+ * gateway or a Service operator's own client, classifies a refusal the same way
11
+ * rather than each inventing its own status codes.
12
+ *
13
+ * ## The revert set is the real one, read off the contract
14
+ *
15
+ * `recordDelivery(agent, serviceId, asset, tool, units, expectedUnitPrice,
16
+ * witness)` runs six steps in a fixed order, and each step has its own refusals.
17
+ * In call order: the registry read (`UnknownService`), the operator check
18
+ * (`NotServiceOperator`), the delinquency check (`TabIsDelinquent`), pricing
19
+ * (`ZeroUnits`, `UnknownTool`, `PriceListChangedMidCall`, `AmountOutOfRange`),
20
+ * the authorisation (`AuthorisationMissing`, `AuthorisationExpired`,
21
+ * `AuthorisationExceeded`), and the headroom check (`LimitExceeded`, plus the
22
+ * witness-validation errors the Credit Limit computation raises). Every name in
23
+ * {@link RECORD_DELIVERY_REVERTS} is one of those; nothing in the table was
24
+ * guessed from the design.
25
+ *
26
+ * ## Whose fault it is decides who sees it
27
+ *
28
+ * Each row carries a {@link RefusalDisposition}, and that field is the whole
29
+ * policy of this module:
30
+ *
31
+ * - `refuse-request`, the refusal is the Agent's to fix. Its Credit Limit has no
32
+ * headroom, its spending authorisation is missing, lapsed, or spent, or its tab
33
+ * went past its Settlement Window. The Agent is told, with the figures it needs
34
+ * to act on.
35
+ * - `deliver-anyway`, the refusal is the Service's to fix, or ours. A wrong
36
+ * operator key, a tool that is not in the applied price list, a price that
37
+ * moved under the call, a witness the Service assembled wrongly. The Agent did
38
+ * nothing, so the Agent's response is delivered and the Service is told through
39
+ * its own failure channel. A Service whose billing is broken eats the cost of
40
+ * the delivery; it does not hand the cost to the caller.
41
+ *
42
+ * `refuse-request` is the smaller set on purpose. The plugin exists to accrue, not
43
+ * to gate.
44
+ *
45
+ * ## Amounts
46
+ *
47
+ * Every amount here is an integer count of Asset base units as a `bigint`. USDC
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
+ */
52
+ import { type Address, type Bytes32, type ErrorCategory, type Hex, type Result, type TabError } from "../_shared/index.js";
53
+ import type { AssetRef } from "../payments/strategy.js";
54
+ /**
55
+ * The one delivery a Service is charging for.
56
+ *
57
+ * The fields are `recordDelivery`'s own parameters minus the witness, because the
58
+ * witness is not the plugin's to build: it is the full ordered
59
+ * Settlement history plus one Bond entry per counterparty, validated on chain
60
+ * against the rolling commitment, and assembling it needs chain reads the plugin
61
+ * does not make. A {@link TabBookClient} owns that.
62
+ */
63
+ export interface MeteredDelivery {
64
+ /** The Agent's Monad address. Identified from the request, never from a session. */
65
+ readonly agent: Address;
66
+ readonly serviceId: Bytes32;
67
+ readonly asset: AssetRef;
68
+ /** The named priced unit, as the 32-byte word the applied price list is keyed by. */
69
+ readonly tool: Bytes32;
70
+ /** Count of priced units. A `uint32` on chain, and zero reverts `ZeroUnits`. */
71
+ readonly units: number;
72
+ /**
73
+ * The unit price the Service quoted, in Asset base units.
74
+ *
75
+ * Compared for exact equality against the applied price list, so a price that
76
+ * moved between the quote and the charge reverts `PriceListChangedMidCall`
77
+ * rather than charging a figure the Agent never saw. Per unit rather than
78
+ * total, so the count cannot be adjusted to match a total already committed to.
79
+ */
80
+ readonly expectedUnitPrice: bigint;
81
+ }
82
+ /** What `recordDelivery` returned, in the order the contract returns it. */
83
+ export interface DeliveryReceipt {
84
+ /** `units * expectedUnitPrice`, in Asset base units. */
85
+ readonly charged: bigint;
86
+ /** The Open Tab for this Agent, Service, and Asset after the charge. */
87
+ readonly openAfter: bigint;
88
+ /** Headroom left for this Agent across every Service in this Asset. */
89
+ readonly headroomAfter: bigint;
90
+ /** The Monad transaction the delivery was recorded in, when the client knows it. */
91
+ readonly monadTxHash?: Hex;
92
+ /** Milliseconds since the epoch, on the recording client's clock. */
93
+ readonly recordedAt: number;
94
+ }
95
+ /**
96
+ * The two calls the post-paid plugin makes.
97
+ *
98
+ * Narrow on purpose: this is what lets a Service meter against a real signer in
99
+ * production and against a stub in a test without the plugin knowing the
100
+ * difference. Both methods return a `Result` and neither throws (design section
101
+ * 13.1). On a revert, `recordDelivery` returns the `TabError`
102
+ * {@link classifyRecordDeliveryRevert} produces, so the plugin reads the
103
+ * disposition off `details.disposition` rather than re-decoding contract data.
104
+ */
105
+ export interface TabBookClient {
106
+ recordDelivery(delivery: MeteredDelivery): Promise<Result<DeliveryReceipt>>;
107
+ /**
108
+ * The Open Tab for this Agent, Service, and Asset, read without metering.
109
+ *
110
+ * A second method rather than one, because the `402` needs a figure a revert
111
+ * does not carry. `LimitExceeded` reverts with the requested amount and the
112
+ * headroom and says nothing about the Open Tab, and the charge block on the wire
113
+ * is all six headers or none of them, so a 402 that could not name the Open Tab
114
+ * would reach the 402 client as a malformed charge block instead of as the credit
115
+ * decision it is. The call is a view, no witness, no signature, and the figure
116
+ * is truthful precisely because the metering transaction reverted and moved
117
+ * nothing.
118
+ *
119
+ * Called on the refusal path alone. It is never on the path of a successful
120
+ * delivery, which reads its figures off `recordDelivery`'s own return.
121
+ */
122
+ openTabOf(delivery: MeteredDelivery): Promise<Result<bigint>>;
123
+ }
124
+ /**
125
+ * The human-readable ABI fragment of the real entrypoint, field for field.
126
+ *
127
+ * Carried here so a {@link TabBookClient} implementation encodes against the
128
+ * deployed signature rather than a remembered one. The witness is one tuple of
129
+ * two arrays: `LimitLib.SettlementRecord[]` and `LimitLib.BondEntry[]`, in that
130
+ * order.
131
+ */
132
+ export declare const TAB_BOOK_RECORD_DELIVERY_ABI: readonly ["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)"];
133
+ /**
134
+ * Whether a refusal changes what the caller receives.
135
+ *
136
+ * `refuse-request` replaces the handler's response. `deliver-anyway` does not:
137
+ * the response goes out, and the Service learns about the refusal through
138
+ * `onMeteringFailed`.
139
+ */
140
+ export type RefusalDisposition = "refuse-request" | "deliver-anyway";
141
+ /** One row of the revert mapping table. */
142
+ export interface RevertMapping {
143
+ /** The `TabError` category, which is also what fixes the HTTP status (design section 13.1). */
144
+ readonly category: ErrorCategory;
145
+ /** Stable and machine-readable, for example `LIMIT_EXCEEDED`. */
146
+ readonly code: string;
147
+ readonly disposition: RefusalDisposition;
148
+ readonly retryable: boolean;
149
+ /**
150
+ * The revert's argument names, positionally. Decoded arguments are attached to
151
+ * `TabError.details` under these names, so the 402 body can carry the required
152
+ * amount and the headroom without this module knowing which row it is on.
153
+ */
154
+ readonly args: readonly string[];
155
+ /** What the Agent or the Service operator is supposed to do about it. */
156
+ readonly action: string;
157
+ }
158
+ /**
159
+ * Every revert `recordDelivery` can produce, and what each one becomes.
160
+ *
161
+ * The status each row maps to is not stored: it is `httpStatusOf(category)`, so
162
+ * `LIMIT` is 402 and nothing else is, which is the whole of design section 9.5's
163
+ * rule expressed as a lookup rather than as a comment.
164
+ */
165
+ export declare const RECORD_DELIVERY_REVERTS: Readonly<Record<string, RevertMapping>>;
166
+ /** The revert name and decoded arguments, as `ethers` v6 reports them. */
167
+ export interface DecodedRevert {
168
+ readonly name: string;
169
+ readonly args?: readonly unknown[];
170
+ }
171
+ /**
172
+ * Pulls a decoded revert out of whatever a client caught.
173
+ *
174
+ * `ethers` v6 attaches `error.revert` as `{ name, signature, args }` when it can
175
+ * match the returndata against the contract's ABI; older shapes carry
176
+ * `errorName` and `errorArgs`; and a plain object with a `name` is what a test
177
+ * or a non-`ethers` client hands over. All three are read, and anything else
178
+ * yields undefined rather than a guess.
179
+ */
180
+ export declare function decodeRevert(thrown: unknown): DecodedRevert | undefined;
181
+ /** The mapping row for one revert name, or undefined when the name is not one of them. */
182
+ export declare const revertMappingFor: (name: string) => RevertMapping | undefined;
183
+ /**
184
+ * Turns a `recordDelivery` revert into the `TabError` the plugin acts on.
185
+ *
186
+ * `details` always carries `revert` and `disposition`, and carries each decoded
187
+ * argument under the name the row gives it. That is how the plugin builds a 402
188
+ * body naming the required amount and the current headroom without a special
189
+ * case for `LimitExceeded`: it reads `details.requested` and `details.headroom`,
190
+ * which the table put there.
191
+ *
192
+ * A revert this table does not name is `CHAIN`/`RECORD_DELIVERY_REVERTED` with
193
+ * `deliver-anyway`, because an unclassified refusal is not something to charge an
194
+ * Agent for and not something to refuse a delivered response over either.
195
+ */
196
+ export declare function classifyRecordDeliveryRevert(thrown: unknown): TabError;
197
+ /** The disposition carried on a classified error, defaulting to the safe one. */
198
+ export declare const dispositionOf: (error: TabError) => RefusalDisposition;
199
+ /** The HTTP status a refusal is served with, taken from its category alone. */
200
+ export declare const refusalStatusOf: (error: TabError) => number;
201
+ /**
202
+ * Reads one decoded argument off a classified error as a `bigint`.
203
+ *
204
+ * Arguments land in `details` as decimal strings, because `TabError.details`
205
+ * holds no `bigint`. This reads one back, and returns undefined rather than
206
+ * `0n` when it is absent, a headroom nobody reported is not a headroom of zero.
207
+ */
208
+ export declare function detailAmount(error: TabError, name: string): bigint | undefined;
209
+ //# sourceMappingURL=metering.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"metering.d.ts","sourceRoot":"","sources":["../../src/server/metering.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,365 @@
1
+ /**
2
+ * The metering seam: what the post-paid plugin calls to record one Metered
3
+ * Delivery, and how a `recordDelivery` revert becomes a `TabError`.
4
+ *
5
+ * Two things live here and they are deliberately separate. {@link TabBookClient}
6
+ * is the interface the plugin depends on, so the plugin has no opinion about
7
+ * `ethers`, an RPC endpoint, a signer, or a witness. {@link RECORD_DELIVERY_REVERTS}
8
+ * is the mapping table from `TabBook.recordDelivery`'s revert set to the
9
+ * `TabError` each one becomes, so every implementation of that interface, the
10
+ * gateway or a Service operator's own client, classifies a refusal the same way
11
+ * rather than each inventing its own status codes.
12
+ *
13
+ * ## The revert set is the real one, read off the contract
14
+ *
15
+ * `recordDelivery(agent, serviceId, asset, tool, units, expectedUnitPrice,
16
+ * witness)` runs six steps in a fixed order, and each step has its own refusals.
17
+ * In call order: the registry read (`UnknownService`), the operator check
18
+ * (`NotServiceOperator`), the delinquency check (`TabIsDelinquent`), pricing
19
+ * (`ZeroUnits`, `UnknownTool`, `PriceListChangedMidCall`, `AmountOutOfRange`),
20
+ * the authorisation (`AuthorisationMissing`, `AuthorisationExpired`,
21
+ * `AuthorisationExceeded`), and the headroom check (`LimitExceeded`, plus the
22
+ * witness-validation errors the Credit Limit computation raises). Every name in
23
+ * {@link RECORD_DELIVERY_REVERTS} is one of those; nothing in the table was
24
+ * guessed from the design.
25
+ *
26
+ * ## Whose fault it is decides who sees it
27
+ *
28
+ * Each row carries a {@link RefusalDisposition}, and that field is the whole
29
+ * policy of this module:
30
+ *
31
+ * - `refuse-request`, the refusal is the Agent's to fix. Its Credit Limit has no
32
+ * headroom, its spending authorisation is missing, lapsed, or spent, or its tab
33
+ * went past its Settlement Window. The Agent is told, with the figures it needs
34
+ * to act on.
35
+ * - `deliver-anyway`, the refusal is the Service's to fix, or ours. A wrong
36
+ * operator key, a tool that is not in the applied price list, a price that
37
+ * moved under the call, a witness the Service assembled wrongly. The Agent did
38
+ * nothing, so the Agent's response is delivered and the Service is told through
39
+ * its own failure channel. A Service whose billing is broken eats the cost of
40
+ * the delivery; it does not hand the cost to the caller.
41
+ *
42
+ * `refuse-request` is the smaller set on purpose. The plugin exists to accrue, not
43
+ * to gate.
44
+ *
45
+ * ## Amounts
46
+ *
47
+ * Every amount here is an integer count of Asset base units as a `bigint`. USDC
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
+ */
52
+ import { httpStatusOf, } from "../_shared/index.js";
53
+ import { tabError } from "../errors.js";
54
+ /**
55
+ * The human-readable ABI fragment of the real entrypoint, field for field.
56
+ *
57
+ * Carried here so a {@link TabBookClient} implementation encodes against the
58
+ * deployed signature rather than a remembered one. The witness is one tuple of
59
+ * two arrays: `LimitLib.SettlementRecord[]` and `LimitLib.BondEntry[]`, in that
60
+ * order.
61
+ */
62
+ export const TAB_BOOK_RECORD_DELIVERY_ABI = [
63
+ "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)",
64
+ ];
65
+ /**
66
+ * Every revert `recordDelivery` can produce, and what each one becomes.
67
+ *
68
+ * 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 of design section 9.5's
70
+ * rule expressed as a lookup rather than as a comment.
71
+ */
72
+ export const RECORD_DELIVERY_REVERTS = {
73
+ // --- the Agent's to fix: these replace the response -----------------------
74
+ /** The one 402. The Agent's Open Tab for the Asset has no room for this charge. */
75
+ LimitExceeded: {
76
+ category: "LIMIT",
77
+ code: "LIMIT_EXCEEDED",
78
+ disposition: "refuse-request",
79
+ retryable: false,
80
+ args: ["agent", "asset", "requested", "headroom"],
81
+ action: "settle the Open Tab, or raise the Credit Limit by growing proven settlement history, then repeat the request",
82
+ },
83
+ AuthorisationMissing: {
84
+ category: "AUTHORISATION",
85
+ code: "AUTHORISATION_MISSING",
86
+ disposition: "refuse-request",
87
+ retryable: false,
88
+ args: ["agent", "serviceId", "asset"],
89
+ action: "the Agent must call TabBook.authorise for this Service and Asset before any delivery is metered",
90
+ },
91
+ AuthorisationExpired: {
92
+ category: "AUTHORISATION",
93
+ code: "AUTHORISATION_EXPIRED",
94
+ disposition: "refuse-request",
95
+ retryable: false,
96
+ args: ["expiry", "nowTs"],
97
+ action: "the Agent must call TabBook.authorise again with a later expiry",
98
+ },
99
+ AuthorisationExceeded: {
100
+ category: "AUTHORISATION",
101
+ code: "AUTHORISATION_EXCEEDED",
102
+ disposition: "refuse-request",
103
+ retryable: false,
104
+ args: ["maxCumulative", "spent", "requested"],
105
+ action: "the Agent must raise maxCumulative on its authorisation",
106
+ },
107
+ /**
108
+ * Past the Settlement Window, so the tab is delinquent and the Agent's Credit
109
+ * Limit for the Asset is zero.
110
+ *
111
+ * A 409 rather than a 402, deliberately. Design section 9.5 fixes 402 to
112
+ * `LimitExceeded` alone on this surface, and delinquency is a different fact
113
+ * from a full tab: the state of the tab conflicts with metering at all, and no
114
+ * headroom figure would make this request succeed. The Agent settles the named
115
+ * tab and the state clears.
116
+ */
117
+ TabIsDelinquent: {
118
+ category: "CONFLICT",
119
+ code: "TAB_DELINQUENT",
120
+ disposition: "refuse-request",
121
+ retryable: false,
122
+ args: ["tabId"],
123
+ action: "the Agent must settle the named tab; its Credit Limit for this Asset is zero until it does",
124
+ },
125
+ // --- the Service's to fix, or ours: the response is delivered anyway ------
126
+ /**
127
+ * The applied price moved between the Service's quote and the charge.
128
+ *
129
+ * The Service's problem, not the Agent's: the Agent was quoted a price and the
130
+ * work was delivered at it. Retryable once with the price the registry now
131
+ * serves.
132
+ */
133
+ PriceListChangedMidCall: {
134
+ category: "CONFLICT",
135
+ code: "PRICE_LIST_CHANGED_MID_CALL",
136
+ disposition: "deliver-anyway",
137
+ retryable: true,
138
+ args: ["serviceId", "asset", "tool", "quoted", "applied"],
139
+ action: "re-quote from the applied price list and record the delivery again",
140
+ },
141
+ UnknownTool: {
142
+ category: "NOT_FOUND",
143
+ code: "UNKNOWN_TOOL",
144
+ disposition: "deliver-anyway",
145
+ retryable: false,
146
+ args: ["serviceId", "asset", "tool"],
147
+ action: "register the tool in the Service's price list; a priced unit that is not listed cannot be charged",
148
+ },
149
+ UnknownService: {
150
+ category: "NOT_FOUND",
151
+ code: "UNKNOWN_SERVICE",
152
+ disposition: "deliver-anyway",
153
+ retryable: false,
154
+ args: ["serviceId"],
155
+ action: "register the Service before metering into it",
156
+ },
157
+ NotServiceOperator: {
158
+ category: "AUTHORISATION",
159
+ code: "NOT_SERVICE_OPERATOR",
160
+ disposition: "deliver-anyway",
161
+ retryable: false,
162
+ args: ["serviceId", "caller"],
163
+ action: "meter with the Service operator's own Monad key",
164
+ },
165
+ ZeroUnits: {
166
+ category: "VALIDATION",
167
+ code: "ZERO_UNITS",
168
+ disposition: "deliver-anyway",
169
+ retryable: false,
170
+ args: [],
171
+ action: "price the delivery at one unit or more, or treat it as unbillable and record nothing",
172
+ },
173
+ AmountOutOfRange: {
174
+ category: "VALIDATION",
175
+ code: "AMOUNT_OUT_OF_RANGE",
176
+ disposition: "deliver-anyway",
177
+ retryable: false,
178
+ args: ["amount"],
179
+ action: "reduce the unit count; the Open Tab is kept in uint128 base units",
180
+ },
181
+ HistoryCommitmentMismatch: {
182
+ category: "CONFLICT",
183
+ code: "HISTORY_COMMITMENT_MISMATCH",
184
+ disposition: "deliver-anyway",
185
+ retryable: true,
186
+ args: ["expected", "provided"],
187
+ action: "refresh the witness from chain state and record the delivery again",
188
+ },
189
+ HistoryLengthMismatch: {
190
+ category: "CONFLICT",
191
+ code: "HISTORY_LENGTH_MISMATCH",
192
+ disposition: "deliver-anyway",
193
+ retryable: true,
194
+ args: ["expected", "provided"],
195
+ action: "refresh the witness from chain state and record the delivery again",
196
+ },
197
+ DuplicateBondEntry: {
198
+ category: "INTERNAL",
199
+ code: "DUPLICATE_BOND_ENTRY",
200
+ disposition: "deliver-anyway",
201
+ retryable: false,
202
+ args: ["serviceId"],
203
+ action: "fix the witness builder: one Bond entry per counterparty Service",
204
+ },
205
+ IneligibleBondEntry: {
206
+ category: "INTERNAL",
207
+ code: "INELIGIBLE_BOND_ENTRY",
208
+ disposition: "deliver-anyway",
209
+ retryable: false,
210
+ args: ["serviceId", "asset"],
211
+ action: "fix the witness builder: every Bond entry must name a counterparty from the history",
212
+ },
213
+ TooManyBondEntries: {
214
+ category: "INTERNAL",
215
+ code: "TOO_MANY_BOND_ENTRIES",
216
+ disposition: "deliver-anyway",
217
+ retryable: false,
218
+ args: ["count", "maximum"],
219
+ action: "compact the history; this is the documented scaling limit of the pure computation",
220
+ },
221
+ HistoryTooLong: {
222
+ category: "INTERNAL",
223
+ code: "HISTORY_TOO_LONG",
224
+ disposition: "deliver-anyway",
225
+ retryable: false,
226
+ args: ["count", "maximum"],
227
+ action: "compact the history; this is the documented scaling limit of the pure computation",
228
+ },
229
+ TooManyCounterparties: {
230
+ category: "INTERNAL",
231
+ code: "TOO_MANY_COUNTERPARTIES",
232
+ disposition: "deliver-anyway",
233
+ retryable: false,
234
+ args: ["count", "maximum"],
235
+ action: "compact the history; this is the documented scaling limit of the pure computation",
236
+ },
237
+ };
238
+ /**
239
+ * Pulls a decoded revert out of whatever a client caught.
240
+ *
241
+ * `ethers` v6 attaches `error.revert` as `{ name, signature, args }` when it can
242
+ * match the returndata against the contract's ABI; older shapes carry
243
+ * `errorName` and `errorArgs`; and a plain object with a `name` is what a test
244
+ * or a non-`ethers` client hands over. All three are read, and anything else
245
+ * yields undefined rather than a guess.
246
+ */
247
+ export function decodeRevert(thrown) {
248
+ if (typeof thrown !== "object" || thrown === null)
249
+ return undefined;
250
+ const candidate = thrown;
251
+ const revert = candidate.revert;
252
+ if (revert !== undefined && revert !== null && typeof revert.name === "string") {
253
+ return { name: revert.name, ...(Array.isArray(revert.args) ? { args: revert.args } : {}) };
254
+ }
255
+ if (typeof candidate.errorName === "string") {
256
+ return {
257
+ name: candidate.errorName,
258
+ ...(Array.isArray(candidate.errorArgs) ? { args: candidate.errorArgs } : {}),
259
+ };
260
+ }
261
+ if (typeof candidate.name === "string" && RECORD_DELIVERY_REVERTS[candidate.name] !== undefined) {
262
+ return { name: candidate.name, ...(Array.isArray(candidate.args) ? { args: candidate.args } : {}) };
263
+ }
264
+ return undefined;
265
+ }
266
+ /** The mapping row for one revert name, or undefined when the name is not one of them. */
267
+ export const revertMappingFor = (name) => RECORD_DELIVERY_REVERTS[name];
268
+ /**
269
+ * Turns a `recordDelivery` revert into the `TabError` the plugin acts on.
270
+ *
271
+ * `details` always carries `revert` and `disposition`, and carries each decoded
272
+ * argument under the name the row gives it. That is how the plugin builds a 402
273
+ * body naming the required amount and the current headroom without a special
274
+ * case for `LimitExceeded`: it reads `details.requested` and `details.headroom`,
275
+ * which the table put there.
276
+ *
277
+ * A revert this table does not name is `CHAIN`/`RECORD_DELIVERY_REVERTED` with
278
+ * `deliver-anyway`, because an unclassified refusal is not something to charge an
279
+ * Agent for and not something to refuse a delivered response over either.
280
+ */
281
+ export function classifyRecordDeliveryRevert(thrown) {
282
+ const decoded = decodeRevert(thrown);
283
+ const mapping = decoded === undefined ? undefined : revertMappingFor(decoded.name);
284
+ const cause = causeFrom(thrown);
285
+ const carriedCause = cause === undefined ? {} : { cause };
286
+ if (decoded === undefined || mapping === undefined) {
287
+ return tabError("CHAIN", "RECORD_DELIVERY_REVERTED", decoded === undefined
288
+ ? "recordDelivery failed and the returndata matched no known revert"
289
+ : `recordDelivery reverted \`${decoded.name}\`, which is not a revert this SDK version classifies`, {
290
+ retryable: false,
291
+ details: {
292
+ disposition: "deliver-anyway",
293
+ ...(decoded === undefined ? {} : { revert: decoded.name }),
294
+ },
295
+ ...carriedCause,
296
+ });
297
+ }
298
+ return tabError(mapping.category, mapping.code, `recordDelivery reverted \`${decoded.name}\`: ${mapping.action}`, {
299
+ retryable: mapping.retryable,
300
+ details: {
301
+ revert: decoded.name,
302
+ disposition: mapping.disposition,
303
+ ...namedArgs(mapping.args, decoded.args),
304
+ },
305
+ ...carriedCause,
306
+ });
307
+ }
308
+ /** The disposition carried on a classified error, defaulting to the safe one. */
309
+ export const dispositionOf = (error) => error.details?.["disposition"] === "refuse-request" ? "refuse-request" : "deliver-anyway";
310
+ /** The HTTP status a refusal is served with, taken from its category alone. */
311
+ export const refusalStatusOf = (error) => httpStatusOf(error);
312
+ /**
313
+ * Reads one decoded argument off a classified error as a `bigint`.
314
+ *
315
+ * Arguments land in `details` as decimal strings, because `TabError.details`
316
+ * holds no `bigint`. This reads one back, and returns undefined rather than
317
+ * `0n` when it is absent, a headroom nobody reported is not a headroom of zero.
318
+ */
319
+ export function detailAmount(error, name) {
320
+ const raw = error.details?.[name];
321
+ if (typeof raw === "bigint")
322
+ return raw;
323
+ if (typeof raw === "number" && Number.isSafeInteger(raw))
324
+ return BigInt(raw);
325
+ if (typeof raw !== "string" || !/^\d+$/.test(raw))
326
+ return undefined;
327
+ return BigInt(raw);
328
+ }
329
+ /** Names the positional revert arguments and renders each into `details`. */
330
+ function namedArgs(names, args) {
331
+ if (args === undefined)
332
+ return {};
333
+ const named = {};
334
+ for (const [index, name] of names.entries()) {
335
+ if (index >= args.length)
336
+ break;
337
+ const value = args[index];
338
+ if (typeof value === "bigint")
339
+ named[name] = value.toString(10);
340
+ else if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") {
341
+ named[name] = value;
342
+ }
343
+ else if (value !== undefined && value !== null)
344
+ named[name] = String(value);
345
+ }
346
+ return named;
347
+ }
348
+ /** Narrows a caught value into the `cause` shape a `TabError` accepts. */
349
+ function causeFrom(thrown) {
350
+ if (thrown instanceof Error)
351
+ return { code: thrown.name, message: thrown.message };
352
+ if (typeof thrown === "object" && thrown !== null) {
353
+ const candidate = thrown;
354
+ const message = typeof candidate.shortMessage === "string"
355
+ ? candidate.shortMessage
356
+ : typeof candidate.message === "string"
357
+ ? candidate.message
358
+ : undefined;
359
+ if (message === undefined)
360
+ return undefined;
361
+ return { code: typeof candidate.code === "string" ? candidate.code : "UNKNOWN", message };
362
+ }
363
+ return undefined;
364
+ }
365
+ //# sourceMappingURL=metering.js.map