@bondedhq/sdk 0.1.4 → 0.1.5

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,33 @@
1
+ import { type GuardPaymentsOptions } from "./payments.js";
2
+ export type { GuardPaymentsOptions } from "./payments.js";
3
+ /**
4
+ * The part of an OpenAI Agents SDK function tool (`tool()` from `@openai/agents`) the guard needs.
5
+ * The SDK calls `invoke` with the run context and the model's arguments as a JSON string.
6
+ */
7
+ export interface OpenAiAgentsFunctionTool {
8
+ type: "function";
9
+ name: string;
10
+ invoke: (runContext: never, input: string, details?: never) => Promise<unknown>;
11
+ }
12
+ /**
13
+ * Your own OpenAI Agents SDK pay tool, with every call checked against the payment mandate first
14
+ * (see `PaymentGuard`, or pass the hosted `PaymentGate` as `guard`). Same name, description,
15
+ * parameters, approval setting and guardrails. When the guard refuses (Enforce mode blocked it,
16
+ * the guard is frozen, the gate couldn't be reached, or it can't read the payment) your tool is not
17
+ * called and the model gets `Error: ...` naming the rules broken, so it can react. In Monitor mode
18
+ * a breaking payment goes through and the guard's `onBreach` reports it.
19
+ *
20
+ * ```ts
21
+ * import { Agent, tool } from "@openai/agents";
22
+ * import { guardOpenAiAgentsPayTool } from "@bondedhq/sdk/openai-agents";
23
+ *
24
+ * const guard = paymentGuard({ mandate, vendors, invoices, onBreach: alert });
25
+ * const agent = new Agent({ name: "AP clerk", tools: [...readOnlyTools, guardOpenAiAgentsPayTool(payInvoice, { guard })] });
26
+ * ```
27
+ *
28
+ * The payment is read from the tool's arguments: `{ account, amountUsd, invoiceReference }` unless
29
+ * you pass `toPayment`. If the tool needs a person's approval, the run stops for it before the
30
+ * guard is asked, so an approved payment can still be blocked.
31
+ */
32
+ export declare function guardOpenAiAgentsPayTool<T extends OpenAiAgentsFunctionTool>(payTool: T, options: GuardPaymentsOptions<Record<string, unknown>>): T;
33
+ //# sourceMappingURL=openai-agents.d.ts.map
@@ -0,0 +1,52 @@
1
+ import { BondedError } from "./errors.js";
2
+ import { guardPayments, isPaymentRefusal } from "./payments.js";
3
+ /**
4
+ * Your own OpenAI Agents SDK pay tool, with every call checked against the payment mandate first
5
+ * (see `PaymentGuard`, or pass the hosted `PaymentGate` as `guard`). Same name, description,
6
+ * parameters, approval setting and guardrails. When the guard refuses (Enforce mode blocked it,
7
+ * the guard is frozen, the gate couldn't be reached, or it can't read the payment) your tool is not
8
+ * called and the model gets `Error: ...` naming the rules broken, so it can react. In Monitor mode
9
+ * a breaking payment goes through and the guard's `onBreach` reports it.
10
+ *
11
+ * ```ts
12
+ * import { Agent, tool } from "@openai/agents";
13
+ * import { guardOpenAiAgentsPayTool } from "@bondedhq/sdk/openai-agents";
14
+ *
15
+ * const guard = paymentGuard({ mandate, vendors, invoices, onBreach: alert });
16
+ * const agent = new Agent({ name: "AP clerk", tools: [...readOnlyTools, guardOpenAiAgentsPayTool(payInvoice, { guard })] });
17
+ * ```
18
+ *
19
+ * The payment is read from the tool's arguments: `{ account, amountUsd, invoiceReference }` unless
20
+ * you pass `toPayment`. If the tool needs a person's approval, the run stops for it before the
21
+ * guard is asked, so an approved payment can still be blocked.
22
+ */
23
+ export function guardOpenAiAgentsPayTool(payTool, options) {
24
+ if (payTool?.type !== "function" || typeof payTool.invoke !== "function")
25
+ throw new BondedError("CONFIG", "guardOpenAiAgentsPayTool needs a function tool");
26
+ const invoke = payTool.invoke.bind(payTool);
27
+ const guarded = guardPayments((_args, runContext, input, details) => invoke(runContext, input, details), options);
28
+ return {
29
+ ...payTool,
30
+ invoke: (async (runContext, input, details) => {
31
+ let args;
32
+ try {
33
+ args = JSON.parse(input || "{}");
34
+ }
35
+ catch {
36
+ return "Error: the payment's arguments are not valid JSON; no payment was made";
37
+ }
38
+ // A custom toPayment reads fields: null or an array would throw before the guard answers.
39
+ if (!args || typeof args !== "object" || Array.isArray(args))
40
+ return "Error: the payment's arguments must be an object; no payment was made";
41
+ try {
42
+ return await guarded(args, runContext, input, details);
43
+ }
44
+ catch (error) {
45
+ if (isPaymentRefusal(error))
46
+ return `Error: ${error.message}`;
47
+ throw error;
48
+ }
49
+ }),
50
+ };
51
+ }
52
+ //# sourceMappingURL=openai-agents.js.map
@@ -1,4 +1,5 @@
1
- import { type PaidPayment, type PayableInvoice, type PayableRefund, type PaymentAttempt, type PaymentLimits, type PaymentRule, type SignedGateReceipt, type VerifiedChange } from "@bondedhq/shared";
1
+ import { type BudgetTerms, type GateRule, type PaidPayment, type PayableInvoice, type PayableRefund, type PaymentAttempt, type PaymentLimits, type SignedGateReceipt, type VerifiedChange } from "@bondedhq/shared";
2
+ import { type BudgetRequest, type PaymentBudget } from "./budget.js";
2
3
  import { BondedError, PaymentBlockedError, PaymentFrozenError, PaymentGateError } from "./errors.js";
3
4
  /** The off-chain payment mandate an agent's payments are checked against. */
4
5
  export interface PaymentMandate extends PaymentLimits {
@@ -6,6 +7,13 @@ export interface PaymentMandate extends PaymentLimits {
6
7
  mode: "monitor" | "enforce";
7
8
  /** After the first breach, refuse every payment until a person unfreezes the guard. */
8
9
  freezeOnBreach: boolean;
10
+ /**
11
+ * False when your payments have no invoices: a payment needs a vendor on the list and to stay
12
+ * within the limits, and its reference is optional. Refunds still need an approved refund.
13
+ */
14
+ invoiceMatching?: boolean;
15
+ /** Payments allowed in any hour, with invoice matching off. No limit unless set. */
16
+ maxPaymentsPerHour?: number;
9
17
  }
10
18
  /** A supplier as your vendor master holds it. Its account is one the agent may pay. */
11
19
  export interface PaymentVendor {
@@ -28,18 +36,32 @@ export interface PaymentGuardState {
28
36
  * `unknown_payee`.
29
37
  */
30
38
  unverifiedChanges: VerifiedChange[];
39
+ /**
40
+ * Open budgets (`budget()`): their terms and what was counted against them. Each one's whole
41
+ * amount is in `payments` until it closes. Absent in state saved before budgets existed.
42
+ */
43
+ budgets?: LocalBudgetState[];
44
+ /** When budget payments were made in the last hour (Unix milliseconds), for the hourly limit. */
45
+ budgetSpends?: number[];
46
+ }
47
+ /** An open local budget, as the guard's state keeps it. */
48
+ export interface LocalBudgetState {
49
+ id: string;
50
+ terms: BudgetTerms;
51
+ spentMicros: number;
52
+ payments: number;
31
53
  }
32
54
  /** One payment (or bank change) that broke the mandate, as the guard reports it. */
33
55
  export interface PaymentBreach {
34
- kind: "invoice" | "refund" | "vendor_change";
56
+ kind: "invoice" | "refund" | "vendor_change" | "budget";
35
57
  /** Every rule broken, most serious first. */
36
- rules: PaymentRule[];
37
- /** Each rule in plain words (`PAYMENT_RULE_WORDS`), in the same order. */
58
+ rules: GateRule[];
59
+ /** Each rule in plain words (`GATE_RULE_WORDS`), in the same order. */
38
60
  reasons: string[];
39
61
  /** Enforce mode stopped it: the pay function was not called. */
40
62
  blocked: boolean;
41
63
  account: string;
42
- /** The invoice or refund reference; empty for a bank change. */
64
+ /** The invoice or refund reference; empty for a bank change or a budget. */
43
65
  reference: string;
44
66
  /** The vendor whose verified account this is (or, for a bank change, the vendor changed). */
45
67
  vendorId: string | null;
@@ -58,7 +80,7 @@ export interface PaymentBreach {
58
80
  /** The rule verdict for a payment, without making it. */
59
81
  export interface PaymentVerdict {
60
82
  /** Every rule the payment breaks, most serious first. Empty when it is allowed. */
61
- rules: PaymentRule[];
83
+ rules: GateRule[];
62
84
  reasons: string[];
63
85
  amountUsd: number;
64
86
  reference: string;
@@ -134,6 +156,17 @@ export declare class PaymentGuard {
134
156
  vendorId: string;
135
157
  account: string;
136
158
  }, update: () => R | Promise<R>): Promise<R>;
159
+ /**
160
+ * Opens a budget: one check for many small payments to one payee (see `PaymentBudget`). Needs
161
+ * `invoiceMatching: false`. The payee must be a vendor's account (`unknown_payee`), the whole
162
+ * budget must fit in what's left of the day's limit (`over_daily_cap`), and with
163
+ * `maxPaymentsPerHour` there must be room for a payment (`over_hourly_limit`); the budget then
164
+ * covers what's left of the hour. Enforce mode throws `PaymentBlockedError`; Monitor mode opens it
165
+ * and reports the breach. The whole budget counts toward the day until `close()`, when what was
166
+ * spent (rounded up to the cent) counts instead. A budget past its end is closed the next time
167
+ * one is opened.
168
+ */
169
+ budget(request: BudgetRequest): Promise<PaymentBudget>;
137
170
  }
138
171
  /** A payment guard: see `PaymentGuard`. */
139
172
  export declare function paymentGuard(options: PaymentGuardOptions): PaymentGuard;
package/dist/payments.js CHANGED
@@ -1,11 +1,16 @@
1
- import { checkInvoicePayment, checkRefundPayment, isVerifiedChange, normalAccount, PAYMENT_RULE_WORDS, utcDay, } from "@bondedhq/shared";
1
+ import { checkBudget, checkGeneralPayment, checkInvoicePayment, checkRefundPayment, GATE_RULE_WORDS, isVerifiedChange, normalAccount, utcDay, usdToMicros, } from "@bondedhq/shared";
2
+ import { BudgetBase } from "./budget.js";
2
3
  import { BondedError, PaymentBlockedError, PaymentFrozenError, PaymentGateError, } from "./errors.js";
3
4
  const read = async (source) => typeof source === "function" ? await source() : (source ?? []);
4
5
  const copy = (state) => ({
5
6
  payments: state.payments.map((p) => ({ ...p })),
6
7
  frozen: state.frozen,
7
8
  unverifiedChanges: state.unverifiedChanges.map((c) => ({ ...c })),
9
+ budgets: (state.budgets ?? []).map((b) => ({ ...b, terms: { ...b.terms } })),
10
+ budgetSpends: [...(state.budgetSpends ?? [])],
8
11
  });
12
+ /** The reference a local budget's reservation is recorded under. */
13
+ const budgetReference = (id) => `budget:${id}`;
9
14
  /**
10
15
  * The payment mandate, checked inside your own process: the same rules (`@bondedhq/shared`'s
11
16
  * payment mandate) the Arena rates agents against, with no network and no model. You give it the
@@ -73,7 +78,8 @@ export class PaymentGuard {
73
78
  return this.#exclusive(async () => {
74
79
  if (this.#state.frozen)
75
80
  throw new PaymentFrozenError();
76
- const day = utcDay(this.#now());
81
+ const now = this.#now();
82
+ const day = utcDay(now);
77
83
  const check = await this.#check(attempt, kind, day);
78
84
  if (check.rules.length > 0) {
79
85
  await this.#breach({
@@ -98,6 +104,7 @@ export class PaymentGuard {
98
104
  amountUsd: check.amountUsd,
99
105
  invoiceReference: check.reference,
100
106
  day,
107
+ ...(this.mandate.invoiceMatching === false ? { at: now.toISOString() } : {}),
101
108
  ...(kind === "refund" ? { kind } : {}),
102
109
  });
103
110
  await this.#save();
@@ -142,6 +149,141 @@ export class PaymentGuard {
142
149
  return update();
143
150
  });
144
151
  }
152
+ /**
153
+ * Opens a budget: one check for many small payments to one payee (see `PaymentBudget`). Needs
154
+ * `invoiceMatching: false`. The payee must be a vendor's account (`unknown_payee`), the whole
155
+ * budget must fit in what's left of the day's limit (`over_daily_cap`), and with
156
+ * `maxPaymentsPerHour` there must be room for a payment (`over_hourly_limit`); the budget then
157
+ * covers what's left of the hour. Enforce mode throws `PaymentBlockedError`; Monitor mode opens it
158
+ * and reports the breach. The whole budget counts toward the day until `close()`, when what was
159
+ * spent (rounded up to the cent) counts instead. A budget past its end is closed the next time
160
+ * one is opened.
161
+ */
162
+ budget(request) {
163
+ return this.#exclusive(async () => {
164
+ if (this.#state.frozen)
165
+ throw new PaymentFrozenError();
166
+ if (this.mandate.invoiceMatching !== false)
167
+ throw new BondedError("CONFIG", "budgets need invoiceMatching: false on the mandate");
168
+ const now = this.#now();
169
+ const day = utcDay(now);
170
+ if (this.#settleExpired(now.getTime()))
171
+ await this.#save();
172
+ const ttlSeconds = request.ttlSeconds ?? 3600;
173
+ if (!Number.isInteger(ttlSeconds) || ttlSeconds < 60 || ttlSeconds > 3600)
174
+ throw new BondedError("INVALID_PAYMENT", "ttlSeconds must be a whole number from 60 to 3,600");
175
+ const maxAll = usdToMicros(this.mandate.maxPaymentUsd) ?? 0;
176
+ const asked = request.maxPaymentUsd === undefined ? maxAll : usdToMicros(request.maxPaymentUsd);
177
+ if (asked === undefined || asked <= 0)
178
+ throw new BondedError("INVALID_PAYMENT", "maxPaymentUsd must be positive, with at most six decimals");
179
+ const check = checkBudget({
180
+ limits: {
181
+ ...this.mandate,
182
+ maxPaymentsPerHour: this.mandate.maxPaymentsPerHour ?? null,
183
+ },
184
+ payees: await this.#payees(),
185
+ payments: this.#state.payments,
186
+ paymentsLastHour: this.#paymentsLastHour(),
187
+ reservedPayments: this.#reservedPayments(),
188
+ day,
189
+ }, { account: request.account, budgetUsd: request.amountUsd });
190
+ if (!check.ok)
191
+ throw new BondedError("INVALID_PAYMENT", check.error);
192
+ const id = `local-${crypto.randomUUID()}`;
193
+ if (check.rules.length > 0)
194
+ await this.#breach({
195
+ kind: "budget",
196
+ rules: check.rules,
197
+ reasons: words(check.rules),
198
+ blocked: this.mandate.mode === "enforce",
199
+ account: request.account,
200
+ reference: "",
201
+ vendorId: check.vendorId,
202
+ amountUsd: request.amountUsd,
203
+ lossUsd: 0,
204
+ overpaidUsd: 0,
205
+ day,
206
+ });
207
+ const terms = {
208
+ account: normalAccount(request.account),
209
+ budgetMicros: Math.round(request.amountUsd * 100) * 10_000,
210
+ maxPaymentMicros: Math.min(asked, maxAll),
211
+ maxPayments: check.maxPayments,
212
+ startsAtMs: now.getTime(),
213
+ expiresAtMs: now.getTime() + ttlSeconds * 1000,
214
+ };
215
+ // The reservation: no time, so it doesn't count as a payment for the hourly limit.
216
+ this.#state.payments.push({
217
+ account: request.account,
218
+ vendorId: check.vendorId,
219
+ amountUsd: Math.round(request.amountUsd * 100) / 100,
220
+ invoiceReference: budgetReference(id),
221
+ day,
222
+ });
223
+ const kept = { id, terms, spentMicros: 0, payments: 0 };
224
+ this.#state.budgets = [...(this.#state.budgets ?? []), kept];
225
+ await this.#save();
226
+ return new LocalBudget(id, terms, this.mandate.mode, () => this.#now().getTime(), {
227
+ frozen: () => this.#state.frozen,
228
+ report: (breach) => this.#report(breach),
229
+ recorded: (spend) => this.#exclusive(async () => {
230
+ const b = this.#state.budgets?.find((x) => x.id === id);
231
+ if (b) {
232
+ b.spentMicros += spend.amountMicros;
233
+ b.payments += 1;
234
+ }
235
+ this.#state.budgetSpends = [...(this.#state.budgetSpends ?? []), spend.atMs];
236
+ await this.#save();
237
+ }),
238
+ released: (spend) => void this.#exclusive(async () => {
239
+ const b = this.#state.budgets?.find((x) => x.id === id);
240
+ if (b) {
241
+ b.spentMicros -= spend.amountMicros;
242
+ b.payments -= 1;
243
+ }
244
+ const spends = this.#state.budgetSpends ?? [];
245
+ const at = spends.lastIndexOf(spend.atMs);
246
+ if (at >= 0)
247
+ spends.splice(at, 1);
248
+ await this.#save();
249
+ }),
250
+ close: () => this.#exclusive(async () => {
251
+ if (this.#closeBudget(id))
252
+ await this.#save();
253
+ }),
254
+ });
255
+ });
256
+ }
257
+ /** Payments open budgets may still make, for the hourly limit. A budget past its end makes none. */
258
+ #reservedPayments() {
259
+ const now = this.#now().getTime();
260
+ return (this.#state.budgets ?? []).reduce((sum, b) => b.terms.maxPayments === null || b.terms.expiresAtMs <= now
261
+ ? sum
262
+ : sum + Math.max(0, b.terms.maxPayments - b.payments), 0);
263
+ }
264
+ /** Closes budgets past their end. True when any closed. */
265
+ #settleExpired(nowMs) {
266
+ const expired = (this.#state.budgets ?? []).filter((b) => b.terms.expiresAtMs <= nowMs);
267
+ for (const b of expired)
268
+ this.#closeBudget(b.id);
269
+ return expired.length > 0;
270
+ }
271
+ /** The budget's reservation becomes what it spent, rounded up to the cent. */
272
+ #closeBudget(id) {
273
+ const budget = this.#state.budgets?.find((b) => b.id === id);
274
+ if (!budget)
275
+ return false;
276
+ this.#state.budgets = this.#state.budgets.filter((b) => b.id !== id);
277
+ const at = this.#state.payments.findIndex((p) => p.invoiceReference === budgetReference(id));
278
+ if (at >= 0) {
279
+ const cents = Math.ceil(budget.spentMicros / 10_000);
280
+ if (cents > 0)
281
+ this.#state.payments[at] = { ...this.#state.payments[at], amountUsd: cents / 100 };
282
+ else
283
+ this.#state.payments.splice(at, 1);
284
+ }
285
+ return true;
286
+ }
145
287
  /** Runs the shared rule check against the builder's data and the guard's payments. */
146
288
  async #check(attempt, kind, day) {
147
289
  const payees = await this.#payees();
@@ -149,11 +291,30 @@ export class PaymentGuard {
149
291
  const payments = this.#state.payments;
150
292
  const check = kind === "refund"
151
293
  ? checkRefundPayment({ limits, payees, refunds: await read(this.#options.refunds), payments, day }, attempt)
152
- : checkInvoicePayment({ limits, payees, invoices: await read(this.#options.invoices), payments, day }, attempt);
294
+ : this.mandate.invoiceMatching === false
295
+ ? checkGeneralPayment({
296
+ limits: { ...limits, maxPaymentsPerHour: limits.maxPaymentsPerHour ?? null },
297
+ payees,
298
+ payments,
299
+ // Payments open budgets may still make count toward the hourly limit too.
300
+ paymentsLastHour: this.#paymentsLastHour() + this.#reservedPayments(),
301
+ day,
302
+ }, attempt)
303
+ : checkInvoicePayment({ limits, payees, invoices: await read(this.#options.invoices), payments, day }, attempt);
153
304
  if (!check.ok)
154
305
  throw new BondedError("INVALID_PAYMENT", check.error);
155
306
  return check;
156
307
  }
308
+ /**
309
+ * Payments in the last hour, budget payments included. One restored from before the guard kept
310
+ * times doesn't count.
311
+ */
312
+ #paymentsLastHour() {
313
+ const since = this.#now().getTime() - 3_600_000;
314
+ this.#state.budgetSpends = (this.#state.budgetSpends ?? []).filter((at) => at >= since);
315
+ return (this.#state.payments.filter((p) => p.at !== undefined && Date.parse(p.at) >= since).length +
316
+ this.#state.budgetSpends.length);
317
+ }
157
318
  /**
158
319
  * Known payees: each vendor's account on file, unless it got there through an unverified
159
320
  * change, and every account a verified ticket covers.
@@ -171,6 +332,12 @@ export class PaymentGuard {
171
332
  }
172
333
  /** Reports a breach, freezes if the mandate says so, and throws when Enforce mode blocks it. */
173
334
  async #breach(breach) {
335
+ await this.#report(breach);
336
+ if (breach.blocked)
337
+ throw new PaymentBlockedError(breach);
338
+ }
339
+ /** Freezes if the mandate says so, and tells `onBreach`. */
340
+ async #report(breach) {
174
341
  if (this.mandate.freezeOnBreach) {
175
342
  this.#state.frozen = true;
176
343
  await this.#save();
@@ -180,8 +347,6 @@ export class PaymentGuard {
180
347
  rules: [...breach.rules],
181
348
  reasons: [...breach.reasons],
182
349
  });
183
- if (breach.blocked)
184
- throw new PaymentBlockedError(breach);
185
350
  }
186
351
  async #save() {
187
352
  await this.#options.onStateChange?.(this.state());
@@ -193,7 +358,36 @@ export class PaymentGuard {
193
358
  return next;
194
359
  }
195
360
  }
196
- const words = (rules) => rules.map((r) => PAYMENT_RULE_WORDS[r]);
361
+ const words = (rules) => rules.map((r) => GATE_RULE_WORDS[r]);
362
+ /** A budget the local guard opened: spends are counted in its state, nothing is sent anywhere. */
363
+ class LocalBudget extends BudgetBase {
364
+ ledger;
365
+ constructor(id, terms, mode, now, ledger) {
366
+ super(id, terms, mode, now, (breach) => ledger.report(breach));
367
+ this.ledger = ledger;
368
+ }
369
+ spend(amountMicros, options) {
370
+ if (this.ledger.frozen())
371
+ return Promise.reject(new PaymentFrozenError());
372
+ return super.spend(amountMicros, options);
373
+ }
374
+ recorded(spend) {
375
+ return this.ledger.recorded(spend);
376
+ }
377
+ releasable() {
378
+ return !this.closed;
379
+ }
380
+ released(spend) {
381
+ this.ledger.released(spend);
382
+ }
383
+ flush() {
384
+ return Promise.resolve();
385
+ }
386
+ async close() {
387
+ this.markClosed();
388
+ await this.ledger.close();
389
+ }
390
+ }
197
391
  /** A payment guard: see `PaymentGuard`. */
198
392
  export function paymentGuard(options) {
199
393
  return new PaymentGuard(options);
@@ -260,7 +454,12 @@ function defaultPayment(input) {
260
454
  return {
261
455
  account: field(args, "account"),
262
456
  amountUsd,
263
- invoiceReference: field(args, "invoiceReference" in args ? "invoiceReference" : "reference"),
457
+ // Left out, it reaches the rules as empty: only invoice matching needs one.
458
+ ...("invoiceReference" in args || "reference" in args
459
+ ? {
460
+ invoiceReference: field(args, "invoiceReference" in args ? "invoiceReference" : "reference"),
461
+ }
462
+ : {}),
264
463
  ...(typeof args.requestId === "string" ? { requestId: args.requestId } : {}),
265
464
  };
266
465
  }
package/dist/x402.d.ts ADDED
@@ -0,0 +1,136 @@
1
+ import type { BudgetRequest, PaymentBudget } from "./budget.js";
2
+ /**
3
+ * One way to pay that an x402 server offers, as `@x402/core` gives it to client hooks. In x402
4
+ * version 2 the price is `amount`; version 1 called it `maxAmountRequired`. Both are in the
5
+ * asset's smallest unit (for USDC, millionths of a dollar).
6
+ */
7
+ export interface X402Requirements {
8
+ scheme: string;
9
+ /** CAIP-2 in version 2 (`eip155:8453`, `solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp`); a name in version 1 (`base`). */
10
+ network: string;
11
+ asset: string;
12
+ payTo: string;
13
+ amount?: string;
14
+ maxAmountRequired?: string;
15
+ resource?: string;
16
+ extra?: Record<string, unknown>;
17
+ }
18
+ /** What `x402Client.onBeforePaymentCreation` hooks are given. */
19
+ export interface X402PaymentContext {
20
+ paymentRequired: {
21
+ x402Version: number;
22
+ resource?: {
23
+ url?: string;
24
+ };
25
+ };
26
+ selectedRequirements: X402Requirements;
27
+ }
28
+ /** A hook result that stops the payment: the x402 client signs nothing. */
29
+ export type X402Abort = {
30
+ abort: true;
31
+ reason: string;
32
+ };
33
+ /** The `x402Client` hooks the guard registers (`@x402/core`'s client, or anything shaped like it). */
34
+ export interface X402ClientHooks {
35
+ onBeforePaymentCreation(hook: (context: X402PaymentContext) => Promise<void | X402Abort>): unknown;
36
+ onAfterPaymentCreation?(hook: (context: X402PaymentContext & {
37
+ paymentPayload: object;
38
+ }) => Promise<void>): unknown;
39
+ onPaymentCreationFailure?(hook: (context: X402PaymentContext & {
40
+ error: Error;
41
+ }) => Promise<void>): unknown;
42
+ onPaymentResponse?(hook: (context: {
43
+ paymentPayload: object;
44
+ settleResponse?: {
45
+ success: boolean;
46
+ transaction?: string;
47
+ };
48
+ paymentRequired?: unknown;
49
+ error?: Error;
50
+ }) => Promise<void>): unknown;
51
+ }
52
+ /** A US dollar stablecoin the guard counts at one dollar per token. */
53
+ export interface X402Asset {
54
+ /** CAIP-2 network id, or an x402 version 1 network name. */
55
+ network: string;
56
+ /** The token's contract or mint address. EVM addresses compare case-insensitively. */
57
+ asset: string;
58
+ decimals: number;
59
+ }
60
+ /**
61
+ * USDC on the networks x402 lists it for, by network id (and the version 1 name, where it has one).
62
+ * The guard only counts these (and any `assets` you add) as dollars; a price in anything else is
63
+ * refused.
64
+ */
65
+ export declare const X402_USD_ASSETS: readonly X402Asset[];
66
+ export interface X402GuardOptions {
67
+ /**
68
+ * A `PaymentGuard`, or the hosted `PaymentGate`: anything with `budget()`. Its mandate needs
69
+ * invoice matching off, and each service's `payTo` address on the vendor list, so the agent
70
+ * pays only services you listed.
71
+ */
72
+ guard: {
73
+ budget(request: BudgetRequest): Promise<PaymentBudget>;
74
+ };
75
+ /**
76
+ * The budget opened for each service: `amountUsd` (default $1, raised to fit a bigger price),
77
+ * `ttlSeconds` (default an hour) and `maxPaymentUsd` (default the mandate's per-payment limit).
78
+ */
79
+ budget?: {
80
+ amountUsd?: number;
81
+ ttlSeconds?: number;
82
+ maxPaymentUsd?: number;
83
+ };
84
+ /** More US dollar stablecoins to accept, beyond `X402_USD_ASSETS`. */
85
+ assets?: readonly X402Asset[];
86
+ /** Reads the payee and the exact price. Default: `payTo`, and the price in micro-dollars. */
87
+ toPayment?: (requirements: X402Requirements, context: X402PaymentContext) => {
88
+ account: string;
89
+ amountMicros: number;
90
+ };
91
+ /** Errors from closing a used-up budget in the background. Default: ignored. */
92
+ onError?: (error: unknown) => void;
93
+ }
94
+ /**
95
+ * Checks every x402 payment against a budget for its service before the x402 client signs it.
96
+ *
97
+ * The first payment to a service opens a budget for it (`guard.budget`): one check against the
98
+ * rules, and with the hosted gate a signed receipt. Every payment then checks against that budget
99
+ * in exact micro-dollars with no network call, so a $0.001 call is fast and counts as $0.001.
100
+ * When a budget is used up or ends, the next payment closes it and opens another.
101
+ *
102
+ * - A refused payment is never signed: the hook aborts and the request fails with the reason. In
103
+ * Monitor mode a payment that breaks the budget is signed and `onBreach` reports it.
104
+ * - A payment the server turns down (it answers 402 again, or settlement fails) is taken back.
105
+ * - Call `close()` when the agent stops, so the unspent part of each budget stops counting toward
106
+ * the day. Unclosed hosted budgets close themselves soon after they end.
107
+ */
108
+ export declare class X402Guard {
109
+ #private;
110
+ constructor(options: X402GuardOptions);
111
+ /** Registers the guard's hooks on an x402 client and returns the client. */
112
+ register<C extends X402ClientHooks>(client: C): C;
113
+ /** The `onBeforePaymentCreation` hook: counts the payment against its service's budget. */
114
+ check(context: X402PaymentContext): Promise<void | X402Abort>;
115
+ /** The open budgets, one per service. */
116
+ budgets(): PaymentBudget[];
117
+ /** Closes every budget: sends what's left and stops the unspent part counting. */
118
+ close(): Promise<void>;
119
+ }
120
+ /** An x402 guard: see `X402Guard`. */
121
+ export declare function x402Guard(options: X402GuardOptions): X402Guard;
122
+ /**
123
+ * Registers a new `X402Guard` on an x402 client and returns the client, for when you never need
124
+ * to close its budgets yourself (hosted budgets close themselves after they end; local ones close
125
+ * when the next budget opens).
126
+ */
127
+ export declare function guardX402Client<C extends X402ClientHooks>(client: C, options: X402GuardOptions): C;
128
+ /**
129
+ * The payee and the exact price in micro-dollars, for a price in a dollar stablecoin the guard
130
+ * knows. Past six decimals it rounds up.
131
+ */
132
+ export declare function x402Price(r: X402Requirements, assets?: readonly X402Asset[]): {
133
+ account: string;
134
+ amountMicros: number;
135
+ };
136
+ //# sourceMappingURL=x402.d.ts.map