@dev.sail.money/sailor 1.3.0-226 → 1.3.0-227
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/examples/custom-mandate/.sail/contracts/interfaces/IPermission.sol +4 -0
- package/examples/custom-mandate/README.md +4 -1
- package/examples/permissions/interfaces/IBatchPermission.sol +2 -0
- package/examples/permissions/interfaces/IPermission.sol +4 -0
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +53 -8
- package/packages/sdk/dist/intelligence.d.ts +1 -1
- package/packages/sdk/dist/intelligence.js +1 -1
- package/templates/default/.agents/skills/sail-mandates/references/examples-index.md +2 -2
- package/templates/default/test/BoundedCallPermission.t.sol +2 -1
|
@@ -10,6 +10,10 @@ struct Context {
|
|
|
10
10
|
uint256 value;
|
|
11
11
|
uint256 blockTimestamp;
|
|
12
12
|
uint256 blockNumber;
|
|
13
|
+
/// @dev Kernel registrationEpoch(account, permission) at dispatch. Configurable
|
|
14
|
+
/// permissions fail closed on a mismatch with the epoch stamped at configure()
|
|
15
|
+
/// time; permissions without post-deploy configuration can ignore it.
|
|
16
|
+
uint256 configEpoch;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
interface IPermission {
|
|
@@ -36,11 +36,14 @@ struct Context {
|
|
|
36
36
|
uint256 value; // msg.value (native asset) sent with the call
|
|
37
37
|
uint256 blockTimestamp; // block.timestamp at evaluation
|
|
38
38
|
uint256 blockNumber; // block.number at evaluation
|
|
39
|
+
uint256 configEpoch; // kernel registrationEpoch(account, permission) at dispatch;
|
|
40
|
+
// ignore it unless your permission takes post-deploy configuration
|
|
39
41
|
}
|
|
40
42
|
```
|
|
41
43
|
|
|
42
44
|
- `evaluate` — your policy. Return `true` to permit the call, `false` to block it. Runs under a
|
|
43
|
-
|
|
45
|
+
150,000-gas `staticcall` (`SailKernel.PERMISSION_GAS_CAP`); a revert or gas overage is treated
|
|
46
|
+
as `false`.
|
|
44
47
|
- `discriminator` — a stable `bytes32` name for your permission (e.g. `keccak256("MyMandate")`).
|
|
45
48
|
|
|
46
49
|
Keep all policy parameters constructor-configured so each deployment is a complete, reviewable
|
|
@@ -26,6 +26,8 @@ struct BatchContext {
|
|
|
26
26
|
bytes32 batchHash; // keccak256(abi.encode(calls)) — stable id for the exact sequence
|
|
27
27
|
uint256 blockTimestamp; // block.timestamp at dispatch
|
|
28
28
|
uint256 blockNumber; // block.number at dispatch
|
|
29
|
+
uint256 configEpoch; // kernel registrationEpoch(account, permission) at dispatch —
|
|
30
|
+
// same fail-closed freshness check as Context.configEpoch
|
|
29
31
|
}
|
|
30
32
|
|
|
31
33
|
interface IBatchPermission {
|
|
@@ -10,6 +10,10 @@ struct Context {
|
|
|
10
10
|
uint256 value;
|
|
11
11
|
uint256 blockTimestamp;
|
|
12
12
|
uint256 blockNumber;
|
|
13
|
+
/// @dev Kernel registrationEpoch(account, permission) at dispatch. Configurable
|
|
14
|
+
/// permissions fail closed on a mismatch with the epoch stamped at configure()
|
|
15
|
+
/// time; permissions without post-deploy configuration can ignore it.
|
|
16
|
+
uint256 configEpoch;
|
|
13
17
|
}
|
|
14
18
|
|
|
15
19
|
interface IPermission {
|
package/package.json
CHANGED
|
@@ -40545,15 +40545,36 @@ var IPERMISSION_ABI = [
|
|
|
40545
40545
|
{ name: "selector", type: "bytes4" },
|
|
40546
40546
|
{ name: "value", type: "uint256" },
|
|
40547
40547
|
{ name: "blockTimestamp", type: "uint256" },
|
|
40548
|
-
{ name: "blockNumber", type: "uint256" }
|
|
40548
|
+
{ name: "blockNumber", type: "uint256" },
|
|
40549
|
+
{ name: "configEpoch", type: "uint256" }
|
|
40549
40550
|
]
|
|
40550
40551
|
}
|
|
40551
40552
|
],
|
|
40552
40553
|
outputs: [{ type: "bool" }]
|
|
40553
40554
|
}
|
|
40554
40555
|
];
|
|
40556
|
+
var REGISTRATION_EPOCH_ABI2 = [
|
|
40557
|
+
{
|
|
40558
|
+
type: "function",
|
|
40559
|
+
name: "registrationEpoch",
|
|
40560
|
+
stateMutability: "view",
|
|
40561
|
+
inputs: [
|
|
40562
|
+
{ name: "account", type: "address" },
|
|
40563
|
+
{ name: "permission", type: "address" }
|
|
40564
|
+
],
|
|
40565
|
+
outputs: [{ type: "uint256" }]
|
|
40566
|
+
}
|
|
40567
|
+
];
|
|
40568
|
+
async function readRegistrationEpoch(publicClient, kernel, account2, permission) {
|
|
40569
|
+
return await publicClient.readContract({
|
|
40570
|
+
address: kernel,
|
|
40571
|
+
abi: REGISTRATION_EPOCH_ABI2,
|
|
40572
|
+
functionName: "registrationEpoch",
|
|
40573
|
+
args: [account2, permission]
|
|
40574
|
+
});
|
|
40575
|
+
}
|
|
40555
40576
|
function buildPermissionContext(params) {
|
|
40556
|
-
const { account: account2, manager, call: call2, blockInfo } = params;
|
|
40577
|
+
const { account: account2, manager, call: call2, blockInfo, configEpoch } = params;
|
|
40557
40578
|
const selector = call2.data.length >= 10 ? call2.data.slice(0, 10) : "0x00000000";
|
|
40558
40579
|
return {
|
|
40559
40580
|
account: account2,
|
|
@@ -40564,12 +40585,23 @@ function buildPermissionContext(params) {
|
|
|
40564
40585
|
selector,
|
|
40565
40586
|
value: call2.value,
|
|
40566
40587
|
blockTimestamp: blockInfo.timestamp,
|
|
40567
|
-
blockNumber: blockInfo.number
|
|
40588
|
+
blockNumber: blockInfo.number,
|
|
40589
|
+
configEpoch
|
|
40568
40590
|
};
|
|
40569
40591
|
}
|
|
40570
40592
|
async function probePermissionForCall(params) {
|
|
40571
|
-
const { publicClient, permission, account: account2, manager, call: call2, blockInfo } = params;
|
|
40572
|
-
|
|
40593
|
+
const { publicClient, kernel, permission, account: account2, manager, call: call2, blockInfo } = params;
|
|
40594
|
+
let configEpoch;
|
|
40595
|
+
try {
|
|
40596
|
+
configEpoch = await readRegistrationEpoch(publicClient, kernel, account2, permission);
|
|
40597
|
+
} catch (err) {
|
|
40598
|
+
return {
|
|
40599
|
+
accepted: false,
|
|
40600
|
+
reverted: true,
|
|
40601
|
+
error: `could not read registrationEpoch from kernel ${kernel} (${err.message.split("\n")[0]})`
|
|
40602
|
+
};
|
|
40603
|
+
}
|
|
40604
|
+
const ctx = buildPermissionContext({ account: account2, manager, call: call2, blockInfo, configEpoch });
|
|
40573
40605
|
try {
|
|
40574
40606
|
const accepted = await publicClient.readContract({
|
|
40575
40607
|
address: permission,
|
|
@@ -40583,10 +40615,11 @@ async function probePermissionForCall(params) {
|
|
|
40583
40615
|
}
|
|
40584
40616
|
}
|
|
40585
40617
|
async function resolvePermissionForCall(params) {
|
|
40586
|
-
const { publicClient, account: account2, manager, call: call2, registeredPermissions, blockInfo } = params;
|
|
40587
|
-
const ctx = buildPermissionContext({ account: account2, manager, call: call2, blockInfo });
|
|
40618
|
+
const { publicClient, kernel, account: account2, manager, call: call2, registeredPermissions, blockInfo } = params;
|
|
40588
40619
|
for (const permission of registeredPermissions) {
|
|
40589
40620
|
try {
|
|
40621
|
+
const configEpoch = await readRegistrationEpoch(publicClient, kernel, account2, permission);
|
|
40622
|
+
const ctx = buildPermissionContext({ account: account2, manager, call: call2, blockInfo, configEpoch });
|
|
40590
40623
|
const accepted = await publicClient.readContract({
|
|
40591
40624
|
address: permission,
|
|
40592
40625
|
abi: IPERMISSION_ABI,
|
|
@@ -40656,7 +40689,10 @@ async function probePassThrough(pc, permission, account2) {
|
|
|
40656
40689
|
selector: PROBE_SELECTOR,
|
|
40657
40690
|
value: 0n,
|
|
40658
40691
|
blockTimestamp: 0n,
|
|
40659
|
-
blockNumber: 0n
|
|
40692
|
+
blockNumber: 0n,
|
|
40693
|
+
// Pass-through probes only run on conjunctive kernels, which predate
|
|
40694
|
+
// registrationEpoch — there is no live epoch to read, so probe with 0.
|
|
40695
|
+
configEpoch: 0n
|
|
40660
40696
|
}
|
|
40661
40697
|
]
|
|
40662
40698
|
});
|
|
@@ -40952,6 +40988,10 @@ struct Context {
|
|
|
40952
40988
|
uint256 value; // native ETH forwarded (wei)
|
|
40953
40989
|
uint256 blockTimestamp; // block.timestamp at dispatch
|
|
40954
40990
|
uint256 blockNumber; // block.number at dispatch
|
|
40991
|
+
uint256 configEpoch; // kernel registrationEpoch(account, permission) at dispatch;
|
|
40992
|
+
// configurable permissions fail closed on a mismatch with the
|
|
40993
|
+
// epoch stamped at configure() time. Custom permissions that
|
|
40994
|
+
// take no post-deploy configuration can ignore it.
|
|
40955
40995
|
}
|
|
40956
40996
|
|
|
40957
40997
|
/// @title IPermission
|
|
@@ -40987,6 +41027,8 @@ struct BatchContext {
|
|
|
40987
41027
|
bytes32 batchHash; // keccak256(abi.encode(calls)) \u2014 stable id for the exact sequence
|
|
40988
41028
|
uint256 blockTimestamp; // block.timestamp at dispatch
|
|
40989
41029
|
uint256 blockNumber; // block.number at dispatch
|
|
41030
|
+
uint256 configEpoch; // kernel registrationEpoch(account, permission) at dispatch \u2014
|
|
41031
|
+
// same fail-closed freshness check as Context.configEpoch
|
|
40990
41032
|
}
|
|
40991
41033
|
|
|
40992
41034
|
/// @title IBatchPermission
|
|
@@ -44364,6 +44406,7 @@ async function mandateSimulate(options) {
|
|
|
44364
44406
|
const [probe, codeCheck] = await Promise.all([
|
|
44365
44407
|
probePermissionForCall({
|
|
44366
44408
|
publicClient: pc,
|
|
44409
|
+
kernel: project.contracts.kernel,
|
|
44367
44410
|
permission,
|
|
44368
44411
|
account: account2,
|
|
44369
44412
|
manager,
|
|
@@ -45323,6 +45366,8 @@ Configure the chain in the SDK chain registry or set KERNEL_ADDRESS in .sail/.en
|
|
|
45323
45366
|
} else {
|
|
45324
45367
|
permission = await resolvePermissionForCall({
|
|
45325
45368
|
publicClient,
|
|
45369
|
+
kernel,
|
|
45370
|
+
// narrowed: runCommand validates kernel before runTick runs
|
|
45326
45371
|
account: accountAddr,
|
|
45327
45372
|
manager: agentManager.address,
|
|
45328
45373
|
call: firstCall,
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Do not edit manually — run `pnpm build` to regenerate.
|
|
6
6
|
*
|
|
7
7
|
* Spec version : 1.2.1
|
|
8
|
-
* Generated at : 2026-07-
|
|
8
|
+
* Generated at : 2026-07-02T16:26:37.498Z
|
|
9
9
|
*/
|
|
10
10
|
export declare const SAIL_INTELLIGENCE_BASE_URL = "https://api.sail.money";
|
|
11
11
|
export declare const SAIL_INTELLIGENCE_DOCS_URL = "https://api.sail.money/docs";
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Do not edit manually — run `pnpm build` to regenerate.
|
|
6
6
|
*
|
|
7
7
|
* Spec version : 1.2.1
|
|
8
|
-
* Generated at : 2026-07-
|
|
8
|
+
* Generated at : 2026-07-02T16:26:37.498Z
|
|
9
9
|
*/
|
|
10
10
|
export const SAIL_INTELLIGENCE_BASE_URL = "https://api.sail.money";
|
|
11
11
|
export const SAIL_INTELLIGENCE_DOCS_URL = "https://api.sail.money/docs";
|
|
@@ -29,6 +29,6 @@ Permissions only bound on-chain actions: venues with off-chain order matching (P
|
|
|
29
29
|
|
|
30
30
|
## Interfaces (vendored in `examples/permissions/interfaces/`)
|
|
31
31
|
|
|
32
|
-
- `IPermission.evaluate(bytes txData, Context ctx) → bool`; Context: `account, manager, submitter, target, selector, value, blockTimestamp, blockNumber`.
|
|
33
|
-
- `IBatchPermission.evaluateBatch(Call[] calls, BatchContext ctx) → bool` + `isBatchPermission() → true`; Call: `(target, value, data)`; BatchContext: `account, manager, submitter, permission, batchHash, blockTimestamp, blockNumber`.
|
|
32
|
+
- `IPermission.evaluate(bytes txData, Context ctx) → bool`; Context: `account, manager, submitter, target, selector, value, blockTimestamp, blockNumber, configEpoch`.
|
|
33
|
+
- `IBatchPermission.evaluateBatch(Call[] calls, BatchContext ctx) → bool` + `isBatchPermission() → true`; Call: `(target, value, data)`; BatchContext: `account, manager, submitter, permission, batchHash, blockTimestamp, blockNumber, configEpoch`.
|
|
34
34
|
- `SailCalldata.sol`: bounded slot-indexed calldata readers (`hasParams`, `asAddress`, `asUint256`, …) — use these instead of `abi.decode` to avoid wrong-offset reads.
|
|
@@ -39,7 +39,8 @@ contract BoundedCallPermissionTest {
|
|
|
39
39
|
selector: selector,
|
|
40
40
|
value: value,
|
|
41
41
|
blockTimestamp: block.timestamp,
|
|
42
|
-
blockNumber: block.number
|
|
42
|
+
blockNumber: block.number,
|
|
43
|
+
configEpoch: 0 // BoundedCallPermission takes no post-deploy config; value unused
|
|
43
44
|
});
|
|
44
45
|
}
|
|
45
46
|
|