@resvary/sdk 0.5.0-alpha.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 +13 -0
- package/README.md +17 -0
- package/dist/billing/index.d.ts +3 -0
- package/dist/billing/index.d.ts.map +1 -0
- package/dist/billing/index.js +3 -0
- package/dist/billing/index.js.map +1 -0
- package/dist/billing/meter.d.ts +97 -0
- package/dist/billing/meter.d.ts.map +1 -0
- package/dist/billing/meter.js +109 -0
- package/dist/billing/meter.js.map +1 -0
- package/dist/billing/plans.d.ts +77 -0
- package/dist/billing/plans.d.ts.map +1 -0
- package/dist/billing/plans.js +65 -0
- package/dist/billing/plans.js.map +1 -0
- package/dist/client/buyer.d.ts +82 -0
- package/dist/client/buyer.d.ts.map +1 -0
- package/dist/client/buyer.js +168 -0
- package/dist/client/buyer.js.map +1 -0
- package/dist/client/index.d.ts +2 -0
- package/dist/client/index.d.ts.map +1 -0
- package/dist/client/index.js +2 -0
- package/dist/client/index.js.map +1 -0
- package/dist/constants.d.ts +36 -0
- package/dist/constants.d.ts.map +1 -0
- package/dist/constants.js +42 -0
- package/dist/constants.js.map +1 -0
- package/dist/credits/amount.d.ts +7 -0
- package/dist/credits/amount.d.ts.map +1 -0
- package/dist/credits/amount.js +36 -0
- package/dist/credits/amount.js.map +1 -0
- package/dist/credits/errors.d.ts +20 -0
- package/dist/credits/errors.d.ts.map +1 -0
- package/dist/credits/errors.js +35 -0
- package/dist/credits/errors.js.map +1 -0
- package/dist/credits/index.d.ts +7 -0
- package/dist/credits/index.d.ts.map +1 -0
- package/dist/credits/index.js +6 -0
- package/dist/credits/index.js.map +1 -0
- package/dist/credits/ledger.d.ts +175 -0
- package/dist/credits/ledger.d.ts.map +1 -0
- package/dist/credits/ledger.js +705 -0
- package/dist/credits/ledger.js.map +1 -0
- package/dist/credits/store.d.ts +104 -0
- package/dist/credits/store.d.ts.map +1 -0
- package/dist/credits/store.js +420 -0
- package/dist/credits/store.js.map +1 -0
- package/dist/credits/types.d.ts +234 -0
- package/dist/credits/types.d.ts.map +1 -0
- package/dist/credits/types.js +2 -0
- package/dist/credits/types.js.map +1 -0
- package/dist/credits/webhooks.d.ts +5 -0
- package/dist/credits/webhooks.d.ts.map +1 -0
- package/dist/credits/webhooks.js +13 -0
- package/dist/credits/webhooks.js.map +1 -0
- package/dist/funding/arc-worker.d.ts +55 -0
- package/dist/funding/arc-worker.d.ts.map +1 -0
- package/dist/funding/arc-worker.js +133 -0
- package/dist/funding/arc-worker.js.map +1 -0
- package/dist/funding/arc.d.ts +38 -0
- package/dist/funding/arc.d.ts.map +1 -0
- package/dist/funding/arc.js +83 -0
- package/dist/funding/arc.js.map +1 -0
- package/dist/funding/index.d.ts +3 -0
- package/dist/funding/index.d.ts.map +1 -0
- package/dist/funding/index.js +3 -0
- package/dist/funding/index.js.map +1 -0
- package/dist/gateway/client.d.ts +51 -0
- package/dist/gateway/client.d.ts.map +1 -0
- package/dist/gateway/client.js +59 -0
- package/dist/gateway/client.js.map +1 -0
- package/dist/gateway/index.d.ts +2 -0
- package/dist/gateway/index.d.ts.map +1 -0
- package/dist/gateway/index.js +2 -0
- package/dist/gateway/index.js.map +1 -0
- package/dist/index.d.ts +21 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +20 -0
- package/dist/index.js.map +1 -0
- package/dist/middleware/express.d.ts +25 -0
- package/dist/middleware/express.d.ts.map +1 -0
- package/dist/middleware/express.js +61 -0
- package/dist/middleware/express.js.map +1 -0
- package/dist/middleware/index.d.ts +4 -0
- package/dist/middleware/index.d.ts.map +1 -0
- package/dist/middleware/index.js +4 -0
- package/dist/middleware/index.js.map +1 -0
- package/dist/middleware/next.d.ts +27 -0
- package/dist/middleware/next.d.ts.map +1 -0
- package/dist/middleware/next.js +67 -0
- package/dist/middleware/next.js.map +1 -0
- package/dist/middleware/paywall.d.ts +54 -0
- package/dist/middleware/paywall.d.ts.map +1 -0
- package/dist/middleware/paywall.js +102 -0
- package/dist/middleware/paywall.js.map +1 -0
- package/dist/pricing/index.d.ts +3 -0
- package/dist/pricing/index.d.ts.map +1 -0
- package/dist/pricing/index.js +2 -0
- package/dist/pricing/index.js.map +1 -0
- package/dist/pricing/rating.d.ts +21 -0
- package/dist/pricing/rating.d.ts.map +1 -0
- package/dist/pricing/rating.js +109 -0
- package/dist/pricing/rating.js.map +1 -0
- package/dist/receipts/amount.d.ts +8 -0
- package/dist/receipts/amount.d.ts.map +1 -0
- package/dist/receipts/amount.js +27 -0
- package/dist/receipts/amount.js.map +1 -0
- package/dist/receipts/index.d.ts +16 -0
- package/dist/receipts/index.d.ts.map +1 -0
- package/dist/receipts/index.js +15 -0
- package/dist/receipts/index.js.map +1 -0
- package/dist/receipts/invoice.d.ts +18 -0
- package/dist/receipts/invoice.d.ts.map +1 -0
- package/dist/receipts/invoice.js +112 -0
- package/dist/receipts/invoice.js.map +1 -0
- package/dist/receipts/ledger.d.ts +30 -0
- package/dist/receipts/ledger.d.ts.map +1 -0
- package/dist/receipts/ledger.js +109 -0
- package/dist/receipts/ledger.js.map +1 -0
- package/dist/receipts/memo-payment.d.ts +100 -0
- package/dist/receipts/memo-payment.d.ts.map +1 -0
- package/dist/receipts/memo-payment.js +91 -0
- package/dist/receipts/memo-payment.js.map +1 -0
- package/dist/receipts/memo.d.ts +14 -0
- package/dist/receipts/memo.d.ts.map +1 -0
- package/dist/receipts/memo.js +39 -0
- package/dist/receipts/memo.js.map +1 -0
- package/dist/receipts/persistent-ledger.d.ts +27 -0
- package/dist/receipts/persistent-ledger.d.ts.map +1 -0
- package/dist/receipts/persistent-ledger.js +111 -0
- package/dist/receipts/persistent-ledger.js.map +1 -0
- package/dist/receipts/persistent-webhook-inbox.d.ts +32 -0
- package/dist/receipts/persistent-webhook-inbox.d.ts.map +1 -0
- package/dist/receipts/persistent-webhook-inbox.js +93 -0
- package/dist/receipts/persistent-webhook-inbox.js.map +1 -0
- package/dist/receipts/proof.d.ts +80 -0
- package/dist/receipts/proof.d.ts.map +1 -0
- package/dist/receipts/proof.js +223 -0
- package/dist/receipts/proof.js.map +1 -0
- package/dist/receipts/serialization.d.ts +3 -0
- package/dist/receipts/serialization.d.ts.map +1 -0
- package/dist/receipts/serialization.js +24 -0
- package/dist/receipts/serialization.js.map +1 -0
- package/dist/receipts/store.d.ts +69 -0
- package/dist/receipts/store.d.ts.map +1 -0
- package/dist/receipts/store.js +85 -0
- package/dist/receipts/store.js.map +1 -0
- package/dist/receipts/types.d.ts +134 -0
- package/dist/receipts/types.d.ts.map +1 -0
- package/dist/receipts/types.js +5 -0
- package/dist/receipts/types.js.map +1 -0
- package/dist/receipts/watcher.d.ts +92 -0
- package/dist/receipts/watcher.d.ts.map +1 -0
- package/dist/receipts/watcher.js +323 -0
- package/dist/receipts/watcher.js.map +1 -0
- package/dist/receipts/webhook-inbox.d.ts +34 -0
- package/dist/receipts/webhook-inbox.d.ts.map +1 -0
- package/dist/receipts/webhook-inbox.js +101 -0
- package/dist/receipts/webhook-inbox.js.map +1 -0
- package/dist/receipts/webhook-route.d.ts +22 -0
- package/dist/receipts/webhook-route.d.ts.map +1 -0
- package/dist/receipts/webhook-route.js +37 -0
- package/dist/receipts/webhook-route.js.map +1 -0
- package/dist/receipts/webhooks.d.ts +20 -0
- package/dist/receipts/webhooks.d.ts.map +1 -0
- package/dist/receipts/webhooks.js +72 -0
- package/dist/receipts/webhooks.js.map +1 -0
- package/dist/types.d.ts +101 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +5 -0
- package/dist/types.js.map +1 -0
- package/package.json +116 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Copyright 2025-2026 horn111
|
|
2
|
+
|
|
3
|
+
Licensed under the Apache License, Version 2.0 (the "License");
|
|
4
|
+
you may not use this file except in compliance with the License.
|
|
5
|
+
You may obtain a copy of the License at
|
|
6
|
+
|
|
7
|
+
https://www.apache.org/licenses/LICENSE-2.0
|
|
8
|
+
|
|
9
|
+
Unless required by applicable law or agreed to in writing, software
|
|
10
|
+
distributed under the License is distributed on an "AS IS" BASIS,
|
|
11
|
+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
12
|
+
See the License for the specific language governing permissions and
|
|
13
|
+
limitations under the License.
|
package/README.md
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
# @resvary/sdk
|
|
2
|
+
|
|
3
|
+
Open-source prepaid credits and usage billing for AI products.
|
|
4
|
+
|
|
5
|
+
Primary imports:
|
|
6
|
+
|
|
7
|
+
```typescript
|
|
8
|
+
import { CreditLedger } from '@resvary/sdk/credits';
|
|
9
|
+
import { createMeterDefinition, createPriceVersion, rateUsage } from '@resvary/sdk/pricing';
|
|
10
|
+
import { ArcCreditFunding } from '@resvary/sdk/funding/arc';
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
The SDK provides USD credit accounts, manual grants, reserve/commit/release, immutable usage receipts, idempotency, a transactional outbox, and signed credit webhooks. It has no database or AI provider dependency.
|
|
14
|
+
|
|
15
|
+
Legacy stablecoin payment imports under `/receipts`, `/middleware`, `/client`, and `/gateway` remain compatible. `UsageMeter` and `createBillingPlan` are deprecated for new balance systems.
|
|
16
|
+
|
|
17
|
+
See the repository README and `docs/` for the complete quickstart and security model.
|
|
@@ -0,0 +1,3 @@
|
|
|
1
|
+
export { UsageMeter, type UsageRecord, type MeterConfig, type UsageSummary } from './meter.js';
|
|
2
|
+
export { createBillingPlan, type BillingPlan, type PricingModel, type PerRequestPricing, type PerSecondPricing, type PerJobPricing, type UsageInput, } from './plans.js';
|
|
3
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/billing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,KAAK,WAAW,EAAE,KAAK,WAAW,EAAE,KAAK,YAAY,EAAE,MAAM,YAAY,CAAC;AAC/F,OAAO,EACL,iBAAiB,EACjB,KAAK,WAAW,EAChB,KAAK,YAAY,EACjB,KAAK,iBAAiB,EACtB,KAAK,gBAAgB,EACrB,KAAK,aAAa,EAClB,KAAK,UAAU,GAChB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/billing/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAyD,MAAM,YAAY,CAAC;AAC/F,OAAO,EACL,iBAAiB,GAOlB,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage metering for tracking API consumption and billing.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { UsageMeter } from '@resvary/sdk/billing';
|
|
7
|
+
*
|
|
8
|
+
* const meter = new UsageMeter({ endpoint: '/api/data' });
|
|
9
|
+
*
|
|
10
|
+
* // Record usage
|
|
11
|
+
* meter.record({ buyer: '0x...', amount: '0.001' });
|
|
12
|
+
*
|
|
13
|
+
* // Get usage summary
|
|
14
|
+
* const summary = meter.getSummary('0x...');
|
|
15
|
+
* console.log(summary.totalSpent); // '0.003'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
/** Configuration for the usage meter */
|
|
19
|
+
export interface MeterConfig {
|
|
20
|
+
/** Endpoint being metered */
|
|
21
|
+
endpoint: string;
|
|
22
|
+
/** Maximum records to keep in memory (default: 10000) */
|
|
23
|
+
maxRecords?: number;
|
|
24
|
+
}
|
|
25
|
+
/** A single usage record */
|
|
26
|
+
export interface UsageRecord {
|
|
27
|
+
/** Buyer's wallet address */
|
|
28
|
+
buyer: `0x${string}`;
|
|
29
|
+
/** Amount paid in USDC */
|
|
30
|
+
amount: string;
|
|
31
|
+
/** Endpoint accessed */
|
|
32
|
+
endpoint: string;
|
|
33
|
+
/** Timestamp of the request */
|
|
34
|
+
timestamp: number;
|
|
35
|
+
/** Settlement status */
|
|
36
|
+
status: 'pending' | 'batched' | 'settled';
|
|
37
|
+
/** Optional metadata */
|
|
38
|
+
metadata?: Record<string, unknown>;
|
|
39
|
+
}
|
|
40
|
+
/** Usage summary for a buyer */
|
|
41
|
+
export interface UsageSummary {
|
|
42
|
+
/** Buyer's address */
|
|
43
|
+
buyer: `0x${string}`;
|
|
44
|
+
/** Total amount spent in USDC */
|
|
45
|
+
totalSpent: string;
|
|
46
|
+
/** Total number of requests */
|
|
47
|
+
totalRequests: number;
|
|
48
|
+
/** First usage timestamp */
|
|
49
|
+
firstSeen: number;
|
|
50
|
+
/** Last usage timestamp */
|
|
51
|
+
lastSeen: number;
|
|
52
|
+
/** Average cost per request */
|
|
53
|
+
avgCostPerRequest: string;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* In-memory usage meter for tracking API consumption.
|
|
57
|
+
*
|
|
58
|
+
* For production use, extend with a persistent store
|
|
59
|
+
* (Supabase, Postgres, Redis, etc.)
|
|
60
|
+
*/
|
|
61
|
+
/** @deprecated Use CreditLedger with meters, price versions, and usage receipts instead. */
|
|
62
|
+
export declare class UsageMeter {
|
|
63
|
+
private readonly records;
|
|
64
|
+
private readonly endpoint;
|
|
65
|
+
private readonly maxRecords;
|
|
66
|
+
constructor(config: MeterConfig);
|
|
67
|
+
/**
|
|
68
|
+
* Record a usage event.
|
|
69
|
+
*/
|
|
70
|
+
record(params: {
|
|
71
|
+
buyer: `0x${string}`;
|
|
72
|
+
amount: string;
|
|
73
|
+
metadata?: Record<string, unknown>;
|
|
74
|
+
}): UsageRecord;
|
|
75
|
+
/**
|
|
76
|
+
* Get usage summary for a specific buyer.
|
|
77
|
+
*/
|
|
78
|
+
getSummary(buyer: `0x${string}`): UsageSummary;
|
|
79
|
+
/**
|
|
80
|
+
* Get all usage records, optionally filtered.
|
|
81
|
+
*/
|
|
82
|
+
getRecords(filter?: {
|
|
83
|
+
buyer?: `0x${string}`;
|
|
84
|
+
since?: number;
|
|
85
|
+
status?: UsageRecord['status'];
|
|
86
|
+
}): UsageRecord[];
|
|
87
|
+
/**
|
|
88
|
+
* Get aggregate stats across all buyers.
|
|
89
|
+
*/
|
|
90
|
+
getAggregateStats(): {
|
|
91
|
+
totalRevenue: string;
|
|
92
|
+
totalRequests: number;
|
|
93
|
+
uniqueBuyers: number;
|
|
94
|
+
avgRevenuePerRequest: string;
|
|
95
|
+
};
|
|
96
|
+
}
|
|
97
|
+
//# sourceMappingURL=meter.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter.d.ts","sourceRoot":"","sources":["../../src/billing/meter.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;GAgBG;AAEH,wCAAwC;AACxC,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,QAAQ,EAAE,MAAM,CAAC;IACjB,yDAAyD;IACzD,UAAU,CAAC,EAAE,MAAM,CAAC;CACrB;AAED,4BAA4B;AAC5B,MAAM,WAAW,WAAW;IAC1B,6BAA6B;IAC7B,KAAK,EAAE,KAAK,MAAM,EAAE,CAAC;IACrB,0BAA0B;IAC1B,MAAM,EAAE,MAAM,CAAC;IACf,wBAAwB;IACxB,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,SAAS,EAAE,MAAM,CAAC;IAClB,wBAAwB;IACxB,MAAM,EAAE,SAAS,GAAG,SAAS,GAAG,SAAS,CAAC;IAC1C,wBAAwB;IACxB,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;CACpC;AAED,gCAAgC;AAChC,MAAM,WAAW,YAAY;IAC3B,sBAAsB;IACtB,KAAK,EAAE,KAAK,MAAM,EAAE,CAAC;IACrB,iCAAiC;IACjC,UAAU,EAAE,MAAM,CAAC;IACnB,+BAA+B;IAC/B,aAAa,EAAE,MAAM,CAAC;IACtB,4BAA4B;IAC5B,SAAS,EAAE,MAAM,CAAC;IAClB,2BAA2B;IAC3B,QAAQ,EAAE,MAAM,CAAC;IACjB,+BAA+B;IAC/B,iBAAiB,EAAE,MAAM,CAAC;CAC3B;AAED;;;;;GAKG;AACH,4FAA4F;AAC5F,qBAAa,UAAU;IACrB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAqB;IAC7C,OAAO,CAAC,QAAQ,CAAC,QAAQ,CAAS;IAClC,OAAO,CAAC,QAAQ,CAAC,UAAU,CAAS;gBAExB,MAAM,EAAE,WAAW;IAK/B;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE;QAAE,KAAK,EAAE,KAAK,MAAM,EAAE,CAAC;QAAC,MAAM,EAAE,MAAM,CAAC;QAAC,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAA;KAAE,GAAG,WAAW;IAoBzG;;OAEG;IACH,UAAU,CAAC,KAAK,EAAE,KAAK,MAAM,EAAE,GAAG,YAAY;IA+B9C;;OAEG;IACH,UAAU,CAAC,MAAM,CAAC,EAAE;QAClB,KAAK,CAAC,EAAE,KAAK,MAAM,EAAE,CAAC;QACtB,KAAK,CAAC,EAAE,MAAM,CAAC;QACf,MAAM,CAAC,EAAE,WAAW,CAAC,QAAQ,CAAC,CAAC;KAChC,GAAG,WAAW,EAAE;IAoBjB;;OAEG;IACH,iBAAiB,IAAI;QACnB,YAAY,EAAE,MAAM,CAAC;QACrB,aAAa,EAAE,MAAM,CAAC;QACtB,YAAY,EAAE,MAAM,CAAC;QACrB,oBAAoB,EAAE,MAAM,CAAC;KAC9B;CAiBF"}
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Usage metering for tracking API consumption and billing.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { UsageMeter } from '@resvary/sdk/billing';
|
|
7
|
+
*
|
|
8
|
+
* const meter = new UsageMeter({ endpoint: '/api/data' });
|
|
9
|
+
*
|
|
10
|
+
* // Record usage
|
|
11
|
+
* meter.record({ buyer: '0x...', amount: '0.001' });
|
|
12
|
+
*
|
|
13
|
+
* // Get usage summary
|
|
14
|
+
* const summary = meter.getSummary('0x...');
|
|
15
|
+
* console.log(summary.totalSpent); // '0.003'
|
|
16
|
+
* ```
|
|
17
|
+
*/
|
|
18
|
+
/**
|
|
19
|
+
* In-memory usage meter for tracking API consumption.
|
|
20
|
+
*
|
|
21
|
+
* For production use, extend with a persistent store
|
|
22
|
+
* (Supabase, Postgres, Redis, etc.)
|
|
23
|
+
*/
|
|
24
|
+
/** @deprecated Use CreditLedger with meters, price versions, and usage receipts instead. */
|
|
25
|
+
export class UsageMeter {
|
|
26
|
+
records = [];
|
|
27
|
+
endpoint;
|
|
28
|
+
maxRecords;
|
|
29
|
+
constructor(config) {
|
|
30
|
+
this.endpoint = config.endpoint;
|
|
31
|
+
this.maxRecords = config.maxRecords ?? 10_000;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Record a usage event.
|
|
35
|
+
*/
|
|
36
|
+
record(params) {
|
|
37
|
+
const record = {
|
|
38
|
+
buyer: params.buyer,
|
|
39
|
+
amount: params.amount,
|
|
40
|
+
endpoint: this.endpoint,
|
|
41
|
+
timestamp: Date.now(),
|
|
42
|
+
status: 'pending',
|
|
43
|
+
metadata: params.metadata,
|
|
44
|
+
};
|
|
45
|
+
this.records.push(record);
|
|
46
|
+
// Evict oldest records if over limit
|
|
47
|
+
if (this.records.length > this.maxRecords) {
|
|
48
|
+
this.records.splice(0, this.records.length - this.maxRecords);
|
|
49
|
+
}
|
|
50
|
+
return record;
|
|
51
|
+
}
|
|
52
|
+
/**
|
|
53
|
+
* Get usage summary for a specific buyer.
|
|
54
|
+
*/
|
|
55
|
+
getSummary(buyer) {
|
|
56
|
+
const buyerRecords = this.records.filter((r) => r.buyer.toLowerCase() === buyer.toLowerCase());
|
|
57
|
+
if (buyerRecords.length === 0) {
|
|
58
|
+
return {
|
|
59
|
+
buyer,
|
|
60
|
+
totalSpent: '0',
|
|
61
|
+
totalRequests: 0,
|
|
62
|
+
firstSeen: 0,
|
|
63
|
+
lastSeen: 0,
|
|
64
|
+
avgCostPerRequest: '0',
|
|
65
|
+
};
|
|
66
|
+
}
|
|
67
|
+
const totalSpent = buyerRecords.reduce((sum, r) => sum + parseFloat(r.amount), 0);
|
|
68
|
+
return {
|
|
69
|
+
buyer,
|
|
70
|
+
totalSpent: totalSpent.toFixed(6),
|
|
71
|
+
totalRequests: buyerRecords.length,
|
|
72
|
+
firstSeen: buyerRecords[0].timestamp,
|
|
73
|
+
lastSeen: buyerRecords[buyerRecords.length - 1].timestamp,
|
|
74
|
+
avgCostPerRequest: (totalSpent / buyerRecords.length).toFixed(6),
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
/**
|
|
78
|
+
* Get all usage records, optionally filtered.
|
|
79
|
+
*/
|
|
80
|
+
getRecords(filter) {
|
|
81
|
+
let filtered = [...this.records];
|
|
82
|
+
if (filter?.buyer) {
|
|
83
|
+
filtered = filtered.filter((r) => r.buyer.toLowerCase() === filter.buyer.toLowerCase());
|
|
84
|
+
}
|
|
85
|
+
if (filter?.since) {
|
|
86
|
+
filtered = filtered.filter((r) => r.timestamp >= filter.since);
|
|
87
|
+
}
|
|
88
|
+
if (filter?.status) {
|
|
89
|
+
filtered = filtered.filter((r) => r.status === filter.status);
|
|
90
|
+
}
|
|
91
|
+
return filtered;
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Get aggregate stats across all buyers.
|
|
95
|
+
*/
|
|
96
|
+
getAggregateStats() {
|
|
97
|
+
const totalRevenue = this.records.reduce((sum, r) => sum + parseFloat(r.amount), 0);
|
|
98
|
+
const uniqueBuyers = new Set(this.records.map((r) => r.buyer.toLowerCase())).size;
|
|
99
|
+
return {
|
|
100
|
+
totalRevenue: totalRevenue.toFixed(6),
|
|
101
|
+
totalRequests: this.records.length,
|
|
102
|
+
uniqueBuyers,
|
|
103
|
+
avgRevenuePerRequest: this.records.length > 0
|
|
104
|
+
? (totalRevenue / this.records.length).toFixed(6)
|
|
105
|
+
: '0',
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
//# sourceMappingURL=meter.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"meter.js","sourceRoot":"","sources":["../../src/billing/meter.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Billing plan definitions for different pricing models.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { createBillingPlan } from '@resvary/sdk/billing';
|
|
7
|
+
*
|
|
8
|
+
* const apiPlan = createBillingPlan({
|
|
9
|
+
* name: 'API Standard',
|
|
10
|
+
* pricing: { model: 'per-request', pricePerRequest: '0.001' },
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* const cost = apiPlan.calculateCost({ requests: 100 });
|
|
14
|
+
* console.log(cost); // '0.1'
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
/** Per-request pricing: fixed cost per API call */
|
|
18
|
+
export interface PerRequestPricing {
|
|
19
|
+
model: 'per-request';
|
|
20
|
+
/** Price in USDC per request */
|
|
21
|
+
pricePerRequest: string;
|
|
22
|
+
}
|
|
23
|
+
/** Per-second pricing: time-based billing */
|
|
24
|
+
export interface PerSecondPricing {
|
|
25
|
+
model: 'per-second';
|
|
26
|
+
/** Price in USDC per second */
|
|
27
|
+
pricePerSecond: string;
|
|
28
|
+
/** Minimum billable seconds (default: 1) */
|
|
29
|
+
minimumSeconds?: number;
|
|
30
|
+
}
|
|
31
|
+
/** Per-job pricing: batch operation billing */
|
|
32
|
+
export interface PerJobPricing {
|
|
33
|
+
model: 'per-job';
|
|
34
|
+
/** Base price in USDC per job */
|
|
35
|
+
basePrice: string;
|
|
36
|
+
/** Additional price per unit of work (e.g., per MB, per token) */
|
|
37
|
+
pricePerUnit?: string;
|
|
38
|
+
/** Name of the unit (e.g., 'MB', 'token', 'row') */
|
|
39
|
+
unitName?: string;
|
|
40
|
+
}
|
|
41
|
+
/** Union of all pricing model types */
|
|
42
|
+
export type PricingModel = PerRequestPricing | PerSecondPricing | PerJobPricing;
|
|
43
|
+
/** Billing plan configuration */
|
|
44
|
+
export interface BillingPlan {
|
|
45
|
+
/** Plan name */
|
|
46
|
+
name: string;
|
|
47
|
+
/** Plan description */
|
|
48
|
+
description?: string;
|
|
49
|
+
/** Pricing model */
|
|
50
|
+
pricing: PricingModel;
|
|
51
|
+
/** Calculate cost based on usage */
|
|
52
|
+
calculateCost: (usage: UsageInput) => string;
|
|
53
|
+
/** Get the minimum payment amount for this plan */
|
|
54
|
+
getMinimumPayment: () => string;
|
|
55
|
+
}
|
|
56
|
+
/** Input for cost calculation */
|
|
57
|
+
export interface UsageInput {
|
|
58
|
+
/** Number of requests (for per-request model) */
|
|
59
|
+
requests?: number;
|
|
60
|
+
/** Duration in seconds (for per-second model) */
|
|
61
|
+
seconds?: number;
|
|
62
|
+
/** Number of units processed (for per-job model) */
|
|
63
|
+
units?: number;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Create a billing plan with a specific pricing model.
|
|
67
|
+
*
|
|
68
|
+
* @param config - Plan configuration
|
|
69
|
+
* @returns A billing plan with cost calculation methods
|
|
70
|
+
*/
|
|
71
|
+
/** @deprecated Use createMeterDefinition, createPriceVersion, and rateUsage instead. */
|
|
72
|
+
export declare function createBillingPlan(config: {
|
|
73
|
+
name: string;
|
|
74
|
+
description?: string;
|
|
75
|
+
pricing: PricingModel;
|
|
76
|
+
}): BillingPlan;
|
|
77
|
+
//# sourceMappingURL=plans.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plans.d.ts","sourceRoot":"","sources":["../../src/billing/plans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,mDAAmD;AACnD,MAAM,WAAW,iBAAiB;IAChC,KAAK,EAAE,aAAa,CAAC;IACrB,gCAAgC;IAChC,eAAe,EAAE,MAAM,CAAC;CACzB;AAED,6CAA6C;AAC7C,MAAM,WAAW,gBAAgB;IAC/B,KAAK,EAAE,YAAY,CAAC;IACpB,+BAA+B;IAC/B,cAAc,EAAE,MAAM,CAAC;IACvB,4CAA4C;IAC5C,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,+CAA+C;AAC/C,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,SAAS,CAAC;IACjB,iCAAiC;IACjC,SAAS,EAAE,MAAM,CAAC;IAClB,kEAAkE;IAClE,YAAY,CAAC,EAAE,MAAM,CAAC;IACtB,oDAAoD;IACpD,QAAQ,CAAC,EAAE,MAAM,CAAC;CACnB;AAED,uCAAuC;AACvC,MAAM,MAAM,YAAY,GAAG,iBAAiB,GAAG,gBAAgB,GAAG,aAAa,CAAC;AAEhF,iCAAiC;AACjC,MAAM,WAAW,WAAW;IAC1B,gBAAgB;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,uBAAuB;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,oBAAoB;IACpB,OAAO,EAAE,YAAY,CAAC;IACtB,oCAAoC;IACpC,aAAa,EAAE,CAAC,KAAK,EAAE,UAAU,KAAK,MAAM,CAAC;IAC7C,mDAAmD;IACnD,iBAAiB,EAAE,MAAM,MAAM,CAAC;CACjC;AAED,iCAAiC;AACjC,MAAM,WAAW,UAAU;IACzB,iDAAiD;IACjD,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,iDAAiD;IACjD,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,oDAAoD;IACpD,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED;;;;;GAKG;AACH,wFAAwF;AACxF,wBAAgB,iBAAiB,CAAC,MAAM,EAAE;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,EAAE,YAAY,CAAC;CACvB,GAAG,WAAW,CAmDd"}
|
|
@@ -0,0 +1,65 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Billing plan definitions for different pricing models.
|
|
3
|
+
*
|
|
4
|
+
* @example
|
|
5
|
+
* ```typescript
|
|
6
|
+
* import { createBillingPlan } from '@resvary/sdk/billing';
|
|
7
|
+
*
|
|
8
|
+
* const apiPlan = createBillingPlan({
|
|
9
|
+
* name: 'API Standard',
|
|
10
|
+
* pricing: { model: 'per-request', pricePerRequest: '0.001' },
|
|
11
|
+
* });
|
|
12
|
+
*
|
|
13
|
+
* const cost = apiPlan.calculateCost({ requests: 100 });
|
|
14
|
+
* console.log(cost); // '0.1'
|
|
15
|
+
* ```
|
|
16
|
+
*/
|
|
17
|
+
/**
|
|
18
|
+
* Create a billing plan with a specific pricing model.
|
|
19
|
+
*
|
|
20
|
+
* @param config - Plan configuration
|
|
21
|
+
* @returns A billing plan with cost calculation methods
|
|
22
|
+
*/
|
|
23
|
+
/** @deprecated Use createMeterDefinition, createPriceVersion, and rateUsage instead. */
|
|
24
|
+
export function createBillingPlan(config) {
|
|
25
|
+
const { name, description, pricing } = config;
|
|
26
|
+
return {
|
|
27
|
+
name,
|
|
28
|
+
description,
|
|
29
|
+
pricing,
|
|
30
|
+
calculateCost(usage) {
|
|
31
|
+
switch (pricing.model) {
|
|
32
|
+
case 'per-request': {
|
|
33
|
+
const requests = usage.requests ?? 1;
|
|
34
|
+
return (requests * parseFloat(pricing.pricePerRequest)).toFixed(6);
|
|
35
|
+
}
|
|
36
|
+
case 'per-second': {
|
|
37
|
+
const seconds = Math.max(usage.seconds ?? 1, pricing.minimumSeconds ?? 1);
|
|
38
|
+
return (seconds * parseFloat(pricing.pricePerSecond)).toFixed(6);
|
|
39
|
+
}
|
|
40
|
+
case 'per-job': {
|
|
41
|
+
const base = parseFloat(pricing.basePrice);
|
|
42
|
+
const unitCost = pricing.pricePerUnit
|
|
43
|
+
? (usage.units ?? 0) * parseFloat(pricing.pricePerUnit)
|
|
44
|
+
: 0;
|
|
45
|
+
return (base + unitCost).toFixed(6);
|
|
46
|
+
}
|
|
47
|
+
default:
|
|
48
|
+
throw new Error(`Unknown pricing model: ${pricing.model}`);
|
|
49
|
+
}
|
|
50
|
+
},
|
|
51
|
+
getMinimumPayment() {
|
|
52
|
+
switch (pricing.model) {
|
|
53
|
+
case 'per-request':
|
|
54
|
+
return pricing.pricePerRequest;
|
|
55
|
+
case 'per-second':
|
|
56
|
+
return (parseFloat(pricing.pricePerSecond) * (pricing.minimumSeconds ?? 1)).toFixed(6);
|
|
57
|
+
case 'per-job':
|
|
58
|
+
return pricing.basePrice;
|
|
59
|
+
default:
|
|
60
|
+
return '0.000001';
|
|
61
|
+
}
|
|
62
|
+
},
|
|
63
|
+
};
|
|
64
|
+
}
|
|
65
|
+
//# sourceMappingURL=plans.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"plans.js","sourceRoot":"","sources":["../../src/billing/plans.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAwDH;;;;;GAKG;AACH,wFAAwF;AACxF,MAAM,UAAU,iBAAiB,CAAC,MAIjC;IACC,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,OAAO,EAAE,GAAG,MAAM,CAAC;IAE9C,OAAO;QACL,IAAI;QACJ,WAAW;QACX,OAAO;QAEP,aAAa,CAAC,KAAiB;YAC7B,QAAQ,OAAO,CAAC,KAAK,EAAE,CAAC;gBACtB,KAAK,aAAa,CAAC,CAAC,CAAC;oBACnB,MAAM,QAAQ,GAAG,KAAK,CAAC,QAAQ,IAAI,CAAC,CAAC;oBACrC,OAAO,CAAC,QAAQ,GAAG,UAAU,CAAC,OAAO,CAAC,eAAe,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACrE,CAAC;gBAED,KAAK,YAAY,CAAC,CAAC,CAAC;oBAClB,MAAM,OAAO,GAAG,IAAI,CAAC,GAAG,CACtB,KAAK,CAAC,OAAO,IAAI,CAAC,EAClB,OAAO,CAAC,cAAc,IAAI,CAAC,CAC5B,CAAC;oBACF,OAAO,CAAC,OAAO,GAAG,UAAU,CAAC,OAAO,CAAC,cAAc,CAAC,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACnE,CAAC;gBAED,KAAK,SAAS,CAAC,CAAC,CAAC;oBACf,MAAM,IAAI,GAAG,UAAU,CAAC,OAAO,CAAC,SAAS,CAAC,CAAC;oBAC3C,MAAM,QAAQ,GAAG,OAAO,CAAC,YAAY;wBACnC,CAAC,CAAC,CAAC,KAAK,CAAC,KAAK,IAAI,CAAC,CAAC,GAAG,UAAU,CAAC,OAAO,CAAC,YAAY,CAAC;wBACvD,CAAC,CAAC,CAAC,CAAC;oBACN,OAAO,CAAC,IAAI,GAAG,QAAQ,CAAC,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACtC,CAAC;gBAED;oBACE,MAAM,IAAI,KAAK,CAAC,0BAA2B,OAAwB,CAAC,KAAK,EAAE,CAAC,CAAC;YACjF,CAAC;QACH,CAAC;QAED,iBAAiB;YACf,QAAQ,OAAO,CAAC,KAAK,EAAE,CAAC;gBACtB,KAAK,aAAa;oBAChB,OAAO,OAAO,CAAC,eAAe,CAAC;gBACjC,KAAK,YAAY;oBACf,OAAO,CACL,UAAU,CAAC,OAAO,CAAC,cAAc,CAAC,GAAG,CAAC,OAAO,CAAC,cAAc,IAAI,CAAC,CAAC,CACnE,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;gBACf,KAAK,SAAS;oBACZ,OAAO,OAAO,CAAC,SAAS,CAAC;gBAC3B;oBACE,OAAO,UAAU,CAAC;YACtB,CAAC;QACH,CAAC;KACF,CAAC;AACJ,CAAC"}
|
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buyer SDK for automated x402 payment flow.
|
|
3
|
+
*
|
|
4
|
+
* Handles the complete payment lifecycle:
|
|
5
|
+
* 1. Send request to a paywalled endpoint
|
|
6
|
+
* 2. Receive 402 + payment requirements
|
|
7
|
+
* 3. Sign EIP-3009 transferWithAuthorization
|
|
8
|
+
* 4. Retry request with X-PAYMENT header
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { BuyerClient } from '@resvary/sdk/client';
|
|
13
|
+
*
|
|
14
|
+
* const buyer = new BuyerClient({
|
|
15
|
+
* privateKey: '0x...',
|
|
16
|
+
* rpcUrl: 'https://rpc.testnet.arc.network',
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* const response = await buyer.request('https://api.example.com/premium/data');
|
|
20
|
+
* console.log(response.data);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
/** Configuration for the BuyerClient */
|
|
24
|
+
export interface BuyerClientConfig {
|
|
25
|
+
/** Buyer's private key (hex string with 0x prefix) */
|
|
26
|
+
privateKey: `0x${string}`;
|
|
27
|
+
/** RPC URL for the target network (default: Arc Testnet) */
|
|
28
|
+
rpcUrl?: string;
|
|
29
|
+
/** Chain ID (default: Arc Testnet) */
|
|
30
|
+
chainId?: number;
|
|
31
|
+
/** Maximum number of payment retries */
|
|
32
|
+
maxRetries?: number;
|
|
33
|
+
/** Custom fetch implementation */
|
|
34
|
+
fetch?: typeof globalThis.fetch;
|
|
35
|
+
}
|
|
36
|
+
/** Response from a paid request */
|
|
37
|
+
export interface PaidResponse<T = unknown> {
|
|
38
|
+
/** Response data */
|
|
39
|
+
data: T;
|
|
40
|
+
/** HTTP status code */
|
|
41
|
+
status: number;
|
|
42
|
+
/** Response headers */
|
|
43
|
+
headers: Record<string, string>;
|
|
44
|
+
/** Payment details (if payment was made) */
|
|
45
|
+
payment?: {
|
|
46
|
+
amount: string;
|
|
47
|
+
to: `0x${string}`;
|
|
48
|
+
network: string;
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Client for consuming x402-paywalled APIs.
|
|
53
|
+
*
|
|
54
|
+
* Automatically handles the 402 payment flow: detects paywalled endpoints,
|
|
55
|
+
* signs payment authorizations, and retries requests with valid payments.
|
|
56
|
+
*/
|
|
57
|
+
export declare class BuyerClient {
|
|
58
|
+
private readonly account;
|
|
59
|
+
private readonly chainId;
|
|
60
|
+
private readonly fetchFn;
|
|
61
|
+
constructor(config: BuyerClientConfig);
|
|
62
|
+
/** Get the buyer's wallet address */
|
|
63
|
+
get address(): `0x${string}`;
|
|
64
|
+
/**
|
|
65
|
+
* Make a request to a potentially paywalled endpoint.
|
|
66
|
+
* Automatically handles 402 → sign → retry flow.
|
|
67
|
+
*
|
|
68
|
+
* @param url - The endpoint URL
|
|
69
|
+
* @param init - Optional fetch init options
|
|
70
|
+
* @returns Response with data and payment info
|
|
71
|
+
*/
|
|
72
|
+
request<T = unknown>(url: string, init?: RequestInit): Promise<PaidResponse<T>>;
|
|
73
|
+
/**
|
|
74
|
+
* Parse payment requirements from a 402 response.
|
|
75
|
+
*/
|
|
76
|
+
private parsePaymentRequirements;
|
|
77
|
+
/**
|
|
78
|
+
* Sign a payment authorization using EIP-3009.
|
|
79
|
+
*/
|
|
80
|
+
private signPayment;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=buyer.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"buyer.d.ts","sourceRoot":"","sources":["../../src/client/buyer.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;GAqBG;AAOH,wCAAwC;AACxC,MAAM,WAAW,iBAAiB;IAChC,sDAAsD;IACtD,UAAU,EAAE,KAAK,MAAM,EAAE,CAAC;IAC1B,4DAA4D;IAC5D,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sCAAsC;IACtC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wCAAwC;IACxC,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,kCAAkC;IAClC,KAAK,CAAC,EAAE,OAAO,UAAU,CAAC,KAAK,CAAC;CACjC;AAED,mCAAmC;AACnC,MAAM,WAAW,YAAY,CAAC,CAAC,GAAG,OAAO;IACvC,oBAAoB;IACpB,IAAI,EAAE,CAAC,CAAC;IACR,uBAAuB;IACvB,MAAM,EAAE,MAAM,CAAC;IACf,uBAAuB;IACvB,OAAO,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAChC,4CAA4C;IAC5C,OAAO,CAAC,EAAE;QACR,MAAM,EAAE,MAAM,CAAC;QACf,EAAE,EAAE,KAAK,MAAM,EAAE,CAAC;QAClB,OAAO,EAAE,MAAM,CAAC;KACjB,CAAC;CACH;AAED;;;;;GAKG;AACH,qBAAa,WAAW;IACtB,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAU;IAClC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAAS;IACjC,OAAO,CAAC,QAAQ,CAAC,OAAO,CAA0B;gBAEtC,MAAM,EAAE,iBAAiB;IAMrC,qCAAqC;IACrC,IAAI,OAAO,IAAI,KAAK,MAAM,EAAE,CAE3B;IAED;;;;;;;OAOG;IACG,OAAO,CAAC,CAAC,GAAG,OAAO,EACvB,GAAG,EAAE,MAAM,EACX,IAAI,CAAC,EAAE,WAAW,GACjB,OAAO,CAAC,YAAY,CAAC,CAAC,CAAC,CAAC;IAkD3B;;OAEG;YACW,wBAAwB;IAmBtC;;OAEG;YACW,WAAW;CA2D1B"}
|
|
@@ -0,0 +1,168 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Buyer SDK for automated x402 payment flow.
|
|
3
|
+
*
|
|
4
|
+
* Handles the complete payment lifecycle:
|
|
5
|
+
* 1. Send request to a paywalled endpoint
|
|
6
|
+
* 2. Receive 402 + payment requirements
|
|
7
|
+
* 3. Sign EIP-3009 transferWithAuthorization
|
|
8
|
+
* 4. Retry request with X-PAYMENT header
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```typescript
|
|
12
|
+
* import { BuyerClient } from '@resvary/sdk/client';
|
|
13
|
+
*
|
|
14
|
+
* const buyer = new BuyerClient({
|
|
15
|
+
* privateKey: '0x...',
|
|
16
|
+
* rpcUrl: 'https://rpc.testnet.arc.network',
|
|
17
|
+
* });
|
|
18
|
+
*
|
|
19
|
+
* const response = await buyer.request('https://api.example.com/premium/data');
|
|
20
|
+
* console.log(response.data);
|
|
21
|
+
* ```
|
|
22
|
+
*/
|
|
23
|
+
import { privateKeyToAccount } from 'viem/accounts';
|
|
24
|
+
import { ARC_TESTNET, DEFAULTS, HTTP_402, USDC_DECIMALS, X402_HEADER, PAYMENT_REQUIRED_HEADER } from '../constants.js';
|
|
25
|
+
/**
|
|
26
|
+
* Client for consuming x402-paywalled APIs.
|
|
27
|
+
*
|
|
28
|
+
* Automatically handles the 402 payment flow: detects paywalled endpoints,
|
|
29
|
+
* signs payment authorizations, and retries requests with valid payments.
|
|
30
|
+
*/
|
|
31
|
+
export class BuyerClient {
|
|
32
|
+
account;
|
|
33
|
+
chainId;
|
|
34
|
+
fetchFn;
|
|
35
|
+
constructor(config) {
|
|
36
|
+
this.account = privateKeyToAccount(config.privateKey);
|
|
37
|
+
this.chainId = config.chainId ?? ARC_TESTNET.chainId;
|
|
38
|
+
this.fetchFn = config.fetch ?? globalThis.fetch;
|
|
39
|
+
}
|
|
40
|
+
/** Get the buyer's wallet address */
|
|
41
|
+
get address() {
|
|
42
|
+
return this.account.address;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* Make a request to a potentially paywalled endpoint.
|
|
46
|
+
* Automatically handles 402 → sign → retry flow.
|
|
47
|
+
*
|
|
48
|
+
* @param url - The endpoint URL
|
|
49
|
+
* @param init - Optional fetch init options
|
|
50
|
+
* @returns Response with data and payment info
|
|
51
|
+
*/
|
|
52
|
+
async request(url, init) {
|
|
53
|
+
// Initial request
|
|
54
|
+
const initialResponse = await this.fetchFn(url, init);
|
|
55
|
+
// If not 402, return directly
|
|
56
|
+
if (initialResponse.status !== HTTP_402) {
|
|
57
|
+
const data = await initialResponse.json();
|
|
58
|
+
return {
|
|
59
|
+
data,
|
|
60
|
+
status: initialResponse.status,
|
|
61
|
+
headers: Object.fromEntries(initialResponse.headers.entries()),
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
// Parse payment requirements from 402 response
|
|
65
|
+
const requirements = await this.parsePaymentRequirements(initialResponse);
|
|
66
|
+
if (!requirements) {
|
|
67
|
+
throw new Error('Received 402 but could not parse payment requirements');
|
|
68
|
+
}
|
|
69
|
+
// Sign the payment
|
|
70
|
+
const paymentPayload = await this.signPayment(requirements);
|
|
71
|
+
// Encode payment as base64
|
|
72
|
+
const encodedPayment = Buffer.from(JSON.stringify(paymentPayload)).toString('base64');
|
|
73
|
+
// Retry with payment header
|
|
74
|
+
const paidResponse = await this.fetchFn(url, {
|
|
75
|
+
...init,
|
|
76
|
+
headers: {
|
|
77
|
+
...init?.headers,
|
|
78
|
+
[X402_HEADER]: encodedPayment,
|
|
79
|
+
},
|
|
80
|
+
});
|
|
81
|
+
const data = await paidResponse.json();
|
|
82
|
+
return {
|
|
83
|
+
data,
|
|
84
|
+
status: paidResponse.status,
|
|
85
|
+
headers: Object.fromEntries(paidResponse.headers.entries()),
|
|
86
|
+
payment: {
|
|
87
|
+
amount: requirements.maxAmountRequired,
|
|
88
|
+
to: requirements.payTo,
|
|
89
|
+
network: requirements.network,
|
|
90
|
+
},
|
|
91
|
+
};
|
|
92
|
+
}
|
|
93
|
+
/**
|
|
94
|
+
* Parse payment requirements from a 402 response.
|
|
95
|
+
*/
|
|
96
|
+
async parsePaymentRequirements(response) {
|
|
97
|
+
try {
|
|
98
|
+
// Try to get requirements from header
|
|
99
|
+
const headerValue = response.headers.get(PAYMENT_REQUIRED_HEADER);
|
|
100
|
+
if (headerValue) {
|
|
101
|
+
const decoded = Buffer.from(headerValue, 'base64').toString('utf-8');
|
|
102
|
+
return JSON.parse(decoded);
|
|
103
|
+
}
|
|
104
|
+
// Fallback: try to get from response body
|
|
105
|
+
const body = await response.json();
|
|
106
|
+
return body.requirements ?? null;
|
|
107
|
+
}
|
|
108
|
+
catch {
|
|
109
|
+
return null;
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
/**
|
|
113
|
+
* Sign a payment authorization using EIP-3009.
|
|
114
|
+
*/
|
|
115
|
+
async signPayment(requirements) {
|
|
116
|
+
const now = Math.floor(Date.now() / 1000);
|
|
117
|
+
const nonce = `0x${Buffer.from(crypto.getRandomValues(new Uint8Array(32))).toString('hex')}`;
|
|
118
|
+
const authorization = {
|
|
119
|
+
from: this.account.address,
|
|
120
|
+
to: requirements.payTo,
|
|
121
|
+
value: String(BigInt(Math.ceil(parseFloat(requirements.maxAmountRequired) * 10 ** USDC_DECIMALS))),
|
|
122
|
+
validAfter: String(now - 60), // Valid from 1 minute ago (clock skew buffer)
|
|
123
|
+
validBefore: String(now + DEFAULTS.paymentValiditySeconds),
|
|
124
|
+
nonce,
|
|
125
|
+
};
|
|
126
|
+
// Sign the authorization using EIP-712 typed data
|
|
127
|
+
if (!this.account.signTypedData) {
|
|
128
|
+
throw new Error('Account does not support signTypedData');
|
|
129
|
+
}
|
|
130
|
+
const signature = await this.account.signTypedData({
|
|
131
|
+
domain: {
|
|
132
|
+
name: 'USD Coin',
|
|
133
|
+
version: '2',
|
|
134
|
+
chainId: BigInt(this.chainId),
|
|
135
|
+
verifyingContract: ARC_TESTNET.usdcAddress,
|
|
136
|
+
},
|
|
137
|
+
types: {
|
|
138
|
+
TransferWithAuthorization: [
|
|
139
|
+
{ name: 'from', type: 'address' },
|
|
140
|
+
{ name: 'to', type: 'address' },
|
|
141
|
+
{ name: 'value', type: 'uint256' },
|
|
142
|
+
{ name: 'validAfter', type: 'uint256' },
|
|
143
|
+
{ name: 'validBefore', type: 'uint256' },
|
|
144
|
+
{ name: 'nonce', type: 'bytes32' },
|
|
145
|
+
],
|
|
146
|
+
},
|
|
147
|
+
primaryType: 'TransferWithAuthorization',
|
|
148
|
+
message: {
|
|
149
|
+
from: authorization.from,
|
|
150
|
+
to: authorization.to,
|
|
151
|
+
value: BigInt(authorization.value),
|
|
152
|
+
validAfter: BigInt(authorization.validAfter),
|
|
153
|
+
validBefore: BigInt(authorization.validBefore),
|
|
154
|
+
nonce: authorization.nonce,
|
|
155
|
+
},
|
|
156
|
+
});
|
|
157
|
+
return {
|
|
158
|
+
x402Version: DEFAULTS.x402Version,
|
|
159
|
+
scheme: requirements.scheme ?? 'exact',
|
|
160
|
+
network: requirements.network,
|
|
161
|
+
payload: {
|
|
162
|
+
signature,
|
|
163
|
+
authorization,
|
|
164
|
+
},
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
//# sourceMappingURL=buyer.js.map
|