@obbyx/sdk 0.1.1 → 0.2.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.
- package/README.md +14 -0
- package/dist/client/paying-fetch.d.ts.map +1 -1
- package/dist/client/paying-fetch.js +68 -61
- package/dist/client/paying-fetch.js.map +1 -1
- package/dist/index.d.ts +2 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/seller/consumed-proofs.d.ts.map +1 -1
- package/dist/seller/consumed-proofs.js +8 -15
- package/dist/seller/consumed-proofs.js.map +1 -1
- package/dist/seller/cors.d.ts +33 -0
- package/dist/seller/cors.d.ts.map +1 -0
- package/dist/seller/cors.js +77 -0
- package/dist/seller/cors.js.map +1 -0
- package/dist/seller/paywall.d.ts +32 -0
- package/dist/seller/paywall.d.ts.map +1 -1
- package/dist/seller/paywall.js +183 -127
- package/dist/seller/paywall.js.map +1 -1
- package/dist/seller/x402-server.d.ts +48 -8
- package/dist/seller/x402-server.d.ts.map +1 -1
- package/dist/seller/x402-server.js +63 -22
- package/dist/seller/x402-server.js.map +1 -1
- package/package.json +3 -3
- package/src/client/paying-fetch.ts +96 -64
- package/src/index.ts +10 -1
- package/src/seller/consumed-proofs.ts +10 -16
- package/src/seller/cors.ts +106 -0
- package/src/seller/paywall.ts +237 -177
- package/src/seller/x402-server.ts +101 -29
|
@@ -9,6 +9,7 @@ import type {
|
|
|
9
9
|
SchemeServerHooks,
|
|
10
10
|
} from '@x402/core/types';
|
|
11
11
|
import {
|
|
12
|
+
CHANNEL_BASE_AA,
|
|
12
13
|
ObbyxError,
|
|
13
14
|
fromDisplay,
|
|
14
15
|
getNetwork,
|
|
@@ -17,14 +18,24 @@ import {
|
|
|
17
18
|
parseAmount,
|
|
18
19
|
} from '@obbyx/obyte';
|
|
19
20
|
import { type Logger, moduleLogger } from '@obbyx/obyte/logger';
|
|
20
|
-
import {
|
|
21
|
+
import {
|
|
22
|
+
BATCH_SCHEME,
|
|
23
|
+
EXACT_SCHEME,
|
|
24
|
+
batchExtraSchema,
|
|
25
|
+
bindingsEqual,
|
|
26
|
+
canonical,
|
|
27
|
+
exactExtraSchema,
|
|
28
|
+
sha256Hex,
|
|
29
|
+
} from '@obbyx/x402-obyte';
|
|
21
30
|
import type { ConsumedProofsStore } from './consumed-proofs.js';
|
|
22
31
|
import { DEFAULT_QUOTE_TTL_SECONDS, DEFAULT_SERVING_LEASE_SECONDS } from './defaults.js';
|
|
23
32
|
import { type Quote, bodyHashOf, newQuote, offerBinding } from './quote.js';
|
|
24
33
|
|
|
25
34
|
/**
|
|
26
|
-
* Obyte
|
|
27
|
-
* `@x402/express`, `@x402/hono`, …):
|
|
35
|
+
* Obyte schemes for the OFFICIAL x402 resource server (`@x402/core/server`,
|
|
36
|
+
* `@x402/express`, `@x402/hono`, …): SchemeNetworkServer implementations for
|
|
37
|
+
* `exact` ({@link ObyteExactServer}) and `batch-settlement`
|
|
38
|
+
* ({@link ObyteBatchServer}), sharing {@link ObyteSchemeServer}.
|
|
28
39
|
*
|
|
29
40
|
* - Binding (I1/I2): `enrichPaymentRequiredResponse` adds a per-request HMAC
|
|
30
41
|
* binding + quote to every offer. On a paid request it recomputes the HMAC
|
|
@@ -38,7 +49,7 @@ import { type Quote, bodyHashOf, newQuote, offerBinding } from './quote.js';
|
|
|
38
49
|
* Unlike `@obbyx/sdk`'s ObytePaywall, a repeated request gets a 402 instead
|
|
39
50
|
* of the stored response (the official middleware cannot replay responses).
|
|
40
51
|
*/
|
|
41
|
-
export interface
|
|
52
|
+
export interface ObyteSchemeServerOptions {
|
|
42
53
|
/** How long a request may hold a payment in `serving` before a retry takes over (default 300 s). */
|
|
43
54
|
readonly servingLeaseSeconds?: number;
|
|
44
55
|
readonly sellerId: string;
|
|
@@ -72,8 +83,32 @@ function requestOf(transportContext: unknown): RequestLike | undefined {
|
|
|
72
83
|
};
|
|
73
84
|
}
|
|
74
85
|
|
|
75
|
-
|
|
76
|
-
|
|
86
|
+
/** @deprecated Use {@link ObyteSchemeServerOptions}. */
|
|
87
|
+
export type ObyteExactServerOptions = ObyteSchemeServerOptions;
|
|
88
|
+
|
|
89
|
+
/** Options of {@link ObyteBatchServer}. */
|
|
90
|
+
export interface ObyteBatchServerOptions extends ObyteSchemeServerOptions {
|
|
91
|
+
/** Minimum channel dispute window the seller accepts (seconds); must match the facilitator's. */
|
|
92
|
+
readonly minChannelTimeoutSeconds: number;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/** Per-scheme parts of an Obyte resource-server scheme. */
|
|
96
|
+
export interface ObyteSchemeSpec {
|
|
97
|
+
readonly scheme: typeof EXACT_SCHEME | typeof BATCH_SCHEME;
|
|
98
|
+
/** `extra` fields the scheme adds to every offer besides binding and quote. */
|
|
99
|
+
readonly staticExtra: Record<string, unknown>;
|
|
100
|
+
/** Parses the quote fields of the accepted offer. */
|
|
101
|
+
readonly acceptedExtra: (extra: unknown) => {
|
|
102
|
+
readonly binding: string;
|
|
103
|
+
readonly quoteId: string;
|
|
104
|
+
readonly validUntil: number;
|
|
105
|
+
};
|
|
106
|
+
/** Proof id and key kind of a payment payload (undefined when malformed). */
|
|
107
|
+
readonly proofOf: (payload: Record<string, unknown>) => { kind: string; id: string } | undefined;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
export abstract class ObyteSchemeServer implements SchemeNetworkServer {
|
|
111
|
+
readonly scheme: string;
|
|
77
112
|
readonly defaultAssetTransferMethod = 'default';
|
|
78
113
|
readonly paymentFlows = {
|
|
79
114
|
default: { supported: ['upfront'] as const, default: 'upfront' as const },
|
|
@@ -83,24 +118,25 @@ export class ObyteExactServer implements SchemeNetworkServer {
|
|
|
83
118
|
private readonly log: Logger;
|
|
84
119
|
private readonly now: () => number;
|
|
85
120
|
|
|
86
|
-
constructor(
|
|
87
|
-
|
|
121
|
+
protected constructor(
|
|
122
|
+
private readonly options: ObyteSchemeServerOptions,
|
|
123
|
+
private readonly spec: ObyteSchemeSpec,
|
|
124
|
+
) {
|
|
125
|
+
this.scheme = spec.scheme;
|
|
126
|
+
this.log = moduleLogger(options.logger, 'sdk.x402-server', { scheme: spec.scheme });
|
|
88
127
|
this.now = options.now ?? (() => Math.floor(Date.now() / 1000));
|
|
89
128
|
this.schemeHooks = {
|
|
90
129
|
onBeforeVerify: async (ctx) => {
|
|
91
|
-
const
|
|
130
|
+
const key = this.keyOf(ctx);
|
|
92
131
|
const binding = (ctx.requirements.extra as { binding?: unknown }).binding;
|
|
93
|
-
if (
|
|
132
|
+
if (key === undefined || typeof binding !== 'string') {
|
|
94
133
|
return { abort: true, reason: 'invalid_payload' };
|
|
95
134
|
}
|
|
96
|
-
const { record } = this.options.proofs.begin(
|
|
97
|
-
this.key(ctx.requirements.network, unit),
|
|
98
|
-
binding,
|
|
99
|
-
);
|
|
135
|
+
const { record } = this.options.proofs.begin(key, binding);
|
|
100
136
|
if (!bindingsEqual(record.binding, binding))
|
|
101
137
|
return { abort: true, reason: 'invalid_payment_reused' };
|
|
102
138
|
if (record.state === 'served') {
|
|
103
|
-
this.log.warn({ proofId:
|
|
139
|
+
this.log.warn({ proofId: key }, 'payment already served a request; refused');
|
|
104
140
|
return { abort: true, reason: 'payment_already_used' };
|
|
105
141
|
}
|
|
106
142
|
return undefined;
|
|
@@ -183,7 +219,7 @@ export class ObyteExactServer implements SchemeNetworkServer {
|
|
|
183
219
|
return asset === 'base' ? 9 : undefined;
|
|
184
220
|
}
|
|
185
221
|
|
|
186
|
-
/** Nothing to add from /supported
|
|
222
|
+
/** Nothing to add from /supported; the per-request fields come from the enrich hook. */
|
|
187
223
|
async enhancePaymentRequirements(
|
|
188
224
|
requirements: PaymentRequirements,
|
|
189
225
|
): Promise<PaymentRequirements> {
|
|
@@ -196,20 +232,21 @@ export class ObyteExactServer implements SchemeNetworkServer {
|
|
|
196
232
|
): Promise<PaymentRequirements[]> => {
|
|
197
233
|
const request = requestOf(ctx.transportContext);
|
|
198
234
|
if (request === undefined)
|
|
199
|
-
throw new ObbyxError('no_request',
|
|
235
|
+
throw new ObbyxError('no_request', `Obyte ${this.scheme} needs the HTTP request context`);
|
|
200
236
|
const accepted = this.acceptedQuote(request);
|
|
201
237
|
return ctx.requirements.map((req) => {
|
|
202
|
-
if (req.scheme !==
|
|
238
|
+
if (req.scheme !== this.spec.scheme) return req;
|
|
203
239
|
const quote =
|
|
204
240
|
accepted !== undefined &&
|
|
205
241
|
bindingsEqual(this.bindingFor(req, request, accepted), accepted.binding)
|
|
206
242
|
? accepted
|
|
207
243
|
: undefined;
|
|
208
|
-
|
|
244
|
+
const extra = { ...req.extra, ...this.spec.staticExtra };
|
|
245
|
+
if (quote !== undefined) return { ...req, extra: { ...extra, ...quote } };
|
|
209
246
|
const fresh = newQuote(this.now(), this.options.quoteTtlSeconds ?? DEFAULT_QUOTE_TTL_SECONDS);
|
|
210
247
|
return {
|
|
211
248
|
...req,
|
|
212
|
-
extra: { ...
|
|
249
|
+
extra: { ...extra, binding: this.bindingFor(req, request, fresh), ...fresh },
|
|
213
250
|
};
|
|
214
251
|
});
|
|
215
252
|
};
|
|
@@ -217,10 +254,10 @@ export class ObyteExactServer implements SchemeNetworkServer {
|
|
|
217
254
|
private acceptedQuote(request: RequestLike) {
|
|
218
255
|
if (request.paymentHeader === undefined) return undefined;
|
|
219
256
|
try {
|
|
220
|
-
const
|
|
221
|
-
|
|
222
|
-
);
|
|
223
|
-
return extra.
|
|
257
|
+
const accepted = decodePaymentSignatureHeader(request.paymentHeader).accepted;
|
|
258
|
+
if (accepted?.scheme !== this.spec.scheme) return undefined;
|
|
259
|
+
const extra = this.spec.acceptedExtra(accepted.extra);
|
|
260
|
+
return { quoteId: extra.quoteId, validUntil: extra.validUntil, binding: extra.binding };
|
|
224
261
|
} catch {
|
|
225
262
|
return undefined;
|
|
226
263
|
}
|
|
@@ -229,7 +266,7 @@ export class ObyteExactServer implements SchemeNetworkServer {
|
|
|
229
266
|
private bindingFor(req: PaymentRequirements, request: RequestLike, quote: Quote): string {
|
|
230
267
|
return offerBinding(
|
|
231
268
|
this.options,
|
|
232
|
-
|
|
269
|
+
this.spec.scheme,
|
|
233
270
|
quote,
|
|
234
271
|
{ method: request.method, url: request.url, bodyHash: bodyHashOf(request.body) },
|
|
235
272
|
{ network: req.network, asset: req.asset, amount: req.amount, payTo: req.payTo },
|
|
@@ -241,11 +278,46 @@ export class ObyteExactServer implements SchemeNetworkServer {
|
|
|
241
278
|
paymentPayload: { payload: Record<string, unknown> };
|
|
242
279
|
requirements: { network: string };
|
|
243
280
|
}): string | undefined {
|
|
244
|
-
const
|
|
245
|
-
return
|
|
281
|
+
const proof = this.spec.proofOf(ctx.paymentPayload.payload);
|
|
282
|
+
return proof === undefined
|
|
283
|
+
? undefined
|
|
284
|
+
: networkKey(getNetwork(ctx.requirements.network), proof.kind, proof.id);
|
|
246
285
|
}
|
|
286
|
+
}
|
|
247
287
|
|
|
248
|
-
|
|
249
|
-
|
|
288
|
+
/** `exact`: the proof is the unit (key `<network>:unit:<hash>`, as ObytePaywall). */
|
|
289
|
+
export class ObyteExactServer extends ObyteSchemeServer {
|
|
290
|
+
constructor(options: ObyteSchemeServerOptions) {
|
|
291
|
+
super(options, {
|
|
292
|
+
scheme: EXACT_SCHEME,
|
|
293
|
+
staticExtra: {},
|
|
294
|
+
acceptedExtra: (extra) => exactExtraSchema.parse(extra),
|
|
295
|
+
proofOf: (payload) => {
|
|
296
|
+
const unit = (payload as { unit?: { unit?: unknown } }).unit?.unit;
|
|
297
|
+
return typeof unit === 'string' ? { kind: 'unit', id: unit } : undefined;
|
|
298
|
+
},
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
/**
|
|
304
|
+
* `batch-settlement` (AA payment channels): the proof is the voucher (key
|
|
305
|
+
* `<network>:voucher:<sha256 of the canonical signed package>`, as
|
|
306
|
+
* ObytePaywall). Offers carry the channel base AA and the minimum dispute
|
|
307
|
+
* window. Needs a facilitator with batch-settlement and the seller's watcher.
|
|
308
|
+
*/
|
|
309
|
+
export class ObyteBatchServer extends ObyteSchemeServer {
|
|
310
|
+
constructor(options: ObyteBatchServerOptions) {
|
|
311
|
+
super(options, {
|
|
312
|
+
scheme: BATCH_SCHEME,
|
|
313
|
+
staticExtra: { baseAa: CHANNEL_BASE_AA, minTimeout: options.minChannelTimeoutSeconds },
|
|
314
|
+
acceptedExtra: (extra) => batchExtraSchema.parse(extra),
|
|
315
|
+
proofOf: (payload) => {
|
|
316
|
+
const pkg = payload['signedPackage'];
|
|
317
|
+
return pkg !== null && typeof pkg === 'object'
|
|
318
|
+
? { kind: 'voucher', id: sha256Hex(canonical(pkg)) }
|
|
319
|
+
: undefined;
|
|
320
|
+
},
|
|
321
|
+
});
|
|
250
322
|
}
|
|
251
323
|
}
|