@tollxyz/guard 0.6.1 → 0.7.0

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,66 @@
1
+ /**
2
+ * guardHookr (0.7.0): Toll's check on every unsigned call a Hookr builder returns, before the wallet sees it.
3
+ *
4
+ * Hookr's SDK (npm hookr-sdk) and its MCP (hookr_build_*) hand back unsigned calls: `{ to, data, value? }`, one or
5
+ * several. The wallet then signs them. This wraps the builders so each call goes through Toll's check_tx first, under
6
+ * the same policy as every other guard here (BLOCK throws TollBlockedError; a WARN with a high or critical warning is
7
+ * blocked by default; Toll unreachable fails closed).
8
+ *
9
+ * import * as hookr from "hookr-sdk";
10
+ * import { guardHookr } from "@tollxyz/guard";
11
+ *
12
+ * const checked = guardHookr(hookr, { from: account });
13
+ * const swapCall = await checked.buildSwap(manifest, swap); // the one change: `await`
14
+ * await wallet.sendTransaction({ account, ...swapCall });
15
+ *
16
+ * Opt-in by import: nothing else in this package changes. Notes on what it does and does not do:
17
+ * - Only functions whose name starts with `build` are wrapped. `buildQuote` is left as it is: its call is read with
18
+ * eth_call, never signed. Everything else on the object (reads, ABIs, constants) passes through untouched.
19
+ * - A builder's result that carries no call (a predicted token address, a number) is returned as it is, unchecked:
20
+ * there is nothing to sign in it.
21
+ * - The call is returned unchanged. Nothing is added to it and nothing is sent anywhere but to Toll's check.
22
+ * - Toll reads Robinhood Chain (4663) only. A call built for another chain is "unsupported": by default it goes
23
+ * ahead unchecked and is logged and reported as not checked; `onUnsupported: "block"` or `strict` refuses it.
24
+ * - For a swap, Toll's check_tx reads the pool's hook itself (what it can do, the Agent gate): no second request.
25
+ * The builders are synchronous in Hookr's SDK; the wrapped ones return a promise, because the check is a network call.
26
+ */
27
+ import { type Numberish } from "./check.js";
28
+ import { type GuardOptions, type GuardReport } from "./policy.js";
29
+ /** an unsigned call as Hookr's SDK and MCP return it */
30
+ export interface HookrCall {
31
+ to: string;
32
+ data: string;
33
+ value?: Numberish;
34
+ }
35
+ export interface HookrGuardOptions extends GuardOptions {
36
+ /** the wallet that will sign (or a function that returns it): Toll compares who a call pays with it */
37
+ from?: string | (() => string | undefined | null);
38
+ /** the chain the calls were built for (default 4663, Robinhood Chain) */
39
+ chainId?: Numberish;
40
+ /** builder names to leave unwrapped, beside buildQuote */
41
+ skip?: readonly string[];
42
+ /** called with each call's report, after the check (as onReport, but with the call) */
43
+ onCall?: (call: HookrCall, report: GuardReport) => void | Promise<void>;
44
+ }
45
+ /** is this value one unsigned call: an address in `to` and hex calldata in `data` */
46
+ export declare function isHookrCall(x: unknown): x is HookrCall;
47
+ /**
48
+ * The unsigned calls inside what a builder returned: the value itself, an array of calls, or an object with a `calls`
49
+ * array (Hookr's MCP answers `{ calls: [...] }`). Anything else has no call in it: [].
50
+ */
51
+ export declare function hookrCallsOf(built: unknown): HookrCall[];
52
+ /**
53
+ * Check what a Hookr builder (or a hookr_build_* MCP tool) returned, one call after the other, in order. Resolves with
54
+ * the same value, untouched, when every call may go ahead; throws at the first one that may not (the calls after it
55
+ * are not checked: none of them should be signed). A value with no call in it resolves at once, unchecked.
56
+ */
57
+ export declare function checkHookrCalls<T>(built: T, o?: HookrGuardOptions): Promise<T>;
58
+ /** the wrapped object's type: each `build*` function now returns a promise of what it returned; the rest is as it was */
59
+ export type GuardedHookr<T> = {
60
+ [K in keyof T]: K extends `build${string}` ? K extends "buildQuote" ? T[K] : T[K] extends (...a: infer A) => infer R ? (...a: A) => Promise<Awaited<R>> : T[K] : T[K];
61
+ };
62
+ /**
63
+ * Wrap Hookr's SDK (the module namespace, or any object of builders): every `build*` function's result is checked by
64
+ * Toll before it is returned. See the file's header for the one-line change and what is left alone.
65
+ */
66
+ export declare function guardHookr<T extends object>(sdk: T, o?: HookrGuardOptions): GuardedHookr<T>;
package/dist/hookr.js ADDED
@@ -0,0 +1,99 @@
1
+ /**
2
+ * guardHookr (0.7.0): Toll's check on every unsigned call a Hookr builder returns, before the wallet sees it.
3
+ *
4
+ * Hookr's SDK (npm hookr-sdk) and its MCP (hookr_build_*) hand back unsigned calls: `{ to, data, value? }`, one or
5
+ * several. The wallet then signs them. This wraps the builders so each call goes through Toll's check_tx first, under
6
+ * the same policy as every other guard here (BLOCK throws TollBlockedError; a WARN with a high or critical warning is
7
+ * blocked by default; Toll unreachable fails closed).
8
+ *
9
+ * import * as hookr from "hookr-sdk";
10
+ * import { guardHookr } from "@tollxyz/guard";
11
+ *
12
+ * const checked = guardHookr(hookr, { from: account });
13
+ * const swapCall = await checked.buildSwap(manifest, swap); // the one change: `await`
14
+ * await wallet.sendTransaction({ account, ...swapCall });
15
+ *
16
+ * Opt-in by import: nothing else in this package changes. Notes on what it does and does not do:
17
+ * - Only functions whose name starts with `build` are wrapped. `buildQuote` is left as it is: its call is read with
18
+ * eth_call, never signed. Everything else on the object (reads, ABIs, constants) passes through untouched.
19
+ * - A builder's result that carries no call (a predicted token address, a number) is returned as it is, unchecked:
20
+ * there is nothing to sign in it.
21
+ * - The call is returned unchanged. Nothing is added to it and nothing is sent anywhere but to Toll's check.
22
+ * - Toll reads Robinhood Chain (4663) only. A call built for another chain is "unsupported": by default it goes
23
+ * ahead unchecked and is logged and reported as not checked; `onUnsupported: "block"` or `strict` refuses it.
24
+ * - For a swap, Toll's check_tx reads the pool's hook itself (what it can do, the Agent gate): no second request.
25
+ * The builders are synchronous in Hookr's SDK; the wrapped ones return a promise, because the check is a network call.
26
+ */
27
+ import { TX_CHAIN_ID } from "./check.js";
28
+ import { enforce } from "./policy.js";
29
+ const ADDR = /^0x[0-9a-fA-F]{40}$/;
30
+ const HEX = /^0x(?:[0-9a-fA-F]{2})*$/;
31
+ /** never signed: its call is read with eth_call */
32
+ const NOT_SIGNED = new Set(["buildQuote"]);
33
+ /** is this value one unsigned call: an address in `to` and hex calldata in `data` */
34
+ export function isHookrCall(x) {
35
+ if (!x || typeof x !== "object" || Array.isArray(x))
36
+ return false;
37
+ const c = x;
38
+ return typeof c.to === "string" && ADDR.test(c.to) && typeof c.data === "string" && HEX.test(c.data);
39
+ }
40
+ /**
41
+ * The unsigned calls inside what a builder returned: the value itself, an array of calls, or an object with a `calls`
42
+ * array (Hookr's MCP answers `{ calls: [...] }`). Anything else has no call in it: [].
43
+ */
44
+ export function hookrCallsOf(built) {
45
+ if (isHookrCall(built))
46
+ return [built];
47
+ if (Array.isArray(built))
48
+ return built.filter(isHookrCall);
49
+ if (built && typeof built === "object" && Array.isArray(built.calls)) {
50
+ return (built.calls).filter(isHookrCall);
51
+ }
52
+ return [];
53
+ }
54
+ function fromOf(o) {
55
+ const f = typeof o.from === "function" ? o.from() : o.from;
56
+ return typeof f === "string" && ADDR.test(f) ? f : undefined;
57
+ }
58
+ /**
59
+ * Check what a Hookr builder (or a hookr_build_* MCP tool) returned, one call after the other, in order. Resolves with
60
+ * the same value, untouched, when every call may go ahead; throws at the first one that may not (the calls after it
61
+ * are not checked: none of them should be signed). A value with no call in it resolves at once, unchecked.
62
+ */
63
+ export async function checkHookrCalls(built, o = {}) {
64
+ const from = fromOf(o);
65
+ for (const call of hookrCallsOf(built)) {
66
+ const input = { chainId: o.chainId ?? TX_CHAIN_ID, to: call.to, data: call.data,
67
+ ...(call.value !== undefined ? { value: call.value } : {}), ...(from ? { from } : {}) };
68
+ const report = await enforce("transaction", () => input, o);
69
+ if (o.onCall) {
70
+ try {
71
+ await o.onCall(call, report);
72
+ }
73
+ catch { /* a reporting hook never changes the decision */ }
74
+ }
75
+ }
76
+ return built;
77
+ }
78
+ /**
79
+ * Wrap Hookr's SDK (the module namespace, or any object of builders): every `build*` function's result is checked by
80
+ * Toll before it is returned. See the file's header for the one-line change and what is left alone.
81
+ */
82
+ export function guardHookr(sdk, o = {}) {
83
+ const skip = new Set([...NOT_SIGNED, ...(o.skip ?? [])]);
84
+ const made = new Map();
85
+ return new Proxy(sdk, {
86
+ get(target, prop, receiver) {
87
+ // read with the target as `this`: a module namespace object refuses a foreign receiver
88
+ const v = Reflect.get(target, prop, target === receiver ? receiver : target);
89
+ if (typeof prop !== "string" || typeof v !== "function" || !prop.startsWith("build") || skip.has(prop))
90
+ return v;
91
+ const had = made.get(prop);
92
+ if (had)
93
+ return had;
94
+ const wrapped = async (...args) => checkHookrCalls(await v.apply(target, args), o);
95
+ made.set(prop, wrapped);
96
+ return wrapped;
97
+ },
98
+ });
99
+ }
package/dist/index.d.ts CHANGED
@@ -5,6 +5,8 @@ export type { GuardOptions, GuardReport, GuardKind, WarnPolicy, BlockPolicy, Spe
5
5
  export { guard, guardViem, guardEthers, guardSmartAccount, isViemClient, isBundlerClient, primaryTypeOf } from "./wallets.js";
6
6
  export type { ViemGuardOptions } from "./wallets.js";
7
7
  export { guardX402Fetch, readPaymentRequired, readPaymentForms, paymentFormsDiffer, paymentIdOf } from "./x402.js";
8
+ export { guardHookr, checkHookrCalls, hookrCallsOf, isHookrCall } from "./hookr.js";
9
+ export type { HookrCall, HookrGuardOptions, GuardedHookr } from "./hookr.js";
8
10
  export type { X402GuardOptions } from "./x402.js";
9
11
  export { read8470, latestFinding8470, decodeGetObservation, decodeLatestFinding, encodeGetObservation, encodeLatestByToken, encodeLatestFinding, FINDING_KINDS, NOT_READ, OBS_REGISTRY, OBS_DETECTOR, OBS_CHAIN_ID, RH_RPC, } from "./erc8470.js";
10
12
  export type { Observation8470, Finding8470, LatestFinding8470, Read8470Options, Lean, KeyKind, Unit } from "./erc8470.js";
package/dist/index.js CHANGED
@@ -2,6 +2,7 @@ export { checkTx, bodyOf, chainIdOf, paidUrlOf, isAssessment, TollBlockedError,
2
2
  export { enforce, enforceCalls, unsupportedWhy, refuse, gateAuthorization, resolvePolicy, hasHighWarning, isSignature, isEthSign, TollPolicyError, commitSpend, releaseSpend, preview } from "./policy.js";
3
3
  export { guard, guardViem, guardEthers, guardSmartAccount, isViemClient, isBundlerClient, primaryTypeOf } from "./wallets.js";
4
4
  export { guardX402Fetch, readPaymentRequired, readPaymentForms, paymentFormsDiffer, paymentIdOf } from "./x402.js";
5
+ export { guardHookr, checkHookrCalls, hookrCallsOf, isHookrCall } from "./hookr.js";
5
6
  export { read8470, latestFinding8470, decodeGetObservation, decodeLatestFinding, encodeGetObservation, encodeLatestByToken, encodeLatestFinding, FINDING_KINDS, NOT_READ, OBS_REGISTRY, OBS_DETECTOR, OBS_CHAIN_ID, RH_RPC, } from "./erc8470.js";
6
7
  export { spendOf, NATIVE, USDG, WETH, DEFAULT_SPEND_ASSETS, UNLIMITED } from "./spend.js";
7
8
  export { MemorySpendStore, dayStart } from "./store.js";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tollxyz/guard",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "Toll's live pre-sign check in front of an AI agent's wallet: viem, viem smart-account, ethers and x402 fetch wrappers that ask Toll (ALLOW / WARN / BLOCK, ERC-8265) before anything is signed on Robinhood Chain: transactions, messages, typed data, batches, user operations, EIP-7702 authorizations. Spend caps, allowlists and human approval around the verdict; single-use ALLOW tickets for contracts that require one. Zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "Toll",