@dev.sail.money/sailor 0.0.2-16 → 0.0.2-17
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/permissions/BoundedSwap_UniswapV3_Base.sol +4 -3
- package/examples/permissions/BoundedSwap_UniswapV4_Unichain.sol +4 -0
- package/package.json +1 -1
- package/packages/cli/dist/index.cjs +114 -38
- package/packages/sdk/dist/intelligence.d.ts +1 -1
- package/packages/sdk/dist/intelligence.js +1 -1
- package/templates/default/.github/workflows/agent-tick.yml +8 -7
- package/templates/default/AGENTS.md +12 -1
- package/templates/default/src/config.ts +1 -11
- package/templates/default/tsconfig.json +7 -1
|
@@ -90,9 +90,10 @@ contract BoundedSwap_UniswapV3_Base is IPermission {
|
|
|
90
90
|
if (p.tokenIn != FIXED_TOKEN_IN) return false;
|
|
91
91
|
if (!isAllowedTokenOut[p.tokenOut]) return false;
|
|
92
92
|
if (p.amountIn > MAX_AMOUNT_IN) return false;
|
|
93
|
-
// Slippage floor:
|
|
94
|
-
//
|
|
95
|
-
//
|
|
93
|
+
// Slippage floor: amountOutMinimum ≥ amountIn × MIN_BPS / 10 000.
|
|
94
|
+
// WARNING: compares tokenOut against tokenIn base units. For same-price/same-decimal
|
|
95
|
+
// pairs this maps to a slippage %. For cross-price pairs (e.g. USDC→WETH) it is
|
|
96
|
+
// trivially satisfied — real slippage is enforced by the agent off-chain, not here.
|
|
96
97
|
if (p.amountOutMinimum < (p.amountIn * MIN_BPS) / 10_000) return false;
|
|
97
98
|
return true;
|
|
98
99
|
}
|
|
@@ -135,6 +135,10 @@ contract BoundedSwap_UniswapV4_Unichain is IPermission {
|
|
|
135
135
|
if (tokenIn != FIXED_CURRENCY_IN) return false;
|
|
136
136
|
if (!isAllowedCurrenciesOut[tokenOut]) return false;
|
|
137
137
|
if (p.amountIn > MAX_AMOUNT_IN) return false;
|
|
138
|
+
// Slippage floor: amountOutMinimum ≥ amountIn × MIN_BPS / 10 000.
|
|
139
|
+
// WARNING: compares tokenOut against tokenIn base units. For same-price/same-decimal
|
|
140
|
+
// pairs this maps to a slippage %. For cross-price pairs it is trivially satisfied —
|
|
141
|
+
// real slippage is enforced by the agent off-chain, not by this contract.
|
|
138
142
|
if (p.amountOutMinimum < (uint256(p.amountIn) * MIN_BPS) / 10_000) return false;
|
|
139
143
|
|
|
140
144
|
return true;
|
package/package.json
CHANGED
|
@@ -39455,6 +39455,40 @@ ${label} key saved. Address: ${checksum4(keyring.address)}`);
|
|
|
39455
39455
|
}
|
|
39456
39456
|
}
|
|
39457
39457
|
}
|
|
39458
|
+
async function keysExportCi() {
|
|
39459
|
+
const account2 = readJsonFile(sailPath("account.json"));
|
|
39460
|
+
const src = resolveKeyPath("manager", account2?.safe);
|
|
39461
|
+
if (!fileExists(src)) {
|
|
39462
|
+
throw new Error(
|
|
39463
|
+
'No agent wallet keystore found.\nComplete Stage 1 (browser UI) to generate your agent wallet, or run\n"sailor keys generate" and choose "agent wallet" to create one manually.'
|
|
39464
|
+
);
|
|
39465
|
+
}
|
|
39466
|
+
const dest = import_node_path6.default.resolve(process.cwd(), "ci-keystore.json");
|
|
39467
|
+
import_node_fs7.default.copyFileSync(src, dest);
|
|
39468
|
+
console.log(`\u2713 Keystore copied to ci-keystore.json`);
|
|
39469
|
+
console.log(` Source: ${src}`);
|
|
39470
|
+
const gitignorePath = import_node_path6.default.resolve(process.cwd(), ".gitignore");
|
|
39471
|
+
if (import_node_fs7.default.existsSync(gitignorePath)) {
|
|
39472
|
+
const content = import_node_fs7.default.readFileSync(gitignorePath, "utf-8");
|
|
39473
|
+
if (!content.includes("ci-keystore.json")) {
|
|
39474
|
+
import_node_fs7.default.appendFileSync(
|
|
39475
|
+
gitignorePath,
|
|
39476
|
+
"\n# CI keystore \u2014 encrypted agent wallet, safe to commit\n!ci-keystore.json\n"
|
|
39477
|
+
);
|
|
39478
|
+
console.log("\u2713 Added !ci-keystore.json allowlist entry to .gitignore");
|
|
39479
|
+
} else {
|
|
39480
|
+
console.log("\u2713 .gitignore already tracks ci-keystore.json");
|
|
39481
|
+
}
|
|
39482
|
+
}
|
|
39483
|
+
console.log("\nNext steps:");
|
|
39484
|
+
console.log(" 1. Add two GitHub Actions secrets (Settings \u2192 Secrets \u2192 Actions):");
|
|
39485
|
+
console.log(" SAIL_PASSPHRASE \u2014 the passphrase that encrypts your agent wallet");
|
|
39486
|
+
console.log(" RPC_URL \u2014 your RPC endpoint");
|
|
39487
|
+
console.log(" 2. Commit and push ci-keystore.json:");
|
|
39488
|
+
console.log(' git add ci-keystore.json && git commit -m "chore: add CI keystore" && git push');
|
|
39489
|
+
console.log("\n The keystore is encrypted \u2014 the raw private key is never exposed.");
|
|
39490
|
+
console.log(" The workflow at .github/workflows/agent-tick.yml unlocks it with SAIL_PASSPHRASE.");
|
|
39491
|
+
}
|
|
39458
39492
|
async function keysShow() {
|
|
39459
39493
|
const present = ROLES.filter((role) => keyExists(role));
|
|
39460
39494
|
if (present.length === 0) {
|
|
@@ -40403,7 +40437,7 @@ async function resolveSmaChoice(options, json) {
|
|
|
40403
40437
|
if (!isAddress(options.sma, { strict: false })) {
|
|
40404
40438
|
throw new Error(`Invalid --sma address: ${options.sma}`);
|
|
40405
40439
|
}
|
|
40406
|
-
return { kind: "address", address: options.sma };
|
|
40440
|
+
return { kind: "address", address: getAddress(options.sma) };
|
|
40407
40441
|
}
|
|
40408
40442
|
if (options.newSma) return { kind: "new" };
|
|
40409
40443
|
if (json) {
|
|
@@ -40415,7 +40449,7 @@ async function resolveSmaChoice(options, json) {
|
|
|
40415
40449
|
);
|
|
40416
40450
|
if (choice.toLowerCase() === "y" || choice.toLowerCase() === "yes") return { kind: "new" };
|
|
40417
40451
|
if (!isAddress(choice, { strict: false })) throw new Error(`Invalid SMA address: ${choice}`);
|
|
40418
|
-
return { kind: "address", address: choice };
|
|
40452
|
+
return { kind: "address", address: getAddress(choice) };
|
|
40419
40453
|
}
|
|
40420
40454
|
async function resolveTemplate(project, options, json) {
|
|
40421
40455
|
const templates = (() => {
|
|
@@ -40432,7 +40466,7 @@ async function resolveTemplate(project, options, json) {
|
|
|
40432
40466
|
);
|
|
40433
40467
|
if (match) return { address: match.address, label: match.label };
|
|
40434
40468
|
if (isAddress(options.template, { strict: false })) {
|
|
40435
|
-
return { address: options.template, label: options.template };
|
|
40469
|
+
return { address: getAddress(options.template), label: options.template };
|
|
40436
40470
|
}
|
|
40437
40471
|
throw new Error(
|
|
40438
40472
|
`Unknown mandate template "${options.template}". Run "sailor mandate templates".`
|
|
@@ -40779,7 +40813,7 @@ async function runDeploy(project, channel, options) {
|
|
|
40779
40813
|
if (!json) fn();
|
|
40780
40814
|
};
|
|
40781
40815
|
if (options.attach && !options.sma) throw new Error("--attach requires --sma <address>");
|
|
40782
|
-
if (options.sma && !isAddress(options.sma)) {
|
|
40816
|
+
if (options.sma && !isAddress(options.sma, { strict: false })) {
|
|
40783
40817
|
throw new Error(`Invalid --sma address: ${options.sma}`);
|
|
40784
40818
|
}
|
|
40785
40819
|
if (project.chainId !== 8453) {
|
|
@@ -40855,16 +40889,17 @@ async function runDeploy(project, channel, options) {
|
|
|
40855
40889
|
});
|
|
40856
40890
|
let attachTxHash;
|
|
40857
40891
|
if (options.attach && options.sma) {
|
|
40892
|
+
const sma = getAddress(options.sma);
|
|
40858
40893
|
attachTxHash = await attachToSma(
|
|
40859
40894
|
project,
|
|
40860
40895
|
channel,
|
|
40861
40896
|
publicClient,
|
|
40862
|
-
|
|
40897
|
+
sma,
|
|
40863
40898
|
deployed,
|
|
40864
40899
|
record.name,
|
|
40865
40900
|
json
|
|
40866
40901
|
);
|
|
40867
|
-
store.recordAttachment(deployed, { sma
|
|
40902
|
+
store.recordAttachment(deployed, { sma, txHash: attachTxHash });
|
|
40868
40903
|
} else {
|
|
40869
40904
|
say(
|
|
40870
40905
|
() => console.log(
|
|
@@ -40877,7 +40912,7 @@ Register it later with: sailor mandate attach --address ${deployed} --sma <SMA>`
|
|
|
40877
40912
|
}, {
|
|
40878
40913
|
status: "ok",
|
|
40879
40914
|
mandate: { name: record.name, address: deployed, txHash: response.txHash, chainId },
|
|
40880
|
-
attached: options.attach ? { sma: options.sma, txHash: attachTxHash } : null
|
|
40915
|
+
attached: options.attach ? { sma: getAddress(options.sma), txHash: attachTxHash } : null
|
|
40881
40916
|
});
|
|
40882
40917
|
}
|
|
40883
40918
|
var CLONE_INIT_PREFIX = "0x3d602d80600a3d3981f3363d3d373d3d3d363d73";
|
|
@@ -40945,9 +40980,9 @@ function parseAddressList(csv, flag) {
|
|
|
40945
40980
|
const list = csv.split(",").map((s) => s.trim()).filter(Boolean);
|
|
40946
40981
|
if (list.length === 0) throw new Error(`${flag} is empty`);
|
|
40947
40982
|
for (const a of list) {
|
|
40948
|
-
if (!isAddress(a)) throw new Error(`${flag} contains an invalid address: ${a}`);
|
|
40983
|
+
if (!isAddress(a, { strict: false })) throw new Error(`${flag} contains an invalid address: ${a}`);
|
|
40949
40984
|
}
|
|
40950
|
-
return list;
|
|
40985
|
+
return list.map((a) => getAddress(a));
|
|
40951
40986
|
}
|
|
40952
40987
|
async function mandateDeployClone(options) {
|
|
40953
40988
|
const project = requireProject();
|
|
@@ -40966,8 +41001,8 @@ async function runDeployClone(project, channel, options) {
|
|
|
40966
41001
|
const say = (fn) => {
|
|
40967
41002
|
if (!json) fn();
|
|
40968
41003
|
};
|
|
40969
|
-
if (!isAddress(options.sma)) throw new Error(`Invalid --sma address: ${options.sma}`);
|
|
40970
|
-
const sma = options.sma;
|
|
41004
|
+
if (!isAddress(options.sma, { strict: false })) throw new Error(`Invalid --sma address: ${options.sma}`);
|
|
41005
|
+
const sma = getAddress(options.sma);
|
|
40971
41006
|
const spec = CLONE_TEMPLATES[options.template];
|
|
40972
41007
|
if (!spec) {
|
|
40973
41008
|
throw new Error(
|
|
@@ -40975,7 +41010,7 @@ async function runDeployClone(project, channel, options) {
|
|
|
40975
41010
|
);
|
|
40976
41011
|
}
|
|
40977
41012
|
const impl = project.deployment.standaloneTemplates?.[options.template];
|
|
40978
|
-
if (!impl || !isAddress(impl)) {
|
|
41013
|
+
if (!impl || !isAddress(impl, { strict: false })) {
|
|
40979
41014
|
throw new Error(
|
|
40980
41015
|
`No "${options.template}" standalone template is bundled for chain ${project.chainId}.`
|
|
40981
41016
|
);
|
|
@@ -41170,15 +41205,17 @@ async function mandateAttach(options) {
|
|
|
41170
41205
|
}
|
|
41171
41206
|
async function runAttach(project, channel, options) {
|
|
41172
41207
|
const json = !!options.json;
|
|
41173
|
-
if (!isAddress(options.sma)) throw new Error(`Invalid --sma address: ${options.sma}`);
|
|
41208
|
+
if (!isAddress(options.sma, { strict: false })) throw new Error(`Invalid --sma address: ${options.sma}`);
|
|
41209
|
+
const sma = getAddress(options.sma);
|
|
41174
41210
|
const store = new MandateStore();
|
|
41175
41211
|
const tracked = store.find(options.address);
|
|
41176
|
-
const
|
|
41177
|
-
if (!isAddress(
|
|
41212
|
+
const rawAddress = tracked?.address ?? options.address;
|
|
41213
|
+
if (!isAddress(rawAddress, { strict: false })) {
|
|
41178
41214
|
throw new Error(
|
|
41179
41215
|
`--address must be a deployed mandate address or a tracked name: ${options.address}`
|
|
41180
41216
|
);
|
|
41181
41217
|
}
|
|
41218
|
+
const mandateAddress = getAddress(rawAddress);
|
|
41182
41219
|
const label = options.label ?? tracked?.name ?? "mandate";
|
|
41183
41220
|
const publicClient = publicClientFor(project);
|
|
41184
41221
|
if (!json) {
|
|
@@ -41193,16 +41230,16 @@ async function runAttach(project, channel, options) {
|
|
|
41193
41230
|
project,
|
|
41194
41231
|
channel,
|
|
41195
41232
|
publicClient,
|
|
41196
|
-
|
|
41233
|
+
sma,
|
|
41197
41234
|
mandateAddress,
|
|
41198
41235
|
label,
|
|
41199
41236
|
json
|
|
41200
41237
|
);
|
|
41201
|
-
if (tracked) store.recordAttachment(mandateAddress, { sma
|
|
41238
|
+
if (tracked) store.recordAttachment(mandateAddress, { sma, txHash });
|
|
41202
41239
|
emit(json, () => {
|
|
41203
41240
|
}, {
|
|
41204
41241
|
status: "ok",
|
|
41205
|
-
attached: { sma
|
|
41242
|
+
attached: { sma, mandate: mandateAddress, txHash }
|
|
41206
41243
|
});
|
|
41207
41244
|
}
|
|
41208
41245
|
async function mandateRevoke(options) {
|
|
@@ -41222,11 +41259,11 @@ async function runRevoke(project, channel, options) {
|
|
|
41222
41259
|
const say = (fn) => {
|
|
41223
41260
|
if (!json) fn();
|
|
41224
41261
|
};
|
|
41225
|
-
if (!isAddress(options.sma)) throw new Error(`Invalid --sma address: ${options.sma}`);
|
|
41262
|
+
if (!isAddress(options.sma, { strict: false })) throw new Error(`Invalid --sma address: ${options.sma}`);
|
|
41226
41263
|
if (!options.all && !options.address) {
|
|
41227
41264
|
throw new Error("Provide --address <permission> (or a tracked name), or --all");
|
|
41228
41265
|
}
|
|
41229
|
-
const sma = options.sma;
|
|
41266
|
+
const sma = getAddress(options.sma);
|
|
41230
41267
|
const kernel = project.contracts.kernel;
|
|
41231
41268
|
const publicClient = publicClientFor(project);
|
|
41232
41269
|
const onchain = await publicClient.readContract({
|
|
@@ -41242,10 +41279,11 @@ async function runRevoke(project, channel, options) {
|
|
|
41242
41279
|
targets = onchain;
|
|
41243
41280
|
} else {
|
|
41244
41281
|
const tracked = store.find(options.address);
|
|
41245
|
-
const
|
|
41246
|
-
if (!isAddress(
|
|
41282
|
+
const rawWanted = tracked?.address ?? options.address;
|
|
41283
|
+
if (!isAddress(rawWanted, { strict: false })) {
|
|
41247
41284
|
throw new Error(`--address must be a permission address or a tracked name: ${options.address}`);
|
|
41248
41285
|
}
|
|
41286
|
+
const wanted = getAddress(rawWanted);
|
|
41249
41287
|
const match = onchain.find((p) => p.toLowerCase() === wanted.toLowerCase());
|
|
41250
41288
|
if (!match) {
|
|
41251
41289
|
throw new Error(`${wanted} is not in the SMA's current permission set; nothing to revoke.`);
|
|
@@ -41567,9 +41605,29 @@ function runForgeBuild() {
|
|
|
41567
41605
|
}
|
|
41568
41606
|
|
|
41569
41607
|
// src/commands/mandate.ts
|
|
41570
|
-
|
|
41608
|
+
init_esm2();
|
|
41609
|
+
async function fetchOnChainPermissions(account2) {
|
|
41610
|
+
try {
|
|
41611
|
+
const project = new ProjectContext();
|
|
41612
|
+
const rpcUrl = getRpcUrl(project.chainId) ?? getChainById(project.chainId).rpcUrls.default.http[0];
|
|
41613
|
+
const pc = createPublicClient({
|
|
41614
|
+
chain: getChainById(project.chainId),
|
|
41615
|
+
transport: http(rpcUrl)
|
|
41616
|
+
});
|
|
41617
|
+
const onChain = await pc.readContract({
|
|
41618
|
+
address: project.contracts.kernel,
|
|
41619
|
+
abi: SailKernelAbi,
|
|
41620
|
+
functionName: "getPermissions",
|
|
41621
|
+
args: [account2.safe]
|
|
41622
|
+
});
|
|
41623
|
+
return new Set(onChain.map((a) => a.toLowerCase()));
|
|
41624
|
+
} catch {
|
|
41625
|
+
return null;
|
|
41626
|
+
}
|
|
41627
|
+
}
|
|
41628
|
+
async function trackedPermissionsFor(account2) {
|
|
41571
41629
|
const store = new MandateStore();
|
|
41572
|
-
|
|
41630
|
+
const local = store.list().filter((m) => m.chainId === account2.chainId).map((m) => {
|
|
41573
41631
|
const attachment = m.attachments?.find(
|
|
41574
41632
|
(a) => a.sma.toLowerCase() === account2.safe.toLowerCase()
|
|
41575
41633
|
);
|
|
@@ -41580,6 +41638,15 @@ function trackedPermissionsFor(account2) {
|
|
|
41580
41638
|
attachedAt: attachment?.at
|
|
41581
41639
|
};
|
|
41582
41640
|
});
|
|
41641
|
+
const onChain = await fetchOnChainPermissions(account2);
|
|
41642
|
+
if (onChain !== null) {
|
|
41643
|
+
for (const p of local) {
|
|
41644
|
+
if (p.registeredOnSma && !onChain.has(p.address.toLowerCase())) {
|
|
41645
|
+
p.revokedOnChain = true;
|
|
41646
|
+
}
|
|
41647
|
+
}
|
|
41648
|
+
}
|
|
41649
|
+
return local;
|
|
41583
41650
|
}
|
|
41584
41651
|
function printNoPermissionsGuidance() {
|
|
41585
41652
|
console.log(
|
|
@@ -41591,7 +41658,7 @@ async function mandatePrepare() {
|
|
|
41591
41658
|
if (!account2) {
|
|
41592
41659
|
throw new Error('No account found at .sail/account.json.\nRun "sailor account create" first.');
|
|
41593
41660
|
}
|
|
41594
|
-
const permissions = trackedPermissionsFor(account2);
|
|
41661
|
+
const permissions = await trackedPermissionsFor(account2);
|
|
41595
41662
|
if (permissions.length === 0) {
|
|
41596
41663
|
printNoPermissionsGuidance();
|
|
41597
41664
|
return;
|
|
@@ -41600,7 +41667,7 @@ async function mandatePrepare() {
|
|
|
41600
41667
|
${permissions.length} permission(s) tracked for SMA ${account2.safe}:
|
|
41601
41668
|
`);
|
|
41602
41669
|
for (const p of permissions) {
|
|
41603
|
-
const status2 = p.registeredOnSma ? `registered on this SMA${p.attachedAt ? ` (${p.attachedAt})` : ""}` : "not yet registered on this SMA";
|
|
41670
|
+
const status2 = p.revokedOnChain ? "revoked on-chain (local record is stale)" : p.registeredOnSma ? `registered on this SMA${p.attachedAt ? ` (${p.attachedAt})` : ""}` : "not yet registered on this SMA";
|
|
41604
41671
|
console.log(`\u2022 ${p.label}`);
|
|
41605
41672
|
console.log(` ${p.address}`);
|
|
41606
41673
|
console.log(` ${status2}`);
|
|
@@ -41608,7 +41675,8 @@ ${permissions.length} permission(s) tracked for SMA ${account2.safe}:
|
|
|
41608
41675
|
const draft = {
|
|
41609
41676
|
account: account2.safe,
|
|
41610
41677
|
chainId: account2.chainId,
|
|
41611
|
-
|
|
41678
|
+
// Exclude permissions revoked on-chain — the draft reflects the live set.
|
|
41679
|
+
permissions: permissions.filter((p) => !p.revokedOnChain).map((p) => ({ address: p.address, label: p.label })),
|
|
41612
41680
|
createdAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
41613
41681
|
};
|
|
41614
41682
|
writeJsonFile(sailPath("mandate-draft.json"), draft);
|
|
@@ -41619,7 +41687,7 @@ async function mandateSign(opts = {}) {
|
|
|
41619
41687
|
if (!account2) {
|
|
41620
41688
|
throw new Error('No account found at .sail/account.json.\nRun "sailor account create" first.');
|
|
41621
41689
|
}
|
|
41622
|
-
const permissions = trackedPermissionsFor(account2);
|
|
41690
|
+
const permissions = await trackedPermissionsFor(account2);
|
|
41623
41691
|
if (permissions.length === 0) {
|
|
41624
41692
|
printNoPermissionsGuidance();
|
|
41625
41693
|
return;
|
|
@@ -41629,22 +41697,25 @@ Permissions tracked for SMA ${account2.safe}:
|
|
|
41629
41697
|
`);
|
|
41630
41698
|
for (const p of permissions) {
|
|
41631
41699
|
console.log(`\u2022 ${p.label} (${p.address})`);
|
|
41632
|
-
console.log(
|
|
41700
|
+
console.log(
|
|
41701
|
+
` ${p.revokedOnChain ? "revoked on-chain (local record is stale)" : p.registeredOnSma ? "registered on-chain" : "NOT yet registered on this SMA"}`
|
|
41702
|
+
);
|
|
41633
41703
|
}
|
|
41634
41704
|
console.log(
|
|
41635
41705
|
"\nNote: `sailor mandate sign` reviews and confirms the permissions attached to your SMA.\nOn-chain registration happens via `sailor mandate attach` (or `sailor mandate deploy --attach`)."
|
|
41636
41706
|
);
|
|
41707
|
+
const activePermissions = permissions.filter((p) => !p.revokedOnChain);
|
|
41637
41708
|
const proceed = opts.yes || await confirm(
|
|
41638
|
-
`Confirm these ${
|
|
41709
|
+
`Confirm these ${activePermissions.length} permission(s) are authorized for your SMA?`
|
|
41639
41710
|
);
|
|
41640
41711
|
if (!proceed) {
|
|
41641
41712
|
console.log("No permissions confirmed.");
|
|
41642
41713
|
return;
|
|
41643
41714
|
}
|
|
41644
|
-
const unregistered =
|
|
41715
|
+
const unregistered = activePermissions.filter((p) => !p.registeredOnSma);
|
|
41645
41716
|
if (unregistered.length === 0) {
|
|
41646
41717
|
console.log(`
|
|
41647
|
-
\u2713 Confirmed ${
|
|
41718
|
+
\u2713 Confirmed ${activePermissions.length} permission(s) for ${account2.safe}.`);
|
|
41648
41719
|
} else {
|
|
41649
41720
|
console.log(
|
|
41650
41721
|
`
|
|
@@ -41660,7 +41731,8 @@ ${unregistered.length} permission(s) are not yet registered on this SMA. Initiat
|
|
|
41660
41731
|
signedAt: (/* @__PURE__ */ new Date()).toISOString(),
|
|
41661
41732
|
signature: "",
|
|
41662
41733
|
registeredOnChain: true,
|
|
41663
|
-
|
|
41734
|
+
// Only include permissions that are currently active on-chain.
|
|
41735
|
+
permissions: activePermissions.map((p) => ({ template: p.label, params: {} }))
|
|
41664
41736
|
};
|
|
41665
41737
|
writeJsonFile(sailPath("mandate.json"), storedMandate);
|
|
41666
41738
|
console.log(`
|
|
@@ -41999,14 +42071,15 @@ async function resolveNewManager(options, oldManager, json, say) {
|
|
|
41999
42071
|
if (!isAddress(options.to, { strict: false })) {
|
|
42000
42072
|
throw new Error(`Invalid --to address: ${options.to}`);
|
|
42001
42073
|
}
|
|
42074
|
+
const to = getAddress(options.to);
|
|
42002
42075
|
say(
|
|
42003
42076
|
() => console.log(
|
|
42004
42077
|
`
|
|
42005
|
-
Rotating to existing address ${
|
|
42078
|
+
Rotating to existing address ${to}. The local agent keystore is left unchanged \u2014
|
|
42006
42079
|
ensure the agent that signs dispatches holds this key.`
|
|
42007
42080
|
)
|
|
42008
42081
|
);
|
|
42009
|
-
return
|
|
42082
|
+
return to;
|
|
42010
42083
|
}
|
|
42011
42084
|
if (json) {
|
|
42012
42085
|
throw new Error("Pass --to <address> in --json mode (key generation is interactive).");
|
|
@@ -42545,8 +42618,8 @@ async function scan(options) {
|
|
|
42545
42618
|
process.exit(1);
|
|
42546
42619
|
}
|
|
42547
42620
|
const project = new ProjectContext();
|
|
42548
|
-
const
|
|
42549
|
-
if (!
|
|
42621
|
+
const rawOwner = options.owner ?? project.getOwner() ?? void 0;
|
|
42622
|
+
if (!rawOwner || !isAddress(rawOwner, { strict: false })) {
|
|
42550
42623
|
emit(
|
|
42551
42624
|
options.json,
|
|
42552
42625
|
() => console.log(
|
|
@@ -42556,6 +42629,7 @@ async function scan(options) {
|
|
|
42556
42629
|
);
|
|
42557
42630
|
process.exit(1);
|
|
42558
42631
|
}
|
|
42632
|
+
const owner2 = getAddress(rawOwner);
|
|
42559
42633
|
const chainId = project.chainId;
|
|
42560
42634
|
const kernel = project.contracts.kernel;
|
|
42561
42635
|
const publicClient = createPublicClient({
|
|
@@ -42979,6 +43053,9 @@ ui.action(action(uiCommand));
|
|
|
42979
43053
|
var keys = program2.command("keys").description("Manage local signing keys");
|
|
42980
43054
|
keys.command("generate").description("Generate and encrypt an agent wallet or mandate signer key").action(action(keysGenerate));
|
|
42981
43055
|
keys.command("show").description("Show the address of each stored key").action(action(keysShow));
|
|
43056
|
+
keys.command("export-ci").description(
|
|
43057
|
+
"Copy the encrypted agent wallet keystore to ci-keystore.json for committing to CI"
|
|
43058
|
+
).action(action(keysExportCi));
|
|
42982
43059
|
var account = program2.command("account").description("Manage the Sail SMA");
|
|
42983
43060
|
account.command("create").description("Create a new Sail SMA on-chain").action(action(accountCreate));
|
|
42984
43061
|
account.command("rotate-signer").description("Rotate the SMA's delegated signer (agent wallet) and re-approve its mandates").option("--sma <address>", "SMA to rotate (defaults to the active account)").option("--to <address>", "Rotate to an existing agent-wallet address instead of generating one").option("--generate", "Generate a fresh local agent wallet (default when --to is omitted)").option("--skip-reattach", "Do not re-approve the previously-attached mandates").option("--reattach-only", "Skip rotation; only re-approve mandates (resume after funding)").option("--json", "Machine-readable output").action(actionWith(rotateSigner));
|
|
@@ -43025,7 +43102,6 @@ function stub(name, description) {
|
|
|
43025
43102
|
console.log(`sailor ${name}: not implemented yet`);
|
|
43026
43103
|
});
|
|
43027
43104
|
}
|
|
43028
|
-
stub("setup", "Walk through the Sailor setup guide");
|
|
43029
43105
|
stub("dispatch preview", "Preview a dispatch without submitting");
|
|
43030
43106
|
program2.parse(process.argv);
|
|
43031
43107
|
/*! Bundled license information:
|
|
@@ -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-06T21:23:50.341Z
|
|
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-06T21:23:50.341Z
|
|
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";
|
|
@@ -12,21 +12,22 @@ jobs:
|
|
|
12
12
|
steps:
|
|
13
13
|
- uses: actions/checkout@v4
|
|
14
14
|
|
|
15
|
-
- uses: pnpm/action-setup@v4
|
|
16
|
-
with:
|
|
17
|
-
version: 9
|
|
18
|
-
|
|
19
15
|
- uses: actions/setup-node@v4
|
|
20
16
|
with:
|
|
21
17
|
node-version: 20
|
|
22
|
-
cache: "
|
|
18
|
+
cache: "npm"
|
|
23
19
|
|
|
24
20
|
- name: Install dependencies
|
|
25
|
-
run:
|
|
21
|
+
run: npm ci
|
|
22
|
+
|
|
23
|
+
- name: Copy keystore to expected path
|
|
24
|
+
run: |
|
|
25
|
+
mkdir -p .sail/keys
|
|
26
|
+
cp ci-keystore.json .sail/keys/manager.json
|
|
26
27
|
|
|
27
28
|
- name: Run agent tick
|
|
28
29
|
env:
|
|
29
30
|
RPC_URL: ${{ secrets.RPC_URL }}
|
|
30
|
-
|
|
31
|
+
SAIL_PASSPHRASE: ${{ secrets.SAIL_PASSPHRASE }}
|
|
31
32
|
CHAIN_ID: ${{ vars.CHAIN_ID || '8453' }}
|
|
32
33
|
run: npx sailor run --once
|
|
@@ -68,7 +68,18 @@ sailor run # local, continuous
|
|
|
68
68
|
sailor run --once # single tick — confirm it works before automating
|
|
69
69
|
```
|
|
70
70
|
|
|
71
|
-
For GitHub Actions:
|
|
71
|
+
For GitHub Actions:
|
|
72
|
+
|
|
73
|
+
1. Run `sailor keys export-ci` — copies your encrypted agent wallet to `ci-keystore.json` in the project root and adds it to `.gitignore` as an allowed file. The keystore is geth v3 encrypted; the raw private key is never exposed.
|
|
74
|
+
2. Commit and push `ci-keystore.json`:
|
|
75
|
+
```bash
|
|
76
|
+
git add ci-keystore.json && git commit -m "chore: add CI keystore" && git push
|
|
77
|
+
```
|
|
78
|
+
3. Add two secrets in GitHub (Settings → Secrets → Actions):
|
|
79
|
+
- `SAIL_PASSPHRASE` — the passphrase that encrypts your agent wallet
|
|
80
|
+
- `RPC_URL` — your RPC endpoint
|
|
81
|
+
|
|
82
|
+
The scaffolded workflow at `.github/workflows/agent-tick.yml` picks up `ci-keystore.json`, unlocks it with `SAIL_PASSPHRASE`, and runs on the configured schedule. No private key ever appears in the workflow or in secrets.
|
|
72
83
|
|
|
73
84
|
## Stage 5 — Extend
|
|
74
85
|
|
|
@@ -1,13 +1,10 @@
|
|
|
1
|
-
import { SailorClient } from "@sail/sdk";
|
|
2
|
-
|
|
3
1
|
/** Reads RPC_URL and CHAIN_ID from environment (set via .sail/.env.local or GitHub Secrets). */
|
|
4
2
|
export function getEnvConfig(): { rpcUrl: string; chainId: number } {
|
|
5
3
|
const rpcUrl = process.env["RPC_URL"];
|
|
6
4
|
if (!rpcUrl) {
|
|
7
5
|
throw new Error(
|
|
8
6
|
"RPC_URL is not set.\n" +
|
|
9
|
-
"
|
|
10
|
-
"add RPC_URL to .sail/.env.local manually.",
|
|
7
|
+
"Add RPC_URL to .sail/.env.local or set it as an environment variable.",
|
|
11
8
|
);
|
|
12
9
|
}
|
|
13
10
|
|
|
@@ -25,10 +22,3 @@ export function getEnvConfig(): { rpcUrl: string; chainId: number } {
|
|
|
25
22
|
|
|
26
23
|
return { rpcUrl, chainId };
|
|
27
24
|
}
|
|
28
|
-
|
|
29
|
-
/** Builds a SailorClient from environment config. */
|
|
30
|
-
export function createClient(): SailorClient {
|
|
31
|
-
const { rpcUrl, chainId } = getEnvConfig();
|
|
32
|
-
// SailorClient constructor is not implemented yet — this is illustrative.
|
|
33
|
-
return new SailorClient({ rpcUrl, chainId });
|
|
34
|
-
}
|
|
@@ -1,6 +1,12 @@
|
|
|
1
1
|
{
|
|
2
|
-
"extends": "../../tsconfig.base.json",
|
|
3
2
|
"compilerOptions": {
|
|
3
|
+
"strict": true,
|
|
4
|
+
"target": "ES2022",
|
|
5
|
+
"module": "NodeNext",
|
|
6
|
+
"moduleResolution": "NodeNext",
|
|
7
|
+
"esModuleInterop": true,
|
|
8
|
+
"skipLibCheck": true,
|
|
9
|
+
"resolveJsonModule": true,
|
|
4
10
|
"rootDir": "src",
|
|
5
11
|
"noEmit": true
|
|
6
12
|
},
|