@tollxyz/guard 0.1.2 → 0.2.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.
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # @tollxyz/guard
2
2
 
3
+ > **Defaults changed in 0.2.0.** A WARN with any `high` or `critical` warning is now blocked (`onWarn: "block-high"`,
4
+ > the new default); a WARN with only `medium`/`low` warnings still goes ahead, logged. To keep the 0.1.x behaviour,
5
+ > pass `onWarn: "allow"`. New `strict: true` preset: WARN, unavailable and unsupported all block. Any option you pass
6
+ > explicitly still wins over the preset (`{ strict: true, onUnavailable: "allow" }` stays fail-open).
7
+
3
8
  Toll's live pre-sign check in front of an AI agent's wallet. Before the agent signs a transaction, an EIP-712
4
9
  signature or an x402 payment on Robinhood Chain, the guard asks [Toll](https://t0ll.xyz/agents/x402#presign) and
5
10
  gets ALLOW / WARN / BLOCK with the reasons. Zero runtime dependencies, ESM only, Node >= 20.19, with types.
@@ -15,7 +20,7 @@ const wallet = guard(walletClient); // viem WalletClient or ethers v6 Signer;
15
20
  ```ts
16
21
  import { guard, TollBlockedError } from "@tollxyz/guard";
17
22
 
18
- const wallet = guard(walletClient, { onWarn: "allow", onUnavailable: "allow" });
23
+ const wallet = guard(walletClient, { onWarn: "block-high", onUnavailable: "allow" });
19
24
  try {
20
25
  await wallet.writeContract({ address: token, abi: erc20Abi, functionName: "approve", args: [spender, maxUint256] });
21
26
  } catch (e) {
@@ -107,10 +112,11 @@ is at most `maxRetryWaitMs` (default 10 s); a coin still not read stays WARN, ne
107
112
 
108
113
  | Option | Default | |
109
114
  | --- | --- | --- |
110
- | `onWarn` | `"allow"` | `"allow"` (logged and reported), `"block"`, or `(assessment) => boolean` (true lets it through) |
115
+ | `strict` | `false` | preset: the defaults of `onWarn`, `onUnavailable` and `onUnsupported` become `"block"`; each one passed explicitly still wins |
116
+ | `onWarn` | `"block-high"` | `"block-high"` (blocked when any warning is `high` or `critical`, else allowed and logged), `"allow"` (every WARN goes ahead, logged; the 0.1.x default), `"block"`, or `(assessment) => boolean` (true lets it through) |
111
117
  | `onBlock` | `"throw"` | `"throw"` (TollBlockedError), or `(assessment) => boolean`, e.g. ask a human; true lets it through |
112
- | `onUnavailable` | `"allow"` | Toll unreachable, timed out, 5xx, 429, or a request it could not read: `"allow"` (fail-open, logged) or `"block"` (fail-closed, TollUnavailableError) |
113
- | `onUnsupported` | `"allow"` | another chain, a deployment: `"allow"` or `"block"` |
118
+ | `onUnavailable` | `"allow"` (`strict`: `"block"`) | Toll unreachable, timed out, 5xx, 429, or a request it could not read: `"allow"` (fail-open, logged) or `"block"` (fail-closed, TollUnavailableError) |
119
+ | `onUnsupported` | `"allow"` (`strict`: `"block"`) | another chain, a deployment: `"allow"` or `"block"` |
114
120
  | `onAuthorization` | `"block"` | an EIP-7702 authorization: `"block"` (TollBlockedError) or `"allow"` (unchecked, logged) |
115
121
  | `onReport` | none | `(report) => void`: every decision, with `assessment`, `decision`, `basis`, `paidUrl` |
116
122
  | `logger` | `console.warn` | where WARN and unavailable lines go; `false` = silent |
@@ -126,13 +132,13 @@ Errors: `TollBlockedError` (`.assessment`, `.codes`, message lists each code and
126
132
  The default is fail-open: if Toll cannot answer, the signature goes ahead, with a `[toll-guard] ... NOT checked`
127
133
  line and an `onReport` call with `basis: "unavailable"`. The agent must never get stuck because of Toll, and the
128
134
  free route is rate limited per IP (a busy agent will see 429s). If an unchecked signature is worse for you than a
129
- stalled agent, set `onUnavailable: "block"`.
135
+ stalled agent, set `onUnavailable: "block"` (or `strict: true`, which also blocks every WARN and unsupported request).
130
136
 
131
137
  ## Free limit and the paid route
132
138
 
133
139
  The check is free, rate limited per IP. The coin a swap buys is read through the same free coins as Toll's coin
134
140
  check (10 new coins a day per caller, coins already read stay free); past that the coin is not read and the answer
135
- is WARN `coin-over-quota` naming the paid route, `https://t0ll.xyz/api/x402/tx` (0.01 USDG over x402, no daily
141
+ is WARN `coin-over-quota` naming the paid route, `https://t0ll.xyz/api/x402/tx` (0.005 USDG over x402, no daily
136
142
  limit). The guard never pays: it puts that URL in the log line, in `report.paidUrl` and in `paidUrlOf(assessment)`.
137
143
 
138
144
  ## Privacy
package/dist/check.d.ts CHANGED
@@ -5,7 +5,7 @@
5
5
  export declare const DEFAULT_BASE_URL = "https://t0ll.xyz";
6
6
  /** Robinhood Chain: the only chain the pre-sign check reads for now */
7
7
  export declare const TX_CHAIN_ID = 4663;
8
- /** the paid route without the daily limit (0.01 USDG over x402); the guard never pays it */
8
+ /** the paid route without the daily limit (0.005 USDG over x402); the guard never pays it */
9
9
  export declare const PAID_TX_PATH = "/api/x402/tx";
10
10
  export declare const PRESIGN_DOCS = "https://t0ll.xyz/agents/x402#presign";
11
11
  export declare const DEFAULT_TIMEOUT_MS = 15000;
package/dist/check.js CHANGED
@@ -5,7 +5,7 @@
5
5
  export const DEFAULT_BASE_URL = "https://t0ll.xyz";
6
6
  /** Robinhood Chain: the only chain the pre-sign check reads for now */
7
7
  export const TX_CHAIN_ID = 4663;
8
- /** the paid route without the daily limit (0.01 USDG over x402); the guard never pays it */
8
+ /** the paid route without the daily limit (0.005 USDG over x402); the guard never pays it */
9
9
  export const PAID_TX_PATH = "/api/x402/tx";
10
10
  export const PRESIGN_DOCS = "https://t0ll.xyz/agents/x402#presign";
11
11
  export const DEFAULT_TIMEOUT_MS = 15_000;
package/dist/index.d.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  export { checkTx, chainIdOf, paidUrlOf, isAssessment, TollBlockedError, TollUnavailableError, DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRY_WAIT_MS, PAID_TX_PATH, PRESIGN_DOCS, TX_CHAIN_ID, } from "./check.js";
2
2
  export type { Action, Severity, RiskWarning, ScannerVerdict, TollExtra, RiskAssessment, CheckInput, CheckOptions, TypedDataLike, FetchLike, Hex, Numberish, UnavailableReason, } from "./check.js";
3
- export { enforce, unsupportedWhy, refuse, gateAuthorization } from "./policy.js";
3
+ export { enforce, unsupportedWhy, refuse, gateAuthorization, resolvePolicy, hasHighWarning } from "./policy.js";
4
4
  export type { GuardOptions, GuardReport, GuardKind, WarnPolicy, BlockPolicy } from "./policy.js";
5
5
  export { guard, guardViem, guardEthers, isViemClient, primaryTypeOf } from "./wallets.js";
6
6
  export type { ViemGuardOptions } from "./wallets.js";
package/dist/index.js CHANGED
@@ -1,5 +1,5 @@
1
1
  export { checkTx, chainIdOf, paidUrlOf, isAssessment, TollBlockedError, TollUnavailableError, DEFAULT_BASE_URL, DEFAULT_TIMEOUT_MS, DEFAULT_MAX_RETRY_WAIT_MS, PAID_TX_PATH, PRESIGN_DOCS, TX_CHAIN_ID, } from "./check.js";
2
- export { enforce, unsupportedWhy, refuse, gateAuthorization } from "./policy.js";
2
+ export { enforce, unsupportedWhy, refuse, gateAuthorization, resolvePolicy, hasHighWarning } from "./policy.js";
3
3
  export { guard, guardViem, guardEthers, isViemClient, primaryTypeOf } from "./wallets.js";
4
4
  export { guardX402Fetch, readPaymentRequired, readPaymentForms, paymentFormsDiffer } from "./x402.js";
5
5
  export { read8470, latestFinding8470, decodeGetObservation, decodeLatestFinding, encodeGetObservation, encodeLatestByToken, encodeLatestFinding, FINDING_KINDS, NOT_READ, OBS_REGISTRY, OBS_DETECTOR, OBS_CHAIN_ID, RH_RPC, } from "./erc8470.js";
package/dist/policy.d.ts CHANGED
@@ -1,5 +1,5 @@
1
1
  import { TollUnavailableError, type CheckInput, type CheckOptions, type RiskAssessment } from "./check.js";
2
- export type WarnPolicy = "allow" | "block" | ((assessment: RiskAssessment) => boolean | Promise<boolean>);
2
+ export type WarnPolicy = "allow" | "block" | "block-high" | ((assessment: RiskAssessment) => boolean | Promise<boolean>);
3
3
  export type BlockPolicy = "throw" | ((assessment: RiskAssessment) => boolean | Promise<boolean>);
4
4
  export type GuardKind = "transaction" | "typedData" | "x402";
5
5
  export interface GuardReport {
@@ -17,17 +17,25 @@ export interface GuardReport {
17
17
  error?: TollUnavailableError;
18
18
  }
19
19
  export interface GuardOptions extends CheckOptions {
20
- /** WARN: "allow" (default, reported and logged), "block", or a function; true lets it through */
20
+ /**
21
+ * WARN: "block-high" (default since 0.2.0: blocked when any warning is high or critical, else allowed and logged),
22
+ * "allow" (every WARN goes ahead, logged; the 0.1.x default), "block", or a function (true lets it through)
23
+ */
21
24
  onWarn?: WarnPolicy;
22
25
  /** BLOCK: "throw" (default, TollBlockedError), or a function (e.g. ask a human); true lets it through */
23
26
  onBlock?: BlockPolicy;
24
27
  /**
25
28
  * Toll unreachable, timed out, 5xx, 429, or a request it could not read: "allow" (default, fail-open, logged) or
26
- * "block" (fail-closed, throws TollUnavailableError).
29
+ * "block" (fail-closed, throws TollUnavailableError). `strict` makes "block" the default.
27
30
  */
28
31
  onUnavailable?: "allow" | "block";
29
- /** a request Toll does not read (another chain, a contract deploy): "allow" (default) or "block" */
32
+ /** a request Toll does not read (another chain, a contract deploy): "allow" (default) or "block"; `strict`: "block" */
30
33
  onUnsupported?: "allow" | "block";
34
+ /**
35
+ * The strict preset: the defaults become onWarn "block", onUnavailable "block" and onUnsupported "block".
36
+ * Any of those three passed explicitly (not undefined) still wins over the preset.
37
+ */
38
+ strict?: boolean;
31
39
  /** every decision, with Toll's answer; errors thrown here are caught and logged */
32
40
  onReport?: (report: GuardReport) => void | Promise<void>;
33
41
  /** where WARN / unavailable lines go (default console.warn); false = silent */
@@ -41,6 +49,14 @@ export interface GuardOptions extends CheckOptions {
41
49
  */
42
50
  onAuthorization?: "block" | "allow";
43
51
  }
52
+ /** the effective policy: explicit options win, then the strict preset, then the defaults */
53
+ export declare function resolvePolicy(o?: GuardOptions): {
54
+ onWarn: WarnPolicy;
55
+ onUnavailable: "allow" | "block";
56
+ onUnsupported: "allow" | "block";
57
+ };
58
+ /** true when any warning is high or critical */
59
+ export declare const hasHighWarning: (a: RiskAssessment) => boolean;
44
60
  /** why Toll would not read this request, or null when it does */
45
61
  export declare function unsupportedWhy(input: CheckInput): string | null;
46
62
  /**
package/dist/policy.js CHANGED
@@ -1,4 +1,16 @@
1
1
  import { TX_CHAIN_ID, TollBlockedError, TollUnavailableError, checkTx, chainIdOf, paidUrlOf, } from "./check.js";
2
+ /** the effective policy: explicit options win, then the strict preset, then the defaults */
3
+ export function resolvePolicy(o = {}) {
4
+ const strict = o.strict === true;
5
+ return {
6
+ onWarn: o.onWarn ?? (strict ? "block" : "block-high"),
7
+ onUnavailable: o.onUnavailable ?? (strict ? "block" : "allow"),
8
+ onUnsupported: o.onUnsupported ?? (strict ? "block" : "allow"),
9
+ };
10
+ }
11
+ const HIGH = new Set(["high", "critical"]);
12
+ /** true when any warning is high or critical */
13
+ export const hasHighWarning = (a) => (a.warnings ?? []).some((w) => HIGH.has(String(w?.severity).toLowerCase()));
2
14
  const say = (o, msg) => {
3
15
  if (o.logger === false)
4
16
  return;
@@ -57,11 +69,11 @@ export async function enforce(kind, build, o = {}) {
57
69
  }
58
70
  const why = unsupportedWhy(input);
59
71
  if (why) {
60
- const blocked = o.onUnsupported === "block";
72
+ const blocked = resolvePolicy(o).onUnsupported === "block";
61
73
  const r = { kind, input, assessment: null, decision: blocked ? "blocked" : "allowed", basis: "unsupported", paidUrl: null };
62
74
  await report(o, r);
63
75
  if (blocked)
64
- throw new TollUnavailableError(`Not checked by Toll: ${why}. onUnsupported is "block".`, { reason: "unsupported" });
76
+ throw new TollUnavailableError(`Not checked by Toll: ${why}. onUnsupported is "block"${o.strict === true && o.onUnsupported === undefined ? " (strict)" : ""}.`, { reason: "unsupported" });
65
77
  return r;
66
78
  }
67
79
  let a;
@@ -90,8 +102,8 @@ export async function enforce(kind, build, o = {}) {
90
102
  throw new TollBlockedError(a, "BLOCK");
91
103
  }
92
104
  // WARN, and anything that is not one of the three actions: never treated as an ALLOW
93
- const p = o.onWarn ?? "allow";
94
- const ok = p === "allow" ? true : p === "block" ? false : (await p(a)) === true;
105
+ const p = resolvePolicy(o).onWarn;
106
+ const ok = p === "allow" ? true : p === "block" ? false : p === "block-high" ? !hasHighWarning(a) : (await p(a)) === true;
95
107
  if (ok) {
96
108
  say(o, `WARN (${kind}), going ahead: ${a.warnings.map((w) => `${w.code}: ${w.message}`).join(" | ")}${paidUrl ? ` Paid, no daily limit: ${paidUrl}` : ""}`);
97
109
  return done("allowed", "WARN");
@@ -100,7 +112,7 @@ export async function enforce(kind, build, o = {}) {
100
112
  throw new TollBlockedError(a, "WARN");
101
113
  }
102
114
  async function unavailable(kind, input, err, o) {
103
- const blocked = o.onUnavailable === "block";
115
+ const blocked = resolvePolicy(o).onUnavailable === "block";
104
116
  const r = { kind, input, assessment: null, decision: blocked ? "blocked" : "allowed", basis: "unavailable", paidUrl: err.paidUrl, error: err };
105
117
  await report(o, r);
106
118
  if (blocked)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@tollxyz/guard",
3
- "version": "0.1.2",
3
+ "version": "0.2.0",
4
4
  "description": "Toll's live pre-sign check in front of an AI agent's wallet: viem, ethers and x402 fetch wrappers that ask Toll (ALLOW / WARN / BLOCK, ERC-8265) before anything is signed on Robinhood Chain. Zero runtime dependencies.",
5
5
  "license": "MIT",
6
6
  "author": "Toll",