@umpledger/sdk 2.0.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.
@@ -0,0 +1,257 @@
1
+ import type {
2
+ Wallet, Balance, LedgerEntry, FundWalletOptions, ValueUnitType,
3
+ } from '../types';
4
+ import { generateId, parseMoney, round, hrTimestamp } from '../utils/id';
5
+ import { InsufficientFundsError, WalletFrozenError, UMPError } from '../utils/errors';
6
+
7
+ /**
8
+ * WalletManager — Layer 1 primitive
9
+ *
10
+ * Manages wallets, balances, reserves, and the immutable spending ledger.
11
+ * Every financial operation is recorded as a LedgerEntry.
12
+ */
13
+ export class WalletManager {
14
+ private wallets: Map<string, Wallet> = new Map();
15
+ private ledgers: Map<string, LedgerEntry[]> = new Map(); // walletId -> entries
16
+
17
+ /**
18
+ * Create a wallet for an agent.
19
+ */
20
+ create(ownerAgentId: string): Wallet {
21
+ const walletId = generateId('wal');
22
+ const wallet: Wallet = {
23
+ walletId,
24
+ ownerAgentId,
25
+ balances: [{
26
+ valueUnitType: 'FIAT',
27
+ currency: 'USD',
28
+ amount: 0,
29
+ reserved: 0,
30
+ available: 0,
31
+ }],
32
+ frozen: false,
33
+ createdAt: hrTimestamp(),
34
+ };
35
+ this.wallets.set(walletId, wallet);
36
+ this.ledgers.set(walletId, []);
37
+ return wallet;
38
+ }
39
+
40
+ /**
41
+ * Get wallet by ID.
42
+ */
43
+ get(walletId: string): Wallet {
44
+ const wallet = this.wallets.get(walletId);
45
+ if (!wallet) throw new UMPError(`Wallet not found: ${walletId}`, 'WALLET_NOT_FOUND');
46
+ return wallet;
47
+ }
48
+
49
+ /**
50
+ * Get wallet by owner agent ID.
51
+ */
52
+ getByAgent(agentId: string): Wallet {
53
+ for (const w of this.wallets.values()) {
54
+ if (w.ownerAgentId === agentId) return w;
55
+ }
56
+ throw new UMPError(`No wallet found for agent: ${agentId}`, 'WALLET_NOT_FOUND');
57
+ }
58
+
59
+ /**
60
+ * Fund a wallet (add money).
61
+ */
62
+ fund(walletId: string, options: FundWalletOptions): LedgerEntry {
63
+ const wallet = this.get(walletId);
64
+ if (wallet.frozen) throw new WalletFrozenError(walletId);
65
+
66
+ const amount = parseMoney(options.amount);
67
+ const vut = options.valueUnitType || 'FIAT';
68
+ const currency = options.currency || 'USD';
69
+
70
+ const balance = this.getOrCreateBalance(wallet, vut, currency);
71
+ balance.amount = round(balance.amount + amount);
72
+ balance.available = round(balance.available + amount);
73
+
74
+ const entry = this.appendLedger(walletId, {
75
+ type: 'TOPUP',
76
+ amount,
77
+ valueUnitType: vut,
78
+ currency,
79
+ description: `Funded ${amount} ${currency}`,
80
+ balanceAfter: balance.available,
81
+ });
82
+
83
+ return entry;
84
+ }
85
+
86
+ /**
87
+ * Reserve funds for a pending transaction (escrow pattern).
88
+ */
89
+ reserve(walletId: string, amount: number, counterpartyAgentId: string, transactionId: string): LedgerEntry {
90
+ const wallet = this.get(walletId);
91
+ if (wallet.frozen) throw new WalletFrozenError(walletId);
92
+
93
+ const balance = this.getPrimaryBalance(wallet);
94
+ if (balance.available < amount) {
95
+ throw new InsufficientFundsError(balance.available, amount);
96
+ }
97
+
98
+ balance.reserved = round(balance.reserved + amount);
99
+ balance.available = round(balance.available - amount);
100
+
101
+ return this.appendLedger(walletId, {
102
+ type: 'RESERVE',
103
+ amount,
104
+ valueUnitType: balance.valueUnitType,
105
+ currency: balance.currency,
106
+ counterpartyAgentId,
107
+ transactionId,
108
+ description: `Reserved ${amount} for transaction ${transactionId}`,
109
+ balanceAfter: balance.available,
110
+ });
111
+ }
112
+
113
+ /**
114
+ * Debit wallet (immediate drawdown).
115
+ */
116
+ debit(walletId: string, amount: number, counterpartyAgentId: string, transactionId: string): LedgerEntry {
117
+ const wallet = this.get(walletId);
118
+ if (wallet.frozen) throw new WalletFrozenError(walletId);
119
+
120
+ const balance = this.getPrimaryBalance(wallet);
121
+ if (balance.available < amount) {
122
+ throw new InsufficientFundsError(balance.available, amount);
123
+ }
124
+
125
+ balance.amount = round(balance.amount - amount);
126
+ balance.available = round(balance.available - amount);
127
+
128
+ return this.appendLedger(walletId, {
129
+ type: 'DEBIT',
130
+ amount,
131
+ valueUnitType: balance.valueUnitType,
132
+ currency: balance.currency,
133
+ counterpartyAgentId,
134
+ transactionId,
135
+ description: `Debited ${amount} to ${counterpartyAgentId}`,
136
+ balanceAfter: balance.available,
137
+ });
138
+ }
139
+
140
+ /**
141
+ * Credit wallet (receive payment).
142
+ */
143
+ credit(walletId: string, amount: number, counterpartyAgentId: string, transactionId: string): LedgerEntry {
144
+ const wallet = this.get(walletId);
145
+
146
+ const balance = this.getPrimaryBalance(wallet);
147
+ balance.amount = round(balance.amount + amount);
148
+ balance.available = round(balance.available + amount);
149
+
150
+ return this.appendLedger(walletId, {
151
+ type: 'CREDIT',
152
+ amount,
153
+ valueUnitType: balance.valueUnitType,
154
+ currency: balance.currency,
155
+ counterpartyAgentId,
156
+ transactionId,
157
+ description: `Credited ${amount} from ${counterpartyAgentId}`,
158
+ balanceAfter: balance.available,
159
+ });
160
+ }
161
+
162
+ /**
163
+ * Release reserved funds (escrow release after outcome).
164
+ */
165
+ releaseReservation(walletId: string, amount: number, transactionId: string): LedgerEntry {
166
+ const wallet = this.get(walletId);
167
+ const balance = this.getPrimaryBalance(wallet);
168
+
169
+ const releaseAmount = Math.min(amount, balance.reserved);
170
+ balance.reserved = round(balance.reserved - releaseAmount);
171
+ balance.amount = round(balance.amount - releaseAmount);
172
+
173
+ return this.appendLedger(walletId, {
174
+ type: 'RELEASE',
175
+ amount: releaseAmount,
176
+ valueUnitType: balance.valueUnitType,
177
+ currency: balance.currency,
178
+ transactionId,
179
+ description: `Released ${releaseAmount} from escrow for ${transactionId}`,
180
+ balanceAfter: balance.available,
181
+ });
182
+ }
183
+
184
+ /**
185
+ * Emergency freeze — blocks all transactions.
186
+ */
187
+ freeze(walletId: string): void {
188
+ const wallet = this.get(walletId);
189
+ wallet.frozen = true;
190
+ }
191
+
192
+ /**
193
+ * Unfreeze wallet.
194
+ */
195
+ unfreeze(walletId: string): void {
196
+ const wallet = this.get(walletId);
197
+ wallet.frozen = false;
198
+ }
199
+
200
+ /**
201
+ * Get immutable ledger entries for a wallet.
202
+ */
203
+ getLedger(walletId: string, limit = 100, offset = 0): LedgerEntry[] {
204
+ const entries = this.ledgers.get(walletId) || [];
205
+ return entries.slice(offset, offset + limit);
206
+ }
207
+
208
+ /**
209
+ * Get real-time balance summary.
210
+ */
211
+ getBalance(walletId: string): Balance[] {
212
+ const wallet = this.get(walletId);
213
+ return wallet.balances.map(b => ({ ...b })); // return copies
214
+ }
215
+
216
+ // ── Private helpers ──
217
+
218
+ private getPrimaryBalance(wallet: Wallet): Balance {
219
+ if (wallet.balances.length === 0) {
220
+ const balance: Balance = {
221
+ valueUnitType: 'FIAT',
222
+ currency: 'USD',
223
+ amount: 0,
224
+ reserved: 0,
225
+ available: 0,
226
+ };
227
+ wallet.balances.push(balance);
228
+ return balance;
229
+ }
230
+ return wallet.balances[0];
231
+ }
232
+
233
+ private getOrCreateBalance(wallet: Wallet, vut: ValueUnitType, currency?: string): Balance {
234
+ let balance = wallet.balances.find(
235
+ b => b.valueUnitType === vut && b.currency === currency
236
+ );
237
+ if (!balance) {
238
+ balance = { valueUnitType: vut, currency, amount: 0, reserved: 0, available: 0 };
239
+ wallet.balances.push(balance);
240
+ }
241
+ return balance;
242
+ }
243
+
244
+ private appendLedger(
245
+ walletId: string,
246
+ data: Omit<LedgerEntry, 'entryId' | 'timestamp'>
247
+ ): LedgerEntry {
248
+ const entry: LedgerEntry = {
249
+ entryId: generateId('led'),
250
+ timestamp: hrTimestamp(),
251
+ ...data,
252
+ };
253
+ const ledger = this.ledgers.get(walletId);
254
+ if (ledger) ledger.push(entry);
255
+ return entry;
256
+ }
257
+ }
package/src/index.ts ADDED
@@ -0,0 +1,188 @@
1
+ /**
2
+ * ═══════════════════════════════════════════════════════════════
3
+ * @ump/sdk — Universal Monetization Protocol v2.0
4
+ *
5
+ * The payment rail for the autonomous economy.
6
+ * When AI agents transact with AI agents, UMP governs
7
+ * how value flows between them.
8
+ *
9
+ * Quick Start:
10
+ * const ump = new UMP({ apiKey: "ump_sk_..." });
11
+ * const agent = ump.agents.create({ name: "my-agent", type: "AI_AGENT", authority: { maxPerTransaction: "$50", maxPerDay: "$500" } });
12
+ * await ump.transact({ from: agent.agentId, to: "agent_target", service: "code_review" });
13
+ *
14
+ * Architecture: 3 layers
15
+ * L1: Identity & Value (AgentManager, WalletManager)
16
+ * L2: Terms & Metering (ContractManager, MeteringEngine, PricingEngine)
17
+ * L3: Settlement & Gov (SettlementBus, AuditTrail)
18
+ *
19
+ * © 2026 UMPLedger — Apache 2.0 License
20
+ * ═══════════════════════════════════════════════════════════════
21
+ */
22
+
23
+ import type {
24
+ UMPConfig, TransactOptions, TransactionResult,
25
+ CreateAgentOptions, AgentIdentity, FundWalletOptions, PricingRule,
26
+ } from './types';
27
+ import { AgentManager } from './core/agent-manager';
28
+ import { WalletManager } from './core/wallet-manager';
29
+ import { AuditTrail } from './core/audit-trail';
30
+ import { PricingEngine } from './pricing/engine';
31
+ import { ContractManager } from './terms/contract-manager';
32
+ import { MeteringEngine } from './terms/metering';
33
+ import { SettlementBus } from './settlement/bus';
34
+ import { generateId, parseMoney } from './utils/id';
35
+ import { AgentRevokedError, UMPError } from './utils/errors';
36
+
37
+ /**
38
+ * Convenience wrapper: an agent + its wallet in one object.
39
+ */
40
+ export interface AgentHandle {
41
+ id: string;
42
+ agent: AgentIdentity;
43
+ wallet: {
44
+ fund: (options: FundWalletOptions) => void;
45
+ balance: () => number;
46
+ freeze: () => void;
47
+ unfreeze: () => void;
48
+ };
49
+ }
50
+
51
+ /**
52
+ * UMP — Main SDK Entry Point
53
+ *
54
+ * Orchestrates all three protocol layers through a clean API.
55
+ */
56
+ export class UMP {
57
+ // ── Layer 1: Identity & Value ──
58
+ public readonly agents: AgentManager;
59
+ public readonly wallets: WalletManager;
60
+
61
+ // ── Layer 2: Terms & Metering ──
62
+ public readonly contracts: ContractManager;
63
+ public readonly metering: MeteringEngine;
64
+ public readonly pricing: PricingEngine;
65
+
66
+ // ── Layer 3: Settlement & Governance ──
67
+ public readonly settlement: SettlementBus;
68
+ public readonly audit: AuditTrail;
69
+
70
+ constructor(config: UMPConfig) {
71
+ // Initialize all subsystems
72
+ this.agents = new AgentManager();
73
+ this.wallets = new WalletManager();
74
+ this.audit = new AuditTrail(config.onAudit);
75
+ this.pricing = new PricingEngine();
76
+ this.contracts = new ContractManager();
77
+ this.metering = new MeteringEngine();
78
+ this.settlement = new SettlementBus(this.wallets, this.audit, this.pricing);
79
+ }
80
+
81
+ /**
82
+ * High-level: Register an agent and create its wallet.
83
+ * Returns an AgentHandle with convenience methods.
84
+ */
85
+ registerAgent(options: CreateAgentOptions): AgentHandle {
86
+ const agent = this.agents.create(options);
87
+ const wallet = this.wallets.create(agent.agentId);
88
+
89
+ return {
90
+ id: agent.agentId,
91
+ agent,
92
+ wallet: {
93
+ fund: (opts: FundWalletOptions) => {
94
+ this.wallets.fund(wallet.walletId, opts);
95
+ },
96
+ balance: () => {
97
+ const balances = this.wallets.getBalance(wallet.walletId);
98
+ return balances[0]?.available ?? 0;
99
+ },
100
+ freeze: () => this.wallets.freeze(wallet.walletId),
101
+ unfreeze: () => this.wallets.unfreeze(wallet.walletId),
102
+ },
103
+ };
104
+ }
105
+
106
+ /**
107
+ * High-level: Execute a priced transaction between two agents.
108
+ *
109
+ * This is the "10 lines of code" Quick Start method.
110
+ * It finds or creates a contract, meters the event, rates it,
111
+ * settles payment, and returns the result — all in one call.
112
+ */
113
+ async transact(options: TransactOptions): Promise<TransactionResult> {
114
+ const startTime = Date.now();
115
+ const txnId = generateId('txn');
116
+
117
+ // 1. Validate agents
118
+ const sourceCheck = this.agents.verify(options.from);
119
+ if (!sourceCheck.valid) {
120
+ throw new AgentRevokedError(options.from);
121
+ }
122
+
123
+ // 2. Find or create contract
124
+ let contract = this.contracts.findActive(options.from, options.to);
125
+ if (!contract) {
126
+ // Auto-create a template contract with default per-unit pricing
127
+ contract = this.contracts.create(options.from, {
128
+ targetAgentId: options.to,
129
+ pricingRules: [{
130
+ name: 'Default per-unit',
131
+ primitive: 'UNIT_RATE',
132
+ } as Omit<PricingRule, 'ruleId'>],
133
+ });
134
+ }
135
+
136
+ // 3. Check authority
137
+ const maxCost = options.maxCost ? parseMoney(options.maxCost) : undefined;
138
+ if (maxCost) {
139
+ const authCheck = this.agents.checkAuthority(options.from, maxCost, options.to, options.service);
140
+ if (!authCheck.allowed) {
141
+ throw new UMPError(`Authority check failed: ${authCheck.reason}`, 'AUTHORITY_EXCEEDED');
142
+ }
143
+ }
144
+
145
+ // 4. Meter the event
146
+ const event = this.metering.record({
147
+ sourceAgentId: options.from,
148
+ targetAgentId: options.to,
149
+ contractId: contract.contractId,
150
+ serviceId: options.service,
151
+ quantity: 1,
152
+ unit: 'API_CALL',
153
+ dimensions: (options.payload as Record<string, string | number>) || {},
154
+ });
155
+
156
+ // 5. Rate + Settle
157
+ const rule = contract.pricingRules[0];
158
+ const { settlement, auditId } = await this.settlement.transact(
159
+ options.from,
160
+ options.to,
161
+ event,
162
+ rule,
163
+ );
164
+
165
+ return {
166
+ transactionId: txnId,
167
+ cost: settlement.totalAmount,
168
+ currency: settlement.currency,
169
+ outcome: event.outcome,
170
+ auditId,
171
+ settledAt: settlement.settledAt || new Date(),
172
+ duration: Date.now() - startTime,
173
+ };
174
+ }
175
+ }
176
+
177
+ // ── Re-exports ──────────────────────────────────────────────
178
+
179
+ export { AgentManager } from './core/agent-manager';
180
+ export { WalletManager } from './core/wallet-manager';
181
+ export { AuditTrail } from './core/audit-trail';
182
+ export { PricingEngine, type RatingContext } from './pricing/engine';
183
+ export { PricingTemplates } from './pricing/templates';
184
+ export { ContractManager } from './terms/contract-manager';
185
+ export { MeteringEngine } from './terms/metering';
186
+ export { SettlementBus, type SettlementResult } from './settlement/bus';
187
+ export * from './types';
188
+ export * from './utils/errors';