@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.
@@ -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: require amountOutMinimum ≥ amountIn * MIN_BPS / 10 000.
94
- // This is not a USD check it's a ratio check in tokenIn units.
95
- // For a USDC→WETH swap, set MIN_BPS conservatively (e.g. 9 900 for 1% slippage).
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dev.sail.money/sailor",
3
- "version": "0.0.2-16",
3
+ "version": "0.0.2-17",
4
4
  "description": "Operator toolkit for Sail Protocol",
5
5
  "bin": {
6
6
  "sailor": "packages/cli/dist/index.cjs"
@@ -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
- options.sma,
40897
+ sma,
40863
40898
  deployed,
40864
40899
  record.name,
40865
40900
  json
40866
40901
  );
40867
- store.recordAttachment(deployed, { sma: options.sma, txHash: attachTxHash });
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 mandateAddress = tracked?.address ?? options.address;
41177
- if (!isAddress(mandateAddress)) {
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
- options.sma,
41233
+ sma,
41197
41234
  mandateAddress,
41198
41235
  label,
41199
41236
  json
41200
41237
  );
41201
- if (tracked) store.recordAttachment(mandateAddress, { sma: options.sma, txHash });
41238
+ if (tracked) store.recordAttachment(mandateAddress, { sma, txHash });
41202
41239
  emit(json, () => {
41203
41240
  }, {
41204
41241
  status: "ok",
41205
- attached: { sma: options.sma, mandate: mandateAddress, txHash }
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 wanted = tracked?.address ?? options.address;
41246
- if (!isAddress(wanted)) {
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
- function trackedPermissionsFor(account2) {
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
- return store.list().filter((m) => m.chainId === account2.chainId).map((m) => {
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
- permissions: permissions.map((p) => ({ address: p.address, label: p.label })),
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(` ${p.registeredOnSma ? "registered on-chain" : "NOT yet registered on this SMA"}`);
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 ${permissions.length} permission(s) are authorized for your SMA?`
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 = permissions.filter((p) => !p.registeredOnSma);
41715
+ const unregistered = activePermissions.filter((p) => !p.registeredOnSma);
41645
41716
  if (unregistered.length === 0) {
41646
41717
  console.log(`
41647
- \u2713 Confirmed ${permissions.length} permission(s) for ${account2.safe}.`);
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
- permissions: permissions.map((p) => ({ template: p.label, params: {} }))
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 ${options.to}. The local agent keystore is left unchanged \u2014
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 options.to;
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 owner2 = options.owner ?? project.getOwner() ?? void 0;
42549
- if (!owner2 || !isAddress(owner2, { strict: false })) {
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-06T19:10:39.495Z
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-06T19:10:39.495Z
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: "pnpm"
18
+ cache: "npm"
23
19
 
24
20
  - name: Install dependencies
25
- run: pnpm install --frozen-lockfile
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
- MANAGER_KEY: ${{ secrets.MANAGER_KEY }}
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: push repo, add `RPC_URL` and `SAIL_PASSPHRASE` as secrets. The scaffolded workflow at `.github/workflows/agent-tick.yml` runs on a schedule.
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
- "Run the Sailor setup guide (open this folder in your LLM tool and say 'start') or\n" +
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
  },