@bondedhq/sdk 0.1.4 → 0.1.6

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/dist/x402.d.ts ADDED
@@ -0,0 +1,137 @@
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
+ export { remoteX402Signer, X402_SIGNER_ERROR_CODES, X402_SIGNER_PATHS, type RemoteX402Signer, type RemoteX402SignerOptions, type X402SignerInfo, type X402SignerProblem, type X402SignRequest, type X402SignResponse, type X402TypedData, } from "./x402-remote.js";
137
+ //# sourceMappingURL=x402.d.ts.map
package/dist/x402.js ADDED
@@ -0,0 +1,183 @@
1
+ import { normalAccount } from "@bondedhq/shared";
2
+ import { BondedError } from "./errors.js";
3
+ import { isPaymentRefusal } from "./payments.js";
4
+ /**
5
+ * USDC on the networks x402 lists it for, by network id (and the version 1 name, where it has one).
6
+ * The guard only counts these (and any `assets` you add) as dollars; a price in anything else is
7
+ * refused.
8
+ */
9
+ export const X402_USD_ASSETS = [
10
+ ...evm(["eip155:8453", "base"], "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913"),
11
+ ...evm(["eip155:84532", "base-sepolia"], "0x036CbD53842c5426634e7929541eC2318f3dCF7e"),
12
+ ...evm(["eip155:1"], "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
13
+ ...evm(["eip155:42161"], "0xaf88d065e77c8cC2239327C5EDb3A432268e5831"),
14
+ ...evm(["eip155:137", "polygon"], "0x3c499c542cEF5E3811e1192ce70d8cC03d5c3359"),
15
+ ...evm(["eip155:43114", "avalanche"], "0xB97EF9Ef8734C71904D8002F8b6Bc66Dd9c48a6E"),
16
+ ...["solana:5eykt4UsFv8P8NJdTREpY1vzqKqZKvdp", "solana"].map((network) => ({
17
+ network,
18
+ asset: "EPjFWdd5AufqSSqeM2qN1xzybapC8G4wEGGkZwyTDt1v",
19
+ decimals: 6,
20
+ })),
21
+ ...["solana:EtWTRABZaYq6iMfeYKouRu166VU2xqa1", "solana-devnet"].map((network) => ({
22
+ network,
23
+ asset: "4zMMC9srt5Ri5X14GAgXhaHii3GnPAEERYPJgZJDncDU",
24
+ decimals: 6,
25
+ })),
26
+ ];
27
+ function evm(networks, asset) {
28
+ return networks.map((network) => ({ network, asset, decimals: 6 }));
29
+ }
30
+ /**
31
+ * Checks every x402 payment against a budget for its service before the x402 client signs it.
32
+ *
33
+ * The first payment to a service opens a budget for it (`guard.budget`): one check against the
34
+ * rules, and with the hosted gate a signed receipt. Every payment then checks against that budget
35
+ * in exact micro-dollars with no network call, so a $0.001 call is fast and counts as $0.001.
36
+ * When a budget is used up or ends, the next payment closes it and opens another.
37
+ *
38
+ * - A refused payment is never signed: the hook aborts and the request fails with the reason. In
39
+ * Monitor mode a payment that breaks the budget is signed and `onBreach` reports it.
40
+ * - A payment the server turns down (it answers 402 again, or settlement fails) is taken back.
41
+ * - Call `close()` when the agent stops, so the unspent part of each budget stops counting toward
42
+ * the day. Unclosed hosted budgets close themselves soon after they end.
43
+ */
44
+ export class X402Guard {
45
+ #options;
46
+ #assets;
47
+ #budgets = new Map();
48
+ #locks = new Map();
49
+ #byRequirements = new WeakMap();
50
+ #byPayload = new WeakMap();
51
+ constructor(options) {
52
+ this.#options = options;
53
+ this.#assets = [...(options.assets ?? []), ...X402_USD_ASSETS];
54
+ }
55
+ /** Registers the guard's hooks on an x402 client and returns the client. */
56
+ register(client) {
57
+ client.onBeforePaymentCreation((context) => this.check(context));
58
+ client.onAfterPaymentCreation?.(async (context) => {
59
+ const counted = this.#byRequirements.get(context.selectedRequirements);
60
+ if (counted)
61
+ this.#byPayload.set(context.paymentPayload, counted);
62
+ });
63
+ client.onPaymentCreationFailure?.(async (context) => {
64
+ const counted = this.#byRequirements.get(context.selectedRequirements);
65
+ if (counted)
66
+ counted.budget.release(counted.spend.id);
67
+ });
68
+ client.onPaymentResponse?.(async (context) => {
69
+ const counted = this.#byPayload.get(context.paymentPayload);
70
+ if (!counted)
71
+ return;
72
+ if (context.settleResponse?.success) {
73
+ if (context.settleResponse.transaction)
74
+ counted.budget.settle(counted.spend.id, context.settleResponse.transaction);
75
+ }
76
+ else if (context.paymentRequired || context.settleResponse?.success === false) {
77
+ counted.budget.release(counted.spend.id);
78
+ }
79
+ });
80
+ return client;
81
+ }
82
+ /** The `onBeforePaymentCreation` hook: counts the payment against its service's budget. */
83
+ async check(context) {
84
+ try {
85
+ const { account, amountMicros } = (this.#options.toPayment ?? ((r) => x402Price(r, this.#assets)))(context.selectedRequirements, context);
86
+ const counted = await this.#exclusive(normalAccount(account), async (key) => {
87
+ const budget = await this.#budgetFor(key, account, amountMicros);
88
+ const spend = await budget.spend(amountMicros, {
89
+ reference: context.paymentRequired.resource?.url?.slice(0, 200) ?? "",
90
+ });
91
+ return { budget, spend };
92
+ });
93
+ this.#byRequirements.set(context.selectedRequirements, counted);
94
+ return undefined;
95
+ }
96
+ catch (error) {
97
+ if (isPaymentRefusal(error))
98
+ return { abort: true, reason: error.message };
99
+ throw error;
100
+ }
101
+ }
102
+ /** The open budgets, one per service. */
103
+ budgets() {
104
+ return [...this.#budgets.values()];
105
+ }
106
+ /** Closes every budget: sends what's left and stops the unspent part counting. */
107
+ async close() {
108
+ const open = [...this.#budgets.values()];
109
+ this.#budgets.clear();
110
+ await Promise.all(open.map((b) => b.close()));
111
+ }
112
+ /** The service's budget, or a new one when there's none, it ended, or the payment won't fit. */
113
+ async #budgetFor(key, account, amountMicros) {
114
+ const current = this.#budgets.get(key);
115
+ if (current && !current.closed) {
116
+ // It fits; or it's over the per-payment limit, which a new budget would have too, so the
117
+ // current one says no.
118
+ if (!current.problem(amountMicros) || amountMicros > current.terms.maxPaymentMicros)
119
+ return current;
120
+ }
121
+ if (current) {
122
+ this.#budgets.delete(key);
123
+ current.close().catch(this.#options.onError ?? (() => undefined));
124
+ }
125
+ const asked = this.#options.budget?.amountUsd ?? 1;
126
+ const budget = await this.#options.guard.budget({
127
+ account,
128
+ amountUsd: Math.max(asked, Math.ceil(amountMicros / 10_000) / 100),
129
+ ...(this.#options.budget?.ttlSeconds === undefined
130
+ ? {}
131
+ : { ttlSeconds: this.#options.budget.ttlSeconds }),
132
+ ...(this.#options.budget?.maxPaymentUsd === undefined
133
+ ? {}
134
+ : { maxPaymentUsd: this.#options.budget.maxPaymentUsd }),
135
+ });
136
+ this.#budgets.set(key, budget);
137
+ return budget;
138
+ }
139
+ /** One payment per service at a time, so two can't both open a budget or both take the last of one. */
140
+ #exclusive(key, run) {
141
+ const next = (this.#locks.get(key) ?? Promise.resolve()).then(() => run(key), () => run(key));
142
+ this.#locks.set(key, next.catch(() => undefined));
143
+ return next;
144
+ }
145
+ }
146
+ /** An x402 guard: see `X402Guard`. */
147
+ export function x402Guard(options) {
148
+ return new X402Guard(options);
149
+ }
150
+ /**
151
+ * Registers a new `X402Guard` on an x402 client and returns the client, for when you never need
152
+ * to close its budgets yourself (hosted budgets close themselves after they end; local ones close
153
+ * when the next budget opens).
154
+ */
155
+ export function guardX402Client(client, options) {
156
+ return new X402Guard(options).register(client);
157
+ }
158
+ const sameAsset = (a, b) => a.startsWith("0x") && b.startsWith("0x") ? a.toLowerCase() === b.toLowerCase() : a === b;
159
+ /**
160
+ * The payee and the exact price in micro-dollars, for a price in a dollar stablecoin the guard
161
+ * knows. Past six decimals it rounds up.
162
+ */
163
+ export function x402Price(r, assets = X402_USD_ASSETS) {
164
+ const units = r.amount ?? r.maxAmountRequired;
165
+ if (typeof units !== "string" || !/^\d+$/.test(units))
166
+ throw new BondedError("INVALID_PAYMENT", "the x402 price must be a whole number of units");
167
+ if (typeof r.payTo !== "string" || !r.payTo)
168
+ throw new BondedError("INVALID_PAYMENT", "the x402 payment has no payTo address");
169
+ const known = assets.find((a) => a.network === r.network && sameAsset(a.asset, r.asset));
170
+ if (!known)
171
+ throw new BondedError("INVALID_PAYMENT", `the x402 price is in ${r.asset} on ${r.network}, not a US dollar stablecoin the guard knows`);
172
+ const value = BigInt(units);
173
+ const micros = known.decimals <= 6
174
+ ? value * 10n ** BigInt(6 - known.decimals)
175
+ : (value + 10n ** BigInt(known.decimals - 6) - 1n) / 10n ** BigInt(known.decimals - 6);
176
+ if (micros === 0n)
177
+ throw new BondedError("INVALID_PAYMENT", "the x402 price is zero");
178
+ if (micros > BigInt(Number.MAX_SAFE_INTEGER))
179
+ throw new BondedError("INVALID_PAYMENT", "the x402 price is too large");
180
+ return { account: r.payTo, amountMicros: Number(micros) };
181
+ }
182
+ export { remoteX402Signer, X402_SIGNER_ERROR_CODES, X402_SIGNER_PATHS, } from "./x402-remote.js";
183
+ //# sourceMappingURL=x402.js.map
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@bondedhq/sdk",
3
- "version": "0.1.4",
4
- "description": "Trade through a Bonded Mandate Vault and get rated by the Bonded Arena from any TypeScript agent: a typed vault client with dry runs, Sentinel checks and a mandate builder; the Arena episode's tools for LangChain, the Vercel AI SDK, ElizaOS and Coinbase AgentKit; and an Agent URL handler.",
3
+ "version": "0.1.6",
4
+ "description": "Trade through a Bonded Mandate Vault and get rated by the Bonded Arena from any TypeScript agent: a typed vault client with dry runs, Sentinel checks and a mandate builder; the Arena episode's tools for LangChain, the Vercel AI SDK, ElizaOS and Coinbase AgentKit; a payment guard for pay tools (LangChain, the AI SDK, MCP, the OpenAI Agents SDK) and x402 clients; and an Agent URL handler.",
5
5
  "license": "MIT",
6
6
  "keywords": [
7
7
  "bonded",
@@ -14,6 +14,9 @@
14
14
  "agentkit",
15
15
  "solana-agent-kit",
16
16
  "elizaos",
17
+ "openai-agents",
18
+ "x402",
19
+ "payments",
17
20
  "viem"
18
21
  ],
19
22
  "homepage": "https://github.com/ddboy19912/bonded/tree/main/packages/sdk#readme",
@@ -65,6 +68,14 @@
65
68
  "types": "./dist/eliza.d.ts",
66
69
  "import": "./dist/eliza.js"
67
70
  },
71
+ "./openai-agents": {
72
+ "types": "./dist/openai-agents.d.ts",
73
+ "import": "./dist/openai-agents.js"
74
+ },
75
+ "./x402": {
76
+ "types": "./dist/x402.d.ts",
77
+ "import": "./dist/x402.js"
78
+ },
68
79
  "./package.json": "./package.json"
69
80
  },
70
81
  "files": [
@@ -83,18 +94,21 @@
83
94
  "@modelcontextprotocol/sdk": "1.30.1",
84
95
  "viem": "^2.56.9",
85
96
  "zod": "^4.6.5",
86
- "@bondedhq/shared": "^0.1.4"
97
+ "@bondedhq/shared": "^0.1.5"
87
98
  },
88
99
  "devDependencies": {
89
100
  "@coinbase/agentkit": "0.10.4",
90
101
  "@elizaos/core": "1.7.2",
91
102
  "@langchain/core": "1.2.12",
103
+ "@openai/agents-core": "0.21.0",
92
104
  "@solana/spl-token": "0.4.15",
93
105
  "@solana/web3.js": "^1.98.4",
106
+ "@x402/core": "2.28.0",
107
+ "@x402/fetch": "2.28.0",
94
108
  "ai": "7.0.114",
95
109
  "solana-agent-kit": "2.0.10",
96
- "@bonded/sandbox": "0.1.0",
97
- "@bondedhq/solana": "0.1.2"
110
+ "@bondedhq/solana": "0.1.2",
111
+ "@bonded/sandbox": "0.1.0"
98
112
  },
99
113
  "peerDependencies": {
100
114
  "@coinbase/agentkit": "^0.10.4",