@dev.sail.money/sailor 1.2.1-86 → 1.2.1-87
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 +1 -1
- package/examples/custom-mandate/README.md +8 -1
- package/examples/permissions/README.md +3 -0
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +140 -1
- package/packages/sdk/dist/intelligence.d.ts +1 -1
- package/packages/sdk/dist/intelligence.js +1 -1
- package/templates/default/.agents/skills/sail-mandates/SKILL.md +7 -2
- package/templates/default/.agents/skills/sail-transactions/SKILL.md +1 -1
package/README.md
CHANGED
|
@@ -143,7 +143,7 @@ sailor onboard --new-sma # deploy SMA and optionally attach a mandate
|
|
|
143
143
|
sailor mandate simulate # probe a permission off-chain (no gas) before registering
|
|
144
144
|
sailor mandate sign # sign the mandate — reconciles against live on-chain state
|
|
145
145
|
sailor mandate deploy # deploy a Foundry-compiled permission contract
|
|
146
|
-
sailor mandate attach # register
|
|
146
|
+
sailor mandate attach # register a deployed permission on an SMA (or a comma-separated list, in one signature)
|
|
147
147
|
|
|
148
148
|
# Agent operation
|
|
149
149
|
sailor run --once # single tick — confirm it works before automating
|
|
@@ -64,7 +64,14 @@ sailor mandate deploy --contract <Name> # prints the deployed address
|
|
|
64
64
|
sailor mandate attach --address <deployedAddress> --sma <SMA>
|
|
65
65
|
```
|
|
66
66
|
|
|
67
|
-
|
|
67
|
+
To attach several permissions, deploy each one first, then register them all in a single
|
|
68
|
+
signature by passing a comma-separated list:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
sailor mandate attach --address <addr1>,<addr2>,<addr3> --sma <SMA>
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
These attach commands open the browser signing station so the owner authorizes the registration
|
|
68
75
|
(EIP-712 `RegisterPermission`); the agent submits the on-chain transaction.
|
|
69
76
|
|
|
70
77
|
## Prerequisites
|
|
@@ -74,6 +74,9 @@ To deploy within a Sailor project (copy the .sol file to `mandates/` first):
|
|
|
74
74
|
sailor mandate deploy --contract <Name> --args '[...]' --attach --sma <SMA>
|
|
75
75
|
```
|
|
76
76
|
|
|
77
|
+
For several permissions, deploy each one (without `--attach`), then register them together in
|
|
78
|
+
one signature: `sailor mandate attach --address <addr1>,<addr2> --sma <SMA>`.
|
|
79
|
+
|
|
77
80
|
## Verify before you authorize
|
|
78
81
|
|
|
79
82
|
Prove a permission accepts the calls you want and rejects the ones you don't — before paying
|
package/package.json
CHANGED
|
@@ -42343,6 +42343,10 @@ async function runAttach(project, channel, options) {
|
|
|
42343
42343
|
if (!isAddress(options.sma, { strict: false })) throw new Error(`Invalid --sma address: ${options.sma}`);
|
|
42344
42344
|
const sma = getAddress(options.sma);
|
|
42345
42345
|
const store = new MandateStore();
|
|
42346
|
+
if (options.address.includes(",")) {
|
|
42347
|
+
await runAttachBatch(project, channel, options, sma, store);
|
|
42348
|
+
return;
|
|
42349
|
+
}
|
|
42346
42350
|
const tracked = store.find(options.address);
|
|
42347
42351
|
const rawAddress = tracked?.address ?? options.address;
|
|
42348
42352
|
if (!isAddress(rawAddress, { strict: false })) {
|
|
@@ -42370,6 +42374,21 @@ async function runAttach(project, channel, options) {
|
|
|
42370
42374
|
attached: { sma, mandate: mandateAddress, txHash }
|
|
42371
42375
|
});
|
|
42372
42376
|
}
|
|
42377
|
+
async function runAttachBatch(project, channel, options, sma, store) {
|
|
42378
|
+
const json = !!options.json;
|
|
42379
|
+
const permissions = [...new Set(parseAddressList(options.address, "--address"))];
|
|
42380
|
+
const publicClient = publicClientFor(project);
|
|
42381
|
+
announceSigningUrl(json);
|
|
42382
|
+
const txHash = await attachBatchToSma(project, channel, publicClient, sma, permissions, json);
|
|
42383
|
+
for (const permission of permissions) {
|
|
42384
|
+
if (store.find(permission)) store.recordAttachment(permission, { sma, txHash });
|
|
42385
|
+
}
|
|
42386
|
+
emit(json, () => {
|
|
42387
|
+
}, {
|
|
42388
|
+
status: "ok",
|
|
42389
|
+
attached: permissions.map((mandate2) => ({ sma, mandate: mandate2, txHash }))
|
|
42390
|
+
});
|
|
42391
|
+
}
|
|
42373
42392
|
async function mandateRevoke(options) {
|
|
42374
42393
|
const project = requireProject();
|
|
42375
42394
|
const channel = await createSigningChannel(process.cwd());
|
|
@@ -42559,6 +42578,123 @@ async function attachToSma(project, channel, publicClient, sma, mandate2, label,
|
|
|
42559
42578
|
});
|
|
42560
42579
|
return txHash;
|
|
42561
42580
|
}
|
|
42581
|
+
async function attachBatchToSma(project, channel, publicClient, sma, permissions, json = false) {
|
|
42582
|
+
const say = (fn) => {
|
|
42583
|
+
if (!json) fn();
|
|
42584
|
+
};
|
|
42585
|
+
const agentSigner = await loadManagerSigner2();
|
|
42586
|
+
const registered = await publicClient.readContract({
|
|
42587
|
+
address: project.contracts.kernel,
|
|
42588
|
+
abi: SailKernelAbi,
|
|
42589
|
+
functionName: "registered",
|
|
42590
|
+
args: [sma]
|
|
42591
|
+
});
|
|
42592
|
+
if (!registered) {
|
|
42593
|
+
throw new Error(`SMA ${sma} is not registered with SailKernel; cannot register permissions.`);
|
|
42594
|
+
}
|
|
42595
|
+
const kernelConfig = await publicClient.readContract({
|
|
42596
|
+
address: project.contracts.kernel,
|
|
42597
|
+
abi: SailKernelAbi,
|
|
42598
|
+
functionName: "configs",
|
|
42599
|
+
args: [sma]
|
|
42600
|
+
});
|
|
42601
|
+
const permissionSigner = kernelConfig[0];
|
|
42602
|
+
const caps = await detectKernelCapabilities(publicClient, project.contracts.kernel, {
|
|
42603
|
+
chainId: project.chainId
|
|
42604
|
+
});
|
|
42605
|
+
if (caps.dispatchModel !== "selective") {
|
|
42606
|
+
throw new Error(
|
|
42607
|
+
`Batch attach requires a selective kernel, but ${project.contracts.kernel} reports dispatchModel="${caps.dispatchModel}". Attach permissions one at a time instead (sailor mandate attach --address <one> --sma ${sma}).`
|
|
42608
|
+
);
|
|
42609
|
+
}
|
|
42610
|
+
const nonce = await publicClient.readContract({
|
|
42611
|
+
address: project.contracts.kernel,
|
|
42612
|
+
abi: SailKernelAbi,
|
|
42613
|
+
functionName: "signerNonces",
|
|
42614
|
+
args: [sma]
|
|
42615
|
+
});
|
|
42616
|
+
const deadline = BigInt(Math.floor(Date.now() / 1e3) + 600);
|
|
42617
|
+
const typedData = buildRegisterPermissionsBatchTypedData({
|
|
42618
|
+
chainId: project.chainId,
|
|
42619
|
+
kernel: project.contracts.kernel,
|
|
42620
|
+
account: sma,
|
|
42621
|
+
permissions,
|
|
42622
|
+
nonce,
|
|
42623
|
+
deadline
|
|
42624
|
+
});
|
|
42625
|
+
say(
|
|
42626
|
+
() => console.log(
|
|
42627
|
+
`
|
|
42628
|
+
Attaching ${permissions.length} permissions in one signature \u2014 the mandate signer (${permissionSigner}) signs in the browser\u2026`
|
|
42629
|
+
)
|
|
42630
|
+
);
|
|
42631
|
+
const response = await channel.requestSignature({
|
|
42632
|
+
type: "typed-data",
|
|
42633
|
+
kind: "register-permission",
|
|
42634
|
+
title: `Authorize ${permissions.length} permissions`,
|
|
42635
|
+
description: `Sign once to authorize ${permissions.length} permissions on your SMA. The agent submits the registration transaction and pays gas plus the registration fee.`,
|
|
42636
|
+
chainId: project.chainId,
|
|
42637
|
+
details: [
|
|
42638
|
+
{ label: "SMA", value: sma },
|
|
42639
|
+
{ label: "Permissions", value: String(permissions.length) },
|
|
42640
|
+
{ label: "Mandate signer", value: permissionSigner }
|
|
42641
|
+
],
|
|
42642
|
+
typedData
|
|
42643
|
+
});
|
|
42644
|
+
if (response.status === "rejected") {
|
|
42645
|
+
throw new Error(`User rejected mandate authorization: ${response.reason ?? "no reason given"}`);
|
|
42646
|
+
}
|
|
42647
|
+
if (response.status !== "signature") {
|
|
42648
|
+
throw new Error(`Expected an EIP-712 signature response, got: ${response.status}`);
|
|
42649
|
+
}
|
|
42650
|
+
let fee = 0n;
|
|
42651
|
+
for (const permission of permissions) {
|
|
42652
|
+
fee += await estimatePermissionFee(publicClient, project.contracts.governance, permission);
|
|
42653
|
+
}
|
|
42654
|
+
const chain2 = getChainById(project.chainId);
|
|
42655
|
+
const walletClient = createWalletClient({
|
|
42656
|
+
account: agentSigner.viemAccount,
|
|
42657
|
+
chain: chain2,
|
|
42658
|
+
transport: http(getRpcUrl(project.chainId))
|
|
42659
|
+
});
|
|
42660
|
+
const registerData = encodeFunctionData({
|
|
42661
|
+
abi: SailKernelAbi,
|
|
42662
|
+
functionName: "registerPermissions",
|
|
42663
|
+
args: [sma, permissions, deadline, response.signature]
|
|
42664
|
+
});
|
|
42665
|
+
say(() => console.log(`Submitting batch registration (agent pays gas; fee ${fee} wei)\u2026`));
|
|
42666
|
+
const txHash = await walletClient.sendTransaction({
|
|
42667
|
+
to: project.contracts.kernel,
|
|
42668
|
+
data: registerData,
|
|
42669
|
+
value: fee,
|
|
42670
|
+
account: agentSigner.viemAccount,
|
|
42671
|
+
chain: chain2
|
|
42672
|
+
});
|
|
42673
|
+
const receipt = await publicClient.waitForTransactionReceipt({ hash: txHash });
|
|
42674
|
+
if (receipt.status !== "success") {
|
|
42675
|
+
throw new Error(`registerPermissions reverted (tx ${txHash})`);
|
|
42676
|
+
}
|
|
42677
|
+
for (const permission of permissions) {
|
|
42678
|
+
const present = await pollForPermission(publicClient, project.contracts.kernel, sma, permission);
|
|
42679
|
+
appendActivity({
|
|
42680
|
+
ts: nowIso(),
|
|
42681
|
+
actor: "agent",
|
|
42682
|
+
type: "permission_registered",
|
|
42683
|
+
permission,
|
|
42684
|
+
sma,
|
|
42685
|
+
txHash,
|
|
42686
|
+
chainId: project.chainId
|
|
42687
|
+
});
|
|
42688
|
+
say(() => {
|
|
42689
|
+
if (!present) {
|
|
42690
|
+
console.log(`\u26A0 ${permission} not yet visible in the permission set \u2014 verify on-chain.`);
|
|
42691
|
+
} else {
|
|
42692
|
+
console.log("\u2713", `${permission} present in getPermissions(${sma})`);
|
|
42693
|
+
}
|
|
42694
|
+
});
|
|
42695
|
+
}
|
|
42696
|
+
return txHash;
|
|
42697
|
+
}
|
|
42562
42698
|
async function pollForPermission(publicClient, kernel, account2, permission, attempts = 6) {
|
|
42563
42699
|
const needle = permission.toLowerCase();
|
|
42564
42700
|
for (let i = 0; i < attempts; i++) {
|
|
@@ -45238,7 +45374,10 @@ var mandate = program2.command("mandate").description("Manage mandates");
|
|
|
45238
45374
|
mandate.command("prepare").description("Prepare a mandate draft for review and signing in the UI (MetaMask)").action(action(mandatePrepare));
|
|
45239
45375
|
mandate.command("sign").description("Review and confirm the permissions authorized for your SMA").option("--yes", "Skip the confirmation prompt (for non-interactive / CI use)").action(actionWith(mandateSign));
|
|
45240
45376
|
mandate.command("deploy").description("Deploy a Foundry-compiled permission contract via the browser signing UI").option("--artifact <path>", "Path to the Foundry artifact JSON (out/<Name>.sol/<Name>.json)").option("--contract <name>", "Contract name; resolves to <out>/<name>.sol/<name>.json").option("--out <dir>", "Foundry output directory", "out").option("--name <label>", "Label to track this permission under (defaults to contract name)").option("--args <json>", `Constructor args as JSON array. Bash: '["0x..","1"]'. PowerShell: '[\\"0x..\\",\\"1\\"]'. Use --args-file to avoid quoting.`).option("--args-file <path>", "Path to a JSON file containing constructor args array (recommended on PowerShell)").option("--build", "Run `forge build` before deploying").option("--attach", "After deploy, register the permission on --sma").option("--sma <address>", "SMA to register on (required with --attach)").option("--json", "Emit machine-readable JSON").action(actionWith(mandateDeploy));
|
|
45241
|
-
mandate.command("attach").description("Register
|
|
45377
|
+
mandate.command("attach").description("Register one or more already-deployed permissions on an SMA (EIP-712 RegisterPermission; a comma-separated list registers all in one signature)").requiredOption(
|
|
45378
|
+
"--address <mandateOrName>",
|
|
45379
|
+
"Permission address or locally-tracked name; or a comma-separated list of addresses to register together in one signature"
|
|
45380
|
+
).requiredOption("--sma <address>", "SMA to register the permission on").option("--label <label>", "Human-readable label shown in the signing UI").option("--json", "Emit machine-readable JSON").action(actionWith(mandateAttach));
|
|
45242
45381
|
mandate.command("deploy-clone").description("[currently unavailable \u2014 no clone templates deployed on any chain; use `mandate deploy`] Deploy + register a standalone clone permission via the signing UI").requiredOption("--template <key>", "Standalone clone template key (e.g. boundedApprove)").requiredOption("--sma <address>", "SMA to deploy the clone for and register it on").option("--tokens <csv>", "Comma-separated allowed token addresses").option("--spenders <csv>", "Comma-separated allowed spender addresses").option("--max <amount>", "Max amount per tx in base units (default: uint256 max)").option("--label <label>", "Human-readable label to track this permission under").option("--json", "Emit machine-readable JSON").action(actionWith(mandateDeployClone));
|
|
45243
45382
|
mandate.command("revoke").description("Revoke permission(s) from an SMA (EIP-712 RevokePermissions, owner-authorized)").option("--address <permissionOrName>", "Permission address, or a name tracked locally").requiredOption("--sma <address>", "Safe (SMA) to revoke the permission(s) from").option("--all", "Revoke every permission currently registered on the SMA").option("--json", "Output JSON").action(actionWith(mandateRevoke));
|
|
45244
45383
|
mandate.command("templates").description("Show how to author your own permission contract (and any community-deployed addresses)").option("--json", "Emit machine-readable JSON").action(actionWith(mandateTemplates));
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* Do not edit manually — run `pnpm build` to regenerate.
|
|
6
6
|
*
|
|
7
7
|
* Spec version : 1.2.0
|
|
8
|
-
* Generated at : 2026-06-
|
|
8
|
+
* Generated at : 2026-06-19T15:08:30.058Z
|
|
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.0
|
|
8
|
-
* Generated at : 2026-06-
|
|
8
|
+
* Generated at : 2026-06-19T15:08:30.058Z
|
|
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";
|
|
@@ -65,6 +65,8 @@ sailor mandate deploy --contract <Name> --sma <SMA> --json # BLOCKS — owner
|
|
|
65
65
|
|
|
66
66
|
The owner pays gas; the deployed address is read from the receipt and tracked in `.sail/state/mandates.json`. Add `--build` to run `forge build` first.
|
|
67
67
|
|
|
68
|
+
When a strategy needs several permissions, **deploy all of them first** (don't `--attach` yet). Each deploy is its own owner-signed contract-creation transaction — those cannot be combined — but attaching them is a single signature (Gate 7), so deploy the full set, then attach it in one step.
|
|
69
|
+
|
|
68
70
|
Constructor args: `--args '["0xToken","1000000"]'` (JSON array, inline, bash) or `--args-file args.json` (any shell — required on PowerShell). Full per-shell quoting rules: [references/constructor-args.md](references/constructor-args.md). Values are coerced to the constructor's ABI types (uint→bigint, etc.) and the array length is validated.
|
|
69
71
|
|
|
70
72
|
## Gate 6 — Simulate against must-pass AND must-fail samples
|
|
@@ -84,10 +86,13 @@ This is an off-chain `eth_call` — no gas, no signing. It reports what `evaluat
|
|
|
84
86
|
## Gate 7 — Attach (authorize)
|
|
85
87
|
|
|
86
88
|
```bash
|
|
87
|
-
sailor mandate attach --address <PermissionOrName> --sma <SMA> --json
|
|
89
|
+
sailor mandate attach --address <PermissionOrName> --sma <SMA> --json # one permission, one signature
|
|
90
|
+
sailor mandate attach --address <addr1>,<addr2>,<addr3> --sma <SMA> --json # many permissions, ONE signature
|
|
88
91
|
```
|
|
89
92
|
|
|
90
|
-
Only now is the permission live. The owner (mandate signer) signs in the browser; the agent submits the registration and pays gas plus any registration fee. **Fund the agent wallet before attaching**, or this step fails with `gas required exceeds allowance`. The CLI verifies the signature came from the on-chain mandate signer — a wrong connected wallet is rejected. After confirmation it polls `getPermissions()` until the
|
|
93
|
+
Only now is the permission live. The owner (mandate signer) signs in the browser; the agent submits the registration and pays gas plus any registration fee. **Fund the agent wallet before attaching**, or this step fails with `gas required exceeds allowance`. The CLI verifies the signature came from the on-chain mandate signer — a wrong connected wallet is rejected. After confirmation it polls `getPermissions()` until the permissions appear.
|
|
94
|
+
|
|
95
|
+
When a strategy needs several permissions (e.g. a bounded-approve alongside the protocol permission), attach them all at once by passing a comma-separated list of addresses — the registration approvals collapse to a **single** browser signature via the kernel's `registerPermissions`. The earlier per-contract deploy approvals (Gate 5) are separate and unavoidable. A single permission attaches exactly as before with `--address <one>`.
|
|
91
96
|
|
|
92
97
|
## Maintenance
|
|
93
98
|
|
|
@@ -51,7 +51,7 @@ These open a signing channel, push a request, and wait (default timeout 10 minut
|
|
|
51
51
|
| `sailor account deploy-chain --chain <id>` | `create-sma` transaction on the target chain |
|
|
52
52
|
| `sailor account rotate-signer` | Delegate rotation + mandate re-approvals |
|
|
53
53
|
| `sailor mandate deploy` | `deploy-mandate` contract-creation transaction (owner pays gas) |
|
|
54
|
-
| `sailor mandate attach` | `RegisterPermission` EIP-712 (off-chain signature; agent submits and pays gas) |
|
|
54
|
+
| `sailor mandate attach` | `RegisterPermission` EIP-712 — one permission; a comma-separated `--address` list signs `RegisterPermissions` once for all (off-chain signature; agent submits and pays gas) |
|
|
55
55
|
| `sailor mandate deploy-clone` | `RegisterPermission` EIP-712 for the predicted clone address |
|
|
56
56
|
| `sailor mandate revoke` | `RevokePermissions` EIP-712 (agent submits and pays gas) |
|
|
57
57
|
| `sailor owner connect` | Nothing — blocks up to 300s waiting for a wallet to connect |
|