@dexterai/x402 3.8.1 → 3.9.1

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 (50) hide show
  1. package/README.md +313 -702
  2. package/dist/adapters/index.cjs +1 -1
  3. package/dist/adapters/index.d.cts +3 -3
  4. package/dist/adapters/index.d.ts +3 -3
  5. package/dist/adapters/index.js +1 -1
  6. package/dist/batch-settlement/index.d.cts +3 -3
  7. package/dist/batch-settlement/index.d.ts +3 -3
  8. package/dist/batch-settlement/seller/index.d.cts +4 -4
  9. package/dist/batch-settlement/seller/index.d.ts +4 -4
  10. package/dist/client/index.cjs +1 -1
  11. package/dist/client/index.d.cts +308 -238
  12. package/dist/client/index.d.ts +308 -238
  13. package/dist/client/index.js +1 -1
  14. package/dist/{constants-qU-4U3L-.d.cts → constants-D41hDAG6.d.cts} +13 -1
  15. package/dist/{constants-qU-4U3L-.d.ts → constants-D41hDAG6.d.ts} +13 -1
  16. package/dist/react/index.cjs +1 -1
  17. package/dist/react/index.d.cts +12 -4
  18. package/dist/react/index.d.ts +12 -4
  19. package/dist/react/index.js +1 -1
  20. package/dist/server/index.cjs +3 -3
  21. package/dist/server/index.d.cts +63 -5
  22. package/dist/server/index.d.ts +63 -5
  23. package/dist/server/index.js +3 -3
  24. package/dist/tab/adapters/solana/index.cjs +1 -0
  25. package/dist/tab/adapters/solana/index.d.cts +123 -0
  26. package/dist/tab/adapters/solana/index.d.ts +123 -0
  27. package/dist/tab/adapters/solana/index.js +1 -0
  28. package/dist/tab/index.cjs +6 -0
  29. package/dist/tab/index.d.cts +29 -0
  30. package/dist/tab/index.d.ts +29 -0
  31. package/dist/tab/index.js +6 -0
  32. package/dist/tab/seller/index.cjs +6 -0
  33. package/dist/tab/seller/index.d.cts +291 -0
  34. package/dist/tab/seller/index.d.ts +291 -0
  35. package/dist/tab/seller/index.js +6 -0
  36. package/dist/{types-XG8QvfyL.d.cts → types-C8lyIOmX.d.cts} +1 -1
  37. package/dist/{types-DllrEG_Z.d.ts → types-CTl7yVq6.d.ts} +1 -1
  38. package/dist/{types-pOwQlGEV.d.cts → types-CiPcPs0w.d.cts} +1 -1
  39. package/dist/{types-DDBPREEu.d.ts → types-D9VMq7In.d.ts} +1 -1
  40. package/dist/types-DIrmhiD-.d.cts +234 -0
  41. package/dist/types-DIrmhiD-.d.ts +234 -0
  42. package/dist/{types-htvWHuW3.d.cts → types-RxdlGPaG.d.cts} +122 -1
  43. package/dist/{types-htvWHuW3.d.ts → types-RxdlGPaG.d.ts} +122 -1
  44. package/dist/utils/index.cjs +1 -1
  45. package/dist/utils/index.d.cts +10 -1
  46. package/dist/utils/index.d.ts +10 -1
  47. package/dist/utils/index.js +1 -1
  48. package/dist/{sponsored-access-D96FgkQK.d.ts → x402-client-CHrU2Bs6.d.cts} +114 -63
  49. package/dist/{sponsored-access-D7H-womP.d.cts → x402-client-CzseAnIt.d.ts} +114 -63
  50. package/package.json +18 -1
@@ -0,0 +1,291 @@
1
+ import { c as TabNetworkId, H as HumanAmount, e as SignedVoucher, A as AtomicAmount } from '../../types-DIrmhiD-.cjs';
2
+ import { RequestHandler, Request, Response } from 'express';
3
+ import { Connection, PublicKey } from '@solana/web3.js';
4
+
5
+ /**
6
+ * @dexterai/x402/tab/seller — types for the seller side of OTS tab streaming.
7
+ *
8
+ * The seller middleware verifies vouchers locally (microsecond latency, no
9
+ * chain calls) and demands a fresh session-signed voucher before delivering
10
+ * each chunk. Voucher accumulation is internal; the seller's mental model
11
+ * is "charge for what I serve."
12
+ */
13
+
14
+ /**
15
+ * Persistent store for the seller's per-tab voucher state. The middleware
16
+ * writes the latest accepted voucher after every chunk so a process crash
17
+ * loses at most the last in-flight voucher's worth of revenue. Pluggable to
18
+ * match `batch-settlement/store`'s ChannelStore pattern.
19
+ */
20
+ interface VoucherStore {
21
+ get(channelId: string): Promise<SignedVoucher | null>;
22
+ set(channelId: string, voucher: SignedVoucher): Promise<void>;
23
+ delete(channelId: string): Promise<void>;
24
+ }
25
+ /**
26
+ * Tab handle injected by `tabMiddleware` onto the Express request. The route
27
+ * handler reads it to drive a metered stream.
28
+ */
29
+ interface SellerTab {
30
+ readonly channelId: string;
31
+ readonly network: TabNetworkId;
32
+ /** The buyer's session pubkey for this tab (set by the first voucher). */
33
+ readonly sessionPublicKey: Uint8Array | null;
34
+ /** Cumulative human amount already accepted via vouchers. */
35
+ cumulative(): HumanAmount;
36
+ /**
37
+ * Accept a fresh voucher from the buyer that bumps the cumulative amount
38
+ * by `incrementHuman`. Throws if the voucher signature, scope, or
39
+ * monotonicity check fails. The middleware persists on success.
40
+ */
41
+ charge(incrementHuman: HumanAmount): Promise<void>;
42
+ }
43
+ /** Options for `tabMiddleware`. */
44
+ interface TabMiddlewareOptions {
45
+ /** Charge unit denomination (human amount per delivered unit). */
46
+ perUnit: HumanAmount;
47
+ /** Which network the seller accepts. */
48
+ network: TabNetworkId;
49
+ /** When to settle on chain: at tab close (the common case) vs periodically. */
50
+ settle: 'on-close' | 'periodic';
51
+ /** Facilitator base URL. Default: https://facilitator.dexter.cash. */
52
+ facilitatorUrl?: string;
53
+ /** Voucher persistence. Default: file-backed. */
54
+ store?: VoucherStore;
55
+ /**
56
+ * Hard cap on a single voucher's incremental amount. Protects the seller's
57
+ * middleware from accepting a buyer trying to slip in a giant single
58
+ * voucher. Default: 100x `perUnit`.
59
+ */
60
+ maxPerVoucherAtomic?: AtomicAmount;
61
+ }
62
+ /**
63
+ * Options for `openSse` — the Express response → SSE stream helper. Returns
64
+ * a meter the route handler drives.
65
+ */
66
+ interface OpenSseOptions {
67
+ tab: SellerTab;
68
+ /** Per-chunk human amount; default = the middleware's perUnit. */
69
+ perUnit?: HumanAmount;
70
+ }
71
+ /**
72
+ * Meter returned by `openSse`. The route handler calls `charge()` before
73
+ * delivering each chunk and `send()` to actually push the chunk; `end()`
74
+ * closes the SSE stream without settling.
75
+ */
76
+ interface SseMeter {
77
+ charge(units?: number): Promise<void>;
78
+ send(chunk: string | Uint8Array): void;
79
+ end(): void;
80
+ }
81
+ /** Errors thrown by the seller middleware on bad vouchers. */
82
+ declare class InvalidVoucherError extends Error {
83
+ readonly reason: 'signature_invalid' | 'registration_invalid' | 'cap_exceeded' | 'session_expired' | 'wrong_counterparty' | 'non_monotonic';
84
+ constructor(reason: 'signature_invalid' | 'registration_invalid' | 'cap_exceeded' | 'session_expired' | 'wrong_counterparty' | 'non_monotonic', detail?: string);
85
+ }
86
+
87
+ /**
88
+ * Express middleware for accepting OTS tab vouchers.
89
+ *
90
+ * Wire shape:
91
+ * - Buyer sends each paid request with header `X-Tab-Voucher: <base64-json>`
92
+ * - The voucher JSON is the SignedVoucher shape (payload + session pubkey +
93
+ * registration + signature)
94
+ * - On the FIRST voucher of a session, the middleware parses the
95
+ * registration, verifies it against the on-chain vault (one RPC call),
96
+ * and caches the result
97
+ * - On EVERY voucher, the middleware verifies the session-key signature
98
+ * and enforces scope (cap, expiry, counterparty, monotonicity)
99
+ * - The route handler reads `req.tab` and either runs a stream against it
100
+ * or rejects with 402 Payment Required
101
+ *
102
+ * The middleware never blocks on chain in the per-voucher hot path. The
103
+ * one-time on-chain read is amortized across the entire session.
104
+ */
105
+
106
+ declare module 'express-serve-static-core' {
107
+ interface Request {
108
+ tab?: SellerTab;
109
+ }
110
+ }
111
+ interface TabMiddlewareConfig extends TabMiddlewareOptions {
112
+ /** RPC connection used for the one-time on-chain registration read. */
113
+ connection: Connection;
114
+ /** The seller's pubkey — used as allowed_counterparty for scope check. */
115
+ sellerPubkey: string | PublicKey;
116
+ }
117
+ /** Header the buyer sends with each paid request. base64-encoded JSON of SignedVoucher. */
118
+ declare const TAB_VOUCHER_HEADER = "x-tab-voucher";
119
+ declare function tabMiddleware(config: TabMiddlewareConfig): RequestHandler;
120
+ /** Pull the SellerTab off a request. Throws if the middleware didn't run. */
121
+ declare function requireTab(req: Request): SellerTab;
122
+
123
+ /**
124
+ * SSE meter: turn an Express response into a Server-Sent-Events stream
125
+ * tied to a tab. The route handler calls `charge()` before each chunk
126
+ * (which demands a fresh voucher from the buyer) and `send()` to push
127
+ * the chunk down the wire.
128
+ *
129
+ * The streaming pattern this enables:
130
+ *
131
+ * app.post('/inference', tabMiddleware({...}), async (req, res) => {
132
+ * const tab = requireTab(req);
133
+ * const meter = openSse(res, { tab, perUnit: '0.00003' });
134
+ * for await (const token of llm(req.body.prompt)) {
135
+ * await meter.charge(); // demand voucher; throws if cap exceeded
136
+ * meter.send(token); // emit SSE event with the token
137
+ * }
138
+ * meter.end();
139
+ * });
140
+ *
141
+ * NOTE on voucher cadence: this implementation treats EACH `charge()` as
142
+ * "the buyer already presented a voucher covering this chunk via the
143
+ * inbound request header" — i.e. the request's voucher header bounds the
144
+ * cumulative the seller can deliver under. For true per-chunk voucher
145
+ * exchange mid-stream (the buyer presenting fresh vouchers WITHIN one
146
+ * HTTP request), the seller needs to read vouchers off the response
147
+ * stream's reverse direction or via WebSocket. That's an advanced mode
148
+ * left for Phase 4+; the v3 meter ships the simpler "one voucher bounds
149
+ * the whole request" model, which is correct for any reasonable chunk
150
+ * count under a single per-request increment.
151
+ */
152
+
153
+ declare function openSse(res: Response, options: OpenSseOptions): SseMeter;
154
+
155
+ /**
156
+ * Voucher persistence for the seller side.
157
+ *
158
+ * The seller's middleware accepts a fresh voucher per chunk and overwrites
159
+ * the previous one — only the latest cumulative voucher matters for
160
+ * settlement. Persistence exists so a process crash mid-stream doesn't
161
+ * lose the last few seconds of accrued revenue.
162
+ *
163
+ * Two implementations:
164
+ * - InMemoryVoucherStore — zero-config default. Loses state on restart.
165
+ * - FileVoucherStore — writes one JSON file per channel id. Survives
166
+ * restarts; cheap enough for low-concurrency sellers.
167
+ *
168
+ * Production sellers expecting high concurrency or atomic restart-recovery
169
+ * can implement VoucherStore themselves (Redis, Postgres, etc) and pass it
170
+ * into tabMiddleware. The interface is intentionally minimal.
171
+ */
172
+
173
+ declare class InMemoryVoucherStore implements VoucherStore {
174
+ private map;
175
+ get(channelId: string): Promise<SignedVoucher | null>;
176
+ set(channelId: string, voucher: SignedVoucher): Promise<void>;
177
+ delete(channelId: string): Promise<void>;
178
+ }
179
+ declare class FileVoucherStore implements VoucherStore {
180
+ private readonly dir;
181
+ constructor(dir: string);
182
+ private pathFor;
183
+ get(channelId: string): Promise<SignedVoucher | null>;
184
+ set(channelId: string, voucher: SignedVoucher): Promise<void>;
185
+ delete(channelId: string): Promise<void>;
186
+ }
187
+
188
+ /**
189
+ * Local voucher verification for the seller side of OTS tab streaming.
190
+ *
191
+ * Two-layer verification:
192
+ *
193
+ * 1. parseRegistration(registrationBytes)
194
+ * Parses the 180-byte registration message into a scope. Synchronous,
195
+ * no I/O. This gives the seller everything they need to enforce limits
196
+ * LOCALLY (cap, expiry, counterparty) and to know which vault to read
197
+ * for passkey verification.
198
+ *
199
+ * 2. verifyRegistrationOnChain(connection, registration, programId)
200
+ * Reads the vault account on chain ONCE per session and verifies that
201
+ * the buyer's passkey would have produced the registration's signature.
202
+ * Cached after the first call; subsequent vouchers in the same session
203
+ * reuse the cached result.
204
+ *
205
+ * 3. verifyVoucherSignature(voucher, sessionPublicKey, channelIdBytes)
206
+ * Verifies the session-key signature over the 44-byte voucher payload.
207
+ * Synchronous, no I/O, microsecond latency. This is what runs PER
208
+ * CHUNK during streaming.
209
+ *
210
+ * The seller's per-chunk hot path is (3) only. (1) and (2) run once per
211
+ * session.
212
+ */
213
+
214
+ interface ParsedRegistration {
215
+ programId: PublicKey;
216
+ vaultPda: PublicKey;
217
+ sessionPubkey: Uint8Array;
218
+ maxAmount: bigint;
219
+ expiresAt: bigint;
220
+ allowedCounterparty: PublicKey;
221
+ nonce: number;
222
+ }
223
+ declare class InvalidRegistrationError extends Error {
224
+ readonly reason: 'wrong_length' | 'wrong_domain' | 'wrong_program' | 'expiry_in_past' | 'cap_zero';
225
+ constructor(reason: 'wrong_length' | 'wrong_domain' | 'wrong_program' | 'expiry_in_past' | 'cap_zero', detail?: string);
226
+ }
227
+ /**
228
+ * Parse the raw registration bytes the buyer presents with their first
229
+ * voucher. Synchronous, pure. Validates structural correctness only — the
230
+ * passkey signature check is a separate on-chain step.
231
+ */
232
+ declare function parseRegistration(registration: Uint8Array): ParsedRegistration;
233
+ interface OnChainVaultState {
234
+ passkeyPubkey: Uint8Array;
235
+ activeSessionPubkey: Uint8Array | null;
236
+ }
237
+ declare class OnChainVerificationError extends Error {
238
+ readonly reason: 'vault_not_found' | 'session_not_active' | 'session_pubkey_mismatch' | 'wrong_program';
239
+ constructor(reason: 'vault_not_found' | 'session_not_active' | 'session_pubkey_mismatch' | 'wrong_program', detail?: string);
240
+ }
241
+ /**
242
+ * Read the vault account and extract passkey + active session.
243
+ *
244
+ * Reads at `finalized` commitment to avoid the read-replica race that
245
+ * shows up when the buyer just confirmed register_session_key at
246
+ * `confirmed` and the seller's RPC replica hasn't propagated the write
247
+ * yet. This is the same lesson as the dexter-vault test suite — see
248
+ * reference_anchor_test_commitment in repo memory.
249
+ */
250
+ declare function readVaultState(connection: Connection, vaultPda: PublicKey): Promise<OnChainVaultState>;
251
+ /**
252
+ * Verify a registration against on-chain state. Returns the vault's
253
+ * passkey pubkey (caller can cache it). Throws on any mismatch.
254
+ *
255
+ * The "verification" here is structural: the active_session on chain MUST
256
+ * carry the same session pubkey the registration claims. If the program
257
+ * accepted the register_session_key tx (which is what set active_session
258
+ * in the first place), then the passkey signature was verified by the
259
+ * secp256r1 precompile inside that tx. The seller doesn't need to redo
260
+ * that work; they just need to confirm the on-chain witness still holds.
261
+ */
262
+ declare function verifyRegistrationOnChain(connection: Connection, registration: ParsedRegistration): Promise<{
263
+ passkeyPubkey: Uint8Array;
264
+ }>;
265
+ declare class InvalidVoucherSignatureError extends Error {
266
+ constructor(detail?: string);
267
+ }
268
+ /**
269
+ * Verify the session-key signature on a voucher. This is the hot-path
270
+ * check, called on every chunk during streaming. Pure ed25519
271
+ * verification, microsecond latency.
272
+ *
273
+ * The channelIdBytes must be the canonical 32-byte channel id the buyer
274
+ * derived (typically sha256(vault_pda || seller_url || nonce)). The
275
+ * caller is responsible for either deriving it the same way or accepting
276
+ * whatever the buyer presents on the first voucher (treating it as the
277
+ * channel handle for the session).
278
+ */
279
+ declare function verifyVoucherSignature(voucher: SignedVoucher, channelIdBytes: Uint8Array): void;
280
+ declare class ScopeViolationError extends Error {
281
+ readonly reason: 'cumulative_exceeds_cap' | 'session_expired' | 'wrong_counterparty' | 'non_monotonic';
282
+ constructor(reason: 'cumulative_exceeds_cap' | 'session_expired' | 'wrong_counterparty' | 'non_monotonic', detail?: string);
283
+ }
284
+ declare function enforceScope(args: {
285
+ registration: ParsedRegistration;
286
+ voucher: SignedVoucher;
287
+ expectedCounterparty: PublicKey;
288
+ previousCumulativeAtomic?: AtomicAmount;
289
+ }): void;
290
+
291
+ export { FileVoucherStore, InMemoryVoucherStore, InvalidRegistrationError, InvalidVoucherError, InvalidVoucherSignatureError, type OnChainVaultState, OnChainVerificationError, type OpenSseOptions, type ParsedRegistration, ScopeViolationError, type SellerTab, type SseMeter, TAB_VOUCHER_HEADER, type TabMiddlewareConfig, type TabMiddlewareOptions, type VoucherStore, enforceScope, openSse, parseRegistration, readVaultState, requireTab, tabMiddleware, verifyRegistrationOnChain, verifyVoucherSignature };
@@ -0,0 +1,291 @@
1
+ import { c as TabNetworkId, H as HumanAmount, e as SignedVoucher, A as AtomicAmount } from '../../types-DIrmhiD-.js';
2
+ import { RequestHandler, Request, Response } from 'express';
3
+ import { Connection, PublicKey } from '@solana/web3.js';
4
+
5
+ /**
6
+ * @dexterai/x402/tab/seller — types for the seller side of OTS tab streaming.
7
+ *
8
+ * The seller middleware verifies vouchers locally (microsecond latency, no
9
+ * chain calls) and demands a fresh session-signed voucher before delivering
10
+ * each chunk. Voucher accumulation is internal; the seller's mental model
11
+ * is "charge for what I serve."
12
+ */
13
+
14
+ /**
15
+ * Persistent store for the seller's per-tab voucher state. The middleware
16
+ * writes the latest accepted voucher after every chunk so a process crash
17
+ * loses at most the last in-flight voucher's worth of revenue. Pluggable to
18
+ * match `batch-settlement/store`'s ChannelStore pattern.
19
+ */
20
+ interface VoucherStore {
21
+ get(channelId: string): Promise<SignedVoucher | null>;
22
+ set(channelId: string, voucher: SignedVoucher): Promise<void>;
23
+ delete(channelId: string): Promise<void>;
24
+ }
25
+ /**
26
+ * Tab handle injected by `tabMiddleware` onto the Express request. The route
27
+ * handler reads it to drive a metered stream.
28
+ */
29
+ interface SellerTab {
30
+ readonly channelId: string;
31
+ readonly network: TabNetworkId;
32
+ /** The buyer's session pubkey for this tab (set by the first voucher). */
33
+ readonly sessionPublicKey: Uint8Array | null;
34
+ /** Cumulative human amount already accepted via vouchers. */
35
+ cumulative(): HumanAmount;
36
+ /**
37
+ * Accept a fresh voucher from the buyer that bumps the cumulative amount
38
+ * by `incrementHuman`. Throws if the voucher signature, scope, or
39
+ * monotonicity check fails. The middleware persists on success.
40
+ */
41
+ charge(incrementHuman: HumanAmount): Promise<void>;
42
+ }
43
+ /** Options for `tabMiddleware`. */
44
+ interface TabMiddlewareOptions {
45
+ /** Charge unit denomination (human amount per delivered unit). */
46
+ perUnit: HumanAmount;
47
+ /** Which network the seller accepts. */
48
+ network: TabNetworkId;
49
+ /** When to settle on chain: at tab close (the common case) vs periodically. */
50
+ settle: 'on-close' | 'periodic';
51
+ /** Facilitator base URL. Default: https://facilitator.dexter.cash. */
52
+ facilitatorUrl?: string;
53
+ /** Voucher persistence. Default: file-backed. */
54
+ store?: VoucherStore;
55
+ /**
56
+ * Hard cap on a single voucher's incremental amount. Protects the seller's
57
+ * middleware from accepting a buyer trying to slip in a giant single
58
+ * voucher. Default: 100x `perUnit`.
59
+ */
60
+ maxPerVoucherAtomic?: AtomicAmount;
61
+ }
62
+ /**
63
+ * Options for `openSse` — the Express response → SSE stream helper. Returns
64
+ * a meter the route handler drives.
65
+ */
66
+ interface OpenSseOptions {
67
+ tab: SellerTab;
68
+ /** Per-chunk human amount; default = the middleware's perUnit. */
69
+ perUnit?: HumanAmount;
70
+ }
71
+ /**
72
+ * Meter returned by `openSse`. The route handler calls `charge()` before
73
+ * delivering each chunk and `send()` to actually push the chunk; `end()`
74
+ * closes the SSE stream without settling.
75
+ */
76
+ interface SseMeter {
77
+ charge(units?: number): Promise<void>;
78
+ send(chunk: string | Uint8Array): void;
79
+ end(): void;
80
+ }
81
+ /** Errors thrown by the seller middleware on bad vouchers. */
82
+ declare class InvalidVoucherError extends Error {
83
+ readonly reason: 'signature_invalid' | 'registration_invalid' | 'cap_exceeded' | 'session_expired' | 'wrong_counterparty' | 'non_monotonic';
84
+ constructor(reason: 'signature_invalid' | 'registration_invalid' | 'cap_exceeded' | 'session_expired' | 'wrong_counterparty' | 'non_monotonic', detail?: string);
85
+ }
86
+
87
+ /**
88
+ * Express middleware for accepting OTS tab vouchers.
89
+ *
90
+ * Wire shape:
91
+ * - Buyer sends each paid request with header `X-Tab-Voucher: <base64-json>`
92
+ * - The voucher JSON is the SignedVoucher shape (payload + session pubkey +
93
+ * registration + signature)
94
+ * - On the FIRST voucher of a session, the middleware parses the
95
+ * registration, verifies it against the on-chain vault (one RPC call),
96
+ * and caches the result
97
+ * - On EVERY voucher, the middleware verifies the session-key signature
98
+ * and enforces scope (cap, expiry, counterparty, monotonicity)
99
+ * - The route handler reads `req.tab` and either runs a stream against it
100
+ * or rejects with 402 Payment Required
101
+ *
102
+ * The middleware never blocks on chain in the per-voucher hot path. The
103
+ * one-time on-chain read is amortized across the entire session.
104
+ */
105
+
106
+ declare module 'express-serve-static-core' {
107
+ interface Request {
108
+ tab?: SellerTab;
109
+ }
110
+ }
111
+ interface TabMiddlewareConfig extends TabMiddlewareOptions {
112
+ /** RPC connection used for the one-time on-chain registration read. */
113
+ connection: Connection;
114
+ /** The seller's pubkey — used as allowed_counterparty for scope check. */
115
+ sellerPubkey: string | PublicKey;
116
+ }
117
+ /** Header the buyer sends with each paid request. base64-encoded JSON of SignedVoucher. */
118
+ declare const TAB_VOUCHER_HEADER = "x-tab-voucher";
119
+ declare function tabMiddleware(config: TabMiddlewareConfig): RequestHandler;
120
+ /** Pull the SellerTab off a request. Throws if the middleware didn't run. */
121
+ declare function requireTab(req: Request): SellerTab;
122
+
123
+ /**
124
+ * SSE meter: turn an Express response into a Server-Sent-Events stream
125
+ * tied to a tab. The route handler calls `charge()` before each chunk
126
+ * (which demands a fresh voucher from the buyer) and `send()` to push
127
+ * the chunk down the wire.
128
+ *
129
+ * The streaming pattern this enables:
130
+ *
131
+ * app.post('/inference', tabMiddleware({...}), async (req, res) => {
132
+ * const tab = requireTab(req);
133
+ * const meter = openSse(res, { tab, perUnit: '0.00003' });
134
+ * for await (const token of llm(req.body.prompt)) {
135
+ * await meter.charge(); // demand voucher; throws if cap exceeded
136
+ * meter.send(token); // emit SSE event with the token
137
+ * }
138
+ * meter.end();
139
+ * });
140
+ *
141
+ * NOTE on voucher cadence: this implementation treats EACH `charge()` as
142
+ * "the buyer already presented a voucher covering this chunk via the
143
+ * inbound request header" — i.e. the request's voucher header bounds the
144
+ * cumulative the seller can deliver under. For true per-chunk voucher
145
+ * exchange mid-stream (the buyer presenting fresh vouchers WITHIN one
146
+ * HTTP request), the seller needs to read vouchers off the response
147
+ * stream's reverse direction or via WebSocket. That's an advanced mode
148
+ * left for Phase 4+; the v3 meter ships the simpler "one voucher bounds
149
+ * the whole request" model, which is correct for any reasonable chunk
150
+ * count under a single per-request increment.
151
+ */
152
+
153
+ declare function openSse(res: Response, options: OpenSseOptions): SseMeter;
154
+
155
+ /**
156
+ * Voucher persistence for the seller side.
157
+ *
158
+ * The seller's middleware accepts a fresh voucher per chunk and overwrites
159
+ * the previous one — only the latest cumulative voucher matters for
160
+ * settlement. Persistence exists so a process crash mid-stream doesn't
161
+ * lose the last few seconds of accrued revenue.
162
+ *
163
+ * Two implementations:
164
+ * - InMemoryVoucherStore — zero-config default. Loses state on restart.
165
+ * - FileVoucherStore — writes one JSON file per channel id. Survives
166
+ * restarts; cheap enough for low-concurrency sellers.
167
+ *
168
+ * Production sellers expecting high concurrency or atomic restart-recovery
169
+ * can implement VoucherStore themselves (Redis, Postgres, etc) and pass it
170
+ * into tabMiddleware. The interface is intentionally minimal.
171
+ */
172
+
173
+ declare class InMemoryVoucherStore implements VoucherStore {
174
+ private map;
175
+ get(channelId: string): Promise<SignedVoucher | null>;
176
+ set(channelId: string, voucher: SignedVoucher): Promise<void>;
177
+ delete(channelId: string): Promise<void>;
178
+ }
179
+ declare class FileVoucherStore implements VoucherStore {
180
+ private readonly dir;
181
+ constructor(dir: string);
182
+ private pathFor;
183
+ get(channelId: string): Promise<SignedVoucher | null>;
184
+ set(channelId: string, voucher: SignedVoucher): Promise<void>;
185
+ delete(channelId: string): Promise<void>;
186
+ }
187
+
188
+ /**
189
+ * Local voucher verification for the seller side of OTS tab streaming.
190
+ *
191
+ * Two-layer verification:
192
+ *
193
+ * 1. parseRegistration(registrationBytes)
194
+ * Parses the 180-byte registration message into a scope. Synchronous,
195
+ * no I/O. This gives the seller everything they need to enforce limits
196
+ * LOCALLY (cap, expiry, counterparty) and to know which vault to read
197
+ * for passkey verification.
198
+ *
199
+ * 2. verifyRegistrationOnChain(connection, registration, programId)
200
+ * Reads the vault account on chain ONCE per session and verifies that
201
+ * the buyer's passkey would have produced the registration's signature.
202
+ * Cached after the first call; subsequent vouchers in the same session
203
+ * reuse the cached result.
204
+ *
205
+ * 3. verifyVoucherSignature(voucher, sessionPublicKey, channelIdBytes)
206
+ * Verifies the session-key signature over the 44-byte voucher payload.
207
+ * Synchronous, no I/O, microsecond latency. This is what runs PER
208
+ * CHUNK during streaming.
209
+ *
210
+ * The seller's per-chunk hot path is (3) only. (1) and (2) run once per
211
+ * session.
212
+ */
213
+
214
+ interface ParsedRegistration {
215
+ programId: PublicKey;
216
+ vaultPda: PublicKey;
217
+ sessionPubkey: Uint8Array;
218
+ maxAmount: bigint;
219
+ expiresAt: bigint;
220
+ allowedCounterparty: PublicKey;
221
+ nonce: number;
222
+ }
223
+ declare class InvalidRegistrationError extends Error {
224
+ readonly reason: 'wrong_length' | 'wrong_domain' | 'wrong_program' | 'expiry_in_past' | 'cap_zero';
225
+ constructor(reason: 'wrong_length' | 'wrong_domain' | 'wrong_program' | 'expiry_in_past' | 'cap_zero', detail?: string);
226
+ }
227
+ /**
228
+ * Parse the raw registration bytes the buyer presents with their first
229
+ * voucher. Synchronous, pure. Validates structural correctness only — the
230
+ * passkey signature check is a separate on-chain step.
231
+ */
232
+ declare function parseRegistration(registration: Uint8Array): ParsedRegistration;
233
+ interface OnChainVaultState {
234
+ passkeyPubkey: Uint8Array;
235
+ activeSessionPubkey: Uint8Array | null;
236
+ }
237
+ declare class OnChainVerificationError extends Error {
238
+ readonly reason: 'vault_not_found' | 'session_not_active' | 'session_pubkey_mismatch' | 'wrong_program';
239
+ constructor(reason: 'vault_not_found' | 'session_not_active' | 'session_pubkey_mismatch' | 'wrong_program', detail?: string);
240
+ }
241
+ /**
242
+ * Read the vault account and extract passkey + active session.
243
+ *
244
+ * Reads at `finalized` commitment to avoid the read-replica race that
245
+ * shows up when the buyer just confirmed register_session_key at
246
+ * `confirmed` and the seller's RPC replica hasn't propagated the write
247
+ * yet. This is the same lesson as the dexter-vault test suite — see
248
+ * reference_anchor_test_commitment in repo memory.
249
+ */
250
+ declare function readVaultState(connection: Connection, vaultPda: PublicKey): Promise<OnChainVaultState>;
251
+ /**
252
+ * Verify a registration against on-chain state. Returns the vault's
253
+ * passkey pubkey (caller can cache it). Throws on any mismatch.
254
+ *
255
+ * The "verification" here is structural: the active_session on chain MUST
256
+ * carry the same session pubkey the registration claims. If the program
257
+ * accepted the register_session_key tx (which is what set active_session
258
+ * in the first place), then the passkey signature was verified by the
259
+ * secp256r1 precompile inside that tx. The seller doesn't need to redo
260
+ * that work; they just need to confirm the on-chain witness still holds.
261
+ */
262
+ declare function verifyRegistrationOnChain(connection: Connection, registration: ParsedRegistration): Promise<{
263
+ passkeyPubkey: Uint8Array;
264
+ }>;
265
+ declare class InvalidVoucherSignatureError extends Error {
266
+ constructor(detail?: string);
267
+ }
268
+ /**
269
+ * Verify the session-key signature on a voucher. This is the hot-path
270
+ * check, called on every chunk during streaming. Pure ed25519
271
+ * verification, microsecond latency.
272
+ *
273
+ * The channelIdBytes must be the canonical 32-byte channel id the buyer
274
+ * derived (typically sha256(vault_pda || seller_url || nonce)). The
275
+ * caller is responsible for either deriving it the same way or accepting
276
+ * whatever the buyer presents on the first voucher (treating it as the
277
+ * channel handle for the session).
278
+ */
279
+ declare function verifyVoucherSignature(voucher: SignedVoucher, channelIdBytes: Uint8Array): void;
280
+ declare class ScopeViolationError extends Error {
281
+ readonly reason: 'cumulative_exceeds_cap' | 'session_expired' | 'wrong_counterparty' | 'non_monotonic';
282
+ constructor(reason: 'cumulative_exceeds_cap' | 'session_expired' | 'wrong_counterparty' | 'non_monotonic', detail?: string);
283
+ }
284
+ declare function enforceScope(args: {
285
+ registration: ParsedRegistration;
286
+ voucher: SignedVoucher;
287
+ expectedCounterparty: PublicKey;
288
+ previousCumulativeAtomic?: AtomicAmount;
289
+ }): void;
290
+
291
+ export { FileVoucherStore, InMemoryVoucherStore, InvalidRegistrationError, InvalidVoucherError, InvalidVoucherSignatureError, type OnChainVaultState, OnChainVerificationError, type OpenSseOptions, type ParsedRegistration, ScopeViolationError, type SellerTab, type SseMeter, TAB_VOUCHER_HEADER, type TabMiddlewareConfig, type TabMiddlewareOptions, type VoucherStore, enforceScope, openSse, parseRegistration, readVaultState, requireTab, tabMiddleware, verifyRegistrationOnChain, verifyVoucherSignature };
@@ -0,0 +1,6 @@
1
+ var m=class extends Error{constructor(n,r){super(`Invalid voucher: ${n}${r?` (${r})`:""}`);this.reason=n;this.name="InvalidVoucherError"}};import{PublicKey as X}from"@solana/web3.js";import j from"tweetnacl";import{sha256 as fe}from"@noble/hashes/sha256";import{p256 as be}from"@noble/curves/p256";import{PublicKey as E}from"@solana/web3.js";var ie=(()=>{let e=new Uint8Array(32);return e.set(new TextEncoder().encode("OTS_SESSION_REGISTER_V1"),0),e})(),se=(()=>{let e=new Uint8Array(32);return e.set(new TextEncoder().encode("OTS_SESSION_REVOKE_V1"),0),e})();function U(e){if(e.channelId.length!==32)throw new Error(`channelId must be 32 bytes, got ${e.channelId.length}`);let t=new Uint8Array(44),n=new DataView(t.buffer),r=0;if(t.set(e.channelId,r),r+=32,n.setBigUint64(r,e.cumulativeAmount,!0),r+=8,n.setUint32(r,e.sequenceNumber>>>0,!0),r+=4,r!==44)throw new Error(`internal: voucher payload wrong length ${r}, expected 44`);return t}import{PublicKey as I,TransactionInstruction as ce}from"@solana/web3.js";var v=new I("Hg3wRaydFtJhYrdvYrKECacpJYDsC9Px7yKmpncj2fhc"),le=new I("Secp256r1SigVerify1111111111111111111111111"),me=new I("Sysvar1nstructions1111111111111111111111111"),de=new Uint8Array([69,94,60,44,49,199,183,233]),pe=new Uint8Array([81,192,32,110,104,116,144,151]);var T="OTS_SESSION_REGISTER_V1",d=class extends Error{constructor(n,r){super(`Invalid registration: ${n}${r?` (${r})`:""}`);this.reason=n;this.name="InvalidRegistrationError"}};function k(e){if(e.length!==180)throw new d("wrong_length",`expected 180, got ${e.length}`);let t=new TextDecoder().decode(e.slice(0,T.length));if(t!==T)throw new d("wrong_domain",`got "${t}"`);for(let s=T.length;s<32;s++)if(e[s]!==0)throw new d("wrong_domain",`non-NUL padding at byte ${s}`);let n=new DataView(e.buffer,e.byteOffset,e.byteLength),r=new E(e.slice(32,64)),i=new E(e.slice(64,96)),u=e.slice(96,128),p=n.getBigUint64(128,!0),g=n.getBigInt64(136,!0),o=new E(e.slice(144,176)),a=n.getUint32(176,!0);if(!r.equals(v))throw new d("wrong_program",`${r.toBase58()} is not ${v.toBase58()}`);if(p===0n)throw new d("cap_zero");let l=BigInt(Math.floor(Date.now()/1e3));if(g<=l)throw new d("expiry_in_past",`expires_at=${g}, now=${l}`);return{programId:r,vaultPda:i,sessionPubkey:new Uint8Array(u),maxAmount:p,expiresAt:g,allowedCounterparty:o,nonce:a}}var M=10,f=class extends Error{constructor(n,r){super(`On-chain verification failed: ${n}${r?` (${r})`:""}`);this.reason=n;this.name="OnChainVerificationError"}};async function L(e,t){let n=await e.getAccountInfo(t,"finalized");if(!n)throw new f("vault_not_found",t.toBase58());if(!n.owner.equals(v))throw new f("wrong_program",`owner ${n.owner.toBase58()} is not the vault program`);let r=n.data,i=new Uint8Array(r.slice(M,M+33)),a=84+(r[83]===1?48:0)+32+32;if(r[a]!==1)return{passkeyPubkey:i,activeSessionPubkey:null};let s=a+1,y=new Uint8Array(r.slice(s,s+32));return{passkeyPubkey:i,activeSessionPubkey:y}}async function _(e,t){let n=await L(e,t.vaultPda);if(n.activeSessionPubkey===null)throw new f("session_not_active","vault has no active_session \u2014 was it revoked?");if(!J(n.activeSessionPubkey,t.sessionPubkey))throw new f("session_pubkey_mismatch",`on-chain ${q(n.activeSessionPubkey)} != registration ${q(t.sessionPubkey)}`);return{passkeyPubkey:n.passkeyPubkey}}var h=class extends Error{constructor(t){super(`Invalid voucher signature${t?`: ${t}`:""}`),this.name="InvalidVoucherSignatureError"}};function R(e,t){if(t.length!==32)throw new h(`channelIdBytes must be 32 bytes, got ${t.length}`);if(e.sessionPublicKey.length!==32)throw new h(`sessionPublicKey must be 32 bytes, got ${e.sessionPublicKey.length}`);if(e.sessionSignature.length!==64)throw new h(`sessionSignature must be 64 bytes, got ${e.sessionSignature.length}`);let n=U({channelId:t,cumulativeAmount:BigInt(e.payload.cumulativeAmount),sequenceNumber:e.payload.sequenceNumber});if(!j.sign.detached.verify(n,e.sessionSignature,e.sessionPublicKey))throw new h("ed25519 verify rejected")}var c=class extends Error{constructor(n,r){super(`Scope violation: ${n}${r?` (${r})`:""}`);this.reason=n;this.name="ScopeViolationError"}};function V(e){let t=BigInt(e.voucher.payload.cumulativeAmount);if(t>e.registration.maxAmount)throw new c("cumulative_exceeds_cap",`${t} > ${e.registration.maxAmount}`);let n=BigInt(Math.floor(Date.now()/1e3));if(n>=e.registration.expiresAt)throw new c("session_expired",`now=${n} >= expiresAt=${e.registration.expiresAt}`);if(!e.registration.allowedCounterparty.equals(e.expectedCounterparty))throw new c("wrong_counterparty",`${e.registration.allowedCounterparty.toBase58()} != ${e.expectedCounterparty.toBase58()}`);if(e.previousCumulativeAtomic!==void 0){let r=BigInt(e.previousCumulativeAtomic);if(t<=r)throw new c("non_monotonic",`cumulative=${t} not > previous=${r}`)}}function J(e,t){if(e.length!==t.length)return!1;for(let n=0;n<e.length;n++)if(e[n]!==t[n])return!1;return!0}function q(e){let t="";for(let n of e)t+=n.toString(16).padStart(2,"0");return t}import{promises as w}from"fs";import{join as G,dirname as Y}from"path";function W(e){return{payload:e.payload,sessionPublicKey:C(e.sessionPublicKey),sessionRegistration:C(e.sessionRegistration),sessionSignature:C(e.sessionSignature)}}function Z(e){return{payload:e.payload,sessionPublicKey:$(e.sessionPublicKey),sessionRegistration:$(e.sessionRegistration),sessionSignature:$(e.sessionSignature)}}function C(e){let t="";for(let n of e)t+=n.toString(16).padStart(2,"0");return t}function $(e){if(e.length%2!==0)throw new Error(`hex length must be even, got ${e.length}`);let t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substr(n*2,2),16);return t}var b=class{map=new Map;async get(t){return this.map.get(t)??null}async set(t,n){this.map.set(t,n)}async delete(t){this.map.delete(t)}},K=class{constructor(t){this.dir=t}pathFor(t){if(!/^[a-z0-9_-]+$/i.test(t))throw new Error(`unsafe channelId for filesystem: ${t}`);return G(this.dir,`${t}.json`)}async get(t){try{let n=await w.readFile(this.pathFor(t),"utf8");return Z(JSON.parse(n))}catch(n){if(n?.code==="ENOENT")return null;throw n}}async set(t,n){let r=this.pathFor(t);await w.mkdir(Y(r),{recursive:!0});let i=`${r}.tmp`;await w.writeFile(i,JSON.stringify(W(n))),await w.rename(i,r)}async delete(t){try{await w.unlink(this.pathFor(t))}catch(n){if(n?.code!=="ENOENT")throw n}}};import{PublicKey as He}from"@solana/web3.js";import{bytesToHex as qe}from"@noble/hashes/utils";import _e from"tweetnacl";import{sha256 as Ce}from"@noble/hashes/sha256";var z=6;function A(e,t=z){if(!/^\d+(\.\d+)?$/.test(e))throw new Error(`amount must be a non-negative decimal string, got "${e}"`);let[n,r=""]=e.split(".");if(r.length>t)throw new Error(`amount "${e}" has more than ${t} decimals`);let i=r.padEnd(t,"0"),u=`${n}${i}`.replace(/^0+(?=\d)/,"");return u===""?"0":u}function S(e,t=z){if(!/^\d+$/.test(e))throw new Error(`atomic must be a non-negative integer string, got "${e}"`);let n=e.padStart(t+1,"0"),r=n.slice(0,-t).replace(/^0+(?=\d)/,"")||"0",i=n.slice(-t).replace(/0+$/,"");return i?`${r}.${i}`:r}var N="x-tab-voucher",O=class{map=new Map;get(t){return this.map.get(t)}set(t,n){this.map.set(t,n)}update(t,n){let r=this.map.get(t);r&&(r.lastCumulativeAtomic=n)}delete(t){this.map.delete(t)}},B=class{constructor(t,n,r,i){this.chargeImpl=i;this.channelId=t,this.network=n,this.cumulativeAtomic=r}channelId;network;sessionPublicKey=null;cumulativeAtomic;cumulative(){return S(this.cumulativeAtomic.toString())}bumpCumulative(t){this.cumulativeAtomic=t}setSessionPublicKey(t){this.sessionPublicKey=t}async charge(t){return this.chargeImpl(t)}};function Q(e){if(typeof e!="string"||e.length===0)throw new m("signature_invalid",`missing ${N} header`);let t;try{t=Buffer.from(e,"base64").toString("utf8")}catch{throw new m("signature_invalid","malformed base64")}let n;try{n=JSON.parse(t)}catch{throw new m("signature_invalid","malformed JSON")}if(!n||typeof n!="object"||!n.payload||!n.sessionPublicKey)throw new m("signature_invalid","missing required fields");return{payload:n.payload,sessionPublicKey:x(n.sessionPublicKey),sessionRegistration:x(n.sessionRegistration),sessionSignature:x(n.sessionSignature)}}function x(e){if(typeof e!="string"||e.length%2!==0)throw new m("signature_invalid",`bad hex: ${typeof e}`);let t=new Uint8Array(e.length/2);for(let n=0;n<t.length;n++)t[n]=parseInt(e.substr(n*2,2),16);return t}function ee(e){if(!/^[0-9a-f]{64}$/i.test(e))throw new m("signature_invalid",`channelId must be 64-char hex, got "${e}"`);return x(e)}function te(e){let t=e.store??new b,n=new O,r=typeof e.sellerPubkey=="string"?new X(e.sellerPubkey):e.sellerPubkey,i=e.maxPerVoucherAtomic?BigInt(e.maxPerVoucherAtomic):BigInt(A(e.perUnit))*100n;return async(u,p,g)=>{try{let o=Q(u.headers[N]),a=o.payload.channelId,l=ee(a),s=n.get(a);if(!s){let P=k(o.sessionRegistration);await _(e.connection,P),s={registration:P,lastCumulativeAtomic:"0"},n.set(a,s)}R(o,l),V({registration:s.registration,voucher:o,expectedCounterparty:r,previousCumulativeAtomic:s.lastCumulativeAtomic});let y=BigInt(o.payload.cumulativeAmount),F=BigInt(s.lastCumulativeAtomic),D=y-F;if(D>i)throw new c("cumulative_exceeds_cap",`single voucher increment ${D} exceeds maxPerVoucherAtomic ${i}`);await t.set(a,o),n.update(a,o.payload.cumulativeAmount);let H=new B(a,e.network,y,async P=>{throw new Error("SellerTab.charge() is not driven by the route handler; the buyer presents a fresh voucher per chunk. Use openSse(res, tab) for the metered-stream pattern.")});H.setSessionPublicKey(o.sessionPublicKey),u.tab=H,g()}catch(o){if(o instanceof m||o instanceof d||o instanceof f||o instanceof h||o instanceof c){p.status(402).json({error:"invalid_voucher",reason:o.reason??"unknown",detail:o.message});return}g(o)}}}function ne(e){if(!e.tab)throw new Error("req.tab is missing \u2014 did tabMiddleware run on this route?");return e.tab}function re(e,t){if(!t.tab)throw new Error("openSse requires options.tab");e.headersSent||(e.setHeader("Content-Type","text/event-stream"),e.setHeader("Cache-Control","no-cache"),e.setHeader("Connection","keep-alive"),typeof e.flushHeaders=="function"&&e.flushHeaders());let n=t.tab,r=BigInt(A(n.cumulative())),i=t.perUnit?BigInt(A(t.perUnit)):null,u=0n,p=!1;function g(l=1){if(p)return Promise.reject(new Error("meter ended"));if(i===null)return Promise.reject(new Error("charge() needs options.perUnit"));let s=i*BigInt(l),y=u+s;return y>r?Promise.reject(new c("cumulative_exceeds_cap",`chunk would push request total to ${S(y.toString())} beyond voucher-authorized budget ${S(r.toString())}`)):(u=y,Promise.resolve())}function o(l){if(p)throw new Error("meter ended");let y=(typeof l=="string"?l:Buffer.from(l).toString("utf8")).replace(/\n/g,"\\n");e.write(`data: ${y}
2
+
3
+ `)}function a(){p||(p=!0,e.write(`event: end
4
+ data: {"chargedAtomic":"${u}"}
5
+
6
+ `),e.end())}return{charge:g,send:o,end:a}}export{K as FileVoucherStore,b as InMemoryVoucherStore,d as InvalidRegistrationError,m as InvalidVoucherError,h as InvalidVoucherSignatureError,f as OnChainVerificationError,c as ScopeViolationError,N as TAB_VOUCHER_HEADER,V as enforceScope,re as openSse,k as parseRegistration,L as readVaultState,ne as requireTab,te as tabMiddleware,_ as verifyRegistrationOnChain,R as verifyVoucherSignature};
@@ -1,5 +1,5 @@
1
1
  import { ClientChannelStorage } from '@x402/evm/batch-settlement/client';
2
- import { E as EvmWallet } from './types-htvWHuW3.cjs';
2
+ import { E as EvmWallet } from './types-RxdlGPaG.cjs';
3
3
 
4
4
  /**
5
5
  * Buyer withdrawal escape hatch for batch-settlement escrow channels.
@@ -1,5 +1,5 @@
1
1
  import { ClientChannelStorage } from '@x402/evm/batch-settlement/client';
2
- import { E as EvmWallet } from './types-htvWHuW3.js';
2
+ import { E as EvmWallet } from './types-RxdlGPaG.js';
3
3
 
4
4
  /**
5
5
  * Buyer withdrawal escape hatch for batch-settlement escrow channels.
@@ -1,6 +1,6 @@
1
1
  import { RequestHandler } from 'express';
2
2
  import { ChannelStorage } from '@x402/evm/batch-settlement/server';
3
- import { b as CloseReceipt } from './types-XG8QvfyL.cjs';
3
+ import { b as CloseReceipt } from './types-C8lyIOmX.cjs';
4
4
 
5
5
  /**
6
6
  * Result of closing one channel from the seller side. Either a settlement
@@ -1,6 +1,6 @@
1
1
  import { RequestHandler } from 'express';
2
2
  import { ChannelStorage } from '@x402/evm/batch-settlement/server';
3
- import { b as CloseReceipt } from './types-DllrEG_Z.js';
3
+ import { b as CloseReceipt } from './types-CTl7yVq6.js';
4
4
 
5
5
  /**
6
6
  * Result of closing one channel from the seller side. Either a settlement