@tollxyz/guard 0.4.1 → 0.6.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 +483 -420
- package/dist/budget.d.ts +96 -0
- package/dist/budget.js +110 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.js +1 -0
- package/dist/policy.d.ts +12 -5
- package/dist/policy.js +14 -6
- package/dist/spend.js +11 -3
- package/package.json +70 -70
package/dist/budget.d.ts
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import type { SpendPolicy } from "./policy.js";
|
|
2
|
+
/** The budget's EIP-712 domain: no verifyingContract (nothing on chain checks it). */
|
|
3
|
+
export declare const BUDGET_DOMAIN: {
|
|
4
|
+
readonly name: "Toll Facilitator Budget";
|
|
5
|
+
readonly version: "1";
|
|
6
|
+
readonly chainId: 4663;
|
|
7
|
+
};
|
|
8
|
+
export declare const BUDGET_TYPES: {
|
|
9
|
+
readonly Budget: readonly [{
|
|
10
|
+
readonly name: "payer";
|
|
11
|
+
readonly type: "address";
|
|
12
|
+
}, {
|
|
13
|
+
readonly name: "perPayment";
|
|
14
|
+
readonly type: "uint256";
|
|
15
|
+
}, {
|
|
16
|
+
readonly name: "perDay";
|
|
17
|
+
readonly type: "uint256";
|
|
18
|
+
}, {
|
|
19
|
+
readonly name: "perPayTo";
|
|
20
|
+
readonly type: "uint256";
|
|
21
|
+
}, {
|
|
22
|
+
readonly name: "payToCaps";
|
|
23
|
+
readonly type: "PayToCap[]";
|
|
24
|
+
}, {
|
|
25
|
+
readonly name: "allowedPayTos";
|
|
26
|
+
readonly type: "address[]";
|
|
27
|
+
}, {
|
|
28
|
+
readonly name: "validUntil";
|
|
29
|
+
readonly type: "uint64";
|
|
30
|
+
}, {
|
|
31
|
+
readonly name: "nonce";
|
|
32
|
+
readonly type: "uint256";
|
|
33
|
+
}];
|
|
34
|
+
readonly PayToCap: readonly [{
|
|
35
|
+
readonly name: "payTo";
|
|
36
|
+
readonly type: "address";
|
|
37
|
+
}, {
|
|
38
|
+
readonly name: "perDay";
|
|
39
|
+
readonly type: "uint256";
|
|
40
|
+
}];
|
|
41
|
+
};
|
|
42
|
+
/** at most this many payToCaps and allowedPayTos */
|
|
43
|
+
export declare const BUDGET_MAX_PAYTOS = 50;
|
|
44
|
+
export interface GuardBudgetOptions {
|
|
45
|
+
/** the agent's paying wallet (it signs the budget) */
|
|
46
|
+
payer: string;
|
|
47
|
+
/** newer wins at the facilitator: use a growing number, e.g. BigInt(Date.now()) */
|
|
48
|
+
nonce: bigint | number | string;
|
|
49
|
+
/** unix seconds the budget ends; 0 or unset = no end */
|
|
50
|
+
validUntil?: bigint | number | string;
|
|
51
|
+
/** the payTo of each perEndpoint prefix (same keys); a perEndpoint key that is itself an address is its own payTo */
|
|
52
|
+
endpointPayTo?: Record<string, string>;
|
|
53
|
+
/** one day cap for every payTo, base units; 0 or unset = not set */
|
|
54
|
+
perPayTo?: bigint;
|
|
55
|
+
/** perPayment when the policy has no assets[USDG].perCall (the facilitator needs one: 0 lets nothing through) */
|
|
56
|
+
perPayment?: bigint;
|
|
57
|
+
/** perDay when the policy has no assets[USDG].perDay */
|
|
58
|
+
perDay?: bigint;
|
|
59
|
+
}
|
|
60
|
+
export interface GuardBudgetMessage {
|
|
61
|
+
payer: string;
|
|
62
|
+
perPayment: bigint;
|
|
63
|
+
perDay: bigint;
|
|
64
|
+
perPayTo: bigint;
|
|
65
|
+
payToCaps: Array<{
|
|
66
|
+
payTo: string;
|
|
67
|
+
perDay: bigint;
|
|
68
|
+
}>;
|
|
69
|
+
allowedPayTos: string[];
|
|
70
|
+
validUntil: bigint;
|
|
71
|
+
nonce: bigint;
|
|
72
|
+
}
|
|
73
|
+
export interface GuardBudget {
|
|
74
|
+
/** the budget (the typed message), for @tollxyz/sdk's setBudget({ budget, signature }) */
|
|
75
|
+
budget: GuardBudgetMessage;
|
|
76
|
+
/** EIP-712 typed data for the payer to sign (viem: account.signTypedData(typedData)) */
|
|
77
|
+
typedData: {
|
|
78
|
+
domain: {
|
|
79
|
+
name: string;
|
|
80
|
+
version: string;
|
|
81
|
+
chainId: number;
|
|
82
|
+
};
|
|
83
|
+
types: typeof BUDGET_TYPES;
|
|
84
|
+
primaryType: "Budget";
|
|
85
|
+
message: GuardBudgetMessage;
|
|
86
|
+
};
|
|
87
|
+
/** guard rules the facilitator budget cannot hold (guard still enforces them in-process) */
|
|
88
|
+
notMirrored: string[];
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* The facilitator budget that mirrors a guard SpendPolicy. Throws when the policy has no USDG per-call or per-day cap
|
|
92
|
+
* and none is passed (the facilitator treats a missing cap as 0, which refuses every payment), or when a list is over 50.
|
|
93
|
+
*/
|
|
94
|
+
export declare function guardBudget(policy: SpendPolicy | {
|
|
95
|
+
spend?: SpendPolicy;
|
|
96
|
+
}, opts: GuardBudgetOptions): GuardBudget;
|
package/dist/budget.js
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Mirror guard's spend caps into a per-payer budget at Toll's x402 facilitator (x402.t0ll.xyz/budget, COMING: the
|
|
3
|
+
* facilitator answers 404 budgets_off until budgets are switched on). guard enforces caps in the agent's process; the
|
|
4
|
+
* facilitator budget enforces them again where the payment settles, so a second process, a leaked key used elsewhere or
|
|
5
|
+
* a bug in the agent cannot spend past them through that facilitator.
|
|
6
|
+
*
|
|
7
|
+
* The words match: guard's assets[USDG].perCall is the budget's perPayment, assets[USDG].perDay its perDay, perEndpoint
|
|
8
|
+
* day caps become payToCaps (the facilitator sees a payTo, not a URL, so each endpoint needs its payTo) and allow.payTo
|
|
9
|
+
* becomes allowedPayTos. Nothing here changes how guard itself behaves. Amounts are USDG base units (6 decimals).
|
|
10
|
+
*/
|
|
11
|
+
import { isAddress } from "./check.js";
|
|
12
|
+
import { USDG } from "./spend.js";
|
|
13
|
+
/** The budget's EIP-712 domain: no verifyingContract (nothing on chain checks it). */
|
|
14
|
+
export const BUDGET_DOMAIN = { name: "Toll Facilitator Budget", version: "1", chainId: 4663 };
|
|
15
|
+
export const BUDGET_TYPES = {
|
|
16
|
+
Budget: [
|
|
17
|
+
{ name: "payer", type: "address" },
|
|
18
|
+
{ name: "perPayment", type: "uint256" },
|
|
19
|
+
{ name: "perDay", type: "uint256" },
|
|
20
|
+
{ name: "perPayTo", type: "uint256" },
|
|
21
|
+
{ name: "payToCaps", type: "PayToCap[]" },
|
|
22
|
+
{ name: "allowedPayTos", type: "address[]" },
|
|
23
|
+
{ name: "validUntil", type: "uint64" },
|
|
24
|
+
{ name: "nonce", type: "uint256" },
|
|
25
|
+
],
|
|
26
|
+
PayToCap: [
|
|
27
|
+
{ name: "payTo", type: "address" },
|
|
28
|
+
{ name: "perDay", type: "uint256" },
|
|
29
|
+
],
|
|
30
|
+
};
|
|
31
|
+
/** at most this many payToCaps and allowedPayTos */
|
|
32
|
+
export const BUDGET_MAX_PAYTOS = 50;
|
|
33
|
+
const MAX_U64 = (1n << 64n) - 1n;
|
|
34
|
+
function uint(v, name, max = (1n << 256n) - 1n) {
|
|
35
|
+
let n = null;
|
|
36
|
+
if (typeof v === "bigint")
|
|
37
|
+
n = v;
|
|
38
|
+
else if (typeof v === "number" && Number.isSafeInteger(v))
|
|
39
|
+
n = BigInt(v);
|
|
40
|
+
else if (typeof v === "string" && /^\d{1,78}$/.test(v.trim()))
|
|
41
|
+
n = BigInt(v.trim());
|
|
42
|
+
if (n === null || n < 0n || n > max)
|
|
43
|
+
throw new Error(`guardBudget: ${name} must be a whole number from 0`);
|
|
44
|
+
return n;
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* The facilitator budget that mirrors a guard SpendPolicy. Throws when the policy has no USDG per-call or per-day cap
|
|
48
|
+
* and none is passed (the facilitator treats a missing cap as 0, which refuses every payment), or when a list is over 50.
|
|
49
|
+
*/
|
|
50
|
+
export function guardBudget(policy, opts) {
|
|
51
|
+
const sp = (policy && "spend" in policy ? policy.spend : policy) ?? {};
|
|
52
|
+
if (!isAddress(opts.payer))
|
|
53
|
+
throw new Error("guardBudget: payer must be a 0x address");
|
|
54
|
+
const notMirrored = [];
|
|
55
|
+
const assets = sp.assets ?? {};
|
|
56
|
+
const usdgKey = Object.keys(assets).find((k) => k.toLowerCase() === USDG);
|
|
57
|
+
const caps = usdgKey ? assets[usdgKey] : undefined;
|
|
58
|
+
for (const k of Object.keys(assets))
|
|
59
|
+
if (k !== usdgKey)
|
|
60
|
+
notMirrored.push(`assets["${k}"]: the facilitator budget is USDG only`);
|
|
61
|
+
const perPayment = opts.perPayment ?? caps?.perCall;
|
|
62
|
+
const perDay = opts.perDay ?? caps?.perDay;
|
|
63
|
+
if (perPayment === undefined)
|
|
64
|
+
throw new Error("guardBudget: no USDG per-call cap (set spend.assets[USDG].perCall or pass perPayment)");
|
|
65
|
+
if (perDay === undefined)
|
|
66
|
+
throw new Error("guardBudget: no USDG per-day cap (set spend.assets[USDG].perDay or pass perDay)");
|
|
67
|
+
const byPayTo = new Map();
|
|
68
|
+
for (const [prefix, c] of Object.entries(sp.perEndpoint ?? {})) {
|
|
69
|
+
const payTo = opts.endpointPayTo?.[prefix] ?? (isAddress(prefix) ? prefix : undefined);
|
|
70
|
+
if (c.perCall !== undefined)
|
|
71
|
+
notMirrored.push(`perEndpoint["${prefix}"].perCall: the facilitator has no per-payTo per-payment cap (perPayment applies)`);
|
|
72
|
+
if (c.perDay === undefined)
|
|
73
|
+
continue;
|
|
74
|
+
if (!payTo) {
|
|
75
|
+
notMirrored.push(`perEndpoint["${prefix}"].perDay: no payTo known for it (pass endpointPayTo)`);
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
if (!isAddress(payTo))
|
|
79
|
+
throw new Error(`guardBudget: endpointPayTo["${prefix}"] must be a 0x address`);
|
|
80
|
+
const had = byPayTo.get(payTo.toLowerCase());
|
|
81
|
+
// two endpoints paying one wallet: the stricter day cap holds for both
|
|
82
|
+
if (!had || c.perDay < had.perDay)
|
|
83
|
+
byPayTo.set(payTo.toLowerCase(), { payTo, perDay: uint(c.perDay, `perEndpoint["${prefix}"].perDay`) });
|
|
84
|
+
}
|
|
85
|
+
const payToCaps = [...byPayTo.values()];
|
|
86
|
+
const allowed = [];
|
|
87
|
+
for (const a of sp.allow?.payTo ?? []) {
|
|
88
|
+
if (!isAddress(a))
|
|
89
|
+
throw new Error(`guardBudget: allow.payTo entry "${a}" is not an address`);
|
|
90
|
+
if (!allowed.some((x) => x.toLowerCase() === a.toLowerCase()))
|
|
91
|
+
allowed.push(a);
|
|
92
|
+
}
|
|
93
|
+
if (payToCaps.length > BUDGET_MAX_PAYTOS || allowed.length > BUDGET_MAX_PAYTOS)
|
|
94
|
+
throw new Error(`guardBudget: at most ${BUDGET_MAX_PAYTOS} payToCaps and allowed payTos`);
|
|
95
|
+
if (sp.allow?.to?.length || sp.allow?.spender?.length)
|
|
96
|
+
notMirrored.push("allow.to / allow.spender: the facilitator only sees x402 payments (payTo)");
|
|
97
|
+
if (sp.approveAbove || sp.approve)
|
|
98
|
+
notMirrored.push("approveAbove / approve: human approval stays in guard");
|
|
99
|
+
const budget = {
|
|
100
|
+
payer: opts.payer,
|
|
101
|
+
perPayment: uint(perPayment, "perPayment"),
|
|
102
|
+
perDay: uint(perDay, "perDay"),
|
|
103
|
+
perPayTo: uint(opts.perPayTo ?? 0n, "perPayTo"),
|
|
104
|
+
payToCaps,
|
|
105
|
+
allowedPayTos: allowed,
|
|
106
|
+
validUntil: uint(opts.validUntil ?? 0, "validUntil", MAX_U64),
|
|
107
|
+
nonce: uint(opts.nonce, "nonce"),
|
|
108
|
+
};
|
|
109
|
+
return { budget, typedData: { domain: { ...BUDGET_DOMAIN }, types: BUDGET_TYPES, primaryType: "Budget", message: budget }, notMirrored };
|
|
110
|
+
}
|
package/dist/index.d.ts
CHANGED
|
@@ -14,3 +14,5 @@ export { MemorySpendStore, dayStart } from "./store.js";
|
|
|
14
14
|
export type { SpendStore } from "./store.js";
|
|
15
15
|
export { appendTicketSuffix, encodeTicketAndSig, ticketAskOf, ticketOfAnswer, TICKET_SUFFIX_MAGIC, TOLL_ALLOW_SIGNER, ALLOW_DOCS } from "./ticket.js";
|
|
16
16
|
export type { TicketAsk, AllowTicket, TicketResult } from "./ticket.js";
|
|
17
|
+
export { guardBudget, BUDGET_DOMAIN, BUDGET_TYPES, BUDGET_MAX_PAYTOS } from "./budget.js";
|
|
18
|
+
export type { GuardBudget, GuardBudgetOptions, GuardBudgetMessage } from "./budget.js";
|
package/dist/index.js
CHANGED
|
@@ -6,3 +6,4 @@ export { read8470, latestFinding8470, decodeGetObservation, decodeLatestFinding,
|
|
|
6
6
|
export { spendOf, NATIVE, USDG, WETH, DEFAULT_SPEND_ASSETS, UNLIMITED } from "./spend.js";
|
|
7
7
|
export { MemorySpendStore, dayStart } from "./store.js";
|
|
8
8
|
export { appendTicketSuffix, encodeTicketAndSig, ticketAskOf, ticketOfAnswer, TICKET_SUFFIX_MAGIC, TOLL_ALLOW_SIGNER, ALLOW_DOCS } from "./ticket.js";
|
|
9
|
+
export { guardBudget, BUDGET_DOMAIN, BUDGET_TYPES, BUDGET_MAX_PAYTOS } from "./budget.js";
|
package/dist/policy.d.ts
CHANGED
|
@@ -79,7 +79,7 @@ export interface SpendPolicy {
|
|
|
79
79
|
approved: boolean;
|
|
80
80
|
approvalRef?: string;
|
|
81
81
|
}>;
|
|
82
|
-
/** a request whose spend could not be read (a contract call Toll did not simulate): default "allow"
|
|
82
|
+
/** a request whose spend could not be read (a contract call Toll did not simulate, an x402 amount or a value that does not parse): "block" (default since 0.6.0) or "allow" */
|
|
83
83
|
onUnknownSpend?: "allow" | "block";
|
|
84
84
|
}
|
|
85
85
|
/** blocked by your own spend rules; a TollBlockedError, so existing catch blocks still see a block */
|
|
@@ -107,10 +107,18 @@ export interface GuardOptions extends CheckOptions {
|
|
|
107
107
|
/** BLOCK: "throw" (default, TollBlockedError), or a function (e.g. ask a human); true lets it through */
|
|
108
108
|
onBlock?: BlockPolicy;
|
|
109
109
|
/**
|
|
110
|
-
* Toll unreachable, timed out, 5xx, 429,
|
|
111
|
-
* "block" (fail-closed, throws TollUnavailableError)
|
|
110
|
+
* Toll unreachable, timed out, 5xx, 429, any other non-2xx (400, 413), or an answer that is not a RiskAssessment:
|
|
111
|
+
* "block" (default since 0.6.0, fail-closed: nothing is signed, throws TollUnavailableError) or "allow" (fail-open,
|
|
112
|
+
* logged). Passed explicitly it wins over `failOpen` and `strict`.
|
|
112
113
|
*/
|
|
113
114
|
onUnavailable?: "allow" | "block";
|
|
115
|
+
/**
|
|
116
|
+
* 0.6.0: the opt-in to the old default. `true` = when Toll cannot or will not answer, the request goes ahead
|
|
117
|
+
* unchecked (logged "NOT checked"). Know what it means: whoever builds the request can make Toll refuse it (a
|
|
118
|
+
* request padded past Toll's size limit answers HTTP 413) and so switch the check off. Default false.
|
|
119
|
+
* `strict` and an explicit `onUnavailable` win over it.
|
|
120
|
+
*/
|
|
121
|
+
failOpen?: boolean;
|
|
114
122
|
/** a request Toll does not read (another chain, a contract deploy): "allow" (default) or "block"; `strict`: "block" */
|
|
115
123
|
onUnsupported?: "allow" | "block";
|
|
116
124
|
/**
|
|
@@ -139,7 +147,7 @@ export interface GuardOptions extends CheckOptions {
|
|
|
139
147
|
spend?: SpendPolicy;
|
|
140
148
|
/** where caps and paid x402 ids are counted; default an in-process MemorySpendStore per SpendPolicy object */
|
|
141
149
|
store?: SpendStore;
|
|
142
|
-
/** with `spend`: a request whose spend cannot be read is blocked (onUnknownSpend
|
|
150
|
+
/** with `spend`: a request whose spend cannot be read is blocked. Since 0.6.0 that is the default anyway (onUnknownSpend "block"); kept for callers that set it */
|
|
143
151
|
failClosed?: boolean;
|
|
144
152
|
}
|
|
145
153
|
/** fields the wrappers pass to enforce; not part of the public options */
|
|
@@ -159,7 +167,6 @@ export declare function resolvePolicy(o?: GuardOptions): {
|
|
|
159
167
|
onUnavailable: "allow" | "block";
|
|
160
168
|
onUnsupported: "allow" | "block";
|
|
161
169
|
};
|
|
162
|
-
/** true when any warning is high or critical */
|
|
163
170
|
export declare const hasHighWarning: (a: RiskAssessment) => boolean;
|
|
164
171
|
/** true for a signature request (typed data, a message, a signing RPC) rather than a transaction or an x402 payment */
|
|
165
172
|
export declare const isSignature: (input: CheckInput | null | undefined) => boolean;
|
package/dist/policy.js
CHANGED
|
@@ -25,13 +25,15 @@ export function resolvePolicy(o = {}) {
|
|
|
25
25
|
const strict = o.strict === true;
|
|
26
26
|
return {
|
|
27
27
|
onWarn: o.onWarn ?? (strict ? "block" : "block-high"),
|
|
28
|
-
onUnavailable: o.onUnavailable ?? (strict ? "block" : "allow"),
|
|
28
|
+
onUnavailable: o.onUnavailable ?? (strict ? "block" : o.failOpen === true ? "allow" : "block"),
|
|
29
29
|
onUnsupported: o.onUnsupported ?? (strict ? "block" : "allow"),
|
|
30
30
|
};
|
|
31
31
|
}
|
|
32
32
|
const HIGH = new Set(["high", "critical"]);
|
|
33
33
|
/** true when any warning is high or critical */
|
|
34
|
-
|
|
34
|
+
/** warnings that say "this was not checked": refused under block-high whatever their severity (0.6.0) */
|
|
35
|
+
const UNCHECKED = new Set(["coin-not-read"]);
|
|
36
|
+
export const hasHighWarning = (a) => (a.warnings ?? []).some((w) => HIGH.has(String(w?.severity).toLowerCase()) || UNCHECKED.has(String(w?.code)));
|
|
35
37
|
const say = (o, msg) => {
|
|
36
38
|
if (o.logger === false)
|
|
37
39
|
return;
|
|
@@ -187,9 +189,13 @@ async function unavailable(kind, input, err, o) {
|
|
|
187
189
|
const blocked = resolvePolicy(o).onUnavailable === "block" || isEthSign(input);
|
|
188
190
|
const r = { kind, input, assessment: null, decision: blocked ? "blocked" : "allowed", basis: "unavailable", paidUrl: err.paidUrl, error: err };
|
|
189
191
|
await report(o, r);
|
|
190
|
-
if (blocked)
|
|
191
|
-
|
|
192
|
-
|
|
192
|
+
if (blocked) {
|
|
193
|
+
// the same error class and fields; the message says nothing was signed and names the opt-out
|
|
194
|
+
if (/^Not signed/.test(err.message))
|
|
195
|
+
throw err;
|
|
196
|
+
throw new TollUnavailableError(`Not signed: Toll did not check this request. ${err.message} The guard fails closed; failOpen: true lets unchecked requests through.`, { reason: err.reason, status: err.status, paidUrl: err.paidUrl, cause: err });
|
|
197
|
+
}
|
|
198
|
+
say(o, `Toll unavailable, NOT checked, going ahead (${o.failOpen === true && o.onUnavailable === undefined ? "failOpen: true" : 'onUnavailable "allow"'}): ${err.message}`);
|
|
193
199
|
return r;
|
|
194
200
|
}
|
|
195
201
|
/**
|
|
@@ -395,7 +401,9 @@ async function enforceSpend(kind, build, o) {
|
|
|
395
401
|
const why = offList(sp, s);
|
|
396
402
|
if (why)
|
|
397
403
|
return blockWith(base, "not-allowlisted", `Not signed: ${why}.`, s);
|
|
398
|
-
|
|
404
|
+
// 0.6.0: with a spend policy set, what cannot be read is refused by default (it was allowed unless failClosed):
|
|
405
|
+
// caps that bind only on plain transfers are not caps. onUnknownSpend "allow" is the opt-out
|
|
406
|
+
if (s.unknown && (sp.onUnknownSpend ?? "block") === "block") {
|
|
399
407
|
return blockWith(base, "unknown-spend", 'Not signed: what this request spends could not be read (no simulation), and onUnknownSpend is "block".', s);
|
|
400
408
|
}
|
|
401
409
|
const caps = lower(sp.assets);
|
package/dist/spend.js
CHANGED
|
@@ -94,7 +94,11 @@ class Acc {
|
|
|
94
94
|
function decodeTx(i, acc) {
|
|
95
95
|
const from = isAddress(i.from) ? lc(i.from) : null;
|
|
96
96
|
const target = lc(i.to);
|
|
97
|
-
|
|
97
|
+
// a value that cannot be read (a JS number past 2^53, text) is not "nothing sent": unknown (0.6.0)
|
|
98
|
+
const read = big(i.value ?? 0);
|
|
99
|
+
if (read === null)
|
|
100
|
+
acc.unknown = true;
|
|
101
|
+
const value = read ?? 0n;
|
|
98
102
|
const data = typeof i.data === "string" ? i.data : "0x";
|
|
99
103
|
if (value > 0n)
|
|
100
104
|
acc.out(NATIVE, value, { kind: "value", to: target });
|
|
@@ -113,7 +117,8 @@ function decodeTx(i, acc) {
|
|
|
113
117
|
}
|
|
114
118
|
if (sel === SEL.transferFrom && w.length >= 3) {
|
|
115
119
|
const src = wAddr(w[0]);
|
|
116
|
-
|
|
120
|
+
// transferFrom(from, to, amount): the recipient is the SECOND word (0.6.0: it was read from the amount word)
|
|
121
|
+
const to = wAddr(w[1]);
|
|
117
122
|
acc.to.add(to);
|
|
118
123
|
// only the signer's own tokens are its spend
|
|
119
124
|
if (!from || src === from)
|
|
@@ -182,9 +187,12 @@ function decodeX402(x, acc) {
|
|
|
182
187
|
if (payTo)
|
|
183
188
|
acc.payTo.add(payTo);
|
|
184
189
|
const amt = big(e.amount ?? e.maxAmountRequired);
|
|
185
|
-
// the client pays one entry: the largest per asset counts
|
|
190
|
+
// the client pays one entry: the largest per asset counts. An amount that cannot be read (0.6.0: "+5000000",
|
|
191
|
+
// "5e6", none) is not zero: the caps cannot be applied to it, so the spend is unknown
|
|
186
192
|
if (amt !== null)
|
|
187
193
|
acc.out(lc(e.asset), amt, { kind: "x402", payTo }, true);
|
|
194
|
+
else
|
|
195
|
+
acc.unknown = true;
|
|
188
196
|
}
|
|
189
197
|
}
|
|
190
198
|
function readSim(a, from, acc) {
|
package/package.json
CHANGED
|
@@ -1,70 +1,70 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "@tollxyz/guard",
|
|
3
|
-
"version": "0.
|
|
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
|
-
"license": "MIT",
|
|
6
|
-
"author": "Toll",
|
|
7
|
-
"type": "module",
|
|
8
|
-
"main": "./dist/index.js",
|
|
9
|
-
"types": "./dist/index.d.ts",
|
|
10
|
-
"exports": {
|
|
11
|
-
".": {
|
|
12
|
-
"types": "./dist/index.d.ts",
|
|
13
|
-
"import": "./dist/index.js"
|
|
14
|
-
}
|
|
15
|
-
},
|
|
16
|
-
"files": [
|
|
17
|
-
"dist",
|
|
18
|
-
"README.md"
|
|
19
|
-
],
|
|
20
|
-
"engines": {
|
|
21
|
-
"node": ">=20.19"
|
|
22
|
-
},
|
|
23
|
-
"publishConfig": {
|
|
24
|
-
"access": "public"
|
|
25
|
-
},
|
|
26
|
-
"repository": {
|
|
27
|
-
"type": "git",
|
|
28
|
-
"url": "git+https://github.com/t0ll-xyz/toll-plugins.git",
|
|
29
|
-
"directory": "packages/guard"
|
|
30
|
-
},
|
|
31
|
-
"homepage": "https://t0ll.xyz/agents/plugins",
|
|
32
|
-
"keywords": [
|
|
33
|
-
"agent-wallet",
|
|
34
|
-
"transaction-guard",
|
|
35
|
-
"pre-sign",
|
|
36
|
-
"x402",
|
|
37
|
-
"erc-8004",
|
|
38
|
-
"erc-8470",
|
|
39
|
-
"erc-8265",
|
|
40
|
-
"allow-ticket",
|
|
41
|
-
"robinhood-chain",
|
|
42
|
-
"viem",
|
|
43
|
-
"ethers",
|
|
44
|
-
"ai-agent",
|
|
45
|
-
"toll"
|
|
46
|
-
],
|
|
47
|
-
"scripts": {
|
|
48
|
-
"build": "tsc -p tsconfig.json",
|
|
49
|
-
"test": "tsc -p tsconfig.json --noEmit && tsx --test tests/*.test.ts",
|
|
50
|
-
"smoke": "node scripts/smoke.mjs",
|
|
51
|
-
"prepublishOnly": "npm run build"
|
|
52
|
-
},
|
|
53
|
-
"peerDependencies": {
|
|
54
|
-
"ethers": "^6.0.0",
|
|
55
|
-
"viem": "^2.0.0"
|
|
56
|
-
},
|
|
57
|
-
"peerDependenciesMeta": {
|
|
58
|
-
"ethers": {
|
|
59
|
-
"optional": true
|
|
60
|
-
},
|
|
61
|
-
"viem": {
|
|
62
|
-
"optional": true
|
|
63
|
-
}
|
|
64
|
-
},
|
|
65
|
-
"devDependencies": {
|
|
66
|
-
"@types/node": "^22.0.0",
|
|
67
|
-
"tsx": "^4.19.0",
|
|
68
|
-
"typescript": "^5.6.0"
|
|
69
|
-
}
|
|
70
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "@tollxyz/guard",
|
|
3
|
+
"version": "0.6.0",
|
|
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
|
+
"license": "MIT",
|
|
6
|
+
"author": "Toll",
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"engines": {
|
|
21
|
+
"node": ">=20.19"
|
|
22
|
+
},
|
|
23
|
+
"publishConfig": {
|
|
24
|
+
"access": "public"
|
|
25
|
+
},
|
|
26
|
+
"repository": {
|
|
27
|
+
"type": "git",
|
|
28
|
+
"url": "git+https://github.com/t0ll-xyz/toll-plugins.git",
|
|
29
|
+
"directory": "packages/guard"
|
|
30
|
+
},
|
|
31
|
+
"homepage": "https://t0ll.xyz/agents/plugins",
|
|
32
|
+
"keywords": [
|
|
33
|
+
"agent-wallet",
|
|
34
|
+
"transaction-guard",
|
|
35
|
+
"pre-sign",
|
|
36
|
+
"x402",
|
|
37
|
+
"erc-8004",
|
|
38
|
+
"erc-8470",
|
|
39
|
+
"erc-8265",
|
|
40
|
+
"allow-ticket",
|
|
41
|
+
"robinhood-chain",
|
|
42
|
+
"viem",
|
|
43
|
+
"ethers",
|
|
44
|
+
"ai-agent",
|
|
45
|
+
"toll"
|
|
46
|
+
],
|
|
47
|
+
"scripts": {
|
|
48
|
+
"build": "tsc -p tsconfig.json",
|
|
49
|
+
"test": "tsc -p tsconfig.json --noEmit && tsx --test tests/*.test.ts",
|
|
50
|
+
"smoke": "node scripts/smoke.mjs",
|
|
51
|
+
"prepublishOnly": "npm run build"
|
|
52
|
+
},
|
|
53
|
+
"peerDependencies": {
|
|
54
|
+
"ethers": "^6.0.0",
|
|
55
|
+
"viem": "^2.0.0"
|
|
56
|
+
},
|
|
57
|
+
"peerDependenciesMeta": {
|
|
58
|
+
"ethers": {
|
|
59
|
+
"optional": true
|
|
60
|
+
},
|
|
61
|
+
"viem": {
|
|
62
|
+
"optional": true
|
|
63
|
+
}
|
|
64
|
+
},
|
|
65
|
+
"devDependencies": {
|
|
66
|
+
"@types/node": "^22.0.0",
|
|
67
|
+
"tsx": "^4.19.0",
|
|
68
|
+
"typescript": "^5.6.0"
|
|
69
|
+
}
|
|
70
|
+
}
|