@secondlayer/cli 9.8.0 → 9.10.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/dist/cli.js CHANGED
@@ -33160,12 +33160,12 @@ __export(exports_dev_state, {
33160
33160
  clearDevState: () => clearDevState
33161
33161
  });
33162
33162
  import { homedir as homedir4 } from "node:os";
33163
- import { join as join10 } from "node:path";
33163
+ import { join as join11 } from "node:path";
33164
33164
  function getLogsDir() {
33165
33165
  return LOGS_DIR;
33166
33166
  }
33167
33167
  function getLogFile(service) {
33168
- return join10(LOGS_DIR, `${service}.log`);
33168
+ return join11(LOGS_DIR, `${service}.log`);
33169
33169
  }
33170
33170
  async function ensureDirs() {
33171
33171
  await Bun.$`mkdir -p ${STATE_DIR}`.quiet();
@@ -33222,9 +33222,9 @@ async function isDevRunning() {
33222
33222
  }
33223
33223
  var STATE_DIR, DEV_STATE_PATH, LOGS_DIR;
33224
33224
  var init_dev_state = __esm(() => {
33225
- STATE_DIR = join10(homedir4(), ".secondlayer");
33226
- DEV_STATE_PATH = join10(STATE_DIR, "dev.json");
33227
- LOGS_DIR = join10(STATE_DIR, "logs");
33225
+ STATE_DIR = join11(homedir4(), ".secondlayer");
33226
+ DEV_STATE_PATH = join11(STATE_DIR, "dev.json");
33227
+ LOGS_DIR = join11(STATE_DIR, "logs");
33228
33228
  });
33229
33229
 
33230
33230
  // src/lib/network.ts
@@ -33708,7 +33708,7 @@ __export(exports_dev_impl, {
33708
33708
  isDevAlreadyRunning: () => isDevAlreadyRunning
33709
33709
  });
33710
33710
  import { mkdirSync as mkdirSync5 } from "node:fs";
33711
- import { dirname as dirname14, join as join11, resolve as resolve10 } from "node:path";
33711
+ import { dirname as dirname14, join as join12, resolve as resolve10 } from "node:path";
33712
33712
  async function isDevAlreadyRunning() {
33713
33713
  if (await isDevRunning()) {
33714
33714
  const running = await getRunningServices();
@@ -34317,7 +34317,7 @@ async function ensureDevPostgres(dataDir) {
34317
34317
  if (check.stdout.toString().trim()) {
34318
34318
  return false;
34319
34319
  }
34320
- const pgDataDir = join11(dataDir, "postgres");
34320
+ const pgDataDir = join12(dataDir, "postgres");
34321
34321
  mkdirSync5(pgDataDir, { recursive: true });
34322
34322
  const stopped = await Bun.$`docker ps -aq -f name=secondlayer-dev-postgres`.quiet().nothrow();
34323
34323
  if (stopped.stdout.toString().trim()) {
@@ -34817,7 +34817,7 @@ var {
34817
34817
  // package.json
34818
34818
  var package_default = {
34819
34819
  name: "@secondlayer/cli",
34820
- version: "9.8.0",
34820
+ version: "9.10.0",
34821
34821
  repository: {
34822
34822
  type: "git",
34823
34823
  url: "git+https://github.com/ryanwaits/secondlayer.git",
@@ -34861,8 +34861,8 @@ var package_default = {
34861
34861
  "@secondlayer/bundler": "^0.4.1",
34862
34862
  "@secondlayer/scaffold": "^1.5.1",
34863
34863
  "@secondlayer/sdk": "^6.51.0",
34864
- "@secondlayer/shared": "^7.7.0",
34865
- "@secondlayer/stacks": "^3.2.0",
34864
+ "@secondlayer/shared": "^7.8.0",
34865
+ "@secondlayer/stacks": "^3.3.1",
34866
34866
  "@secondlayer/subgraphs": "^3.26.4",
34867
34867
  esbuild: "^0.27.4",
34868
34868
  "smol-toml": "^1.6.1",
@@ -36522,7 +36522,7 @@ POST result (client-side, not logged):`));
36522
36522
  // src/commands/devnet.ts
36523
36523
  import { spawnSync as spawnSync2 } from "node:child_process";
36524
36524
  import { existsSync as existsSync6, mkdirSync as mkdirSync4, writeFileSync as writeFileSync6 } from "node:fs";
36525
- import { dirname as dirname8, join as join8 } from "node:path";
36525
+ import { dirname as dirname8, join as join9 } from "node:path";
36526
36526
 
36527
36527
  // src/lib/devnet-compose.ts
36528
36528
  var DEFAULT_IMAGE_OWNER = "ryanwaits";
@@ -36719,7 +36719,138 @@ stacks_node_events_observers = [${entry}]
36719
36719
 
36720
36720
  // src/commands/devnet.ts
36721
36721
  init_output();
36722
- var COMPOSE_REL = join8(".secondlayer", "docker-compose.yml");
36722
+
36723
+ // src/commands/faucet.ts
36724
+ import { randomBytes as randomBytes2 } from "node:crypto";
36725
+ import { readFile as readFile3 } from "node:fs/promises";
36726
+ import { join as join8 } from "node:path";
36727
+ import { http as http3, createWalletClient } from "@secondlayer/stacks";
36728
+ import { mnemonicToAccount } from "@secondlayer/stacks/accounts";
36729
+ import { devnet } from "@secondlayer/stacks/chains";
36730
+ import { Cl } from "@secondlayer/stacks/clarity";
36731
+ import { parse as parseToml2 } from "smol-toml";
36732
+ init_output();
36733
+ var DUST_LIMIT_SATS = 546n;
36734
+ var DEFAULT_AMOUNT_SATS = 1000000n;
36735
+ async function readDeployerMnemonic(projectDir) {
36736
+ const path11 = join8(projectDir, "settings", "Devnet.toml");
36737
+ let raw;
36738
+ try {
36739
+ raw = await readFile3(path11, "utf8");
36740
+ } catch {
36741
+ throw new Error(`could not read ${path11}`);
36742
+ }
36743
+ const parsed = parseToml2(raw);
36744
+ const mnemonic = parsed.accounts?.deployer?.mnemonic;
36745
+ if (!mnemonic) {
36746
+ throw new Error(`no accounts.deployer.mnemonic in ${path11}`);
36747
+ }
36748
+ return mnemonic;
36749
+ }
36750
+ async function readBurnAnchor(rpcUrl) {
36751
+ const info2 = await (await fetch(`${rpcUrl.replace(/\/$/, "")}/v2/info`, {
36752
+ signal: AbortSignal.timeout(1e4)
36753
+ })).json();
36754
+ const height = info2.burn_block_height;
36755
+ if (typeof height !== "number") {
36756
+ throw new Error("node did not report a burn block height");
36757
+ }
36758
+ const burn = await (await fetch(`${rpcUrl.replace(/\/$/, "")}/extended/v2/burn-blocks/${height}`, { signal: AbortSignal.timeout(1e4) })).json();
36759
+ if (!burn.burn_block_hash) {
36760
+ throw new Error(`node did not report a hash for burn block ${height}`);
36761
+ }
36762
+ return { height, hash: burn.burn_block_hash.replace(/^0x/, "") };
36763
+ }
36764
+ function parseAmount(value) {
36765
+ if (value === undefined)
36766
+ return DEFAULT_AMOUNT_SATS;
36767
+ const parsed = BigInt(value);
36768
+ if (parsed < DUST_LIMIT_SATS) {
36769
+ throw new Error(`--amount must be at least ${DUST_LIMIT_SATS} sats (the protocol dust limit), got ${parsed}`);
36770
+ }
36771
+ return parsed;
36772
+ }
36773
+ function registerFaucetCommand(devnetCommand) {
36774
+ devnetCommand.command("faucet").description("Mint mock sBTC to an address on your devnet").requiredOption("--to <address>", "recipient Stacks address").option("--amount <sats>", `amount in sats (default ${DEFAULT_AMOUNT_SATS}, minimum ${DUST_LIMIT_SATS})`).option("--contract <id>", "sBTC deposit contract (default: <deployer>.sbtc-deposit)").option("--rpc-url <url>", "devnet node RPC (default http://localhost:3999)").option("--json", "Output as JSON").addHelpText("after", `
36775
+ Examples:
36776
+ $ sl devnet faucet --to ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
36777
+ $ sl devnet faucet --to ST2... --amount 5000000
36778
+
36779
+ Requires a devnet running with the sBTC contract set deployed by your
36780
+ Clarinet deployer — that deployer is the signer set on your own chain, which
36781
+ is what makes minting possible without Bitcoin.`).action(async (opts) => {
36782
+ try {
36783
+ const network = process.env.STACKS_NETWORK ?? "devnet";
36784
+ if (network !== "devnet" && network !== "local") {
36785
+ printError(`sl devnet faucet only works on devnet, not ${network}.`, {
36786
+ hint: "Mock sBTC is mintable because you deployed the contracts. On testnet or mainnet the signer set is not yours."
36787
+ });
36788
+ process.exit(2);
36789
+ }
36790
+ const rpcUrl = opts.rpcUrl ?? "http://localhost:3999";
36791
+ const amount = parseAmount(opts.amount);
36792
+ const projectDir = findClarinetProject(process.cwd());
36793
+ if (!projectDir) {
36794
+ printError("No Clarinet project found.", {
36795
+ hint: "Run this from a project containing Clarinet.toml, or pass --contract explicitly."
36796
+ });
36797
+ process.exit(2);
36798
+ }
36799
+ const mnemonic = await readDeployerMnemonic(projectDir);
36800
+ const account = mnemonicToAccount(mnemonic, {
36801
+ addressVersion: devnet.addressVersion.singleSig
36802
+ });
36803
+ const contract = opts.contract ?? `${account.address}.sbtc-deposit`;
36804
+ const anchor = await readBurnAnchor(rpcUrl);
36805
+ const client2 = createWalletClient({
36806
+ account,
36807
+ chain: devnet,
36808
+ transport: http3(rpcUrl)
36809
+ });
36810
+ const depositTxid = randomBytes2(32).toString("hex");
36811
+ const sweepTxid = randomBytes2(32).toString("hex");
36812
+ note(` minting ${amount} sats to ${opts.to}`);
36813
+ note(` via ${contract} as signer ${account.address}`);
36814
+ const txid = await client2.callContract({
36815
+ contract,
36816
+ functionName: "complete-deposit-wrapper",
36817
+ functionArgs: [
36818
+ Cl.bufferFromHex(depositTxid),
36819
+ Cl.uint(0),
36820
+ Cl.uint(amount),
36821
+ Cl.principal(opts.to),
36822
+ Cl.bufferFromHex(anchor.hash),
36823
+ Cl.uint(anchor.height),
36824
+ Cl.bufferFromHex(sweepTxid)
36825
+ ],
36826
+ postConditionMode: "allow"
36827
+ });
36828
+ output({
36829
+ json: opts.json,
36830
+ data: {
36831
+ txid,
36832
+ recipient: opts.to,
36833
+ amount_sats: amount.toString(),
36834
+ contract,
36835
+ burn_height: anchor.height
36836
+ },
36837
+ human: () => {
36838
+ success(`Minted ${amount} sats of mock sBTC to ${opts.to}.`);
36839
+ note(` tx ${txid}`);
36840
+ console.error(dim(" Mock sBTC on your devnet — not the canonical asset."));
36841
+ }
36842
+ });
36843
+ } catch (err) {
36844
+ const message = err instanceof Error ? err.message : String(err);
36845
+ const hint = /ECONNREFUSED|fetch failed/.test(message) ? "Is your devnet running? Start it with `clarinet devnet start`." : /NoSuchContract|does not exist/.test(message) ? "Deploy the sBTC contract set to your devnet first, or pass --contract." : "Check that your Clarinet deployer matches the sBTC contract deployer.";
36846
+ printError(message, { hint });
36847
+ process.exit(1);
36848
+ }
36849
+ });
36850
+ }
36851
+
36852
+ // src/commands/devnet.ts
36853
+ var COMPOSE_REL = join9(".secondlayer", "docker-compose.yml");
36723
36854
  function ensureDocker() {
36724
36855
  const probe = spawnSync2("docker", ["info"], { stdio: "ignore" });
36725
36856
  if (probe.error || probe.status !== 0) {
@@ -36747,23 +36878,24 @@ var SERVICES = [
36747
36878
  "migrate"
36748
36879
  ];
36749
36880
  function registerDevnetCommand(program2) {
36750
- const devnet = program2.command("devnet").description("Run Secondlayer services against a local Clarinet devnet");
36751
- devnet.command("connect").description("Point your clarinet project's devnet at a local Secondlayer stack and start it").option("--project <dir>", "Clarinet project directory (defaults to the nearest Clarinet.toml)").option("--image-tag <tag>", "Published image tag to run", DEFAULT_IMAGE_TAG).option("--owner <owner>", "ghcr image owner (namespace) to pull from").option("--no-up", "Patch config + write compose without starting docker").action(async (options3) => {
36881
+ const devnet2 = program2.command("devnet").description("Run Secondlayer services against a local Clarinet devnet");
36882
+ registerFaucetCommand(devnet2);
36883
+ devnet2.command("connect").description("Point your clarinet project's devnet at a local Secondlayer stack and start it").option("--project <dir>", "Clarinet project directory (defaults to the nearest Clarinet.toml)").option("--image-tag <tag>", "Published image tag to run", DEFAULT_IMAGE_TAG).option("--owner <owner>", "ghcr image owner (namespace) to pull from").option("--no-up", "Patch config + write compose without starting docker").action(async (options3) => {
36752
36884
  await connect(options3);
36753
36885
  });
36754
- devnet.command("down").description("Stop the local Secondlayer stack started by `sl devnet connect`").option("--project <dir>", "Clarinet project directory").option("--purge", "Also remove volumes (wipes the local index)").action(async (options3) => {
36886
+ devnet2.command("down").description("Stop the local Secondlayer stack started by `sl devnet connect`").option("--project <dir>", "Clarinet project directory").option("--purge", "Also remove volumes (wipes the local index)").action(async (options3) => {
36755
36887
  await down(options3);
36756
36888
  });
36757
- devnet.command("status").description("Snapshot of the local stack: ingest, subgraphs, and recent activity").option("-w, --watch", "Refresh every 2s until Ctrl-C").option("--limit <n>", "Recent activity rows to show", "12").action(async (options3) => {
36889
+ devnet2.command("status").description("Snapshot of the local stack: ingest, subgraphs, and recent activity").option("-w, --watch", "Refresh every 2s until Ctrl-C").option("--limit <n>", "Recent activity rows to show", "12").action(async (options3) => {
36758
36890
  await status(options3);
36759
36891
  });
36760
- devnet.command("logs [service]").description("Tail stack logs — all services, or one of: indexer, api, subgraph-processor, postgres").option("--project <dir>", "Clarinet project directory").option("-f, --follow", "Follow log output").option("-n, --lines <n>", "Lines to show from the end of each log").action(async (service, options3) => {
36892
+ devnet2.command("logs [service]").description("Tail stack logs — all services, or one of: indexer, api, subgraph-processor, postgres").option("--project <dir>", "Clarinet project directory").option("-f, --follow", "Follow log output").option("-n, --lines <n>", "Lines to show from the end of each log").action(async (service, options3) => {
36761
36893
  await logs(service, options3);
36762
36894
  });
36763
36895
  }
36764
36896
  function resolveProject(explicit) {
36765
36897
  if (explicit) {
36766
- if (!existsSync6(join8(explicit, "Clarinet.toml"))) {
36898
+ if (!existsSync6(join9(explicit, "Clarinet.toml"))) {
36767
36899
  error(`No Clarinet.toml in ${explicit}`);
36768
36900
  process.exit(1);
36769
36901
  }
@@ -36779,7 +36911,7 @@ function resolveProject(explicit) {
36779
36911
  }
36780
36912
  async function connect(options3) {
36781
36913
  const project = resolveProject(options3.project);
36782
- const devnetToml = join8(project, "settings", "Devnet.toml");
36914
+ const devnetToml = join9(project, "settings", "Devnet.toml");
36783
36915
  mkdirSync4(dirname8(devnetToml), { recursive: true });
36784
36916
  const result = ensureEventObserver(devnetToml, DEFAULT_INDEXER_OBSERVER);
36785
36917
  console.log(`${green("✓")} found Clarinet.toml`);
@@ -36789,7 +36921,7 @@ async function connect(options3) {
36789
36921
  console.log(`${green("✓")} ${result.status === "created" ? "created" : "patched"} settings/Devnet.toml`);
36790
36922
  console.log(dim(` + stacks_node_events_observers = ["${DEFAULT_INDEXER_OBSERVER}"]`));
36791
36923
  }
36792
- const composePath = join8(project, COMPOSE_REL);
36924
+ const composePath = join9(project, COMPOSE_REL);
36793
36925
  mkdirSync4(dirname8(composePath), { recursive: true });
36794
36926
  writeFileSync6(composePath, buildDevnetCompose({ owner: options3.owner, imageTag: options3.imageTag }));
36795
36927
  console.log(`${green("✓")} wrote ${COMPOSE_REL}`);
@@ -36819,7 +36951,7 @@ Stop with: sl devnet down`));
36819
36951
  }
36820
36952
  async function down(options3) {
36821
36953
  const project = resolveProject(options3.project);
36822
- const composePath = join8(project, COMPOSE_REL);
36954
+ const composePath = join9(project, COMPOSE_REL);
36823
36955
  if (!existsSync6(composePath)) {
36824
36956
  error(`No ${COMPOSE_REL} in ${project} — nothing to stop. Run \`sl devnet connect\` first.`);
36825
36957
  process.exit(1);
@@ -36834,7 +36966,7 @@ async function down(options3) {
36834
36966
  }
36835
36967
  async function logs(service, options3) {
36836
36968
  const project = resolveProject(options3.project);
36837
- const composePath = join8(project, COMPOSE_REL);
36969
+ const composePath = join9(project, COMPOSE_REL);
36838
36970
  if (!existsSync6(composePath)) {
36839
36971
  error(`No ${COMPOSE_REL} in ${project} — run \`sl devnet connect\` first.`);
36840
36972
  process.exit(1);
@@ -36960,7 +37092,7 @@ init_subgraphs2();
36960
37092
  init_output();
36961
37093
  init_resolve_auth();
36962
37094
  import { mkdir as mkdir3, writeFile as writeFile3 } from "node:fs/promises";
36963
- import { dirname as dirname9, join as join9 } from "node:path";
37095
+ import { dirname as dirname9, join as join10 } from "node:path";
36964
37096
  import { createStreamsClient } from "@secondlayer/sdk";
36965
37097
  import { STREAMS_EVENT_TYPES } from "@secondlayer/shared";
36966
37098
  var DEFAULT_BASE_URL3 = "https://api.secondlayer.tools";
@@ -37136,7 +37268,7 @@ Examples:
37136
37268
  note(`# downloading ${files.length} file(s) to ${options3.to}`);
37137
37269
  for (const file of files) {
37138
37270
  const bytes = await dumps.download(file);
37139
- const dest = join9(options3.to, file.path);
37271
+ const dest = join10(options3.to, file.path);
37140
37272
  await mkdir3(dirname9(dest), { recursive: true });
37141
37273
  await writeFile3(dest, bytes);
37142
37274
  process.stderr.write(`# ${file.path} (${file.row_count} rows, ${bytes.byteLength} bytes)
@@ -38424,9 +38556,9 @@ init_output();
38424
38556
 
38425
38557
  // src/lib/project-file.ts
38426
38558
  import { existsSync as existsSync7 } from "node:fs";
38427
- import { mkdir as mkdir4, readFile as readFile3, writeFile as writeFile4 } from "node:fs/promises";
38559
+ import { mkdir as mkdir4, readFile as readFile4, writeFile as writeFile4 } from "node:fs/promises";
38428
38560
  import { homedir as homedir5 } from "node:os";
38429
- import { dirname as dirname15, join as join12, parse as parsePath2, resolve as resolve11 } from "node:path";
38561
+ import { dirname as dirname15, join as join13, parse as parsePath2, resolve as resolve11 } from "node:path";
38430
38562
  import { z as z4 } from "zod";
38431
38563
  var ProjectFileSchema = z4.object({
38432
38564
  slug: z4.string().min(1)
@@ -38438,15 +38570,15 @@ async function readActiveProject(cwd, globalDefault) {
38438
38570
  const home2 = resolve11(homedir5());
38439
38571
  const fsRoot = parsePath2(dir).root;
38440
38572
  while (true) {
38441
- const candidate = join12(dir, DIRNAME, FILENAME);
38573
+ const candidate = join13(dir, DIRNAME, FILENAME);
38442
38574
  if (existsSync7(candidate)) {
38443
38575
  try {
38444
- const raw = await readFile3(candidate, "utf8");
38576
+ const raw = await readFile4(candidate, "utf8");
38445
38577
  const parsed = ProjectFileSchema.parse(JSON.parse(raw));
38446
38578
  return { slug: parsed.slug, resolvedFrom: candidate };
38447
38579
  } catch {}
38448
38580
  }
38449
- if (existsSync7(join12(dir, ".git")))
38581
+ if (existsSync7(join13(dir, ".git")))
38450
38582
  break;
38451
38583
  if (dir === home2 || dir === fsRoot)
38452
38584
  break;
@@ -38458,15 +38590,15 @@ async function readActiveProject(cwd, globalDefault) {
38458
38590
  if (globalDefault) {
38459
38591
  return {
38460
38592
  slug: globalDefault,
38461
- resolvedFrom: join12(homedir5(), ".secondlayer", "config.json")
38593
+ resolvedFrom: join13(homedir5(), ".secondlayer", "config.json")
38462
38594
  };
38463
38595
  }
38464
38596
  return null;
38465
38597
  }
38466
38598
  async function writeActiveProject(slug, cwd) {
38467
- const dir = join12(resolve11(cwd), DIRNAME);
38599
+ const dir = join13(resolve11(cwd), DIRNAME);
38468
38600
  await mkdir4(dir, { recursive: true });
38469
- const file = join12(dir, FILENAME);
38601
+ const file = join13(dir, FILENAME);
38470
38602
  await writeFile4(file, `${JSON.stringify({ slug }, null, 2)}
38471
38603
  `, "utf8");
38472
38604
  return file;
@@ -38636,8 +38768,13 @@ import { getDb as getDb2 } from "@secondlayer/shared/db";
38636
38768
 
38637
38769
  // src/lib/archive-reference.ts
38638
38770
  import { createHash as createHash2 } from "node:crypto";
38639
- import { readFile as readFile4 } from "node:fs/promises";
38640
- import { dirname as dirname16, join as join13, resolve as resolve12 } from "node:path";
38771
+ import { readFile as readFile5 } from "node:fs/promises";
38772
+ import { dirname as dirname16, join as join14, resolve as resolve12 } from "node:path";
38773
+ import {
38774
+ checkKeyTrust,
38775
+ publicKeyFor,
38776
+ verifyRegistry
38777
+ } from "@secondlayer/shared/archive/key-registry";
38641
38778
  import { verifyStreamsBulkManifestSignature } from "@secondlayer/shared/streams-bulk-manifest";
38642
38779
  function isLatestPointer(value) {
38643
38780
  const pointer = value;
@@ -38661,7 +38798,7 @@ async function loadReference(source) {
38661
38798
  root = url.toString();
38662
38799
  } else {
38663
38800
  const path11 = resolve12(source);
38664
- manifest = JSON.parse(await readFile4(path11, "utf8"));
38801
+ manifest = JSON.parse(await readFile5(path11, "utf8"));
38665
38802
  root = isLatestPointer(manifest) ? dirname16(path11) : dirname16(dirname16(path11));
38666
38803
  }
38667
38804
  if (!isLatestPointer(manifest)) {
@@ -38669,7 +38806,7 @@ async function loadReference(source) {
38669
38806
  }
38670
38807
  const pointer = manifest;
38671
38808
  const snapshotPath = pointer.snapshot_path;
38672
- const resolved = isRemote ? await fetchJson(`${root.replace(/\/$/, "")}/${snapshotPath}`) : JSON.parse(await readFile4(join13(root, snapshotPath), "utf8"));
38809
+ const resolved = isRemote ? await fetchJson(`${root.replace(/\/$/, "")}/${snapshotPath}`) : JSON.parse(await readFile5(join14(root, snapshotPath), "utf8"));
38673
38810
  if (pointer.snapshot_digest) {
38674
38811
  const digest = createHash2("sha256").update(JSON.stringify((({ signature: _s, key_id: _k, ...rest }) => rest)(resolved))).digest("hex");
38675
38812
  if (digest !== pointer.snapshot_digest) {
@@ -38726,7 +38863,7 @@ async function fetchVerifiedPartition(reference, partition) {
38726
38863
  }
38727
38864
  bytes = Buffer.from(await response2.arrayBuffer());
38728
38865
  } else {
38729
- bytes = await readFile4(join13(reference.root, partition.path));
38866
+ bytes = await readFile5(join14(reference.root, partition.path));
38730
38867
  }
38731
38868
  const digest = createHash2("sha256").update(bytes).digest("hex");
38732
38869
  if (digest !== partition.sha256) {
@@ -38886,7 +39023,7 @@ Exit codes:
38886
39023
  // src/commands/repair.ts
38887
39024
  import { unlink as unlink2, writeFile as writeFile5 } from "node:fs/promises";
38888
39025
  import { tmpdir as tmpdir2 } from "node:os";
38889
- import { join as join14 } from "node:path";
39026
+ import { join as join15 } from "node:path";
38890
39027
  import { ParquetReader } from "@dsnp/parquetjs";
38891
39028
  import {
38892
39029
  compareRangeDigests as compareRangeDigests2,
@@ -38909,7 +39046,7 @@ function asOptionalText(value) {
38909
39046
  return value === null || value === undefined ? null : asText(value);
38910
39047
  }
38911
39048
  async function readBlocksPartition(bytes, label) {
38912
- const path11 = join14(tmpdir2(), `sl-repair-${label}-${process.pid}.parquet`);
39049
+ const path11 = join15(tmpdir2(), `sl-repair-${label}-${process.pid}.parquet`);
38913
39050
  await writeFile5(path11, bytes);
38914
39051
  try {
38915
39052
  const reader = await ParquetReader.openFile(path11);
@@ -39135,7 +39272,7 @@ Exit codes:
39135
39272
  // src/commands/bootstrap.ts
39136
39273
  import { unlink as unlink3, writeFile as writeFile6 } from "node:fs/promises";
39137
39274
  import { tmpdir as tmpdir3 } from "node:os";
39138
- import { join as join15 } from "node:path";
39275
+ import { join as join16 } from "node:path";
39139
39276
  import { ParquetReader as ParquetReader2 } from "@dsnp/parquetjs";
39140
39277
  import { confirm as confirm7 } from "@inquirer/prompts";
39141
39278
  import {
@@ -39143,7 +39280,7 @@ import {
39143
39280
  writeRowsToCopyStream
39144
39281
  } from "@secondlayer/shared/archive/copy-loader";
39145
39282
  import { computeRangeDigest as computeRangeDigest3 } from "@secondlayer/shared/archive/range-digest";
39146
- import { getDb as getDb4, getRawClient } from "@secondlayer/shared/db";
39283
+ import { getDb as getDb4, getRawClient, sql as sql3 } from "@secondlayer/shared/db";
39147
39284
  init_output();
39148
39285
  var BOOTSTRAP_EXIT = {
39149
39286
  OK: 0,
@@ -39152,6 +39289,20 @@ var BOOTSTRAP_EXIT = {
39152
39289
  };
39153
39290
  var BLOCKS_PER_DAY_ESTIMATE = 5400;
39154
39291
  var RESTORE_ROWS_PER_SECOND = 30000;
39292
+ async function readNodeTip() {
39293
+ const url = process.env.STACKS_NODE_RPC_URL ?? "http://localhost:20443";
39294
+ try {
39295
+ const response2 = await fetch(`${url.replace(/\/$/, "")}/v2/info`, {
39296
+ signal: AbortSignal.timeout(5000)
39297
+ });
39298
+ if (!response2.ok)
39299
+ return null;
39300
+ const info2 = await response2.json();
39301
+ return typeof info2.stacks_tip_height === "number" ? info2.stacks_tip_height : null;
39302
+ } catch {
39303
+ return null;
39304
+ }
39305
+ }
39155
39306
  function formatDuration2(seconds2) {
39156
39307
  if (seconds2 < 90)
39157
39308
  return `${Math.round(seconds2)}s`;
@@ -39162,7 +39313,7 @@ function formatDuration2(seconds2) {
39162
39313
  return `${(seconds2 / 86400).toFixed(1)} days`;
39163
39314
  }
39164
39315
  async function* readPartitionRows(bytes, label) {
39165
- const path11 = join15(tmpdir3(), `sl-bootstrap-${label}-${process.pid}.parquet`);
39316
+ const path11 = join16(tmpdir3(), `sl-bootstrap-${label}-${process.pid}.parquet`);
39166
39317
  await writeFile6(path11, bytes);
39167
39318
  try {
39168
39319
  const reader = await ParquetReader2.openFile(path11);
@@ -39256,6 +39407,7 @@ Exit codes:
39256
39407
  process.exit(BOOTSTRAP_EXIT.REFUSED);
39257
39408
  }
39258
39409
  }
39410
+ const nodeTipAtStart = await readNodeTip();
39259
39411
  const order = ["blocks", "transactions", "events"];
39260
39412
  const rawClient = getRawClient("source");
39261
39413
  const loaded = { blocks: 0, transactions: 0, events: 0 };
@@ -39279,6 +39431,24 @@ Exit codes:
39279
39431
  divergent++;
39280
39432
  }
39281
39433
  }
39434
+ const network = process.env.STACKS_NETWORK ?? "mainnet";
39435
+ await sql3`
39436
+ INSERT INTO index_progress (
39437
+ network, last_indexed_block, last_contiguous_block,
39438
+ highest_seen_block, updated_at
39439
+ ) VALUES (
39440
+ ${network}, ${tipHeight}, ${tipHeight}, ${tipHeight}, NOW()
39441
+ )
39442
+ ON CONFLICT (network) DO UPDATE SET
39443
+ last_indexed_block = GREATEST(index_progress.last_indexed_block, EXCLUDED.last_indexed_block),
39444
+ last_contiguous_block = GREATEST(index_progress.last_contiguous_block, EXCLUDED.last_contiguous_block),
39445
+ highest_seen_block = GREATEST(index_progress.highest_seen_block, EXCLUDED.highest_seen_block),
39446
+ updated_at = NOW()
39447
+ `.execute(db);
39448
+ const seam = nodeTipAtStart !== null && nodeTipAtStart > tipHeight ? {
39449
+ nodeTip: nodeTipAtStart,
39450
+ gap: nodeTipAtStart - tipHeight
39451
+ } : null;
39282
39452
  const elapsed = (Date.now() - startedAt) / 1000;
39283
39453
  const report = {
39284
39454
  status: divergent === 0 ? "restored" : "divergent",
@@ -39287,7 +39457,10 @@ Exit codes:
39287
39457
  rows: loaded,
39288
39458
  ranges_verified: referenceDigests.length,
39289
39459
  divergent_ranges: divergent,
39290
- elapsed_seconds: Math.round(elapsed)
39460
+ elapsed_seconds: Math.round(elapsed),
39461
+ resume_from: tipHeight + 1,
39462
+ node_tip_at_start: nodeTipAtStart,
39463
+ catch_up_blocks: seam?.gap ?? null
39291
39464
  };
39292
39465
  output({
39293
39466
  json: opts.json,
@@ -39301,7 +39474,13 @@ Exit codes:
39301
39474
  warn(" archive published no block digests — restore is unverified");
39302
39475
  }
39303
39476
  console.error("");
39304
- console.error(dim(`Your instance holds history through ${tipHeight.toLocaleString()}. Start the indexer to continue from there.`));
39477
+ console.error(dim(`Your instance holds history through ${tipHeight.toLocaleString()} and will resume at ${(tipHeight + 1).toLocaleString()}.`));
39478
+ if (seam) {
39479
+ console.error(dim(`The chain advanced to ${seam.nodeTip.toLocaleString()} while restoring — ${seam.gap.toLocaleString()} blocks to catch up.`));
39480
+ console.error(dim(" Start the indexer; it backfills that range before following the tip."));
39481
+ } else {
39482
+ console.error(dim(" Start the indexer to continue from there. (Node unreachable — catch-up range unknown.)"));
39483
+ }
39305
39484
  } else {
39306
39485
  warn(`Restored ${loaded.blocks.toLocaleString()} blocks but ${divergent} ranges do not match the archive.`);
39307
39486
  console.error(dim(" Investigate with: sl verify --against <manifest>"));
@@ -39381,5 +39560,5 @@ registerAccountCommand(program);
39381
39560
  registerKeysCommand(program);
39382
39561
  program.parse();
39383
39562
 
39384
- //# debugId=C0418C213027DA8564756E2164756E21
39563
+ //# debugId=D839AA90F21AC3D964756E2164756E21
39385
39564
  //# sourceMappingURL=cli.js.map