@keelcodes/policy 0.1.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/LICENSE +201 -0
- package/README.md +137 -0
- package/dist/bounded.d.ts +192 -0
- package/dist/bounded.d.ts.map +1 -0
- package/dist/bounded.js +341 -0
- package/dist/bounded.js.map +1 -0
- package/dist/commitment.d.ts +68 -0
- package/dist/commitment.d.ts.map +1 -0
- package/dist/commitment.js +80 -0
- package/dist/commitment.js.map +1 -0
- package/dist/erc20.d.ts +31 -0
- package/dist/erc20.d.ts.map +1 -0
- package/dist/erc20.js +36 -0
- package/dist/erc20.js.map +1 -0
- package/dist/errors.d.ts +13 -0
- package/dist/errors.d.ts.map +1 -0
- package/dist/errors.js +22 -0
- package/dist/errors.js.map +1 -0
- package/dist/evaluate.d.ts +32 -0
- package/dist/evaluate.d.ts.map +1 -0
- package/dist/evaluate.js +125 -0
- package/dist/evaluate.js.map +1 -0
- package/dist/index.d.ts +28 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +23 -0
- package/dist/index.js.map +1 -0
- package/dist/normalize.d.ts +10 -0
- package/dist/normalize.d.ts.map +1 -0
- package/dist/normalize.js +92 -0
- package/dist/normalize.js.map +1 -0
- package/dist/session.d.ts +98 -0
- package/dist/session.d.ts.map +1 -0
- package/dist/session.js +128 -0
- package/dist/session.js.map +1 -0
- package/dist/types.d.ts +108 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +21 -0
- package/dist/types.js.map +1 -0
- package/package.json +52 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Address, Call, Decision, Hex, Policy, PolicyState, RuleUsage } from './types.js';
|
|
2
|
+
/** Usage of a rule that has never been exercised. */
|
|
3
|
+
export declare const ZERO_USAGE: RuleUsage;
|
|
4
|
+
/** Builds a {@link Call}, deriving the selector from `data`. */
|
|
5
|
+
export declare function toCall(input: {
|
|
6
|
+
target: Address;
|
|
7
|
+
value?: bigint;
|
|
8
|
+
data?: Hex;
|
|
9
|
+
}): Call;
|
|
10
|
+
/**
|
|
11
|
+
* Evaluates a single call against the policy. Validity is checked first, then
|
|
12
|
+
* a matching rule is looked up in declared order (first match wins), then that
|
|
13
|
+
* rule's per-tx / daily / count and per-token ceilings are applied.
|
|
14
|
+
*/
|
|
15
|
+
export declare function evaluateCall(policy: Policy, state: PolicyState, call: Call): Decision;
|
|
16
|
+
export interface SimulationResult {
|
|
17
|
+
readonly allowed: boolean;
|
|
18
|
+
/** The last decision evaluated: the first denial, or the final allow. */
|
|
19
|
+
readonly decision: Decision;
|
|
20
|
+
/** Usage after applying every allowed call; input state is never mutated. */
|
|
21
|
+
readonly usage: readonly RuleUsage[];
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Dry-runs `calls` in order, accumulating usage so that later calls see the
|
|
25
|
+
* effect of earlier ones (e.g. a batch that would breach a daily cap only
|
|
26
|
+
* together). Stops and returns at the first denial.
|
|
27
|
+
*
|
|
28
|
+
* `state.usage` is aligned to `policy.rules` by index; callers reset
|
|
29
|
+
* `dailySpent` / `tokenSpent` themselves when the day window rolls over.
|
|
30
|
+
*/
|
|
31
|
+
export declare function simulateCalls(policy: Policy, state: PolicyState, calls: readonly Call[]): SimulationResult;
|
|
32
|
+
//# sourceMappingURL=evaluate.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.d.ts","sourceRoot":"","sources":["../src/evaluate.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EACV,OAAO,EACP,IAAI,EACJ,QAAQ,EAER,GAAG,EACH,MAAM,EAEN,WAAW,EACX,SAAS,EACV,MAAM,YAAY,CAAC;AAYpB,qDAAqD;AACrD,eAAO,MAAM,UAAU,EAAE,SAAuD,CAAC;AAEjF,gEAAgE;AAChE,wBAAgB,MAAM,CAAC,KAAK,EAAE;IAAE,MAAM,EAAE,OAAO,CAAC;IAAC,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,IAAI,CAAC,EAAE,GAAG,CAAA;CAAE,GAAG,IAAI,CAKnF;AAmCD;;;;GAIG;AACH,wBAAgB,YAAY,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,IAAI,EAAE,IAAI,GAAG,QAAQ,CAsBrF;AAsBD,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,CAAC,OAAO,EAAE,OAAO,CAAC;IAC1B,yEAAyE;IACzE,QAAQ,CAAC,QAAQ,EAAE,QAAQ,CAAC;IAC5B,6EAA6E;IAC7E,QAAQ,CAAC,KAAK,EAAE,SAAS,SAAS,EAAE,CAAC;CACtC;AAED;;;;;;;GAOG;AACH,wBAAgB,aAAa,CAAC,MAAM,EAAE,MAAM,EAAE,KAAK,EAAE,WAAW,EAAE,KAAK,EAAE,SAAS,IAAI,EAAE,GAAG,gBAAgB,CAa1G"}
|
package/dist/evaluate.js
ADDED
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import { isAddress } from 'viem';
|
|
2
|
+
import { ERC20_SELECTOR, readErc20Amount } from './erc20.js';
|
|
3
|
+
import { PolicyError } from './errors.js';
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Off-chain pre-check and simulation.
|
|
6
|
+
//
|
|
7
|
+
// These are pure functions over (policy, state, call). They mirror the rules
|
|
8
|
+
// the on-chain hook will enforce, so a caller can dry-run a batch before
|
|
9
|
+
// signing: reject it locally with a precise reason instead of paying for a
|
|
10
|
+
// bundler round-trip that ends in a validation failure. The on-chain module
|
|
11
|
+
// remains the source of truth; this layer never widens what is allowed.
|
|
12
|
+
// ============================================================================
|
|
13
|
+
/** Usage of a rule that has never been exercised. */
|
|
14
|
+
export const ZERO_USAGE = Object.freeze({ calls: 0, dailySpent: 0n });
|
|
15
|
+
/** Builds a {@link Call}, deriving the selector from `data`. */
|
|
16
|
+
export function toCall(input) {
|
|
17
|
+
if (!isAddress(input.target))
|
|
18
|
+
throw new PolicyError(`call.target: invalid address "${input.target}"`);
|
|
19
|
+
const data = (input.data ?? '0x');
|
|
20
|
+
const selector = (data.length >= 10 ? data.slice(0, 10) : '0x').toLowerCase();
|
|
21
|
+
return { target: input.target.toLowerCase(), value: input.value ?? 0n, data, selector };
|
|
22
|
+
}
|
|
23
|
+
function matches(rule, call) {
|
|
24
|
+
if (rule.target !== call.target.toLowerCase())
|
|
25
|
+
return false;
|
|
26
|
+
if (rule.selectors.length === 0)
|
|
27
|
+
return true;
|
|
28
|
+
return rule.selectors.includes(call.selector.toLowerCase());
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Applies a rule's per-token ceiling, if one is configured for this call's
|
|
32
|
+
* target. Returns the reason to deny, or `undefined` to allow.
|
|
33
|
+
*
|
|
34
|
+
* `transferFrom` is refused outright while a limit is configured: its amount is
|
|
35
|
+
* readable but bounding a pull from an arbitrary address is ambiguous enough
|
|
36
|
+
* that the conservative choice (matching the existing infraX module) is to
|
|
37
|
+
* reject it. Calls to a limited token that are not standard ERC-20 methods are
|
|
38
|
+
* left to the rule's selector whitelist.
|
|
39
|
+
*/
|
|
40
|
+
function tokenDenial(rule, usage, call) {
|
|
41
|
+
const limit = rule.tokenLimits.find((entry) => entry.token === call.target.toLowerCase());
|
|
42
|
+
if (!limit)
|
|
43
|
+
return undefined;
|
|
44
|
+
const read = readErc20Amount(call);
|
|
45
|
+
if (read.kind === 'malformed')
|
|
46
|
+
return 'token-amount-unparsable';
|
|
47
|
+
if (read.kind === 'not-erc20')
|
|
48
|
+
return undefined;
|
|
49
|
+
if (read.selector === ERC20_SELECTOR.transferFrom)
|
|
50
|
+
return 'token-transfer-from-blocked';
|
|
51
|
+
if (limit.maxPerTx > 0n && read.value > limit.maxPerTx)
|
|
52
|
+
return 'token-per-tx-exceeded';
|
|
53
|
+
const spent = usage.tokenSpent?.[limit.token] ?? 0n;
|
|
54
|
+
if (limit.maxDaily > 0n && spent + read.value > limit.maxDaily)
|
|
55
|
+
return 'token-daily-limit-exceeded';
|
|
56
|
+
return undefined;
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* Evaluates a single call against the policy. Validity is checked first, then
|
|
60
|
+
* a matching rule is looked up in declared order (first match wins), then that
|
|
61
|
+
* rule's per-tx / daily / count and per-token ceilings are applied.
|
|
62
|
+
*/
|
|
63
|
+
export function evaluateCall(policy, state, call) {
|
|
64
|
+
if (policy.validAfter > 0n && state.now < policy.validAfter)
|
|
65
|
+
return { allowed: false, reason: 'not-yet-valid' };
|
|
66
|
+
if (policy.validUntil > 0n && state.now > policy.validUntil)
|
|
67
|
+
return { allowed: false, reason: 'expired' };
|
|
68
|
+
const ruleIndex = policy.rules.findIndex((rule) => matches(rule, call));
|
|
69
|
+
if (ruleIndex === -1)
|
|
70
|
+
return { allowed: false, reason: 'no-matching-rule' };
|
|
71
|
+
const rule = policy.rules[ruleIndex];
|
|
72
|
+
const usage = state.usage[ruleIndex] ?? ZERO_USAGE;
|
|
73
|
+
if (rule.maxPerTx > 0n && call.value > rule.maxPerTx) {
|
|
74
|
+
return { allowed: false, reason: 'value-per-tx-exceeded', ruleIndex };
|
|
75
|
+
}
|
|
76
|
+
const tokenReason = tokenDenial(rule, usage, call);
|
|
77
|
+
if (tokenReason)
|
|
78
|
+
return { allowed: false, reason: tokenReason, ruleIndex };
|
|
79
|
+
if (rule.maxDaily > 0n && usage.dailySpent + call.value > rule.maxDaily) {
|
|
80
|
+
return { allowed: false, reason: 'daily-limit-exceeded', ruleIndex };
|
|
81
|
+
}
|
|
82
|
+
if (rule.maxCalls > 0 && usage.calls + 1 > rule.maxCalls) {
|
|
83
|
+
return { allowed: false, reason: 'call-count-exceeded', ruleIndex };
|
|
84
|
+
}
|
|
85
|
+
return { allowed: true, ruleIndex };
|
|
86
|
+
}
|
|
87
|
+
/** Advances a rule's usage by one allowed call, including any token amount. */
|
|
88
|
+
function advanceUsage(rule, usage, call) {
|
|
89
|
+
const next = {
|
|
90
|
+
calls: usage.calls + 1,
|
|
91
|
+
dailySpent: usage.dailySpent + call.value,
|
|
92
|
+
};
|
|
93
|
+
const limit = rule.tokenLimits.find((entry) => entry.token === call.target.toLowerCase());
|
|
94
|
+
const read = limit ? readErc20Amount(call) : undefined;
|
|
95
|
+
if (limit && read?.kind === 'amount' && read.selector !== ERC20_SELECTOR.transferFrom) {
|
|
96
|
+
const tokenSpent = { ...(usage.tokenSpent ?? {}) };
|
|
97
|
+
tokenSpent[limit.token] = (tokenSpent[limit.token] ?? 0n) + read.value;
|
|
98
|
+
next.tokenSpent = tokenSpent;
|
|
99
|
+
}
|
|
100
|
+
else if (usage.tokenSpent) {
|
|
101
|
+
next.tokenSpent = usage.tokenSpent;
|
|
102
|
+
}
|
|
103
|
+
return next;
|
|
104
|
+
}
|
|
105
|
+
/**
|
|
106
|
+
* Dry-runs `calls` in order, accumulating usage so that later calls see the
|
|
107
|
+
* effect of earlier ones (e.g. a batch that would breach a daily cap only
|
|
108
|
+
* together). Stops and returns at the first denial.
|
|
109
|
+
*
|
|
110
|
+
* `state.usage` is aligned to `policy.rules` by index; callers reset
|
|
111
|
+
* `dailySpent` / `tokenSpent` themselves when the day window rolls over.
|
|
112
|
+
*/
|
|
113
|
+
export function simulateCalls(policy, state, calls) {
|
|
114
|
+
const usage = policy.rules.map((_, index) => state.usage[index] ?? ZERO_USAGE);
|
|
115
|
+
let decision = { allowed: true };
|
|
116
|
+
for (const call of calls) {
|
|
117
|
+
decision = evaluateCall(policy, { now: state.now, usage }, call);
|
|
118
|
+
if (!decision.allowed)
|
|
119
|
+
return { allowed: false, decision, usage };
|
|
120
|
+
const index = decision.ruleIndex;
|
|
121
|
+
usage[index] = advanceUsage(policy.rules[index], usage[index] ?? ZERO_USAGE, call);
|
|
122
|
+
}
|
|
123
|
+
return { allowed: true, decision, usage };
|
|
124
|
+
}
|
|
125
|
+
//# sourceMappingURL=evaluate.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"evaluate.js","sourceRoot":"","sources":["../src/evaluate.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,MAAM,CAAC;AACjC,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,WAAW,EAAE,MAAM,aAAa,CAAC;AAa1C,+EAA+E;AAC/E,sCAAsC;AACtC,EAAE;AACF,6EAA6E;AAC7E,yEAAyE;AACzE,2EAA2E;AAC3E,4EAA4E;AAC5E,wEAAwE;AACxE,+EAA+E;AAE/E,qDAAqD;AACrD,MAAM,CAAC,MAAM,UAAU,GAAc,MAAM,CAAC,MAAM,CAAC,EAAE,KAAK,EAAE,CAAC,EAAE,UAAU,EAAE,EAAE,EAAE,CAAC,CAAC;AAEjF,gEAAgE;AAChE,MAAM,UAAU,MAAM,CAAC,KAAsD;IAC3E,IAAI,CAAC,SAAS,CAAC,KAAK,CAAC,MAAM,CAAC;QAAE,MAAM,IAAI,WAAW,CAAC,iCAAiC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAC;IACtG,MAAM,IAAI,GAAG,CAAC,KAAK,CAAC,IAAI,IAAI,IAAI,CAAQ,CAAC;IACzC,MAAM,QAAQ,GAAG,CAAC,IAAI,CAAC,MAAM,IAAI,EAAE,CAAC,CAAC,CAAC,IAAI,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAC,CAAC,WAAW,EAAS,CAAC;IACrF,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,MAAM,CAAC,WAAW,EAAa,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,IAAI,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AACrG,CAAC;AAED,SAAS,OAAO,CAAC,IAAgB,EAAE,IAAU;IAC3C,IAAI,IAAI,CAAC,MAAM,KAAK,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE;QAAE,OAAO,KAAK,CAAC;IAC5D,IAAI,IAAI,CAAC,SAAS,CAAC,MAAM,KAAK,CAAC;QAAE,OAAO,IAAI,CAAC;IAC7C,OAAO,IAAI,CAAC,SAAS,CAAC,QAAQ,CAAC,IAAI,CAAC,QAAQ,CAAC,WAAW,EAAS,CAAC,CAAC;AACrE,CAAC;AAED;;;;;;;;;GASG;AACH,SAAS,WAAW,CAAC,IAAgB,EAAE,KAAgB,EAAE,IAAU;IACjE,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1F,IAAI,CAAC,KAAK;QAAE,OAAO,SAAS,CAAC;IAE7B,MAAM,IAAI,GAAG,eAAe,CAAC,IAAI,CAAC,CAAC;IACnC,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,yBAAyB,CAAC;IAChE,IAAI,IAAI,CAAC,IAAI,KAAK,WAAW;QAAE,OAAO,SAAS,CAAC;IAChD,IAAI,IAAI,CAAC,QAAQ,KAAK,cAAc,CAAC,YAAY;QAAE,OAAO,6BAA6B,CAAC;IAExF,IAAI,KAAK,CAAC,QAAQ,GAAG,EAAE,IAAI,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ;QAAE,OAAO,uBAAuB,CAAC;IAEvF,MAAM,KAAK,GAAG,KAAK,CAAC,UAAU,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC;IACpD,IAAI,KAAK,CAAC,QAAQ,GAAG,EAAE,IAAI,KAAK,GAAG,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC,QAAQ;QAAE,OAAO,4BAA4B,CAAC;IAEpG,OAAO,SAAS,CAAC;AACnB,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,YAAY,CAAC,MAAc,EAAE,KAAkB,EAAE,IAAU;IACzE,IAAI,MAAM,CAAC,UAAU,GAAG,EAAE,IAAI,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,UAAU;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,eAAe,EAAE,CAAC;IAChH,IAAI,MAAM,CAAC,UAAU,GAAG,EAAE,IAAI,KAAK,CAAC,GAAG,GAAG,MAAM,CAAC,UAAU;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAE1G,MAAM,SAAS,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAC,CAAC,IAAI,EAAE,EAAE,CAAC,OAAO,CAAC,IAAI,EAAE,IAAI,CAAC,CAAC,CAAC;IACxE,IAAI,SAAS,KAAK,CAAC,CAAC;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,kBAAkB,EAAE,CAAC;IAE5E,MAAM,IAAI,GAAG,MAAM,CAAC,KAAK,CAAC,SAAS,CAAE,CAAC;IACtC,MAAM,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,SAAS,CAAC,IAAI,UAAU,CAAC;IAEnD,IAAI,IAAI,CAAC,QAAQ,GAAG,EAAE,IAAI,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QACrD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,uBAAuB,EAAE,SAAS,EAAE,CAAC;IACxE,CAAC;IACD,MAAM,WAAW,GAAG,WAAW,CAAC,IAAI,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC;IACnD,IAAI,WAAW;QAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,WAAW,EAAE,SAAS,EAAE,CAAC;IAC3E,IAAI,IAAI,CAAC,QAAQ,GAAG,EAAE,IAAI,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QACxE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,sBAAsB,EAAE,SAAS,EAAE,CAAC;IACvE,CAAC;IACD,IAAI,IAAI,CAAC,QAAQ,GAAG,CAAC,IAAI,KAAK,CAAC,KAAK,GAAG,CAAC,GAAG,IAAI,CAAC,QAAQ,EAAE,CAAC;QACzD,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,qBAAqB,EAAE,SAAS,EAAE,CAAC;IACtE,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,CAAC;AACtC,CAAC;AAED,+EAA+E;AAC/E,SAAS,YAAY,CAAC,IAAgB,EAAE,KAAgB,EAAE,IAAU;IAClE,MAAM,IAAI,GAA+E;QACvF,KAAK,EAAE,KAAK,CAAC,KAAK,GAAG,CAAC;QACtB,UAAU,EAAE,KAAK,CAAC,UAAU,GAAG,IAAI,CAAC,KAAK;KAC1C,CAAC;IAEF,MAAM,KAAK,GAAG,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC,CAAC,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,KAAK,IAAI,CAAC,MAAM,CAAC,WAAW,EAAE,CAAC,CAAC;IAC1F,MAAM,IAAI,GAAG,KAAK,CAAC,CAAC,CAAC,eAAe,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC,SAAS,CAAC;IACvD,IAAI,KAAK,IAAI,IAAI,EAAE,IAAI,KAAK,QAAQ,IAAI,IAAI,CAAC,QAAQ,KAAK,cAAc,CAAC,YAAY,EAAE,CAAC;QACtF,MAAM,UAAU,GAAG,EAAE,GAAG,CAAC,KAAK,CAAC,UAAU,IAAI,EAAE,CAAC,EAAE,CAAC;QACnD,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,UAAU,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,EAAE,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC;QACvE,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;IAC/B,CAAC;SAAM,IAAI,KAAK,CAAC,UAAU,EAAE,CAAC;QAC5B,IAAI,CAAC,UAAU,GAAG,KAAK,CAAC,UAAU,CAAC;IACrC,CAAC;IAED,OAAO,IAAI,CAAC;AACd,CAAC;AAUD;;;;;;;GAOG;AACH,MAAM,UAAU,aAAa,CAAC,MAAc,EAAE,KAAkB,EAAE,KAAsB;IACtF,MAAM,KAAK,GAAgB,MAAM,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,IAAI,UAAU,CAAC,CAAC;IAC5F,IAAI,QAAQ,GAAa,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC;IAE3C,KAAK,MAAM,IAAI,IAAI,KAAK,EAAE,CAAC;QACzB,QAAQ,GAAG,YAAY,CAAC,MAAM,EAAE,EAAE,GAAG,EAAE,KAAK,CAAC,GAAG,EAAE,KAAK,EAAE,EAAE,IAAI,CAAC,CAAC;QACjE,IAAI,CAAC,QAAQ,CAAC,OAAO;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;QAElE,MAAM,KAAK,GAAG,QAAQ,CAAC,SAAU,CAAC;QAClC,KAAK,CAAC,KAAK,CAAC,GAAG,YAAY,CAAC,MAAM,CAAC,KAAK,CAAC,KAAK,CAAE,EAAE,KAAK,CAAC,KAAK,CAAC,IAAI,UAAU,EAAE,IAAI,CAAC,CAAC;IACtF,CAAC;IAED,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,QAAQ,EAAE,KAAK,EAAE,CAAC;AAC5C,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @keelcodes/policy
|
|
3
|
+
*
|
|
4
|
+
* Account-agnostic authorization policy for agent accounts: a declarative DSL,
|
|
5
|
+
* a canonical commitment hash and an off-chain pre-check / simulation layer.
|
|
6
|
+
*
|
|
7
|
+
* The package is carrier-neutral. Today it is used off-chain to dry-run calls
|
|
8
|
+
* before signing; the same normalised policy and commitment are what the
|
|
9
|
+
* on-chain ERC-7579 hook module (Keel's carrier for on-chain enforcement)
|
|
10
|
+
* recomputes and enforces, so the two layers cannot disagree about what a
|
|
11
|
+
* policy means.
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
export { INSTALL_ABI_PARAMETERS, POLICY_ABI_PARAMETERS, encodeInstallData, encodePolicy, policyCommitment, } from './commitment.js';
|
|
16
|
+
export { CAPABILITY_VERSION, EnvelopeStatus, ZERO_ASSET, ZERO_CURSOR, advanceCursor, applyStatus, approvalsSatisfied, assertConservation, attenuate, canDraw, canSetStatus, capabilityCommitment, contestExpired, cursorCommitment, defaultResolution, effectiveStatus, encodeCapability, encodeCursor, envelopeId, isTerminal, normalizeCapability, openContest, remaining, withinCap, } from './bounded.js';
|
|
17
|
+
export { ERC20_SELECTOR, readErc20Amount } from './erc20.js';
|
|
18
|
+
export { ZERO_USAGE, evaluateCall, simulateCalls, toCall } from './evaluate.js';
|
|
19
|
+
export { EnvelopeError, PolicyError, SessionError } from './errors.js';
|
|
20
|
+
export { normalizePolicy } from './normalize.js';
|
|
21
|
+
export { InMemorySessionStore, createSessionId, isSessionActive, issueSession, listSessions, revokeSession, rotateSession, sessionStatus, } from './session.js';
|
|
22
|
+
export type { Erc20Read } from './erc20.js';
|
|
23
|
+
export type { ApprovalInput, Capability, CapabilityInput, ContestWindow, Cursor, DrawContext, DrawDecision, DrawDenyReason, Envelope, EnvelopeRef, TrustTier, } from './bounded.js';
|
|
24
|
+
export type { SimulationResult } from './evaluate.js';
|
|
25
|
+
export type { IssueSessionInput, Rotation, RotateSessionInput, Session, SessionStatus, SessionStore, SessionView, } from './session.js';
|
|
26
|
+
export type { Address, Call, Decision, DenyReason, Hex, Policy, PolicyInput, PolicyRule, PolicyRuleInput, PolicyState, RuleUsage, TokenLimit, TokenLimitInput, } from './types.js';
|
|
27
|
+
export { POLICY_VERSION } from './types.js';
|
|
28
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,WAAW,EACX,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,SAAS,EACT,OAAO,EACP,YAAY,EACZ,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,UAAU,EACV,mBAAmB,EACnB,WAAW,EACX,SAAS,EACT,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,GACd,MAAM,cAAc,CAAC;AAEtB,YAAY,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AAC5C,YAAY,EACV,aAAa,EACb,UAAU,EACV,eAAe,EACf,aAAa,EACb,MAAM,EACN,WAAW,EACX,YAAY,EACZ,cAAc,EACd,QAAQ,EACR,WAAW,EACX,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,gBAAgB,EAAE,MAAM,eAAe,CAAC;AACtD,YAAY,EACV,iBAAiB,EACjB,QAAQ,EACR,kBAAkB,EAClB,OAAO,EACP,aAAa,EACb,YAAY,EACZ,WAAW,GACZ,MAAM,cAAc,CAAC;AACtB,YAAY,EACV,OAAO,EACP,IAAI,EACJ,QAAQ,EACR,UAAU,EACV,GAAG,EACH,MAAM,EACN,WAAW,EACX,UAAU,EACV,eAAe,EACf,WAAW,EACX,SAAS,EACT,UAAU,EACV,eAAe,GAChB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @keelcodes/policy
|
|
3
|
+
*
|
|
4
|
+
* Account-agnostic authorization policy for agent accounts: a declarative DSL,
|
|
5
|
+
* a canonical commitment hash and an off-chain pre-check / simulation layer.
|
|
6
|
+
*
|
|
7
|
+
* The package is carrier-neutral. Today it is used off-chain to dry-run calls
|
|
8
|
+
* before signing; the same normalised policy and commitment are what the
|
|
9
|
+
* on-chain ERC-7579 hook module (Keel's carrier for on-chain enforcement)
|
|
10
|
+
* recomputes and enforces, so the two layers cannot disagree about what a
|
|
11
|
+
* policy means.
|
|
12
|
+
*
|
|
13
|
+
* @packageDocumentation
|
|
14
|
+
*/
|
|
15
|
+
export { INSTALL_ABI_PARAMETERS, POLICY_ABI_PARAMETERS, encodeInstallData, encodePolicy, policyCommitment, } from './commitment.js';
|
|
16
|
+
export { CAPABILITY_VERSION, EnvelopeStatus, ZERO_ASSET, ZERO_CURSOR, advanceCursor, applyStatus, approvalsSatisfied, assertConservation, attenuate, canDraw, canSetStatus, capabilityCommitment, contestExpired, cursorCommitment, defaultResolution, effectiveStatus, encodeCapability, encodeCursor, envelopeId, isTerminal, normalizeCapability, openContest, remaining, withinCap, } from './bounded.js';
|
|
17
|
+
export { ERC20_SELECTOR, readErc20Amount } from './erc20.js';
|
|
18
|
+
export { ZERO_USAGE, evaluateCall, simulateCalls, toCall } from './evaluate.js';
|
|
19
|
+
export { EnvelopeError, PolicyError, SessionError } from './errors.js';
|
|
20
|
+
export { normalizePolicy } from './normalize.js';
|
|
21
|
+
export { InMemorySessionStore, createSessionId, isSessionActive, issueSession, listSessions, revokeSession, rotateSession, sessionStatus, } from './session.js';
|
|
22
|
+
export { POLICY_VERSION } from './types.js';
|
|
23
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,EACL,sBAAsB,EACtB,qBAAqB,EACrB,iBAAiB,EACjB,YAAY,EACZ,gBAAgB,GACjB,MAAM,iBAAiB,CAAC;AACzB,OAAO,EACL,kBAAkB,EAClB,cAAc,EACd,UAAU,EACV,WAAW,EACX,aAAa,EACb,WAAW,EACX,kBAAkB,EAClB,kBAAkB,EAClB,SAAS,EACT,OAAO,EACP,YAAY,EACZ,oBAAoB,EACpB,cAAc,EACd,gBAAgB,EAChB,iBAAiB,EACjB,eAAe,EACf,gBAAgB,EAChB,YAAY,EACZ,UAAU,EACV,UAAU,EACV,mBAAmB,EACnB,WAAW,EACX,SAAS,EACT,SAAS,GACV,MAAM,cAAc,CAAC;AACtB,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,YAAY,CAAC;AAC7D,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAChF,OAAO,EAAE,aAAa,EAAE,WAAW,EAAE,YAAY,EAAE,MAAM,aAAa,CAAC;AACvE,OAAO,EAAE,eAAe,EAAE,MAAM,gBAAgB,CAAC;AACjD,OAAO,EACL,oBAAoB,EACpB,eAAe,EACf,eAAe,EACf,YAAY,EACZ,YAAY,EACZ,aAAa,EACb,aAAa,EACb,aAAa,GACd,MAAM,cAAc,CAAC;AAyCtB,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC"}
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
import { type Policy, type PolicyInput } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Validates an authored policy and returns its normalised form.
|
|
4
|
+
*
|
|
5
|
+
* Throws {@link PolicyError} rather than returning a partial policy, so a
|
|
6
|
+
* malformed policy surfaces at definition time instead of as a silent on-chain
|
|
7
|
+
* mismatch later.
|
|
8
|
+
*/
|
|
9
|
+
export declare function normalizePolicy(input: PolicyInput): Policy;
|
|
10
|
+
//# sourceMappingURL=normalize.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.d.ts","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"AAEA,OAAO,EAIL,KAAK,MAAM,EACX,KAAK,WAAW,EAKjB,MAAM,YAAY,CAAC;AA6EpB;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,WAAW,GAAG,MAAM,CAkB1D"}
|
|
@@ -0,0 +1,92 @@
|
|
|
1
|
+
import { getAddress, isAddress } from 'viem';
|
|
2
|
+
import { PolicyError } from './errors.js';
|
|
3
|
+
import { POLICY_VERSION, } from './types.js';
|
|
4
|
+
// ============================================================================
|
|
5
|
+
// Authored DSL → normalised Policy.
|
|
6
|
+
//
|
|
7
|
+
// Normalisation fills every optional field with its zero/default so that the
|
|
8
|
+
// commitment hash depends on meaning, not on which fields a caller happened to
|
|
9
|
+
// spell out: `{ maxPerTx: 0n }` and `{}` hash identically. Addresses are
|
|
10
|
+
// lower-cased for the same reason (the ABI encoding already ignores case, but
|
|
11
|
+
// keeping it deterministic makes policies comparable with `===` too).
|
|
12
|
+
// ============================================================================
|
|
13
|
+
const SELECTOR_PATTERN = /^0x[0-9a-fA-F]{8}$/;
|
|
14
|
+
function normalizeAddress(value, field) {
|
|
15
|
+
if (!isAddress(value))
|
|
16
|
+
throw new PolicyError(`${field}: invalid address "${value}"`);
|
|
17
|
+
return getAddress(value).toLowerCase();
|
|
18
|
+
}
|
|
19
|
+
function normalizeSelector(value, field) {
|
|
20
|
+
if (!SELECTOR_PATTERN.test(value)) {
|
|
21
|
+
throw new PolicyError(`${field}: selector must be 4 bytes (0x + 8 hex), got "${value}"`);
|
|
22
|
+
}
|
|
23
|
+
return value.toLowerCase();
|
|
24
|
+
}
|
|
25
|
+
function normalizeNonNegative(value, field) {
|
|
26
|
+
if (value < 0n)
|
|
27
|
+
throw new PolicyError(`${field}: must be >= 0, got ${value}`);
|
|
28
|
+
return value;
|
|
29
|
+
}
|
|
30
|
+
function normalizeTokenLimit(input, field) {
|
|
31
|
+
const token = normalizeAddress(input.token, `${field}.token`);
|
|
32
|
+
return Object.freeze({
|
|
33
|
+
token,
|
|
34
|
+
maxPerTx: normalizeNonNegative(input.maxPerTx ?? 0n, `${field}.maxPerTx`),
|
|
35
|
+
maxDaily: normalizeNonNegative(input.maxDaily ?? 0n, `${field}.maxDaily`),
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
function normalizeRule(input, index) {
|
|
39
|
+
const at = `rules[${index}]`;
|
|
40
|
+
const target = normalizeAddress(input.target, `${at}.target`);
|
|
41
|
+
const selectors = (input.selectors ?? []).map((selector, i) => normalizeSelector(selector, `${at}.selectors[${i}]`));
|
|
42
|
+
const maxCalls = input.maxCalls ?? 0;
|
|
43
|
+
if (!Number.isInteger(maxCalls) || maxCalls < 0) {
|
|
44
|
+
throw new PolicyError(`${at}.maxCalls: must be a non-negative integer, got ${maxCalls}`);
|
|
45
|
+
}
|
|
46
|
+
// A token cap can only be enforced on calls made directly to the token, so a
|
|
47
|
+
// limit for any other address would be dead config — reject it loudly.
|
|
48
|
+
const tokenLimits = (input.tokenLimits ?? []).map((limit, i) => {
|
|
49
|
+
const normalized = normalizeTokenLimit(limit, `${at}.tokenLimits[${i}]`);
|
|
50
|
+
if (normalized.token !== target) {
|
|
51
|
+
throw new PolicyError(`${at}.tokenLimits[${i}]: token ${normalized.token} must equal the rule target ${target}`);
|
|
52
|
+
}
|
|
53
|
+
return normalized;
|
|
54
|
+
});
|
|
55
|
+
if (new Set(tokenLimits.map((limit) => limit.token)).size !== tokenLimits.length) {
|
|
56
|
+
throw new PolicyError(`${at}.tokenLimits: duplicate limit for the same token`);
|
|
57
|
+
}
|
|
58
|
+
return Object.freeze({
|
|
59
|
+
target,
|
|
60
|
+
selectors: Object.freeze(selectors),
|
|
61
|
+
maxPerTx: normalizeNonNegative(input.maxPerTx ?? 0n, `${at}.maxPerTx`),
|
|
62
|
+
maxDaily: normalizeNonNegative(input.maxDaily ?? 0n, `${at}.maxDaily`),
|
|
63
|
+
maxCalls,
|
|
64
|
+
tokenLimits: Object.freeze(tokenLimits),
|
|
65
|
+
});
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Validates an authored policy and returns its normalised form.
|
|
69
|
+
*
|
|
70
|
+
* Throws {@link PolicyError} rather than returning a partial policy, so a
|
|
71
|
+
* malformed policy surfaces at definition time instead of as a silent on-chain
|
|
72
|
+
* mismatch later.
|
|
73
|
+
*/
|
|
74
|
+
export function normalizePolicy(input) {
|
|
75
|
+
const rules = input.rules ?? [];
|
|
76
|
+
if (rules.length === 0)
|
|
77
|
+
throw new PolicyError('policy must declare at least one rule');
|
|
78
|
+
const validAfter = normalizeNonNegative(input.validAfter ?? 0n, 'validAfter');
|
|
79
|
+
const validUntil = normalizeNonNegative(input.validUntil ?? 0n, 'validUntil');
|
|
80
|
+
// validUntil = 0 is the sentinel for "never expires", so only a non-zero
|
|
81
|
+
// value has to be strictly after validAfter.
|
|
82
|
+
if (validUntil !== 0n && validUntil <= validAfter) {
|
|
83
|
+
throw new PolicyError(`validUntil (${validUntil}) must be greater than validAfter (${validAfter})`);
|
|
84
|
+
}
|
|
85
|
+
return Object.freeze({
|
|
86
|
+
version: POLICY_VERSION,
|
|
87
|
+
validAfter,
|
|
88
|
+
validUntil,
|
|
89
|
+
rules: Object.freeze(rules.map(normalizeRule)),
|
|
90
|
+
});
|
|
91
|
+
}
|
|
92
|
+
//# sourceMappingURL=normalize.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"normalize.js","sourceRoot":"","sources":["../src/normalize.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
import type { Address, Hex, Policy, PolicyInput } from './types.js';
|
|
2
|
+
/** Lifecycle state of a session at a given time, derived from its record. */
|
|
3
|
+
export type SessionStatus = 'pending' | 'active' | 'expired' | 'revoked';
|
|
4
|
+
/** One authorization grant on an account. */
|
|
5
|
+
export interface Session {
|
|
6
|
+
/** Unique bytes32 identifier (random unless supplied at issue time). */
|
|
7
|
+
readonly id: Hex;
|
|
8
|
+
/** Smart account the grant applies to (lower-cased). */
|
|
9
|
+
readonly account: Address;
|
|
10
|
+
/** The normalised policy this session grants. */
|
|
11
|
+
readonly policy: Policy;
|
|
12
|
+
/** `keccak256(encodePolicy(policy))` — the value the on-chain carrier stores. */
|
|
13
|
+
readonly commitment: Hex;
|
|
14
|
+
/** Unix seconds the session record was created. */
|
|
15
|
+
readonly createdAt: bigint;
|
|
16
|
+
/** Unix seconds the session was revoked; absent while it has not been. */
|
|
17
|
+
readonly revokedAt?: bigint;
|
|
18
|
+
/** Id of the session this one replaced, when created by a rotation. */
|
|
19
|
+
readonly rotatedFrom?: Hex;
|
|
20
|
+
/** Id of the session that replaced this one, when revoked by a rotation. */
|
|
21
|
+
readonly rotatedTo?: Hex;
|
|
22
|
+
}
|
|
23
|
+
/** A session together with its status at a given time. */
|
|
24
|
+
export interface SessionView extends Session {
|
|
25
|
+
readonly status: SessionStatus;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Persistence port for sessions. Async by design so a database-backed store can
|
|
29
|
+
* implement it directly; {@link InMemorySessionStore} is the reference.
|
|
30
|
+
*/
|
|
31
|
+
export interface SessionStore {
|
|
32
|
+
/** Inserts or replaces the record for `session.id`. */
|
|
33
|
+
save(session: Session): Promise<void>;
|
|
34
|
+
/** The record for `id`, or `undefined` when unknown. */
|
|
35
|
+
get(id: Hex): Promise<Session | undefined>;
|
|
36
|
+
/** Every session of `account`, in store order. */
|
|
37
|
+
listByAccount(account: Address): Promise<readonly Session[]>;
|
|
38
|
+
}
|
|
39
|
+
/** Minimal in-memory {@link SessionStore}; not durable, intended for tests and single-process use. */
|
|
40
|
+
export declare class InMemorySessionStore implements SessionStore {
|
|
41
|
+
#private;
|
|
42
|
+
save(session: Session): Promise<void>;
|
|
43
|
+
get(id: Hex): Promise<Session | undefined>;
|
|
44
|
+
listByAccount(account: Address): Promise<readonly Session[]>;
|
|
45
|
+
}
|
|
46
|
+
export interface IssueSessionInput {
|
|
47
|
+
readonly account: Address;
|
|
48
|
+
readonly policy: PolicyInput;
|
|
49
|
+
/** Current unix time in seconds. */
|
|
50
|
+
readonly now: bigint;
|
|
51
|
+
/** Explicit bytes32 id; a random one is generated when omitted. */
|
|
52
|
+
readonly id?: Hex;
|
|
53
|
+
}
|
|
54
|
+
export interface RotateSessionInput {
|
|
55
|
+
/** Policy for the successor session; usually the same as the previous one. */
|
|
56
|
+
readonly policy: PolicyInput;
|
|
57
|
+
/** Current unix time in seconds. */
|
|
58
|
+
readonly now: bigint;
|
|
59
|
+
/** Explicit bytes32 id for the successor; a random one is generated when omitted. */
|
|
60
|
+
readonly id?: Hex;
|
|
61
|
+
}
|
|
62
|
+
/** The pair of records a rotation produces. */
|
|
63
|
+
export interface Rotation {
|
|
64
|
+
/** The predecessor, now revoked and linked forward. */
|
|
65
|
+
readonly previous: Session;
|
|
66
|
+
/** The successor, linked back to the predecessor. */
|
|
67
|
+
readonly next: Session;
|
|
68
|
+
}
|
|
69
|
+
/** A fresh random bytes32 session id. */
|
|
70
|
+
export declare function createSessionId(): Hex;
|
|
71
|
+
/**
|
|
72
|
+
* Creates an active session for `input.account`.
|
|
73
|
+
*
|
|
74
|
+
* The policy is normalised (so the stored commitment is canonical) and the id
|
|
75
|
+
* must be unused. Throws {@link SessionError} on a duplicate id or a bad
|
|
76
|
+
* account, and {@link PolicyError} on a malformed policy.
|
|
77
|
+
*/
|
|
78
|
+
export declare function issueSession(store: SessionStore, input: IssueSessionInput): Promise<Session>;
|
|
79
|
+
/**
|
|
80
|
+
* Revokes a session. The record is kept (append-only) with `revokedAt` set, so
|
|
81
|
+
* a revocation is auditable and idempotent failures are explicit. Revoking an
|
|
82
|
+
* already-revoked or unknown session throws {@link SessionError}.
|
|
83
|
+
*/
|
|
84
|
+
export declare function revokeSession(store: SessionStore, id: Hex, now: bigint): Promise<Session>;
|
|
85
|
+
/**
|
|
86
|
+
* Rotates a session: issues a successor and revokes the predecessor in one
|
|
87
|
+
* step, cross-linking the two. This is the off-chain half of the migration
|
|
88
|
+
* "uninstall old module + invalidate nonce + install new" (docs/KEEL_PLAN.md
|
|
89
|
+
* §7.5); the successor inherits the predecessor's account.
|
|
90
|
+
*/
|
|
91
|
+
export declare function rotateSession(store: SessionStore, id: Hex, input: RotateSessionInput): Promise<Rotation>;
|
|
92
|
+
/** Derives a session's status from its record at time `now`. */
|
|
93
|
+
export declare function sessionStatus(session: Session, now: bigint): SessionStatus;
|
|
94
|
+
/** Whether the session is usable at time `now` (its status is `active`). */
|
|
95
|
+
export declare function isSessionActive(session: Session, now: bigint): boolean;
|
|
96
|
+
/** Lists an account's sessions with their status at `now`, in store order. */
|
|
97
|
+
export declare function listSessions(store: SessionStore, account: Address, now: bigint): Promise<readonly SessionView[]>;
|
|
98
|
+
//# sourceMappingURL=session.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.d.ts","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|
package/dist/session.js
ADDED
|
@@ -0,0 +1,128 @@
|
|
|
1
|
+
import { bytesToHex, getAddress, isAddress } from 'viem';
|
|
2
|
+
import { policyCommitment } from './commitment.js';
|
|
3
|
+
import { SessionError } from './errors.js';
|
|
4
|
+
import { normalizePolicy } from './normalize.js';
|
|
5
|
+
/** Minimal in-memory {@link SessionStore}; not durable, intended for tests and single-process use. */
|
|
6
|
+
export class InMemorySessionStore {
|
|
7
|
+
#sessions = new Map();
|
|
8
|
+
async save(session) {
|
|
9
|
+
this.#sessions.set(session.id, session);
|
|
10
|
+
}
|
|
11
|
+
async get(id) {
|
|
12
|
+
return this.#sessions.get(id);
|
|
13
|
+
}
|
|
14
|
+
async listByAccount(account) {
|
|
15
|
+
const wanted = account.toLowerCase();
|
|
16
|
+
return [...this.#sessions.values()].filter((session) => session.account === wanted);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const SESSION_ID_PATTERN = /^0x[0-9a-fA-F]{64}$/;
|
|
20
|
+
/** A fresh random bytes32 session id. */
|
|
21
|
+
export function createSessionId() {
|
|
22
|
+
const bytes = new Uint8Array(32);
|
|
23
|
+
crypto.getRandomValues(bytes);
|
|
24
|
+
return bytesToHex(bytes);
|
|
25
|
+
}
|
|
26
|
+
function normalizeSessionId(value) {
|
|
27
|
+
if (!SESSION_ID_PATTERN.test(value)) {
|
|
28
|
+
throw new SessionError(`id: must be 32 bytes (0x + 64 hex), got "${value}"`);
|
|
29
|
+
}
|
|
30
|
+
return value.toLowerCase();
|
|
31
|
+
}
|
|
32
|
+
function normalizeAccount(value) {
|
|
33
|
+
if (!isAddress(value))
|
|
34
|
+
throw new SessionError(`account: invalid address "${value}"`);
|
|
35
|
+
return getAddress(value).toLowerCase();
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Creates an active session for `input.account`.
|
|
39
|
+
*
|
|
40
|
+
* The policy is normalised (so the stored commitment is canonical) and the id
|
|
41
|
+
* must be unused. Throws {@link SessionError} on a duplicate id or a bad
|
|
42
|
+
* account, and {@link PolicyError} on a malformed policy.
|
|
43
|
+
*/
|
|
44
|
+
export async function issueSession(store, input) {
|
|
45
|
+
const id = input.id === undefined ? createSessionId() : normalizeSessionId(input.id);
|
|
46
|
+
if (await store.get(id))
|
|
47
|
+
throw new SessionError(`session ${id} already exists`);
|
|
48
|
+
const account = normalizeAccount(input.account);
|
|
49
|
+
const policy = normalizePolicy(input.policy);
|
|
50
|
+
const session = Object.freeze({
|
|
51
|
+
id,
|
|
52
|
+
account,
|
|
53
|
+
policy,
|
|
54
|
+
commitment: policyCommitment(policy),
|
|
55
|
+
createdAt: input.now,
|
|
56
|
+
});
|
|
57
|
+
await store.save(session);
|
|
58
|
+
return session;
|
|
59
|
+
}
|
|
60
|
+
/**
|
|
61
|
+
* Revokes a session. The record is kept (append-only) with `revokedAt` set, so
|
|
62
|
+
* a revocation is auditable and idempotent failures are explicit. Revoking an
|
|
63
|
+
* already-revoked or unknown session throws {@link SessionError}.
|
|
64
|
+
*/
|
|
65
|
+
export async function revokeSession(store, id, now) {
|
|
66
|
+
const session = await store.get(id);
|
|
67
|
+
if (!session)
|
|
68
|
+
throw new SessionError(`session ${id} not found`);
|
|
69
|
+
if (session.revokedAt !== undefined)
|
|
70
|
+
throw new SessionError(`session ${id} is already revoked`);
|
|
71
|
+
const revoked = Object.freeze({ ...session, revokedAt: now });
|
|
72
|
+
await store.save(revoked);
|
|
73
|
+
return revoked;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Rotates a session: issues a successor and revokes the predecessor in one
|
|
77
|
+
* step, cross-linking the two. This is the off-chain half of the migration
|
|
78
|
+
* "uninstall old module + invalidate nonce + install new" (docs/KEEL_PLAN.md
|
|
79
|
+
* §7.5); the successor inherits the predecessor's account.
|
|
80
|
+
*/
|
|
81
|
+
export async function rotateSession(store, id, input) {
|
|
82
|
+
const previous = await store.get(id);
|
|
83
|
+
if (!previous)
|
|
84
|
+
throw new SessionError(`session ${id} not found`);
|
|
85
|
+
if (previous.revokedAt !== undefined)
|
|
86
|
+
throw new SessionError(`session ${id} is already revoked`);
|
|
87
|
+
const nextId = input.id === undefined ? createSessionId() : normalizeSessionId(input.id);
|
|
88
|
+
if (nextId === id)
|
|
89
|
+
throw new SessionError('a rotated session must have a new id');
|
|
90
|
+
if (await store.get(nextId))
|
|
91
|
+
throw new SessionError(`session ${nextId} already exists`);
|
|
92
|
+
const policy = normalizePolicy(input.policy);
|
|
93
|
+
const next = Object.freeze({
|
|
94
|
+
id: nextId,
|
|
95
|
+
account: previous.account,
|
|
96
|
+
policy,
|
|
97
|
+
commitment: policyCommitment(policy),
|
|
98
|
+
createdAt: input.now,
|
|
99
|
+
rotatedFrom: id,
|
|
100
|
+
});
|
|
101
|
+
const revoked = Object.freeze({ ...previous, revokedAt: input.now, rotatedTo: nextId });
|
|
102
|
+
// Persist the successor first: a crash between the two writes then leaves a
|
|
103
|
+
// usable successor without a back-link, rather than a revoked original with
|
|
104
|
+
// no replacement.
|
|
105
|
+
await store.save(next);
|
|
106
|
+
await store.save(revoked);
|
|
107
|
+
return { previous: revoked, next };
|
|
108
|
+
}
|
|
109
|
+
/** Derives a session's status from its record at time `now`. */
|
|
110
|
+
export function sessionStatus(session, now) {
|
|
111
|
+
if (session.revokedAt !== undefined)
|
|
112
|
+
return 'revoked';
|
|
113
|
+
if (now < session.policy.validAfter)
|
|
114
|
+
return 'pending';
|
|
115
|
+
if (session.policy.validUntil !== 0n && now > session.policy.validUntil)
|
|
116
|
+
return 'expired';
|
|
117
|
+
return 'active';
|
|
118
|
+
}
|
|
119
|
+
/** Whether the session is usable at time `now` (its status is `active`). */
|
|
120
|
+
export function isSessionActive(session, now) {
|
|
121
|
+
return sessionStatus(session, now) === 'active';
|
|
122
|
+
}
|
|
123
|
+
/** Lists an account's sessions with their status at `now`, in store order. */
|
|
124
|
+
export async function listSessions(store, account, now) {
|
|
125
|
+
const sessions = await store.listByAccount(normalizeAccount(account));
|
|
126
|
+
return sessions.map((session) => Object.freeze({ ...session, status: sessionStatus(session, now) }));
|
|
127
|
+
}
|
|
128
|
+
//# sourceMappingURL=session.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"session.js","sourceRoot":"","sources":["../src/session.ts"],"names":[],"mappings":"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"}
|