metered-protocol 1.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (206) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +103 -0
  3. package/contracts/metered.ag +135 -0
  4. package/contracts/metered_ag.tests.json +1226 -0
  5. package/contracts/metered_session.sil +237 -0
  6. package/contracts/metered_session.tests.json +1113 -0
  7. package/dist/contracts/metered.ag +135 -0
  8. package/dist/contracts/metered_ag.tests.json +1226 -0
  9. package/dist/contracts/metered_session.sil +237 -0
  10. package/dist/contracts/metered_session.tests.json +1113 -0
  11. package/dist/src/bias.d.ts +48 -0
  12. package/dist/src/bias.d.ts.map +1 -0
  13. package/dist/src/bias.js +43 -0
  14. package/dist/src/bias.js.map +1 -0
  15. package/dist/src/checkpoint.d.ts +57 -0
  16. package/dist/src/checkpoint.d.ts.map +1 -0
  17. package/dist/src/checkpoint.js +84 -0
  18. package/dist/src/checkpoint.js.map +1 -0
  19. package/dist/src/deadline.d.ts +71 -0
  20. package/dist/src/deadline.d.ts.map +1 -0
  21. package/dist/src/deadline.js +77 -0
  22. package/dist/src/deadline.js.map +1 -0
  23. package/dist/src/encoding.d.ts +72 -0
  24. package/dist/src/encoding.d.ts.map +1 -0
  25. package/dist/src/encoding.js +187 -0
  26. package/dist/src/encoding.js.map +1 -0
  27. package/dist/src/history.d.ts +33 -0
  28. package/dist/src/history.d.ts.map +1 -0
  29. package/dist/src/history.js +38 -0
  30. package/dist/src/history.js.map +1 -0
  31. package/dist/src/http/buyer.d.ts +57 -0
  32. package/dist/src/http/buyer.d.ts.map +1 -0
  33. package/dist/src/http/buyer.js +151 -0
  34. package/dist/src/http/buyer.js.map +1 -0
  35. package/dist/src/http/client.d.ts +55 -0
  36. package/dist/src/http/client.d.ts.map +1 -0
  37. package/dist/src/http/client.js +83 -0
  38. package/dist/src/http/client.js.map +1 -0
  39. package/dist/src/http/harness.d.ts +18 -0
  40. package/dist/src/http/harness.d.ts.map +1 -0
  41. package/dist/src/http/harness.js +16 -0
  42. package/dist/src/http/harness.js.map +1 -0
  43. package/dist/src/http/protocol.d.ts +84 -0
  44. package/dist/src/http/protocol.d.ts.map +1 -0
  45. package/dist/src/http/protocol.js +22 -0
  46. package/dist/src/http/protocol.js.map +1 -0
  47. package/dist/src/http/provider.d.ts +123 -0
  48. package/dist/src/http/provider.d.ts.map +1 -0
  49. package/dist/src/http/provider.js +197 -0
  50. package/dist/src/http/provider.js.map +1 -0
  51. package/dist/src/http/serve.d.ts +34 -0
  52. package/dist/src/http/serve.d.ts.map +1 -0
  53. package/dist/src/http/serve.js +171 -0
  54. package/dist/src/http/serve.js.map +1 -0
  55. package/dist/src/http/service.d.ts +86 -0
  56. package/dist/src/http/service.d.ts.map +1 -0
  57. package/dist/src/http/service.js +161 -0
  58. package/dist/src/http/service.js.map +1 -0
  59. package/dist/src/index.d.ts +68 -0
  60. package/dist/src/index.d.ts.map +1 -0
  61. package/dist/src/index.js +62 -0
  62. package/dist/src/index.js.map +1 -0
  63. package/dist/src/meter.d.ts +39 -0
  64. package/dist/src/meter.d.ts.map +1 -0
  65. package/dist/src/meter.js +100 -0
  66. package/dist/src/meter.js.map +1 -0
  67. package/dist/src/offer.d.ts +6 -0
  68. package/dist/src/offer.d.ts.map +1 -0
  69. package/dist/src/offer.js +92 -0
  70. package/dist/src/offer.js.map +1 -0
  71. package/dist/src/reconcile.d.ts +18 -0
  72. package/dist/src/reconcile.d.ts.map +1 -0
  73. package/dist/src/reconcile.js +101 -0
  74. package/dist/src/reconcile.js.map +1 -0
  75. package/dist/src/reservation.d.ts +51 -0
  76. package/dist/src/reservation.d.ts.map +1 -0
  77. package/dist/src/reservation.js +124 -0
  78. package/dist/src/reservation.js.map +1 -0
  79. package/dist/src/session.d.ts +40 -0
  80. package/dist/src/session.d.ts.map +1 -0
  81. package/dist/src/session.js +92 -0
  82. package/dist/src/session.js.map +1 -0
  83. package/dist/src/signer.d.ts +31 -0
  84. package/dist/src/signer.d.ts.map +1 -0
  85. package/dist/src/signer.js +57 -0
  86. package/dist/src/signer.js.map +1 -0
  87. package/dist/src/store.d.ts +30 -0
  88. package/dist/src/store.d.ts.map +1 -0
  89. package/dist/src/store.js +120 -0
  90. package/dist/src/store.js.map +1 -0
  91. package/dist/src/types.d.ts +80 -0
  92. package/dist/src/types.d.ts.map +1 -0
  93. package/dist/src/types.js +9 -0
  94. package/dist/src/types.js.map +1 -0
  95. package/dist/tools/anchor-cli.d.ts +2 -0
  96. package/dist/tools/anchor-cli.d.ts.map +1 -0
  97. package/dist/tools/anchor-cli.js +101 -0
  98. package/dist/tools/anchor-cli.js.map +1 -0
  99. package/dist/tools/anchor.d.ts +68 -0
  100. package/dist/tools/anchor.d.ts.map +1 -0
  101. package/dist/tools/anchor.js +136 -0
  102. package/dist/tools/anchor.js.map +1 -0
  103. package/dist/tools/chain.d.ts +18 -0
  104. package/dist/tools/chain.d.ts.map +1 -0
  105. package/dist/tools/chain.js +18 -0
  106. package/dist/tools/chain.js.map +1 -0
  107. package/dist/tools/conformance.d.ts +2 -0
  108. package/dist/tools/conformance.d.ts.map +1 -0
  109. package/dist/tools/conformance.js +220 -0
  110. package/dist/tools/conformance.js.map +1 -0
  111. package/dist/tools/contracts.d.ts +2 -0
  112. package/dist/tools/contracts.d.ts.map +1 -0
  113. package/dist/tools/contracts.js +68 -0
  114. package/dist/tools/contracts.js.map +1 -0
  115. package/dist/tools/covenant-binding.d.ts +40 -0
  116. package/dist/tools/covenant-binding.d.ts.map +1 -0
  117. package/dist/tools/covenant-binding.js +60 -0
  118. package/dist/tools/covenant-binding.js.map +1 -0
  119. package/dist/tools/covenant-profile.d.ts +41 -0
  120. package/dist/tools/covenant-profile.d.ts.map +1 -0
  121. package/dist/tools/covenant-profile.js +74 -0
  122. package/dist/tools/covenant-profile.js.map +1 -0
  123. package/dist/tools/covenant.d.ts +30 -0
  124. package/dist/tools/covenant.d.ts.map +1 -0
  125. package/dist/tools/covenant.js +54 -0
  126. package/dist/tools/covenant.js.map +1 -0
  127. package/dist/tools/demo-report.d.ts +24 -0
  128. package/dist/tools/demo-report.d.ts.map +1 -0
  129. package/dist/tools/demo-report.js +40 -0
  130. package/dist/tools/demo-report.js.map +1 -0
  131. package/dist/tools/demo.d.ts +2 -0
  132. package/dist/tools/demo.d.ts.map +1 -0
  133. package/dist/tools/demo.js +204 -0
  134. package/dist/tools/demo.js.map +1 -0
  135. package/dist/tools/dust-map.d.ts +2 -0
  136. package/dist/tools/dust-map.d.ts.map +1 -0
  137. package/dist/tools/dust-map.js +63 -0
  138. package/dist/tools/dust-map.js.map +1 -0
  139. package/dist/tools/expire-cases.d.ts +4 -0
  140. package/dist/tools/expire-cases.d.ts.map +1 -0
  141. package/dist/tools/expire-cases.js +211 -0
  142. package/dist/tools/expire-cases.js.map +1 -0
  143. package/dist/tools/fee-check.d.ts +2 -0
  144. package/dist/tools/fee-check.d.ts.map +1 -0
  145. package/dist/tools/fee-check.js +127 -0
  146. package/dist/tools/fee-check.js.map +1 -0
  147. package/dist/tools/fixtures.d.ts +39 -0
  148. package/dist/tools/fixtures.d.ts.map +1 -0
  149. package/dist/tools/fixtures.js +31 -0
  150. package/dist/tools/fixtures.js.map +1 -0
  151. package/dist/tools/gen-contract-tests.d.ts +2 -0
  152. package/dist/tools/gen-contract-tests.d.ts.map +1 -0
  153. package/dist/tools/gen-contract-tests.js +170 -0
  154. package/dist/tools/gen-contract-tests.js.map +1 -0
  155. package/dist/tools/kaspa.d.ts +13 -0
  156. package/dist/tools/kaspa.d.ts.map +1 -0
  157. package/dist/tools/kaspa.js +76 -0
  158. package/dist/tools/kaspa.js.map +1 -0
  159. package/dist/tools/live-deadline.d.ts +2 -0
  160. package/dist/tools/live-deadline.d.ts.map +1 -0
  161. package/dist/tools/live-deadline.js +122 -0
  162. package/dist/tools/live-deadline.js.map +1 -0
  163. package/dist/tools/live-expire.d.ts +2 -0
  164. package/dist/tools/live-expire.d.ts.map +1 -0
  165. package/dist/tools/live-expire.js +127 -0
  166. package/dist/tools/live-expire.js.map +1 -0
  167. package/dist/tools/live-settle.d.ts +2 -0
  168. package/dist/tools/live-settle.d.ts.map +1 -0
  169. package/dist/tools/live-settle.js +226 -0
  170. package/dist/tools/live-settle.js.map +1 -0
  171. package/dist/tools/live-steps.d.ts +63 -0
  172. package/dist/tools/live-steps.d.ts.map +1 -0
  173. package/dist/tools/live-steps.js +155 -0
  174. package/dist/tools/live-steps.js.map +1 -0
  175. package/dist/tools/model.d.ts +32 -0
  176. package/dist/tools/model.d.ts.map +1 -0
  177. package/dist/tools/model.js +99 -0
  178. package/dist/tools/model.js.map +1 -0
  179. package/dist/tools/send.d.ts +2 -0
  180. package/dist/tools/send.d.ts.map +1 -0
  181. package/dist/tools/send.js +41 -0
  182. package/dist/tools/send.js.map +1 -0
  183. package/dist/tools/session-chain.d.ts +69 -0
  184. package/dist/tools/session-chain.d.ts.map +1 -0
  185. package/dist/tools/session-chain.js +113 -0
  186. package/dist/tools/session-chain.js.map +1 -0
  187. package/dist/tools/sighash.d.ts +9 -0
  188. package/dist/tools/sighash.d.ts.map +1 -0
  189. package/dist/tools/sighash.js +68 -0
  190. package/dist/tools/sighash.js.map +1 -0
  191. package/dist/tools/sigscript-check.d.ts +2 -0
  192. package/dist/tools/sigscript-check.d.ts.map +1 -0
  193. package/dist/tools/sigscript-check.js +113 -0
  194. package/dist/tools/sigscript-check.js.map +1 -0
  195. package/dist/tools/sigscript.d.ts +61 -0
  196. package/dist/tools/sigscript.d.ts.map +1 -0
  197. package/dist/tools/sigscript.js +70 -0
  198. package/dist/tools/sigscript.js.map +1 -0
  199. package/dist/tools/state-layout.d.ts +2 -0
  200. package/dist/tools/state-layout.d.ts.map +1 -0
  201. package/dist/tools/state-layout.js +57 -0
  202. package/dist/tools/state-layout.js.map +1 -0
  203. package/package.json +59 -0
  204. package/spec/CONFORMANCE.md +80 -0
  205. package/spec/SPEC.md +821 -0
  206. package/spec/conformance-vectors.json +677 -0
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 The metered authors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,103 @@
1
+ # metered
2
+
3
+ **A payment protocol for work whose size is only known once it has been delivered.**
4
+
5
+ You cannot price a language model's answer before it writes one. Today that is settled by the
6
+ seller reporting what it used, against a cap the buyer set in advance — the buyer never verifies
7
+ anything, and for a fifth of a cent, disputing it is not worth anyone's time.
8
+
9
+ `metered` replaces the invoice with an agreement. The buyer authorises one slice at a time, both
10
+ sides count what was actually delivered, and a small program on Kaspa releases the money whether or
11
+ not either party cooperates at the end.
12
+
13
+ **The buyer counts what it was given — not what it was told about.** Everything else here is
14
+ bookkeeping around that sentence.
15
+
16
+ - **[Read the explainer](https://kaspahttp402.github.io/metered-protocol/)** — the idea, in plain
17
+ terms, for people who are not going to read a specification.
18
+ - **[Read the specification](spec/SPEC.md)** — normative.
19
+ - **[Implement it](spec/CONFORMANCE.md)** — inputs and exact outputs, in a form no implementation
20
+ can pass by accident.
21
+
22
+ ---
23
+
24
+ ## The two ideas
25
+
26
+ **One babel at a time.** A *babel* is one reserved slice of delivered work. The buyer authorises a
27
+ babel, receives it, counts it, agrees it, and only then authorises the next. The babel is the
28
+ exposure bound: the most either side can lose if the other turns dishonest mid-session, fixed
29
+ before anything is spent.
30
+
31
+ **Both sides count.** After each babel the buyer measures it too, with the same *meter* the seller
32
+ named up front. The seller's figure stops being an invoice and becomes a claim standing next
33
+ to the buyer's. If they agree within tolerance, the lower figure is billed. If they do not, the
34
+ session stops — there is no arbiter, because an arbiter would have to be trusted.
35
+
36
+ Neither party is trusted for anything. The arithmetic does not leave room.
37
+
38
+ ## What is here
39
+
40
+ | | |
41
+ |---|---|
42
+ | `spec/SPEC.md` | the protocol, normatively |
43
+ | `spec/CONFORMANCE.md` | how to check an implementation, and the four traps that catch people |
44
+ | `spec/conformance-vectors.json` | 36 cases: canonical bytes, the settlement preimage, signatures, reconciliation, settlement, meters |
45
+ | `spec/worked-example.txt` | a complete session, generated, with real signatures |
46
+ | `src/` | reference implementation, TypeScript |
47
+ | `impl-py/` | a second implementation, Python, written from the specification alone |
48
+ | `contracts/` | the covenant, twice: hand-written SilverScript and an Argent port |
49
+ | `evidence/` | the measurements behind every number in the specification |
50
+ | `tools/` | chain tooling — settle, close, anchor, and an end-to-end demo |
51
+
52
+ ## Verifying it yourself
53
+
54
+ ```bash
55
+ npm install
56
+ npm test # 193 tests
57
+ npm run conformance # regenerate the vectors
58
+ npm run conformance:py # the second implementation, against the same file
59
+ ```
60
+
61
+ `impl-py/` shares no code with `src/`. It was written from `spec/SPEC.md` and implements BIP340
62
+ verification from the BIP rather than importing it, so the two agree on the specification rather
63
+ than on a shared library. All 69 assertions pass.
64
+
65
+ The covenant suites need [SilverScript](https://github.com/kaspanet/silverscript) and a patched
66
+ debugger; the chain tooling additionally needs a rusty-kaspa WASM build (`METERED_KASPA_SDK`) and a
67
+ funded key. Neither is required to read the specification or run the conformance vectors.
68
+
69
+ ## Status
70
+
71
+ Both covenants and the full session flow run on **Kaspa testnet-10**. The protocol has not been
72
+ deployed to mainnet: SilverScript is unaudited and Argent is pre-release, and the covenant holds
73
+ funds.
74
+
75
+ What has been exercised end to end:
76
+
77
+ - A language model metered through a complete session and settled on chain, with the model's own
78
+ usage figure, the seller's count and the buyer's count agreeing exactly.
79
+ - Both covenants executing under consensus — a claim posted, a stale claim refused by the network,
80
+ a newer claim superseding it, the balance split between the parties, and a full refund where no
81
+ claim was made.
82
+ - Two implementations agreeing on every conformance vector.
83
+ - The tokeniser pinned across languages, token boundary by token boundary, against Python
84
+ `tiktoken` over mixed scripts, emoji, combining marks and pathological whitespace.
85
+ - Two different units settled through the same unchanged protocol — tokens and delivered bytes —
86
+ which is what distinguishes a unit-agnostic design from one that merely uses abstract field names.
87
+
88
+ Figures: the covenant is **504 bytes** of a 520-byte script limit; a State is **72 bytes** signed;
89
+ a checkpoint costs **0.002 KAS**; the response window is **600 blocks, about 60 seconds** at the
90
+ roughly 10 blocks per second Kaspa produces. The same 600-block window is over four days at
91
+ ten-minute block times, which is why the settlement layer is a blockDAG.
92
+
93
+ ## Prior art
94
+
95
+ `metered` adds the two pieces [x402](https://github.com/coinbase/x402) names as out of scope:
96
+ reservation in chunks so a session can span many settlements, and two-sided measurement so the
97
+ buyer's own count is part of what settles. The chunked-quota idea is older still — telephone
98
+ networks standardised it in RFC 4006 for the same reason, that metering every unit is too expensive
99
+ and trusting the total is too risky. See [PRIOR-ART.md](PRIOR-ART.md).
100
+
101
+ ## Licence
102
+
103
+ MIT. See [LICENSE](LICENSE).
@@ -0,0 +1,135 @@
1
+ // The metered session covenant, in Argent.
2
+ //
3
+ // A port of contracts/metered_session.sil, which is hand-written SilverScript at 520 of 520 bytes
4
+ // with zero spare. Argent compiles to SilverScript, so this does not replace that toolchain --
5
+ // it sits above it. The reason to move is not novelty:
6
+ //
7
+ // TWO OF THE FIVE FUND-LOSING BUGS FOUND IN THE HAND-WRITTEN VERSION CANNOT BE WRITTEN HERE.
8
+ //
9
+ // Finding H a one-output close constrained only outputs[0] and left the rest to fee, so a
10
+ // provider could burn the buyer's refund. `emits` declares the COMPLETE output
11
+ // shape, and Argent requires every declared output's value to be disposed of.
12
+ // Finding J two covenant inputs could satisfy one payout, destroying the other's balance.
13
+ // Input-group accounting is what the leader/delegate rules exist for, and
14
+ // `emits {}` lowers to a consensus-level `OpAuthOutputCount(...) == 0`.
15
+ //
16
+ // WHAT DOES NOT CHANGE. Argent proves the actor, template and successor-state relationships.
17
+ // Application rules stay explicit `require(...)` conditions, so every measured constant below --
18
+ // the 400,000 fee allowance, the 2,000,000 dust floor, the 2,400,000 fold threshold -- is the
19
+ // same number for the same reason, each measured rather than chosen.
20
+
21
+ state SessionState {
22
+ // Immutable for the life of the session; carried forward unchanged by every `become`.
23
+ // In the SilverScript version these were constructor constants outside the state span.
24
+ // Here they are state, which means the compiler CHECKS that a continuation preserves them
25
+ // rather than trusting the author to re-emit them correctly.
26
+ byte[32] parties;
27
+ byte[16] session_id;
28
+ int window;
29
+
30
+ // The pending claim. SPEC.md 7.4: integers only.
31
+ int pending_seq;
32
+ int pending_sompi;
33
+ }
34
+
35
+ actor MeteredSession owns SessionState {
36
+ // POST A CLAIM (SPEC.md 7.2 `settle`). Pays nobody: it hands the funds back to this same
37
+ // actor carrying the agreed State, and restarts the response window. Supersede is the seq
38
+ // guard, not a separate entry.
39
+ entry settle(
40
+ pubkey buyer,
41
+ pubkey provider,
42
+ datasig buyer_sig,
43
+ datasig provider_sig,
44
+ int seq,
45
+ int cumulative_units,
46
+ int cumulative_sompi,
47
+ byte[32] prev_state
48
+ ) emits next: MeteredSession {
49
+ require(blake3(byte[](buyer) + byte[](provider)) == parties);
50
+
51
+ // Strictly higher, so a stale State cannot replace a fresher one. pending_seq starts at
52
+ // -1, so this also implies seq >= 0 on every reachable path.
53
+ require(seq > pending_seq);
54
+
55
+ // cumulative_sompi is load-bearing: it becomes pending_sompi, whose value decides the
56
+ // close shape, and it is the amount actually paid out. The other two need no lower bound
57
+ // -- each omission below is argued rather than assumed.
58
+ require(cumulative_sompi >= 0);
59
+
60
+ // SPEC.md 3.4.1: sessionId(16) + seq(8) + cumUnits(8) + cumSompi(8) + prevState(32) = 72.
61
+ byte[32] digest = blake3(
62
+ byte[](session_id)
63
+ + OpNum2Bin(seq, 8)
64
+ + OpNum2Bin(cumulative_units, 8)
65
+ + OpNum2Bin(cumulative_sompi, 8)
66
+ + byte[](prev_state)
67
+ );
68
+ require(checkMsgSig(buyer_sig, digest, buyer));
69
+ require(checkMsgSig(provider_sig, digest, provider));
70
+
71
+ SessionState next_state = {
72
+ parties: parties,
73
+ session_id: session_id,
74
+ window: window,
75
+ pending_seq: seq,
76
+ pending_sompi: cumulative_sompi,
77
+ };
78
+
79
+ // Written as an addition, never `self.value - fee`: subtraction can underflow, underflow
80
+ // is undefined, and SilverScript's undefined behaviour is fail-OPEN.
81
+ require(next.value + 400000 >= self.value);
82
+ become next <- MeteredSession(next_state);
83
+ }
84
+
85
+ // FINALISE (SPEC.md 7.2 `expire`). No successor: the session ends and the money is paid to
86
+ // the parties' own keys. `emits {}` is what says so, and lowers to a consensus check that
87
+ // this input authorises no continuation output at all.
88
+ entry expire(pubkey buyer, pubkey provider, sig party_sig) emits {} {
89
+ require(blake3(byte[](buyer) + byte[](provider)) == parties);
90
+
91
+ // SPEC.md 7.3: the window is RELATIVE, so ageDaa lowers to OpCheckSequenceVerify and
92
+ // reads the spending input's sequence field.
93
+ require(this.ageDaa >= window);
94
+ require(checkSig(party_sig, buyer) || checkSig(party_sig, provider));
95
+
96
+ // SPEC.md 7.2a, Finding J. This covenant accounts for exactly one input -- its own -- and
97
+ // the payouts below go to plain keys that carry no session identifier, so two sessions
98
+ // between the same parties would otherwise share one payout and destroy the other's
99
+ // balance. Measured before the rule existed: 10,400,000 sompi burned.
100
+ require(tx.inputs.length == 1);
101
+
102
+ byte[36] buyer_lock = new ScriptPubKeyP2PK(buyer);
103
+
104
+ // DUST (SPEC.md 7.4a). KIP-9 prices an output by its reciprocal, so MEASURED: see tools/dust-map.ts. Whichever side's share is dust, the other
105
+ // takes the lot: folding costs that party at most 0.02 KAS, and the alternative is
106
+ // locking the whole balance forever, which is what Finding G actually did.
107
+ // DUST (SPEC.md 7.4a), measured by tools/dust-map.ts rather than reasoned: the KIP-9
108
+ // floor is not a constant, and 2,000,000 left an unclosable window at every balance.
109
+ if (pending_sompi < 2600000) {
110
+ // The provider earned dust, which includes earning nothing. SPEC.md 7.2: expire
111
+ // "refunds the buyer entirely if none".
112
+ require(tx.outputs.length == 1);
113
+ require(tx.outputs[0].scriptPubKey == byte[](buyer_lock));
114
+ require(tx.outputs[0].value + 400000 >= self.value);
115
+ } else {
116
+ byte[36] provider_lock = new ScriptPubKeyP2PK(provider);
117
+ require(tx.outputs[0].value == pending_sompi);
118
+ require(tx.outputs[0].scriptPubKey == byte[](provider_lock));
119
+ if (tx.outputs.length == 1) {
120
+ // The buyer's refund is dust, so it goes to fee. The threshold is 2,400,000 and
121
+ // not 2,000,000 because the refund is self.value - pending_sompi - fee: the
122
+ // smaller bound would leave 1,600,000..2,000,000 with no legal shape at all.
123
+ require(pending_sompi + 3000000 >= self.value);
124
+ } else {
125
+ require(tx.outputs.length == 2);
126
+ require(tx.outputs[1].scriptPubKey == byte[](buyer_lock));
127
+ require(tx.outputs[1].value + pending_sompi + 400000 >= self.value);
128
+ }
129
+ }
130
+ }
131
+ }
132
+
133
+ app Metered {
134
+ actor MeteredSession;
135
+ }