@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.
- package/LICENSE +21 -0
- package/README.md +89 -2
- package/dist/client/budget.d.ts +43 -0
- package/dist/client/budget.d.ts.map +1 -0
- package/dist/client/budget.js +56 -0
- package/dist/client/budget.js.map +1 -0
- package/dist/client/paying-fetch.d.ts +46 -0
- package/dist/client/paying-fetch.d.ts.map +1 -0
- package/dist/client/paying-fetch.js +186 -0
- package/dist/client/paying-fetch.js.map +1 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/index.js.map +1 -0
- package/dist/seller/binding.d.ts +2 -0
- package/dist/seller/binding.d.ts.map +1 -0
- package/dist/seller/binding.js +3 -0
- package/dist/seller/binding.js.map +1 -0
- package/dist/seller/consumed-proofs.d.ts +78 -0
- package/dist/seller/consumed-proofs.d.ts.map +1 -0
- package/dist/seller/consumed-proofs.js +150 -0
- package/dist/seller/consumed-proofs.js.map +1 -0
- package/dist/seller/defaults.d.ts +7 -0
- package/dist/seller/defaults.d.ts.map +1 -0
- package/dist/seller/defaults.js +7 -0
- package/dist/seller/defaults.js.map +1 -0
- package/dist/seller/payment-identifier.d.ts +73 -0
- package/dist/seller/payment-identifier.d.ts.map +1 -0
- package/dist/seller/payment-identifier.js +75 -0
- package/dist/seller/payment-identifier.js.map +1 -0
- package/dist/seller/paywall.d.ts +101 -0
- package/dist/seller/paywall.d.ts.map +1 -0
- package/dist/seller/paywall.js +397 -0
- package/dist/seller/paywall.js.map +1 -0
- package/dist/seller/quote.d.ts +38 -0
- package/dist/seller/quote.d.ts.map +1 -0
- package/dist/seller/quote.js +39 -0
- package/dist/seller/quote.js.map +1 -0
- package/dist/seller/x402-server.d.ts +59 -0
- package/dist/seller/x402-server.d.ts.map +1 -0
- package/dist/seller/x402-server.js +173 -0
- package/dist/seller/x402-server.js.map +1 -0
- package/package.json +68 -3
- package/src/client/budget.ts +88 -0
- package/src/client/paying-fetch.ts +256 -0
- package/src/index.ts +35 -0
- package/src/seller/binding.ts +8 -0
- package/src/seller/consumed-proofs.ts +245 -0
- package/src/seller/defaults.ts +6 -0
- package/src/seller/payment-identifier.ts +104 -0
- package/src/seller/paywall.ts +595 -0
- package/src/seller/quote.ts +82 -0
- package/src/seller/x402-server.ts +251 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Alexander Ponomarev
|
|
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
CHANGED
|
@@ -1,3 +1,90 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @obbyx/sdk
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Accept and make [x402](https://x402.org) payments on [Obyte](https://obyte.org).
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npm i @obbyx/sdk @x402/core obyte
|
|
7
|
+
npm i express # for ObytePaywall
|
|
8
|
+
```
|
|
9
|
+
|
|
10
|
+
## Sell: `ObytePaywall` (Express)
|
|
11
|
+
|
|
12
|
+
```ts
|
|
13
|
+
import express from 'express';
|
|
14
|
+
import { HTTPFacilitatorClient } from '@x402/core/http';
|
|
15
|
+
import { createLogger } from '@obbyx/obyte/logger';
|
|
16
|
+
import { ConsumedProofsStore, ObytePaywall } from '@obbyx/sdk';
|
|
17
|
+
|
|
18
|
+
const paywall = new ObytePaywall({
|
|
19
|
+
facilitator: new HTTPFacilitatorClient({ url: 'https://your-facilitator.example' }),
|
|
20
|
+
network: 'obyte:testnet',
|
|
21
|
+
payTo: 'YOUR_OBYTE_ADDRESS',
|
|
22
|
+
sellerId: 'my-api',
|
|
23
|
+
secret: process.env.SELLER_SECRET!, // ≥ 32 chars, HMAC key of request bindings
|
|
24
|
+
store: new ConsumedProofsStore('./data/proofs.db'),
|
|
25
|
+
logger: createLogger(),
|
|
26
|
+
});
|
|
27
|
+
|
|
28
|
+
const app = express();
|
|
29
|
+
app.use(express.json());
|
|
30
|
+
app.post(
|
|
31
|
+
'/api/answer',
|
|
32
|
+
paywall.paid({ price: { amount: '20000' } }, async () => ({ body: { answer: 42 } })),
|
|
33
|
+
);
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
One payment pays one operation (binding over method, URL, body and price); the
|
|
37
|
+
handler runs once; a repeated request with the same payment gets the stored
|
|
38
|
+
response. Optional: payment channels (`batch`), `payment-identifier`,
|
|
39
|
+
`authorization` flow.
|
|
40
|
+
|
|
41
|
+
## Sell with the official `@x402/express`: `ObyteExactServer`
|
|
42
|
+
|
|
43
|
+
```ts
|
|
44
|
+
import { paymentMiddleware, x402ResourceServer } from '@x402/express';
|
|
45
|
+
import { ConsumedProofsStore, ObyteExactServer } from '@obbyx/sdk';
|
|
46
|
+
|
|
47
|
+
const server = new x402ResourceServer(new HTTPFacilitatorClient({ url })).register(
|
|
48
|
+
'obyte:testnet',
|
|
49
|
+
new ObyteExactServer({
|
|
50
|
+
sellerId: 'api',
|
|
51
|
+
secret: SELLER_SECRET,
|
|
52
|
+
proofs: new ConsumedProofsStore('proofs.db'),
|
|
53
|
+
logger,
|
|
54
|
+
}),
|
|
55
|
+
);
|
|
56
|
+
app.use(
|
|
57
|
+
paymentMiddleware(
|
|
58
|
+
{
|
|
59
|
+
'GET /x': {
|
|
60
|
+
accepts: { scheme: 'exact', network: 'obyte:testnet', payTo, price: '20000 bytes' },
|
|
61
|
+
},
|
|
62
|
+
},
|
|
63
|
+
server,
|
|
64
|
+
),
|
|
65
|
+
);
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
## Buy: `payingFetch` with budgets
|
|
69
|
+
|
|
70
|
+
```ts
|
|
71
|
+
import { SpendingBudget, payingFetch } from '@obbyx/sdk';
|
|
72
|
+
|
|
73
|
+
const { response, payment } = await payingFetch(
|
|
74
|
+
url,
|
|
75
|
+
{ method: 'GET' },
|
|
76
|
+
{
|
|
77
|
+
mechanisms: { exact /*, batch */ }, // from @obbyx/x402-obyte
|
|
78
|
+
network: 'obyte:testnet',
|
|
79
|
+
maxAmount: new Map([['base', 50_000n]]), // per call
|
|
80
|
+
budget: new SpendingBudget({ total: new Map([['base', 1_000_000n]]) }),
|
|
81
|
+
logger,
|
|
82
|
+
},
|
|
83
|
+
);
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
Never pays twice: a pending payment is retried with the same payment; unknown
|
|
87
|
+
outcomes stay counted against the budget; a channel is used when one is open,
|
|
88
|
+
otherwise `exact`.
|
|
89
|
+
|
|
90
|
+
Docs: [github.com/Taump/obbyx](https://github.com/Taump/obbyx). Node.js ≥ 24. MIT.
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { ObbyxError } from '@obbyx/obyte';
|
|
2
|
+
/**
|
|
3
|
+
* Spending limits for a paying client (agents, services).
|
|
4
|
+
*
|
|
5
|
+
* - `perCall`: the most one request may cost, per asset.
|
|
6
|
+
* - `total`: the most all requests may cost together, per asset.
|
|
7
|
+
*
|
|
8
|
+
* Amounts are RESERVED before a payment is signed (atomically: the event loop
|
|
9
|
+
* runs reserve() to completion), so concurrent calls can never overshoot the
|
|
10
|
+
* total. A reservation is then:
|
|
11
|
+
* - `spent` when the seller confirmed the payment,
|
|
12
|
+
* - `released` when the payment was definitely not made,
|
|
13
|
+
* - left in place when the outcome is unknown (invariant I7): the budget never
|
|
14
|
+
* under-counts a payment that may still settle.
|
|
15
|
+
*/
|
|
16
|
+
export interface BudgetLimits {
|
|
17
|
+
readonly perCall?: ReadonlyMap<string, bigint>;
|
|
18
|
+
readonly total?: ReadonlyMap<string, bigint>;
|
|
19
|
+
}
|
|
20
|
+
export interface BudgetReservation {
|
|
21
|
+
readonly id: number;
|
|
22
|
+
readonly asset: string;
|
|
23
|
+
readonly amount: bigint;
|
|
24
|
+
}
|
|
25
|
+
export declare class BudgetExceededError extends ObbyxError {
|
|
26
|
+
constructor(message: string, details: Record<string, unknown>);
|
|
27
|
+
}
|
|
28
|
+
export declare class SpendingBudget {
|
|
29
|
+
private readonly limits;
|
|
30
|
+
private readonly spent;
|
|
31
|
+
private readonly reserved;
|
|
32
|
+
private nextId;
|
|
33
|
+
constructor(limits: BudgetLimits);
|
|
34
|
+
reserve(asset: string, amount: bigint): BudgetReservation;
|
|
35
|
+
/** The payment settled: the amount is spent. */
|
|
36
|
+
spend(r: BudgetReservation): void;
|
|
37
|
+
/** The payment was definitely not made: give the amount back. */
|
|
38
|
+
release(r: BudgetReservation): void;
|
|
39
|
+
/** Spent + reserved (unknown outcomes included) for an asset. */
|
|
40
|
+
used(asset: string): bigint;
|
|
41
|
+
remaining(asset: string): bigint | undefined;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=budget.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.d.ts","sourceRoot":"","sources":["../../src/client/budget.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAE1C;;;;;;;;;;;;;GAaG;AACH,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,OAAO,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC/C,QAAQ,CAAC,KAAK,CAAC,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CAC9C;AAED,MAAM,WAAW,iBAAiB;IAChC,QAAQ,CAAC,EAAE,EAAE,MAAM,CAAC;IACpB,QAAQ,CAAC,KAAK,EAAE,MAAM,CAAC;IACvB,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;CACzB;AAED,qBAAa,mBAAoB,SAAQ,UAAU;gBACrC,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC;CAG9D;AAED,qBAAa,cAAc;IAKb,OAAO,CAAC,QAAQ,CAAC,MAAM;IAJnC,OAAO,CAAC,QAAQ,CAAC,KAAK,CAA6B;IACnD,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAwC;IACjE,OAAO,CAAC,MAAM,CAAK;gBAEU,MAAM,EAAE,YAAY;IAEjD,OAAO,CAAC,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAG,iBAAiB;IAyBzD,gDAAgD;IAChD,KAAK,CAAC,CAAC,EAAE,iBAAiB,GAAG,IAAI;IAKjC,iEAAiE;IACjE,OAAO,CAAC,CAAC,EAAE,iBAAiB,GAAG,IAAI;IAInC,iEAAiE;IACjE,IAAI,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM;IAM3B,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS;CAI7C"}
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { ObbyxError } from '@obbyx/obyte';
|
|
2
|
+
export class BudgetExceededError extends ObbyxError {
|
|
3
|
+
constructor(message, details) {
|
|
4
|
+
super('budget_exceeded', message, { details });
|
|
5
|
+
}
|
|
6
|
+
}
|
|
7
|
+
export class SpendingBudget {
|
|
8
|
+
limits;
|
|
9
|
+
spent = new Map();
|
|
10
|
+
reserved = new Map();
|
|
11
|
+
nextId = 1;
|
|
12
|
+
constructor(limits) {
|
|
13
|
+
this.limits = limits;
|
|
14
|
+
}
|
|
15
|
+
reserve(asset, amount) {
|
|
16
|
+
const perCall = this.limits.perCall?.get(asset);
|
|
17
|
+
if (perCall !== undefined && amount > perCall) {
|
|
18
|
+
throw new BudgetExceededError(`Price ${amount} exceeds the per-call limit ${perCall} (${asset})`, {
|
|
19
|
+
asset,
|
|
20
|
+
amount: amount.toString(),
|
|
21
|
+
perCall: perCall.toString(),
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
const total = this.limits.total?.get(asset);
|
|
25
|
+
const committed = this.used(asset);
|
|
26
|
+
if (total !== undefined && committed + amount > total) {
|
|
27
|
+
throw new BudgetExceededError(`Paying ${amount} would exceed the total budget ${total} (${asset}); already used or reserved: ${committed}`, { asset, amount: amount.toString(), total: total.toString(), used: committed.toString() });
|
|
28
|
+
}
|
|
29
|
+
const reservation = { id: this.nextId++, asset, amount };
|
|
30
|
+
this.reserved.set(reservation.id, reservation);
|
|
31
|
+
return reservation;
|
|
32
|
+
}
|
|
33
|
+
/** The payment settled: the amount is spent. */
|
|
34
|
+
spend(r) {
|
|
35
|
+
if (!this.reserved.delete(r.id))
|
|
36
|
+
return;
|
|
37
|
+
this.spent.set(r.asset, (this.spent.get(r.asset) ?? 0n) + r.amount);
|
|
38
|
+
}
|
|
39
|
+
/** The payment was definitely not made: give the amount back. */
|
|
40
|
+
release(r) {
|
|
41
|
+
this.reserved.delete(r.id);
|
|
42
|
+
}
|
|
43
|
+
/** Spent + reserved (unknown outcomes included) for an asset. */
|
|
44
|
+
used(asset) {
|
|
45
|
+
let sum = this.spent.get(asset) ?? 0n;
|
|
46
|
+
for (const r of this.reserved.values())
|
|
47
|
+
if (r.asset === asset)
|
|
48
|
+
sum += r.amount;
|
|
49
|
+
return sum;
|
|
50
|
+
}
|
|
51
|
+
remaining(asset) {
|
|
52
|
+
const total = this.limits.total?.get(asset);
|
|
53
|
+
return total === undefined ? undefined : total - this.used(asset);
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
//# sourceMappingURL=budget.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"budget.js","sourceRoot":"","sources":["../../src/client/budget.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import type { SettleResponse } from '@x402/core/types';
|
|
2
|
+
import { ObbyxError } from '@obbyx/obyte';
|
|
3
|
+
import { type Logger } from '@obbyx/obyte/logger';
|
|
4
|
+
import type { SpendingBudget } from './budget.js';
|
|
5
|
+
import { type BatchObyteClient, type ExactObyteClient } from '@obbyx/x402-obyte';
|
|
6
|
+
/**
|
|
7
|
+
* fetch() that pays x402 `exact` Obyte payments (buyer side).
|
|
8
|
+
*
|
|
9
|
+
* - Pays only offers within `maxAmount` (atomic units per asset).
|
|
10
|
+
* - Redirects are not followed automatically: a payment is never re-sent to
|
|
11
|
+
* another origin.
|
|
12
|
+
* - `settlement_pending` is retried with the SAME payment (never a new one)
|
|
13
|
+
* until `pendingTimeoutMs`; if still unknown, it throws `PaymentPendingError`
|
|
14
|
+
* carrying the payment header so the caller can retry later (I7).
|
|
15
|
+
*/
|
|
16
|
+
export interface PayingFetchOptions {
|
|
17
|
+
/** Payment mechanisms; a channel (batch) is preferred when one exists to the seller. */
|
|
18
|
+
readonly mechanisms: {
|
|
19
|
+
readonly exact?: ExactObyteClient;
|
|
20
|
+
readonly batch?: BatchObyteClient;
|
|
21
|
+
};
|
|
22
|
+
readonly network: string;
|
|
23
|
+
readonly maxAmount: ReadonlyMap<string, bigint>;
|
|
24
|
+
readonly logger: Logger;
|
|
25
|
+
readonly pendingTimeoutMs?: number;
|
|
26
|
+
/** Shared spending budget (per-call and total limits, reserved atomically). */
|
|
27
|
+
readonly budget?: SpendingBudget;
|
|
28
|
+
readonly fetch?: typeof fetch;
|
|
29
|
+
}
|
|
30
|
+
export interface PaidResponse {
|
|
31
|
+
readonly response: Response;
|
|
32
|
+
readonly payment?: SettleResponse;
|
|
33
|
+
/** Base64 PAYMENT-SIGNATURE that was sent, for retries of the same operation. */
|
|
34
|
+
readonly paymentHeader?: string;
|
|
35
|
+
}
|
|
36
|
+
export declare class PaymentPendingError extends ObbyxError {
|
|
37
|
+
readonly paymentHeader: string;
|
|
38
|
+
readonly transaction: string;
|
|
39
|
+
constructor(paymentHeader: string, transaction: string);
|
|
40
|
+
}
|
|
41
|
+
export declare function payingFetch(url: string, init: RequestInit & {
|
|
42
|
+
paymentHeader?: string;
|
|
43
|
+
/** x402 payment-identifier for this operation (reuse it when retrying after a lost response). */
|
|
44
|
+
paymentId?: string;
|
|
45
|
+
}, options: PayingFetchOptions): Promise<PaidResponse>;
|
|
46
|
+
//# sourceMappingURL=paying-fetch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paying-fetch.d.ts","sourceRoot":"","sources":["../../src/client/paying-fetch.ts"],"names":[],"mappings":"AAOA,OAAO,KAAK,EAAuC,cAAc,EAAE,MAAM,kBAAkB,CAAC;AAC5F,OAAO,EAAE,UAAU,EAAE,MAAM,cAAc,CAAC;AAC1C,OAAO,EAAE,KAAK,MAAM,EAAgB,MAAM,qBAAqB,CAAC;AAChE,OAAO,KAAK,EAAqB,cAAc,EAAE,MAAM,aAAa,CAAC;AACrE,OAAO,EAEL,KAAK,gBAAgB,EAErB,KAAK,gBAAgB,EACtB,MAAM,mBAAmB,CAAC;AAE3B;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,wFAAwF;IACxF,QAAQ,CAAC,UAAU,EAAE;QAAE,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAC;QAAC,QAAQ,CAAC,KAAK,CAAC,EAAE,gBAAgB,CAAA;KAAE,CAAC;IAC9F,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,SAAS,EAAE,WAAW,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChD,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,gBAAgB,CAAC,EAAE,MAAM,CAAC;IACnC,+EAA+E;IAC/E,QAAQ,CAAC,MAAM,CAAC,EAAE,cAAc,CAAC;IACjC,QAAQ,CAAC,KAAK,CAAC,EAAE,OAAO,KAAK,CAAC;CAC/B;AAED,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,QAAQ,CAAC,OAAO,CAAC,EAAE,cAAc,CAAC;IAClC,iFAAiF;IACjF,QAAQ,CAAC,aAAa,CAAC,EAAE,MAAM,CAAC;CACjC;AAED,qBAAa,mBAAoB,SAAQ,UAAU;IAE/C,QAAQ,CAAC,aAAa,EAAE,MAAM;IAC9B,QAAQ,CAAC,WAAW,EAAE,MAAM;gBADnB,aAAa,EAAE,MAAM,EACrB,WAAW,EAAE,MAAM;CAO/B;AAED,wBAAsB,WAAW,CAC/B,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,WAAW,GAAG;IAClB,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,iGAAiG;IACjG,SAAS,CAAC,EAAE,MAAM,CAAC;CACpB,EACD,OAAO,EAAE,kBAAkB,GAC1B,OAAO,CAAC,YAAY,CAAC,CAsGvB"}
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
import { decodePaymentRequiredHeader, decodePaymentResponseHeader, decodePaymentSignatureHeader, encodePaymentSignatureHeader, } from '@x402/core/http';
|
|
3
|
+
import { ObbyxError } from '@obbyx/obyte';
|
|
4
|
+
import { moduleLogger } from '@obbyx/obyte/logger';
|
|
5
|
+
import { BATCH_SCHEME, EXACT_SCHEME, } from '@obbyx/x402-obyte';
|
|
6
|
+
export class PaymentPendingError extends ObbyxError {
|
|
7
|
+
paymentHeader;
|
|
8
|
+
transaction;
|
|
9
|
+
constructor(paymentHeader, transaction) {
|
|
10
|
+
super('settlement_pending', `Payment ${transaction} is not final yet; retry the same request later with the same payment`);
|
|
11
|
+
this.paymentHeader = paymentHeader;
|
|
12
|
+
this.transaction = transaction;
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
export async function payingFetch(url, init, options) {
|
|
16
|
+
const log = moduleLogger(options.logger, 'sdk.fetch');
|
|
17
|
+
const doFetch = options.fetch ?? fetch;
|
|
18
|
+
const request = (header) => doFetch(url, {
|
|
19
|
+
...init,
|
|
20
|
+
redirect: 'manual',
|
|
21
|
+
headers: { ...headersOf(init.headers), ...(header ? { 'PAYMENT-SIGNATURE': header } : {}) },
|
|
22
|
+
});
|
|
23
|
+
let header = init.paymentHeader;
|
|
24
|
+
let reservation;
|
|
25
|
+
let response = await request(header);
|
|
26
|
+
if (response.status !== 402 && header === undefined)
|
|
27
|
+
return { response };
|
|
28
|
+
if (header === undefined) {
|
|
29
|
+
const required = response.headers.get('PAYMENT-REQUIRED');
|
|
30
|
+
if (required === null)
|
|
31
|
+
throw new ObbyxError('invalid_402', '402 without PAYMENT-REQUIRED header');
|
|
32
|
+
const offer = decodePaymentRequiredHeader(required);
|
|
33
|
+
// Offers in preference order (channel first). A channel that turns out to
|
|
34
|
+
// be unusable before anything is signed (not funded, closing, exhausted,
|
|
35
|
+
// busy) falls through to the next offer, normally `exact`.
|
|
36
|
+
let accepted;
|
|
37
|
+
let result;
|
|
38
|
+
let lastChannelError;
|
|
39
|
+
for (const candidate of chooseOffers(offer.accepts, options)) {
|
|
40
|
+
const mechanism = candidate.scheme === BATCH_SCHEME ? options.mechanisms.batch : options.mechanisms.exact;
|
|
41
|
+
if (mechanism === undefined)
|
|
42
|
+
continue;
|
|
43
|
+
log.info({
|
|
44
|
+
url,
|
|
45
|
+
scheme: candidate.scheme,
|
|
46
|
+
payTo: candidate.payTo,
|
|
47
|
+
asset: candidate.asset,
|
|
48
|
+
amount: candidate.amount,
|
|
49
|
+
}, 'paying for request');
|
|
50
|
+
// Reserve the price BEFORE signing: concurrent calls cannot overshoot the budget.
|
|
51
|
+
reservation = options.budget?.reserve(candidate.asset, BigInt(candidate.amount));
|
|
52
|
+
try {
|
|
53
|
+
result = await mechanism.createPaymentPayload(2, candidate);
|
|
54
|
+
accepted = candidate;
|
|
55
|
+
break;
|
|
56
|
+
}
|
|
57
|
+
catch (err) {
|
|
58
|
+
if (reservation)
|
|
59
|
+
options.budget?.release(reservation); // nothing was signed or sent
|
|
60
|
+
reservation = undefined;
|
|
61
|
+
if (err instanceof ObbyxError && CHANNEL_UNUSABLE.has(err.code)) {
|
|
62
|
+
lastChannelError = err;
|
|
63
|
+
log.info({ reason: err.code, detail: err.message }, 'channel unusable; trying the next offer');
|
|
64
|
+
continue;
|
|
65
|
+
}
|
|
66
|
+
throw err;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
if (accepted === undefined || result === undefined) {
|
|
70
|
+
// Nothing else to pay with: report why the channel could not (e.g. payment_pending).
|
|
71
|
+
throw lastChannelError ?? new ObbyxError('no_acceptable_offer', 'No offer could be paid');
|
|
72
|
+
}
|
|
73
|
+
const payload = {
|
|
74
|
+
x402Version: 2,
|
|
75
|
+
resource: offer.resource,
|
|
76
|
+
accepted,
|
|
77
|
+
payload: result.payload,
|
|
78
|
+
...(offer.extensions ? { extensions: withPaymentId(offer.extensions, init.paymentId) } : {}),
|
|
79
|
+
};
|
|
80
|
+
header = encodePaymentSignatureHeader(payload);
|
|
81
|
+
response = await request(header);
|
|
82
|
+
}
|
|
83
|
+
const deadline = Date.now() + (options.pendingTimeoutMs ?? 120_000);
|
|
84
|
+
for (;;) {
|
|
85
|
+
const settle = readSettle(response);
|
|
86
|
+
if (response.status !== 402 || settle?.errorReason !== 'settlement_pending') {
|
|
87
|
+
const accepted = acceptedOf(header);
|
|
88
|
+
if (accepted?.scheme === BATCH_SCHEME)
|
|
89
|
+
options.mechanisms.batch?.onSettled(accepted, settle);
|
|
90
|
+
if (reservation !== undefined && options.budget !== undefined) {
|
|
91
|
+
// I7: spend on success; release ONLY on a definite refusal (402: the
|
|
92
|
+
// payment was not used). Anything else (5xx, 409, no settlement info)
|
|
93
|
+
// is an unknown outcome: the reservation stays counted.
|
|
94
|
+
if (settle?.success === true || (response.ok && settle === undefined))
|
|
95
|
+
options.budget.spend(reservation);
|
|
96
|
+
else if (response.status === 402)
|
|
97
|
+
options.budget.release(reservation);
|
|
98
|
+
}
|
|
99
|
+
return { response, ...(settle ? { payment: settle } : {}), paymentHeader: header };
|
|
100
|
+
}
|
|
101
|
+
if (Date.now() > deadline)
|
|
102
|
+
throw new PaymentPendingError(header, settle.transaction);
|
|
103
|
+
const retryAfterMs = Math.min(15, Number(response.headers.get('Retry-After') ?? 5)) * 1000;
|
|
104
|
+
const wait = Math.max(0, Math.min(retryAfterMs, deadline - Date.now()));
|
|
105
|
+
log.info({ transaction: settle.transaction, waitMs: wait }, 'settlement pending; retrying with the same payment');
|
|
106
|
+
await response.body?.cancel();
|
|
107
|
+
await new Promise((r) => setTimeout(r, wait));
|
|
108
|
+
response = await request(header);
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/** An offer from an untrusted seller: integer amount in smallest units, strings elsewhere. */
|
|
112
|
+
const offerShape = z
|
|
113
|
+
.object({
|
|
114
|
+
scheme: z.string(),
|
|
115
|
+
network: z.string(),
|
|
116
|
+
asset: z.string(),
|
|
117
|
+
amount: z.string().regex(/^\d{1,30}$/),
|
|
118
|
+
payTo: z.string(),
|
|
119
|
+
maxTimeoutSeconds: z.number().int().positive(),
|
|
120
|
+
extra: z.record(z.string(), z.unknown()),
|
|
121
|
+
})
|
|
122
|
+
.loose();
|
|
123
|
+
/** Channel states in which the client signs nothing and can pay another way. */
|
|
124
|
+
const CHANNEL_UNUSABLE = new Set([
|
|
125
|
+
'channel_not_funded',
|
|
126
|
+
'channel_closing',
|
|
127
|
+
'channel_exhausted',
|
|
128
|
+
'payment_pending',
|
|
129
|
+
]);
|
|
130
|
+
/** Usable offers within the spending limit, a channel first (instant, no per-call fee). */
|
|
131
|
+
function chooseOffers(accepts, options) {
|
|
132
|
+
const usable = (a) => offerShape.safeParse(a).success &&
|
|
133
|
+
a.network === options.network &&
|
|
134
|
+
((a.scheme === BATCH_SCHEME && options.mechanisms.batch?.hasChannel(a) === true) ||
|
|
135
|
+
(a.scheme === EXACT_SCHEME && options.mechanisms.exact !== undefined));
|
|
136
|
+
const candidates = [
|
|
137
|
+
...accepts.filter((a) => a.scheme === BATCH_SCHEME),
|
|
138
|
+
...accepts.filter((a) => a.scheme !== BATCH_SCHEME),
|
|
139
|
+
].filter(usable);
|
|
140
|
+
if (candidates.length === 0) {
|
|
141
|
+
throw new ObbyxError('no_acceptable_offer', `No usable offer on ${options.network}`);
|
|
142
|
+
}
|
|
143
|
+
const affordable = candidates.filter((offer) => {
|
|
144
|
+
const max = options.maxAmount.get(offer.asset);
|
|
145
|
+
return max !== undefined && BigInt(offer.amount) <= max;
|
|
146
|
+
});
|
|
147
|
+
if (affordable.length === 0) {
|
|
148
|
+
const prices = candidates.map((c) => `${c.amount} ${c.asset}`).join(', ');
|
|
149
|
+
throw new ObbyxError('amount_over_limit', `Price (${prices}) exceeds your spending limit`);
|
|
150
|
+
}
|
|
151
|
+
return affordable;
|
|
152
|
+
}
|
|
153
|
+
/** Echoes the seller's extensions; fills the payment-identifier id if declared. */
|
|
154
|
+
function withPaymentId(extensions, id) {
|
|
155
|
+
const declared = extensions['payment-identifier'];
|
|
156
|
+
if (declared === undefined)
|
|
157
|
+
return extensions;
|
|
158
|
+
const paymentId = id ?? `pay_${globalThis.crypto.randomUUID().replaceAll('-', '')}`;
|
|
159
|
+
return {
|
|
160
|
+
...extensions,
|
|
161
|
+
'payment-identifier': { ...declared, info: { ...declared.info, id: paymentId } },
|
|
162
|
+
};
|
|
163
|
+
}
|
|
164
|
+
function acceptedOf(header) {
|
|
165
|
+
try {
|
|
166
|
+
return decodePaymentSignatureHeader(header).accepted;
|
|
167
|
+
}
|
|
168
|
+
catch {
|
|
169
|
+
return undefined;
|
|
170
|
+
}
|
|
171
|
+
}
|
|
172
|
+
function readSettle(response) {
|
|
173
|
+
const header = response.headers.get('PAYMENT-RESPONSE');
|
|
174
|
+
if (header === null)
|
|
175
|
+
return undefined;
|
|
176
|
+
try {
|
|
177
|
+
return decodePaymentResponseHeader(header);
|
|
178
|
+
}
|
|
179
|
+
catch {
|
|
180
|
+
return undefined;
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
function headersOf(headers) {
|
|
184
|
+
return Object.fromEntries(new Headers(headers).entries());
|
|
185
|
+
}
|
|
186
|
+
//# sourceMappingURL=paying-fetch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"paying-fetch.js","sourceRoot":"","sources":["../../src/client/paying-fetch.ts"],"names":[],"mappings":"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"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { type BindingInput, bindingsEqual, computeBinding, newQuoteId, sha256Hex, } from './seller/binding.js';
|
|
2
|
+
export { ConsumedProofsStore, type ProofRecord, type ProofState, type StoredResponse, } from './seller/consumed-proofs.js';
|
|
3
|
+
export { ObytePaywall, type ObytePaywallOptions, type PaidHandler, type PaidResult, type PaidRouteOptions, type PaywallScheme, type RoutePrice, } from './seller/paywall.js';
|
|
4
|
+
export { type PaidResponse, PaymentPendingError, type PayingFetchOptions, payingFetch, } from './client/paying-fetch.js';
|
|
5
|
+
export { type BudgetLimits, BudgetExceededError, type BudgetReservation, SpendingBudget, } from './client/budget.js';
|
|
6
|
+
export { ObyteExactServer, type ObyteExactServerOptions } from './seller/x402-server.js';
|
|
7
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EACL,KAAK,YAAY,EACjB,aAAa,EACb,cAAc,EACd,UAAU,EACV,SAAS,GACV,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,mBAAmB,EACnB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,cAAc,GACpB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,YAAY,EACZ,KAAK,mBAAmB,EACxB,KAAK,WAAW,EAChB,KAAK,UAAU,EACf,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,KAAK,YAAY,EACjB,mBAAmB,EACnB,KAAK,kBAAkB,EACvB,WAAW,GACZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,EACL,KAAK,YAAY,EACjB,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,cAAc,GACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAE,KAAK,uBAAuB,EAAE,MAAM,yBAAyB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
export { bindingsEqual, computeBinding, newQuoteId, sha256Hex, } from './seller/binding.js';
|
|
2
|
+
export { ConsumedProofsStore, } from './seller/consumed-proofs.js';
|
|
3
|
+
export { ObytePaywall, } from './seller/paywall.js';
|
|
4
|
+
export { PaymentPendingError, payingFetch, } from './client/paying-fetch.js';
|
|
5
|
+
export { BudgetExceededError, SpendingBudget, } from './client/budget.js';
|
|
6
|
+
export { ObyteExactServer } from './seller/x402-server.js';
|
|
7
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAEL,aAAa,EACb,cAAc,EACd,UAAU,EACV,SAAS,GACV,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,mBAAmB,GAIpB,MAAM,6BAA6B,CAAC;AACrC,OAAO,EACL,YAAY,GAOb,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EAEL,mBAAmB,EAEnB,WAAW,GACZ,MAAM,0BAA0B,CAAC;AAClC,OAAO,EAEL,mBAAmB,EAEnB,cAAc,GACf,MAAM,oBAAoB,CAAC;AAC5B,OAAO,EAAE,gBAAgB,EAAgC,MAAM,yBAAyB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.d.ts","sourceRoot":"","sources":["../../src/seller/binding.ts"],"names":[],"mappings":"AACA,OAAO,EACL,KAAK,YAAY,EACjB,aAAa,EACb,cAAc,EACd,UAAU,EACV,SAAS,GACV,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"binding.js","sourceRoot":"","sources":["../../src/seller/binding.ts"],"names":[],"mappings":"AAAA,mFAAmF;AACnF,OAAO,EAEL,aAAa,EACb,cAAc,EACd,UAAU,EACV,SAAS,GACV,MAAM,mBAAmB,CAAC"}
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Seller-side registry of consumed payment proofs (invariant I3).
|
|
3
|
+
*
|
|
4
|
+
* Keyed by `<caip2>:unit:<hash>`, so it holds even when several APIs or
|
|
5
|
+
* facilitators serve the same payTo. Each proof serves exactly one operation
|
|
6
|
+
* (its binding); a repeat of that operation gets the stored response.
|
|
7
|
+
*
|
|
8
|
+
* upfront: settling → paid_not_served → serving → served
|
|
9
|
+
* └────→ failed └──→ paid_not_served (handler failed)
|
|
10
|
+
* authorization: settling → serving → awaiting_settle → served
|
|
11
|
+
* │ └──────→ failed (handler already ran)
|
|
12
|
+
* └──→ settling (handler failed; nothing settled)
|
|
13
|
+
*
|
|
14
|
+
* serving older than the lease (crash) → paid_not_served | settling (reclaimStale)
|
|
15
|
+
*
|
|
16
|
+
* Transitions are compare-and-set, so concurrent requests with the same proof
|
|
17
|
+
* never run the handler twice.
|
|
18
|
+
*/
|
|
19
|
+
export type ProofState = 'settling' | 'paid_not_served' | 'serving' | 'served' | 'failed'
|
|
20
|
+
/** authorization flow: the handler ran, its response waits for the settlement. */
|
|
21
|
+
| 'awaiting_settle';
|
|
22
|
+
export interface StoredResponse {
|
|
23
|
+
readonly status: number;
|
|
24
|
+
readonly headers: Readonly<Record<string, string>>;
|
|
25
|
+
readonly body: Uint8Array;
|
|
26
|
+
}
|
|
27
|
+
export interface ProofRecord {
|
|
28
|
+
readonly key: string;
|
|
29
|
+
readonly binding: string;
|
|
30
|
+
readonly state: ProofState;
|
|
31
|
+
readonly paymentResponse: string | null;
|
|
32
|
+
readonly response: StoredResponse | null;
|
|
33
|
+
readonly updatedAt: number;
|
|
34
|
+
}
|
|
35
|
+
export declare class ConsumedProofsStore {
|
|
36
|
+
private readonly now;
|
|
37
|
+
private readonly db;
|
|
38
|
+
constructor(path: string, now?: () => number);
|
|
39
|
+
/**
|
|
40
|
+
* x402 `payment-identifier`: remembers which proof served a client-chosen id
|
|
41
|
+
* for one operation fingerprint. Returns the stored mapping (existing or new).
|
|
42
|
+
*/
|
|
43
|
+
bindPaymentId(scopeId: string, fingerprint: string, proofKey: string): {
|
|
44
|
+
fingerprint: string;
|
|
45
|
+
proofKey: string;
|
|
46
|
+
created: boolean;
|
|
47
|
+
};
|
|
48
|
+
/** Compare-and-set: moves the id only if it still points at `previous` (one winner). */
|
|
49
|
+
rebindPaymentId(scopeId: string, previous: string, proofKey: string): boolean;
|
|
50
|
+
close(): void;
|
|
51
|
+
get(key: string): ProofRecord | undefined;
|
|
52
|
+
/** Inserts the proof in `settling` unless it exists; returns the stored record. */
|
|
53
|
+
begin(key: string, binding: string): {
|
|
54
|
+
created: boolean;
|
|
55
|
+
record: ProofRecord;
|
|
56
|
+
};
|
|
57
|
+
/** Compare-and-set; returns false if the proof is not in `from`. */
|
|
58
|
+
transition(key: string, from: ProofState, to: ProofState, patch?: {
|
|
59
|
+
paymentResponse?: string;
|
|
60
|
+
response?: StoredResponse;
|
|
61
|
+
}): boolean;
|
|
62
|
+
/**
|
|
63
|
+
* Records a SETTLED payment as paid (`settling → paid_not_served`), also when
|
|
64
|
+
* a concurrent request removed or moved its row meanwhile: the payment did
|
|
65
|
+
* settle, so it must stay servable once. Atomic; returns the stored state.
|
|
66
|
+
*/
|
|
67
|
+
markSettled(key: string, binding: string, paymentResponse: string): ProofState;
|
|
68
|
+
/**
|
|
69
|
+
* Recovers a proof stuck in `serving` (the process died while the handler
|
|
70
|
+
* ran): after `leaseSeconds` it goes back to `paid_not_served` (already
|
|
71
|
+
* settled) or `settling` (authorization flow, not settled yet), so the SAME
|
|
72
|
+
* payment can complete the operation. Compare-and-set: one winner.
|
|
73
|
+
*/
|
|
74
|
+
reclaimStale(key: string, leaseSeconds: number): boolean;
|
|
75
|
+
/** Removes a proof that never reached the network (e.g. rejected at verification). */
|
|
76
|
+
forget(key: string, state: ProofState): void;
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=consumed-proofs.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"consumed-proofs.d.ts","sourceRoot":"","sources":["../../src/seller/consumed-proofs.ts"],"names":[],"mappings":"AAEA;;;;;;;;;;;;;;;;;GAiBG;AACH,MAAM,MAAM,UAAU,GAClB,UAAU,GACV,iBAAiB,GACjB,SAAS,GACT,QAAQ,GACR,QAAQ;AACV,kFAAkF;GAChF,iBAAiB,CAAC;AAEtB,MAAM,WAAW,cAAc;IAC7B,QAAQ,CAAC,MAAM,EAAE,MAAM,CAAC;IACxB,QAAQ,CAAC,OAAO,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC,CAAC;IACnD,QAAQ,CAAC,IAAI,EAAE,UAAU,CAAC;CAC3B;AAED,MAAM,WAAW,WAAW;IAC1B,QAAQ,CAAC,GAAG,EAAE,MAAM,CAAC;IACrB,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAC;IACzB,QAAQ,CAAC,KAAK,EAAE,UAAU,CAAC;IAC3B,QAAQ,CAAC,eAAe,EAAE,MAAM,GAAG,IAAI,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,cAAc,GAAG,IAAI,CAAC;IACzC,QAAQ,CAAC,SAAS,EAAE,MAAM,CAAC;CAC5B;AAaD,qBAAa,mBAAmB;IAK5B,OAAO,CAAC,QAAQ,CAAC,GAAG;IAJtB,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAe;gBAGhC,IAAI,EAAE,MAAM,EACK,GAAG,GAAE,MAAM,MAA4C;IA2B1E;;;OAGG;IACH,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;IAmB9D,wFAAwF;IACxF,eAAe,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,OAAO;IAO7E,KAAK,IAAI,IAAI;IAIb,GAAG,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,GAAG,SAAS;IAMzC,mFAAmF;IACnF,KAAK,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG;QAAE,OAAO,EAAE,OAAO,CAAC;QAAC,MAAM,EAAE,WAAW,CAAA;KAAE;IAa9E,oEAAoE;IACpE,UAAU,CACR,GAAG,EAAE,MAAM,EACX,IAAI,EAAE,UAAU,EAChB,EAAE,EAAE,UAAU,EACd,KAAK,GAAE;QAAE,eAAe,CAAC,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,cAAc,CAAA;KAAO,GAClE,OAAO;IAwBV;;;;OAIG;IACH,WAAW,CAAC,GAAG,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,EAAE,eAAe,EAAE,MAAM,GAAG,UAAU;IAuB9E;;;;;OAKG;IACH,YAAY,CAAC,GAAG,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,OAAO;IAYxD,sFAAsF;IACtF,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,KAAK,EAAE,UAAU,GAAG,IAAI;CAG7C"}
|