@obbyx/sdk 0.0.0-stage → 0.1.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 (53) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +89 -2
  3. package/dist/client/budget.d.ts +43 -0
  4. package/dist/client/budget.d.ts.map +1 -0
  5. package/dist/client/budget.js +56 -0
  6. package/dist/client/budget.js.map +1 -0
  7. package/dist/client/paying-fetch.d.ts +46 -0
  8. package/dist/client/paying-fetch.d.ts.map +1 -0
  9. package/dist/client/paying-fetch.js +186 -0
  10. package/dist/client/paying-fetch.js.map +1 -0
  11. package/dist/index.d.ts +7 -0
  12. package/dist/index.d.ts.map +1 -0
  13. package/dist/index.js +7 -0
  14. package/dist/index.js.map +1 -0
  15. package/dist/seller/binding.d.ts +2 -0
  16. package/dist/seller/binding.d.ts.map +1 -0
  17. package/dist/seller/binding.js +3 -0
  18. package/dist/seller/binding.js.map +1 -0
  19. package/dist/seller/consumed-proofs.d.ts +78 -0
  20. package/dist/seller/consumed-proofs.d.ts.map +1 -0
  21. package/dist/seller/consumed-proofs.js +150 -0
  22. package/dist/seller/consumed-proofs.js.map +1 -0
  23. package/dist/seller/defaults.d.ts +7 -0
  24. package/dist/seller/defaults.d.ts.map +1 -0
  25. package/dist/seller/defaults.js +7 -0
  26. package/dist/seller/defaults.js.map +1 -0
  27. package/dist/seller/payment-identifier.d.ts +73 -0
  28. package/dist/seller/payment-identifier.d.ts.map +1 -0
  29. package/dist/seller/payment-identifier.js +75 -0
  30. package/dist/seller/payment-identifier.js.map +1 -0
  31. package/dist/seller/paywall.d.ts +101 -0
  32. package/dist/seller/paywall.d.ts.map +1 -0
  33. package/dist/seller/paywall.js +397 -0
  34. package/dist/seller/paywall.js.map +1 -0
  35. package/dist/seller/quote.d.ts +38 -0
  36. package/dist/seller/quote.d.ts.map +1 -0
  37. package/dist/seller/quote.js +39 -0
  38. package/dist/seller/quote.js.map +1 -0
  39. package/dist/seller/x402-server.d.ts +59 -0
  40. package/dist/seller/x402-server.d.ts.map +1 -0
  41. package/dist/seller/x402-server.js +173 -0
  42. package/dist/seller/x402-server.js.map +1 -0
  43. package/package.json +68 -3
  44. package/src/client/budget.ts +88 -0
  45. package/src/client/paying-fetch.ts +256 -0
  46. package/src/index.ts +35 -0
  47. package/src/seller/binding.ts +8 -0
  48. package/src/seller/consumed-proofs.ts +245 -0
  49. package/src/seller/defaults.ts +6 -0
  50. package/src/seller/payment-identifier.ts +104 -0
  51. package/src/seller/paywall.ts +595 -0
  52. package/src/seller/quote.ts +82 -0
  53. package/src/seller/x402-server.ts +251 -0
@@ -0,0 +1,150 @@
1
+ import { DatabaseSync } from 'node:sqlite';
2
+ export class ConsumedProofsStore {
3
+ now;
4
+ db;
5
+ constructor(path, now = () => Math.floor(Date.now() / 1000)) {
6
+ this.now = now;
7
+ this.db = new DatabaseSync(path);
8
+ this.db.exec(`
9
+ PRAGMA journal_mode = WAL;
10
+ PRAGMA synchronous = FULL;
11
+ PRAGMA busy_timeout = 5000;
12
+ CREATE TABLE IF NOT EXISTS consumed_proofs (
13
+ key TEXT PRIMARY KEY,
14
+ binding TEXT NOT NULL,
15
+ state TEXT NOT NULL,
16
+ payment_response TEXT,
17
+ response_status INTEGER,
18
+ response_headers TEXT,
19
+ response_body BLOB,
20
+ created_at INTEGER NOT NULL,
21
+ updated_at INTEGER NOT NULL
22
+ ) STRICT;
23
+ CREATE TABLE IF NOT EXISTS payment_ids (
24
+ scope_id TEXT PRIMARY KEY,
25
+ fingerprint TEXT NOT NULL,
26
+ proof_key TEXT NOT NULL,
27
+ created_at INTEGER NOT NULL
28
+ ) STRICT;
29
+ `);
30
+ }
31
+ /**
32
+ * x402 `payment-identifier`: remembers which proof served a client-chosen id
33
+ * for one operation fingerprint. Returns the stored mapping (existing or new).
34
+ */
35
+ bindPaymentId(scopeId, fingerprint, proofKey) {
36
+ const res = this.db
37
+ .prepare('INSERT INTO payment_ids (scope_id, fingerprint, proof_key, created_at) VALUES (?, ?, ?, ?) ON CONFLICT(scope_id) DO NOTHING')
38
+ .run(scopeId, fingerprint, proofKey, this.now());
39
+ const row = this.db
40
+ .prepare('SELECT fingerprint, proof_key FROM payment_ids WHERE scope_id = ?')
41
+ .get(scopeId);
42
+ return {
43
+ fingerprint: row.fingerprint,
44
+ proofKey: row.proof_key,
45
+ created: Number(res.changes) === 1,
46
+ };
47
+ }
48
+ /** Compare-and-set: moves the id only if it still points at `previous` (one winner). */
49
+ rebindPaymentId(scopeId, previous, proofKey) {
50
+ const res = this.db
51
+ .prepare('UPDATE payment_ids SET proof_key = ? WHERE scope_id = ? AND proof_key = ?')
52
+ .run(proofKey, scopeId, previous);
53
+ return Number(res.changes) === 1;
54
+ }
55
+ close() {
56
+ this.db.close();
57
+ }
58
+ get(key) {
59
+ const row = this.db.prepare('SELECT * FROM consumed_proofs WHERE key = ?').get(key);
60
+ return row === undefined ? undefined : toRecord(row);
61
+ }
62
+ /** Inserts the proof in `settling` unless it exists; returns the stored record. */
63
+ begin(key, binding) {
64
+ const ts = this.now();
65
+ const res = this.db
66
+ .prepare(`INSERT INTO consumed_proofs (key, binding, state, created_at, updated_at)
67
+ VALUES (?, ?, 'settling', ?, ?) ON CONFLICT(key) DO NOTHING`)
68
+ .run(key, binding, ts, ts);
69
+ const record = this.get(key);
70
+ if (record === undefined)
71
+ throw new Error(`consumed proof ${key} vanished`);
72
+ return { created: Number(res.changes) === 1, record };
73
+ }
74
+ /** Compare-and-set; returns false if the proof is not in `from`. */
75
+ transition(key, from, to, patch = {}) {
76
+ const res = this.db
77
+ .prepare(`UPDATE consumed_proofs
78
+ SET state = ?, updated_at = ?,
79
+ payment_response = COALESCE(?, payment_response),
80
+ response_status = COALESCE(?, response_status),
81
+ response_headers = COALESCE(?, response_headers),
82
+ response_body = COALESCE(?, response_body)
83
+ WHERE key = ? AND state = ?`)
84
+ .run(to, this.now(), patch.paymentResponse ?? null, patch.response?.status ?? null, patch.response === undefined ? null : JSON.stringify(patch.response.headers), patch.response?.body ?? null, key, from);
85
+ return Number(res.changes) === 1;
86
+ }
87
+ /**
88
+ * Records a SETTLED payment as paid (`settling → paid_not_served`), also when
89
+ * a concurrent request removed or moved its row meanwhile: the payment did
90
+ * settle, so it must stay servable once. Atomic; returns the stored state.
91
+ */
92
+ markSettled(key, binding, paymentResponse) {
93
+ const ts = this.now();
94
+ this.db.exec('BEGIN IMMEDIATE');
95
+ try {
96
+ this.db
97
+ .prepare(`INSERT INTO consumed_proofs (key, binding, state, payment_response, created_at, updated_at)
98
+ VALUES (?, ?, 'paid_not_served', ?, ?, ?)
99
+ ON CONFLICT(key) DO UPDATE SET state = 'paid_not_served', payment_response = excluded.payment_response,
100
+ updated_at = excluded.updated_at
101
+ WHERE consumed_proofs.state = 'settling'`)
102
+ .run(key, binding, paymentResponse, ts, ts);
103
+ this.db.exec('COMMIT');
104
+ }
105
+ catch (err) {
106
+ this.db.exec('ROLLBACK');
107
+ throw err;
108
+ }
109
+ const record = this.get(key);
110
+ if (record === undefined)
111
+ throw new Error(`consumed proof ${key} vanished`);
112
+ return record.state;
113
+ }
114
+ /**
115
+ * Recovers a proof stuck in `serving` (the process died while the handler
116
+ * ran): after `leaseSeconds` it goes back to `paid_not_served` (already
117
+ * settled) or `settling` (authorization flow, not settled yet), so the SAME
118
+ * payment can complete the operation. Compare-and-set: one winner.
119
+ */
120
+ reclaimStale(key, leaseSeconds) {
121
+ const res = this.db
122
+ .prepare(`UPDATE consumed_proofs
123
+ SET state = CASE WHEN payment_response IS NULL THEN 'settling' ELSE 'paid_not_served' END,
124
+ updated_at = ?
125
+ WHERE key = ? AND state = 'serving' AND updated_at <= ?`)
126
+ .run(this.now(), key, this.now() - leaseSeconds);
127
+ return Number(res.changes) === 1;
128
+ }
129
+ /** Removes a proof that never reached the network (e.g. rejected at verification). */
130
+ forget(key, state) {
131
+ this.db.prepare('DELETE FROM consumed_proofs WHERE key = ? AND state = ?').run(key, state);
132
+ }
133
+ }
134
+ function toRecord(row) {
135
+ return {
136
+ key: row.key,
137
+ binding: row.binding,
138
+ state: row.state,
139
+ paymentResponse: row.payment_response,
140
+ response: row.response_status === null
141
+ ? null
142
+ : {
143
+ status: row.response_status,
144
+ headers: JSON.parse(row.response_headers ?? '{}'),
145
+ body: row.response_body ?? new Uint8Array(),
146
+ },
147
+ updatedAt: row.updated_at,
148
+ };
149
+ }
150
+ //# sourceMappingURL=consumed-proofs.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"consumed-proofs.js","sourceRoot":"","sources":["../../src/seller/consumed-proofs.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,7 @@
1
+ /** Seller-side defaults shared by ObytePaywall and ObyteExactServer (seconds). */
2
+ export declare const DEFAULT_QUOTE_TTL_SECONDS = 300;
3
+ /** Freshness window of the payment unit's timestamp offered in `maxTimeoutSeconds`. */
4
+ export declare const DEFAULT_MAX_TIMEOUT_SECONDS = 300;
5
+ /** How long a request may hold a payment in `serving` before a retry takes it over. */
6
+ export declare const DEFAULT_SERVING_LEASE_SECONDS = 300;
7
+ //# sourceMappingURL=defaults.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.d.ts","sourceRoot":"","sources":["../../src/seller/defaults.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,eAAO,MAAM,yBAAyB,MAAM,CAAC;AAC7C,uFAAuF;AACvF,eAAO,MAAM,2BAA2B,MAAM,CAAC;AAC/C,uFAAuF;AACvF,eAAO,MAAM,6BAA6B,MAAM,CAAC"}
@@ -0,0 +1,7 @@
1
+ /** Seller-side defaults shared by ObytePaywall and ObyteExactServer (seconds). */
2
+ export const DEFAULT_QUOTE_TTL_SECONDS = 300;
3
+ /** Freshness window of the payment unit's timestamp offered in `maxTimeoutSeconds`. */
4
+ export const DEFAULT_MAX_TIMEOUT_SECONDS = 300;
5
+ /** How long a request may hold a payment in `serving` before a retry takes it over. */
6
+ export const DEFAULT_SERVING_LEASE_SECONDS = 300;
7
+ //# sourceMappingURL=defaults.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"defaults.js","sourceRoot":"","sources":["../../src/seller/defaults.ts"],"names":[],"mappings":"AAAA,kFAAkF;AAClF,MAAM,CAAC,MAAM,yBAAyB,GAAG,GAAG,CAAC;AAC7C,uFAAuF;AACvF,MAAM,CAAC,MAAM,2BAA2B,GAAG,GAAG,CAAC;AAC/C,uFAAuF;AACvF,MAAM,CAAC,MAAM,6BAA6B,GAAG,GAAG,CAAC"}
@@ -0,0 +1,73 @@
1
+ import type { PaymentPayload } from '@x402/core/types';
2
+ import { z } from 'zod';
3
+ /**
4
+ * x402 `payment-identifier` extension (seller side): a client-chosen id makes
5
+ * a repeated operation return the stored response instead of charging again.
6
+ */
7
+ export declare const PAYMENT_IDENTIFIER = "payment-identifier";
8
+ /** Same constraints as @x402/extensions (16–128 chars of [A-Za-z0-9_-]). */
9
+ export declare const paymentIdSchema: z.ZodString;
10
+ /** Same declaration as @x402/extensions `declarePaymentIdentifierExtension`. */
11
+ export declare function paymentIdentifierDeclaration(required: boolean): {
12
+ info: {
13
+ required: boolean;
14
+ };
15
+ schema: {
16
+ $schema: string;
17
+ type: string;
18
+ properties: {
19
+ required: {
20
+ type: string;
21
+ };
22
+ id: {
23
+ type: string;
24
+ minLength: number;
25
+ maxLength: number;
26
+ pattern: string;
27
+ };
28
+ };
29
+ required: string[];
30
+ };
31
+ };
32
+ /** The client's id: `undefined` when absent, `'invalid'` when malformed. */
33
+ export declare function paymentIdOf(payload: PaymentPayload): string | undefined | 'invalid';
34
+ /** What the store needs for payment-identifier decisions. */
35
+ export interface PaymentIdStore {
36
+ bindPaymentId(scopeId: string, fingerprint: string, proofKey: string): {
37
+ fingerprint: string;
38
+ proofKey: string;
39
+ created: boolean;
40
+ };
41
+ /** Compare-and-set: moves the id to `proofKey` only if it still points at `previous`. */
42
+ rebindPaymentId(scopeId: string, previous: string, proofKey: string): boolean;
43
+ get(key: string): {
44
+ state: string;
45
+ response: unknown;
46
+ paymentResponse: string | null;
47
+ } | undefined;
48
+ }
49
+ export type PaymentIdDecision = {
50
+ kind: 'proceed';
51
+ } | {
52
+ kind: 'replay';
53
+ proofKey: string;
54
+ } | {
55
+ kind: 'reject';
56
+ status: 400 | 409;
57
+ error: string;
58
+ };
59
+ /**
60
+ * One client id = one operation. Contract: the caller has already created the
61
+ * proof row for `proofKey` (so other requests see it as in progress).
62
+ * Concurrent retries with DIFFERENT payments
63
+ * for the same id: exactly one wins the compare-and-set; the others get 409,
64
+ * so an id can never be charged twice.
65
+ */
66
+ export declare function decidePaymentId(store: PaymentIdStore, input: {
67
+ paymentId: string | undefined | 'invalid';
68
+ required: boolean;
69
+ scopeId: (id: string) => string;
70
+ fingerprint: string;
71
+ proofKey: string;
72
+ }): PaymentIdDecision;
73
+ //# sourceMappingURL=payment-identifier.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-identifier.d.ts","sourceRoot":"","sources":["../../src/seller/payment-identifier.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,kBAAkB,CAAC;AACvD,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB;;;GAGG;AACH,eAAO,MAAM,kBAAkB,uBAAuB,CAAC;AAEvD,4EAA4E;AAC5E,eAAO,MAAM,eAAe,aAA8C,CAAC;AAM3E,gFAAgF;AAChF,wBAAgB,4BAA4B,CAAC,QAAQ,EAAE,OAAO;;;;;;;;;;;;;;;;;;;;EAa7D;AAED,4EAA4E;AAC5E,wBAAgB,WAAW,CAAC,OAAO,EAAE,cAAc,GAAG,MAAM,GAAG,SAAS,GAAG,SAAS,CASnF;AAED,6DAA6D;AAC7D,MAAM,WAAW,cAAc;IAC7B,aAAa,CACX,OAAO,EAAE,MAAM,EACf,WAAW,EAAE,MAAM,EACnB,QAAQ,EAAE,MAAM,GACf;QAAE,WAAW,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,OAAO,CAAA;KAAE,CAAC;IAC/D,yFAAyF;IACzF,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC;IAC9E,GAAG,CACD,GAAG,EAAE,MAAM,GACV;QAAE,KAAK,EAAE,MAAM,CAAC;QAAC,QAAQ,EAAE,OAAO,CAAC;QAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAA;KAAE,GAAG,SAAS,CAAC;CACrF;AAED,MAAM,MAAM,iBAAiB,GACzB;IAAE,IAAI,EAAE,SAAS,CAAA;CAAE,GACnB;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAA;CAAE,GACpC;IAAE,IAAI,EAAE,QAAQ,CAAC;IAAC,MAAM,EAAE,GAAG,GAAG,GAAG,CAAC;IAAC,KAAK,EAAE,MAAM,CAAA;CAAE,CAAC;AAEzD;;;;;;GAMG;AACH,wBAAgB,eAAe,CAC7B,KAAK,EAAE,cAAc,EACrB,KAAK,EAAE;IACL,SAAS,EAAE,MAAM,GAAG,SAAS,GAAG,SAAS,CAAC;IAC1C,QAAQ,EAAE,OAAO,CAAC;IAClB,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,KAAK,MAAM,CAAC;IAChC,WAAW,EAAE,MAAM,CAAC;IACpB,QAAQ,EAAE,MAAM,CAAC;CAClB,GACA,iBAAiB,CAwBnB"}
@@ -0,0 +1,75 @@
1
+ import { z } from 'zod';
2
+ /**
3
+ * x402 `payment-identifier` extension (seller side): a client-chosen id makes
4
+ * a repeated operation return the stored response instead of charging again.
5
+ */
6
+ export const PAYMENT_IDENTIFIER = 'payment-identifier';
7
+ /** Same constraints as @x402/extensions (16–128 chars of [A-Za-z0-9_-]). */
8
+ export const paymentIdSchema = z.string().regex(/^[a-zA-Z0-9_-]{16,128}$/);
9
+ const extensionSchema = z
10
+ .object({ info: z.object({ id: z.unknown().optional() }).loose() })
11
+ .loose();
12
+ /** Same declaration as @x402/extensions `declarePaymentIdentifierExtension`. */
13
+ export function paymentIdentifierDeclaration(required) {
14
+ return {
15
+ info: { required },
16
+ schema: {
17
+ $schema: 'https://json-schema.org/draft/2020-12/schema',
18
+ type: 'object',
19
+ properties: {
20
+ required: { type: 'boolean' },
21
+ id: { type: 'string', minLength: 16, maxLength: 128, pattern: '^[a-zA-Z0-9_-]+$' },
22
+ },
23
+ required: ['required'],
24
+ },
25
+ };
26
+ }
27
+ /** The client's id: `undefined` when absent, `'invalid'` when malformed. */
28
+ export function paymentIdOf(payload) {
29
+ const raw = payload.extensions?.[PAYMENT_IDENTIFIER];
30
+ if (raw === undefined)
31
+ return undefined;
32
+ const ext = extensionSchema.safeParse(raw);
33
+ if (!ext.success)
34
+ return 'invalid';
35
+ const id = ext.data.info.id;
36
+ if (id === undefined)
37
+ return undefined;
38
+ const parsed = paymentIdSchema.safeParse(id);
39
+ return parsed.success ? parsed.data : 'invalid';
40
+ }
41
+ /**
42
+ * One client id = one operation. Contract: the caller has already created the
43
+ * proof row for `proofKey` (so other requests see it as in progress).
44
+ * Concurrent retries with DIFFERENT payments
45
+ * for the same id: exactly one wins the compare-and-set; the others get 409,
46
+ * so an id can never be charged twice.
47
+ */
48
+ export function decidePaymentId(store, input) {
49
+ const { paymentId } = input;
50
+ if (paymentId === 'invalid')
51
+ return { kind: 'reject', status: 400, error: 'invalid_payment_identifier' };
52
+ if (paymentId === undefined) {
53
+ return input.required
54
+ ? { kind: 'reject', status: 400, error: 'payment_identifier_required' }
55
+ : { kind: 'proceed' };
56
+ }
57
+ const scopeId = input.scopeId(paymentId);
58
+ const mapping = store.bindPaymentId(scopeId, input.fingerprint, input.proofKey);
59
+ if (mapping.fingerprint !== input.fingerprint) {
60
+ return { kind: 'reject', status: 409, error: 'payment_identifier_conflict' };
61
+ }
62
+ if (mapping.proofKey === input.proofKey)
63
+ return { kind: 'proceed' };
64
+ const original = store.get(mapping.proofKey);
65
+ if (original?.state === 'served')
66
+ return { kind: 'replay', proofKey: mapping.proofKey };
67
+ if (original !== undefined && original.state !== 'failed') {
68
+ return { kind: 'reject', status: 409, error: 'payment_in_progress' };
69
+ }
70
+ // The earlier attempt definitely failed (or never consumed its proof): move the id.
71
+ return store.rebindPaymentId(scopeId, mapping.proofKey, input.proofKey)
72
+ ? { kind: 'proceed' }
73
+ : { kind: 'reject', status: 409, error: 'payment_in_progress' };
74
+ }
75
+ //# sourceMappingURL=payment-identifier.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"payment-identifier.js","sourceRoot":"","sources":["../../src/seller/payment-identifier.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,101 @@
1
+ import type { Request, RequestHandler } from 'express';
2
+ import { type FacilitatorClient } from '@x402/core/http';
3
+ import { type Logger } from '@obbyx/obyte/logger';
4
+ import { BATCH_SCHEME, EXACT_SCHEME } from '@obbyx/x402-obyte';
5
+ import type { ConsumedProofsStore } from './consumed-proofs.js';
6
+ /**
7
+ * Express paywall for x402 `exact` payments on Obyte (seller side).
8
+ *
9
+ * Flow `upfront` (phase 3): settle to the facilitator's finality BEFORE the
10
+ * handler runs; the handler runs at most once per payment proof; a repeat of
11
+ * the same operation with the same proof returns the stored response.
12
+ *
13
+ * Paid handlers return their response instead of writing to `res`, so the
14
+ * paywall can store it for idempotent replays.
15
+ */
16
+ export interface PaidResult {
17
+ readonly status?: number;
18
+ readonly headers?: Readonly<Record<string, string>>;
19
+ /** Objects are sent as JSON; strings/bytes as-is. */
20
+ readonly body: unknown;
21
+ }
22
+ export type PaidHandler = (req: Request) => Promise<PaidResult> | PaidResult;
23
+ export interface RoutePrice {
24
+ /** Atomic units as a decimal string. */
25
+ readonly amount: string;
26
+ /** `'base'` for bytes or an asset id. */
27
+ readonly asset?: string;
28
+ }
29
+ export interface PaidRouteOptions {
30
+ readonly price: RoutePrice;
31
+ readonly description?: string;
32
+ readonly mimeType?: string;
33
+ /** What identifies "the same operation". Default: sha256 of the canonical JSON body. */
34
+ readonly fingerprint?: (req: Request) => string;
35
+ /** Offered schemes in order of preference. Default: batch-settlement (if enabled), exact. */
36
+ readonly schemes?: readonly PaywallScheme[];
37
+ /**
38
+ * `upfront` (default): settle, then run the handler — for anything expensive
39
+ * or irreversible. `authorization`: verify, run the handler, settle, then
40
+ * send — only for cheap, idempotent handlers: if the settlement then fails,
41
+ * the handler's side effects have happened but its response is withheld.
42
+ */
43
+ readonly flow?: 'upfront' | 'authorization';
44
+ }
45
+ export type PaywallScheme = typeof EXACT_SCHEME | typeof BATCH_SCHEME;
46
+ export interface ObytePaywallOptions {
47
+ readonly facilitator: FacilitatorClient;
48
+ readonly network: string;
49
+ readonly payTo: string;
50
+ /** Stable identifier of this seller/API (part of the binding). */
51
+ readonly sellerId: string;
52
+ /** HMAC key for bindings; keep secret, ≥ 32 bytes. */
53
+ readonly secret: string | Uint8Array;
54
+ readonly store: ConsumedProofsStore;
55
+ readonly logger: Logger;
56
+ readonly quoteTtlSeconds?: number;
57
+ readonly maxTimeoutSeconds?: number;
58
+ /** How long one request waits for `settlement_pending` to resolve. */
59
+ readonly settleWaitMs?: number;
60
+ readonly settlePollMs?: number;
61
+ /**
62
+ * How long a request may hold a payment in `serving` (default 300 s). After
63
+ * that a retry with the same payment takes over (crash recovery); set it
64
+ * above the slowest handler.
65
+ */
66
+ readonly servingLeaseSeconds?: number;
67
+ /**
68
+ * Declares the x402 `payment-identifier` extension: a client-chosen id makes a
69
+ * repeated operation return the stored response instead of charging again.
70
+ */
71
+ readonly paymentIdentifier?: {
72
+ readonly required: boolean;
73
+ };
74
+ /** Enables batch-settlement (AA payment channels) offers; requires a running seller watcher. */
75
+ readonly batch?: {
76
+ readonly minChannelTimeoutSeconds: number;
77
+ };
78
+ readonly now?: () => number;
79
+ }
80
+ export declare class ObytePaywall {
81
+ private readonly options;
82
+ private readonly log;
83
+ private readonly now;
84
+ constructor(options: ObytePaywallOptions);
85
+ /** Wraps a handler so it only runs after a settled payment. */
86
+ paid(route: PaidRouteOptions, handler: PaidHandler): RequestHandler;
87
+ private handle;
88
+ /** authorization flow: verify → handler (response stored) → settle → send. */
89
+ private authorize;
90
+ private settleServed;
91
+ private serve;
92
+ private send;
93
+ private paymentRequired;
94
+ /** PAYMENT-RESPONSE of a payment refused before settlement (not used, nothing charged). */
95
+ private refusal;
96
+ private requestFacts;
97
+ private terms;
98
+ private schemesFor;
99
+ private settleWithWait;
100
+ }
101
+ //# sourceMappingURL=paywall.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"paywall.d.ts","sourceRoot":"","sources":["../../src/seller/paywall.ts"],"names":[],"mappings":"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"}