Skip to content

Commit 59ab4c4

Browse files
committed
feat: add x402 upto billing policy
1 parent 7d85261 commit 59ab4c4

7 files changed

Lines changed: 473 additions & 4 deletions

File tree

CHANGELOG.md

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,16 @@
1+
## [6.2.0] — 2026-04-13
2+
3+
### Added
4+
- `UptoBillingPolicy` for x402 usage-based billing, with max authorization tracking, actual settlement recording, unused-capacity release, and wallet ledger deltas
5+
- Test coverage for partial settlement, finalization, over-cap rejection, and multi-settlement flows
6+
7+
### Changed
8+
- Exported `UptoBillingPolicy` from the main SDK entrypoint
9+
- Updated README examples and package metadata to position `agentwallet-sdk` as an x402 usage-based billing reference implementation
10+
- Simplified `PaymentRouter.isRailLive()` boolean handling
11+
12+
---
13+
114
## [6.0.0] — 2026-03-21
215

316
### Added

README.md

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
# AgentWallet SDK
22

3-
> **v6.0.0** · MIT · **Patent Pending**
3+
> **v6.2.0** · MIT · **Patent Pending**
44
>
55
> USPTO Provisional filed March 2026: "Non-Custodial Multi-Chain Financial Infrastructure System for Autonomous AI Agents"
66
@@ -138,6 +138,35 @@ const data = await response.json();
138138
// Every payment: tx hash on Base, auditable on basescan.org
139139
```
140140

141+
### Usage-based billing with x402 "upto"
142+
143+
```typescript
144+
import { UptoBillingPolicy } from 'agentwallet-sdk';
145+
146+
const upto = new UptoBillingPolicy();
147+
148+
const auth = upto.authorize({
149+
authorizationId: 'req-42',
150+
service: 'api.example.com',
151+
resource: 'POST /v1/generate',
152+
network: 'base:8453',
153+
asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
154+
payTo: '0xMerchant',
155+
maxAmount: 2_000_000n, // authorize up to $2.00 USDC
156+
});
157+
158+
const snapshot = upto.recordSettlement(auth.authorizationId, 740_000n, {
159+
txHash: '0xsettlement',
160+
finalize: true,
161+
});
162+
163+
console.log(snapshot.authorization.settledAmount); // 740000n
164+
console.log(snapshot.authorization.releasedAmount); // 1260000n
165+
console.log(upto.getNetWalletDelta(auth.authorizationId)); // -740000n
166+
```
167+
168+
This gives the wallet a clean local ledger for x402 usage-based billing: max authorized, actual settlement, and explicit release of unused capacity.
169+
141170
## The Trust Layer
142171

143172
This is what makes supervised payments different from autonomous payments.

package.json

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
{
22
"name": "agentwallet-sdk",
3-
"version": "6.1.0",
4-
"description": "Non-custodial AI agent wallet SDK. ERC-8004 identity & reputation registries, mutual stake escrow, x402 payments, 17-chain CCTP bridging, ERC-6551 TBA, SpendingPolicy guardrails, Uniswap V3 swap. The agent holds the keys.",
3+
"version": "6.2.0",
4+
"description": "Non-custodial AI agent wallet SDK. ERC-8004 identity & reputation registries, mutual stake escrow, x402 payments, x402 upto billing policy accounting, 17-chain CCTP bridging, ERC-6551 TBA, SpendingPolicy guardrails, Uniswap V3 swap. The agent holds the keys.",
55
"main": "dist/index.js",
66
"types": "dist/index.d.ts",
77
"files": [

src/index.ts

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -588,6 +588,20 @@ export type {
588588
DraftEntry,
589589
} from './policy/SpendingPolicy.js';
590590

591+
// ─── UptoBillingPolicy — x402 usage-based settlement accounting ─────────────
592+
export {
593+
UptoBillingPolicy,
594+
} from './policy/UptoBillingPolicy.js';
595+
export type {
596+
UptoAuthorizationStatus,
597+
UptoAuthorizationRequest,
598+
UptoSettlementOptions,
599+
UptoSettlementRecord,
600+
UptoAuthorizationRecord,
601+
WalletLedgerDelta,
602+
UptoBillingSnapshot,
603+
} from './policy/UptoBillingPolicy.js';
604+
591605
// ─── Mutual Stake Escrow ─────────────────────────────────────────────────────
592606
export { MutualStakeEscrow } from './escrow/MutualStakeEscrow.js';
593607
export type {
Lines changed: 118 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,118 @@
1+
import { describe, expect, it } from 'vitest';
2+
import { UptoBillingPolicy } from './UptoBillingPolicy.js';
3+
4+
describe('UptoBillingPolicy', () => {
5+
it('tracks max authorization and exposes reservation ledger delta', () => {
6+
const policy = new UptoBillingPolicy();
7+
const auth = policy.authorize({
8+
authorizationId: 'auth-1',
9+
service: 'api.example.com',
10+
resource: 'GET /inference',
11+
network: 'base:8453',
12+
asset: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
13+
payTo: '0xabc',
14+
maxAmount: 1_000_000n,
15+
});
16+
17+
expect(auth.maxAmount).toBe(1_000_000n);
18+
expect(auth.remainingAmount).toBe(1_000_000n);
19+
expect(policy.getReservedAmount('auth-1')).toBe(1_000_000n);
20+
21+
const deltas = policy.getWalletLedgerDeltas('auth-1');
22+
expect(deltas).toHaveLength(1);
23+
expect(deltas[0].type).toBe('authorization');
24+
expect(deltas[0].reservedDelta).toBe(1_000_000n);
25+
expect(deltas[0].netWalletDelta).toBe(0n);
26+
});
27+
28+
it('records actual settlement and wallet delta', () => {
29+
const policy = new UptoBillingPolicy();
30+
policy.authorize({
31+
authorizationId: 'auth-2',
32+
service: 'api.example.com',
33+
network: 'base:8453',
34+
asset: 'usdc',
35+
payTo: '0xabc',
36+
maxAmount: 1_000_000n,
37+
});
38+
39+
const snapshot = policy.recordSettlement('auth-2', 420_000n, {
40+
txHash: '0xsettle',
41+
});
42+
43+
expect(snapshot.authorization.settledAmount).toBe(420_000n);
44+
expect(snapshot.authorization.remainingAmount).toBe(580_000n);
45+
expect(snapshot.authorization.status).toBe('partially_settled');
46+
expect(policy.getNetWalletDelta('auth-2')).toBe(-420_000n);
47+
48+
const settlementDelta = snapshot.ledgerDeltas[1];
49+
expect(settlementDelta.type).toBe('settlement');
50+
expect(settlementDelta.reservedDelta).toBe(-420_000n);
51+
expect(settlementDelta.settledDelta).toBe(420_000n);
52+
expect(settlementDelta.netWalletDelta).toBe(-420_000n);
53+
});
54+
55+
it('releases unused authorization capacity on finalize', () => {
56+
const policy = new UptoBillingPolicy();
57+
policy.authorize({
58+
authorizationId: 'auth-3',
59+
service: 'api.example.com',
60+
network: 'base:8453',
61+
asset: 'usdc',
62+
payTo: '0xabc',
63+
maxAmount: 1_000_000n,
64+
});
65+
66+
policy.recordSettlement('auth-3', 250_000n, { finalize: true });
67+
const auth = policy.getAuthorization('auth-3');
68+
const deltas = policy.getWalletLedgerDeltas('auth-3');
69+
70+
expect(auth?.settledAmount).toBe(250_000n);
71+
expect(auth?.releasedAmount).toBe(750_000n);
72+
expect(auth?.remainingAmount).toBe(0n);
73+
expect(auth?.status).toBe('settled');
74+
expect(policy.getReservedAmount('auth-3')).toBe(0n);
75+
expect(deltas.map((delta) => delta.type)).toEqual([
76+
'authorization',
77+
'settlement',
78+
'release',
79+
]);
80+
});
81+
82+
it('rejects settlement above the authorized amount', () => {
83+
const policy = new UptoBillingPolicy();
84+
policy.authorize({
85+
authorizationId: 'auth-4',
86+
service: 'api.example.com',
87+
network: 'base:8453',
88+
asset: 'usdc',
89+
payTo: '0xabc',
90+
maxAmount: 100n,
91+
});
92+
93+
expect(() => policy.recordSettlement('auth-4', 101n)).toThrow(
94+
/exceeds remaining authorized amount/i,
95+
);
96+
});
97+
98+
it('supports multiple settlements up to the authorized cap', () => {
99+
const policy = new UptoBillingPolicy();
100+
policy.authorize({
101+
authorizationId: 'auth-5',
102+
service: 'api.example.com',
103+
network: 'base:8453',
104+
asset: 'usdc',
105+
payTo: '0xabc',
106+
maxAmount: 1_000n,
107+
});
108+
109+
policy.recordSettlement('auth-5', 250n);
110+
policy.recordSettlement('auth-5', 750n);
111+
112+
const auth = policy.getAuthorization('auth-5');
113+
expect(auth?.settledAmount).toBe(1_000n);
114+
expect(auth?.remainingAmount).toBe(0n);
115+
expect(auth?.status).toBe('settled');
116+
expect(policy.getNetWalletDelta('auth-5')).toBe(-1_000n);
117+
});
118+
});

0 commit comments

Comments
 (0)