@secondlayer/cli 10.4.0 → 12.0.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/README.md +3 -4
- package/dist/cli.js +414 -850
- package/dist/cli.js.map +13 -16
- package/package.json +7 -7
package/dist/cli.js
CHANGED
|
@@ -2772,118 +2772,6 @@ var init_output = __esm(() => {
|
|
|
2772
2772
|
ANSI_ESCAPE_PATTERN = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*m`, "g");
|
|
2773
2773
|
});
|
|
2774
2774
|
|
|
2775
|
-
// src/lib/api-client.ts
|
|
2776
|
-
import { ApiError, SecondLayer } from "@secondlayer/sdk";
|
|
2777
|
-
function nextStepHint(status) {
|
|
2778
|
-
if (status === undefined)
|
|
2779
|
-
return;
|
|
2780
|
-
if (status === 403)
|
|
2781
|
-
return "You may not have access — check your active project with `secondlayer whoami`.";
|
|
2782
|
-
if (status === 404)
|
|
2783
|
-
return "Check the name/slug — run the matching `list` command to see what exists.";
|
|
2784
|
-
if (status === 400 || status === 422)
|
|
2785
|
-
return "Check the command arguments and flags — see `--help` for the expected format.";
|
|
2786
|
-
if (status === 429)
|
|
2787
|
-
return "Rate limited — wait a moment and retry.";
|
|
2788
|
-
if (status >= 500)
|
|
2789
|
-
return "Server error — retry shortly, or check `secondlayer status`.";
|
|
2790
|
-
return;
|
|
2791
|
-
}
|
|
2792
|
-
function handleApiError(err, action) {
|
|
2793
|
-
const status = err instanceof ApiError || err instanceof CliHttpError ? err.status : undefined;
|
|
2794
|
-
if (err instanceof CliHttpError && err.code === "SESSION_EXPIRED" || status === 401) {
|
|
2795
|
-
printError("Authentication required.", {
|
|
2796
|
-
hint: isOssMode() ? "Set SL_API_KEY / INSTANCE_TOKEN from `secondlayer init`." : "Run `secondlayer login` to re-authenticate."
|
|
2797
|
-
});
|
|
2798
|
-
process.exit(1);
|
|
2799
|
-
}
|
|
2800
|
-
const detail = err instanceof Error ? err.message : String(err);
|
|
2801
|
-
printError(`Failed to ${action}: ${detail}`, { hint: nextStepHint(status) });
|
|
2802
|
-
process.exit(1);
|
|
2803
|
-
}
|
|
2804
|
-
function withErrorHandling(fn, options) {
|
|
2805
|
-
return async (...args) => {
|
|
2806
|
-
try {
|
|
2807
|
-
await fn(...args);
|
|
2808
|
-
} catch (err) {
|
|
2809
|
-
if (options?.onError) {
|
|
2810
|
-
options.onError(err);
|
|
2811
|
-
} else {
|
|
2812
|
-
handleApiError(err, options?.action ?? "execute command");
|
|
2813
|
-
}
|
|
2814
|
-
}
|
|
2815
|
-
};
|
|
2816
|
-
}
|
|
2817
|
-
async function getPlatformClient() {
|
|
2818
|
-
const { apiUrl, ephemeralKey } = await resolveAuth();
|
|
2819
|
-
return new SecondLayer({ baseUrl: apiUrl, apiKey: ephemeralKey });
|
|
2820
|
-
}
|
|
2821
|
-
async function listSubgraphsApi() {
|
|
2822
|
-
return (await getPlatformClient()).subgraphs.list();
|
|
2823
|
-
}
|
|
2824
|
-
async function getSubgraphApi(name) {
|
|
2825
|
-
return (await getPlatformClient()).subgraphs.get(name);
|
|
2826
|
-
}
|
|
2827
|
-
async function getSubgraphOpenApi(name, options) {
|
|
2828
|
-
return (await getPlatformClient()).subgraphs.openapi(name, options);
|
|
2829
|
-
}
|
|
2830
|
-
async function getSubgraphAgentSchema(name, options) {
|
|
2831
|
-
return (await getPlatformClient()).subgraphs.schema(name, options);
|
|
2832
|
-
}
|
|
2833
|
-
async function getSubgraphMarkdown(name, options) {
|
|
2834
|
-
return (await getPlatformClient()).subgraphs.markdown(name, options);
|
|
2835
|
-
}
|
|
2836
|
-
async function reindexSubgraphApi(name) {
|
|
2837
|
-
return (await getPlatformClient()).subgraphs.reindex(name);
|
|
2838
|
-
}
|
|
2839
|
-
async function backfillSubgraphApi(name, options) {
|
|
2840
|
-
return (await getPlatformClient()).subgraphs.backfill(name, options);
|
|
2841
|
-
}
|
|
2842
|
-
async function stopSubgraphApi(name) {
|
|
2843
|
-
return (await getPlatformClient()).subgraphs.stop(name);
|
|
2844
|
-
}
|
|
2845
|
-
async function deleteSubgraphApi(name, options) {
|
|
2846
|
-
return (await getPlatformClient()).subgraphs.delete(name, options);
|
|
2847
|
-
}
|
|
2848
|
-
async function deploySubgraphApi(data) {
|
|
2849
|
-
return (await getPlatformClient()).subgraphs.deploy(data);
|
|
2850
|
-
}
|
|
2851
|
-
async function publishSubgraphApi(name) {
|
|
2852
|
-
return (await getPlatformClient()).subgraphs.publish(name);
|
|
2853
|
-
}
|
|
2854
|
-
async function unpublishSubgraphApi(name) {
|
|
2855
|
-
return (await getPlatformClient()).subgraphs.unpublish(name);
|
|
2856
|
-
}
|
|
2857
|
-
async function querySubgraphTable(name, table, params = {}) {
|
|
2858
|
-
return (await getPlatformClient()).subgraphs.queryTable(name, table, params);
|
|
2859
|
-
}
|
|
2860
|
-
async function querySubgraphTableCount(name, table, params = {}) {
|
|
2861
|
-
return (await getPlatformClient()).subgraphs.queryTableCount(name, table, params);
|
|
2862
|
-
}
|
|
2863
|
-
async function getSubgraphGaps(name, opts) {
|
|
2864
|
-
return (await getPlatformClient()).subgraphs.gaps(name, opts);
|
|
2865
|
-
}
|
|
2866
|
-
async function getContractPrintSchema(contractId) {
|
|
2867
|
-
let client;
|
|
2868
|
-
try {
|
|
2869
|
-
client = await getPlatformClient();
|
|
2870
|
-
} catch {
|
|
2871
|
-
client = new SecondLayer({ baseUrl: resolveApiUrl() });
|
|
2872
|
-
}
|
|
2873
|
-
return client.index.printSchema(contractId);
|
|
2874
|
-
}
|
|
2875
|
-
async function getAccountProfile() {
|
|
2876
|
-
return httpPlatform("/api/accounts/me");
|
|
2877
|
-
}
|
|
2878
|
-
async function updateAccountProfile(data) {
|
|
2879
|
-
return httpPlatform("/api/accounts/me", { method: "PATCH", body: data });
|
|
2880
|
-
}
|
|
2881
|
-
var init_api_client = __esm(() => {
|
|
2882
|
-
init_http();
|
|
2883
|
-
init_output();
|
|
2884
|
-
init_resolve_auth();
|
|
2885
|
-
});
|
|
2886
|
-
|
|
2887
2775
|
// ../../node_modules/chalk/source/vendor/ansi-styles/index.js
|
|
2888
2776
|
function assembleStyles() {
|
|
2889
2777
|
const codes = new Map;
|
|
@@ -10526,9 +10414,9 @@ function lowercaseKeys(object) {
|
|
|
10526
10414
|
|
|
10527
10415
|
// ../../node_modules/responselike/index.js
|
|
10528
10416
|
import { Readable as ReadableStream2 } from "node:stream";
|
|
10529
|
-
var
|
|
10417
|
+
var Response2;
|
|
10530
10418
|
var init_responselike = __esm(() => {
|
|
10531
|
-
|
|
10419
|
+
Response2 = class Response2 extends ReadableStream2 {
|
|
10532
10420
|
statusCode;
|
|
10533
10421
|
headers;
|
|
10534
10422
|
body;
|
|
@@ -10966,7 +10854,7 @@ class CacheableRequest {
|
|
|
10966
10854
|
response.once("end", resolve);
|
|
10967
10855
|
});
|
|
10968
10856
|
const headers = convertHeaders(revalidatedPolicy.policy.responseHeaders());
|
|
10969
|
-
response = new
|
|
10857
|
+
response = new Response2({ statusCode: revalidate.statusCode, headers, body: revalidate.body, url: revalidate.url });
|
|
10970
10858
|
response.cachePolicy = revalidatedPolicy.policy;
|
|
10971
10859
|
response.fromCache = true;
|
|
10972
10860
|
}
|
|
@@ -11042,7 +10930,7 @@ class CacheableRequest {
|
|
|
11042
10930
|
const policy = import_http_cache_semantics.default.fromObject(cacheEntry.cachePolicy);
|
|
11043
10931
|
if (policy.satisfiesWithoutRevalidation(options_) && !options_.forceRefresh) {
|
|
11044
10932
|
const headers = convertHeaders(policy.responseHeaders());
|
|
11045
|
-
const response = new
|
|
10933
|
+
const response = new Response2({ statusCode: cacheEntry.statusCode, headers, body: cacheEntry.body, url: cacheEntry.url });
|
|
11046
10934
|
response.cachePolicy = policy;
|
|
11047
10935
|
response.fromCache = true;
|
|
11048
10936
|
ee.emit("response", response);
|
|
@@ -28400,6 +28288,93 @@ var init_subgraphs = __esm(() => {
|
|
|
28400
28288
|
init_format();
|
|
28401
28289
|
});
|
|
28402
28290
|
|
|
28291
|
+
// src/lib/api-client.ts
|
|
28292
|
+
import { ApiError, SecondLayer } from "@secondlayer/sdk";
|
|
28293
|
+
function nextStepHint(status) {
|
|
28294
|
+
if (status === undefined)
|
|
28295
|
+
return;
|
|
28296
|
+
if (status === 403)
|
|
28297
|
+
return "You may not have access — check your active project with `secondlayer whoami`.";
|
|
28298
|
+
if (status === 404)
|
|
28299
|
+
return "Check the name/slug — run the matching `list` command to see what exists.";
|
|
28300
|
+
if (status === 400 || status === 422)
|
|
28301
|
+
return "Check the command arguments and flags — see `--help` for the expected format.";
|
|
28302
|
+
if (status === 429)
|
|
28303
|
+
return "Rate limited — wait a moment and retry.";
|
|
28304
|
+
if (status >= 500)
|
|
28305
|
+
return "Server error — retry shortly, or check `secondlayer status`.";
|
|
28306
|
+
return;
|
|
28307
|
+
}
|
|
28308
|
+
function handleApiError(err, action2) {
|
|
28309
|
+
const status = err instanceof ApiError || err instanceof CliHttpError ? err.status : undefined;
|
|
28310
|
+
if (err instanceof CliHttpError && err.code === "SESSION_EXPIRED" || status === 401) {
|
|
28311
|
+
printError("Authentication required.", {
|
|
28312
|
+
hint: isOssMode() ? "Set SL_API_KEY / INSTANCE_TOKEN from `secondlayer init`." : "Run `secondlayer login` to re-authenticate."
|
|
28313
|
+
});
|
|
28314
|
+
process.exit(1);
|
|
28315
|
+
}
|
|
28316
|
+
const detail = err instanceof Error ? err.message : String(err);
|
|
28317
|
+
printError(`Failed to ${action2}: ${detail}`, { hint: nextStepHint(status) });
|
|
28318
|
+
process.exit(1);
|
|
28319
|
+
}
|
|
28320
|
+
async function getPlatformClient() {
|
|
28321
|
+
const { apiUrl, ephemeralKey } = await resolveAuth();
|
|
28322
|
+
return new SecondLayer({ baseUrl: apiUrl, apiKey: ephemeralKey });
|
|
28323
|
+
}
|
|
28324
|
+
async function listSubgraphsApi() {
|
|
28325
|
+
return (await getPlatformClient()).subgraphs.list();
|
|
28326
|
+
}
|
|
28327
|
+
async function getSubgraphApi(name) {
|
|
28328
|
+
return (await getPlatformClient()).subgraphs.get(name);
|
|
28329
|
+
}
|
|
28330
|
+
async function getSubgraphOpenApi(name, options3) {
|
|
28331
|
+
return (await getPlatformClient()).subgraphs.openapi(name, options3);
|
|
28332
|
+
}
|
|
28333
|
+
async function getSubgraphAgentSchema(name, options3) {
|
|
28334
|
+
return (await getPlatformClient()).subgraphs.schema(name, options3);
|
|
28335
|
+
}
|
|
28336
|
+
async function getSubgraphMarkdown(name, options3) {
|
|
28337
|
+
return (await getPlatformClient()).subgraphs.markdown(name, options3);
|
|
28338
|
+
}
|
|
28339
|
+
async function reindexSubgraphApi(name) {
|
|
28340
|
+
return (await getPlatformClient()).subgraphs.reindex(name);
|
|
28341
|
+
}
|
|
28342
|
+
async function backfillSubgraphApi(name, options3) {
|
|
28343
|
+
return (await getPlatformClient()).subgraphs.backfill(name, options3);
|
|
28344
|
+
}
|
|
28345
|
+
async function stopSubgraphApi(name) {
|
|
28346
|
+
return (await getPlatformClient()).subgraphs.stop(name);
|
|
28347
|
+
}
|
|
28348
|
+
async function deleteSubgraphApi(name, options3) {
|
|
28349
|
+
return (await getPlatformClient()).subgraphs.delete(name, options3);
|
|
28350
|
+
}
|
|
28351
|
+
async function deploySubgraphApi(data) {
|
|
28352
|
+
return (await getPlatformClient()).subgraphs.deploy(data);
|
|
28353
|
+
}
|
|
28354
|
+
async function querySubgraphTable(name, table, params = {}) {
|
|
28355
|
+
return (await getPlatformClient()).subgraphs.queryTable(name, table, params);
|
|
28356
|
+
}
|
|
28357
|
+
async function querySubgraphTableCount(name, table, params = {}) {
|
|
28358
|
+
return (await getPlatformClient()).subgraphs.queryTableCount(name, table, params);
|
|
28359
|
+
}
|
|
28360
|
+
async function getSubgraphGaps(name, opts) {
|
|
28361
|
+
return (await getPlatformClient()).subgraphs.gaps(name, opts);
|
|
28362
|
+
}
|
|
28363
|
+
async function getContractPrintSchema(contractId) {
|
|
28364
|
+
let client;
|
|
28365
|
+
try {
|
|
28366
|
+
client = await getPlatformClient();
|
|
28367
|
+
} catch {
|
|
28368
|
+
client = new SecondLayer({ baseUrl: resolveApiUrl() });
|
|
28369
|
+
}
|
|
28370
|
+
return client.index.printSchema(contractId);
|
|
28371
|
+
}
|
|
28372
|
+
var init_api_client = __esm(() => {
|
|
28373
|
+
init_http();
|
|
28374
|
+
init_output();
|
|
28375
|
+
init_resolve_auth();
|
|
28376
|
+
});
|
|
28377
|
+
|
|
28403
28378
|
// src/lib/fs.ts
|
|
28404
28379
|
import { mkdir as mkdir2, readFile as readFile2, stat, unlink, writeFile as writeFile2 } from "node:fs/promises";
|
|
28405
28380
|
async function readJsonFile(path11) {
|
|
@@ -28903,23 +28878,8 @@ var init_require_auth = __esm(() => {
|
|
|
28903
28878
|
});
|
|
28904
28879
|
|
|
28905
28880
|
// src/templates/subgraph.ts
|
|
28906
|
-
function
|
|
28907
|
-
|
|
28908
|
-
switch (slug) {
|
|
28909
|
-
case "sip-010-balances":
|
|
28910
|
-
return sip010Balances(name);
|
|
28911
|
-
case "sbtc-flows":
|
|
28912
|
-
return sbtcFlows(name);
|
|
28913
|
-
case "pox-stacking":
|
|
28914
|
-
return poxStacking(name);
|
|
28915
|
-
case "bns-names":
|
|
28916
|
-
return bnsNames(name);
|
|
28917
|
-
default:
|
|
28918
|
-
return basic(name);
|
|
28919
|
-
}
|
|
28920
|
-
})();
|
|
28921
|
-
return `${nextStepsHeader(name)}
|
|
28922
|
-
${body}`;
|
|
28881
|
+
function generateSubgraphStarter(name) {
|
|
28882
|
+
return `${nextStepsHeader(name)}${starter(name)}`;
|
|
28923
28883
|
}
|
|
28924
28884
|
function nextStepsHeader(name) {
|
|
28925
28885
|
return `// ───────────────────────────────────────────────────────────────────
|
|
@@ -28928,20 +28888,19 @@ function nextStepsHeader(name) {
|
|
|
28928
28888
|
// 1. Edit the source filter + schema below to match what you want to track.
|
|
28929
28889
|
// 2. Edit the handler at the bottom — it runs once per matching event.
|
|
28930
28890
|
// 3. Deploy: secondlayer subgraphs deploy ${name}.ts
|
|
28931
|
-
// (You'll be prompted to log in if this is your first remote deploy.)
|
|
28932
28891
|
// 4. Wait for sync: secondlayer subgraphs status ${name}
|
|
28933
|
-
//
|
|
28934
|
-
//
|
|
28892
|
+
// Backfill from genesis can take an hour or more depending on your
|
|
28893
|
+
// filter scope. Use --start-block to skip ahead.
|
|
28935
28894
|
// 5. Query: secondlayer subgraphs query ${name} <table-name>
|
|
28936
|
-
// Or hit the
|
|
28895
|
+
// Or hit the REST endpoint listed in the deploy output.
|
|
28937
28896
|
//
|
|
28938
|
-
//
|
|
28939
|
-
//
|
|
28897
|
+
// Prefer generating from a real contract:
|
|
28898
|
+
// secondlayer subgraphs create ${name} --from-contract SP....my-contract
|
|
28940
28899
|
// ───────────────────────────────────────────────────────────────────
|
|
28941
28900
|
|
|
28942
28901
|
`;
|
|
28943
28902
|
}
|
|
28944
|
-
function
|
|
28903
|
+
function starter(name) {
|
|
28945
28904
|
return `import { defineSubgraph } from "@secondlayer/subgraphs";
|
|
28946
28905
|
|
|
28947
28906
|
export default defineSubgraph({
|
|
@@ -28949,7 +28908,7 @@ export default defineSubgraph({
|
|
|
28949
28908
|
version: "1.0.0",
|
|
28950
28909
|
description: "TODO: describe what this subgraph tracks",
|
|
28951
28910
|
|
|
28952
|
-
// Sources define what
|
|
28911
|
+
// Sources define what chain data this subgraph processes.
|
|
28953
28912
|
// Each source is named — the name becomes the handler key.
|
|
28954
28913
|
//
|
|
28955
28914
|
// Filter types:
|
|
@@ -28994,345 +28953,6 @@ export default defineSubgraph({
|
|
|
28994
28953
|
});
|
|
28995
28954
|
`;
|
|
28996
28955
|
}
|
|
28997
|
-
function sip010Balances(name) {
|
|
28998
|
-
return `import { defineSchema, defineSubgraph, readContractAt, type TypedSubgraphContext } from "@secondlayer/subgraphs";
|
|
28999
|
-
|
|
29000
|
-
/**
|
|
29001
|
-
* Track SIP-010 token balances per (asset_identifier, holder).
|
|
29002
|
-
* Mirrors the shape of the curated sBTC token-events view
|
|
29003
|
-
* surface — but works for ANY SIP-010 token. Constrain to a single
|
|
29004
|
-
* token by adding \`assetIdentifier: "SP...token::token-name"\` to each
|
|
29005
|
-
* source filter.
|
|
29006
|
-
*
|
|
29007
|
-
* Balances are moved with \`ctx.increment\`, which applies a DELTA in one
|
|
29008
|
-
* atomic statement. That matters: a read-modify-write (findOne → compute →
|
|
29009
|
-
* upsert) loses concurrent updates to the same row, and it is not replay-safe
|
|
29010
|
-
* under \`backfillMode: "concurrent"\`. Deltas commute, so order doesn't
|
|
29011
|
-
* matter and a reorg rewind reverses cleanly via the journal.
|
|
29012
|
-
*
|
|
29013
|
-
* Token metadata (decimals/symbol) comes from the token contract itself via
|
|
29014
|
-
* \`readContractAt\`, so \`amount\` stops being a bare integer nobody can
|
|
29015
|
-
* render. The read is pinned to the block being processed — a handler stays a
|
|
29016
|
-
* pure function of its block — and \`cache: "contract-constant"\` says these
|
|
29017
|
-
* two values can never change for a given token, so each is fetched ONCE
|
|
29018
|
-
* instead of once per block.
|
|
29019
|
-
*
|
|
29020
|
-
* Query examples once deployed:
|
|
29021
|
-
* GET /v1/subgraphs/${name}/balances?_search=SP1...
|
|
29022
|
-
* GET /v1/subgraphs/${name}/balances?holder=SP1...
|
|
29023
|
-
* GET /v1/subgraphs/${name}/tokens
|
|
29024
|
-
*/
|
|
29025
|
-
|
|
29026
|
-
/** The two SIP-010 read-only functions this subgraph calls. */
|
|
29027
|
-
const SIP010_ABI = {
|
|
29028
|
-
functions: [
|
|
29029
|
-
{ name: "get-decimals", access: "read-only", args: [], outputs: { response: { ok: "uint128", error: "uint128" } } },
|
|
29030
|
-
{ name: "get-symbol", access: "read-only", args: [], outputs: { response: { ok: { "string-ascii": { length: 32 } }, error: "uint128" } } },
|
|
29031
|
-
],
|
|
29032
|
-
} as const;
|
|
29033
|
-
|
|
29034
|
-
/** Tokens already labelled in this process — metadata is immutable, so one
|
|
29035
|
-
* resolution per token is enough even across a backfill. */
|
|
29036
|
-
const labelled = new Set<string>();
|
|
29037
|
-
|
|
29038
|
-
// Hoisted so the helper below can be typed against it.
|
|
29039
|
-
const schema = defineSchema({
|
|
29040
|
-
balances: {
|
|
29041
|
-
columns: {
|
|
29042
|
-
asset_identifier: { type: "text", indexed: true, search: true },
|
|
29043
|
-
holder: { type: "principal", indexed: true, search: true },
|
|
29044
|
-
amount: { type: "uint" },
|
|
29045
|
-
},
|
|
29046
|
-
// increment() upserts on this key.
|
|
29047
|
-
uniqueKeys: [["asset_identifier", "holder"]],
|
|
29048
|
-
},
|
|
29049
|
-
tokens: {
|
|
29050
|
-
columns: {
|
|
29051
|
-
asset_identifier: { type: "text", indexed: true },
|
|
29052
|
-
contract_id: { type: "text" },
|
|
29053
|
-
symbol: { type: "text" },
|
|
29054
|
-
decimals: { type: "uint" },
|
|
29055
|
-
},
|
|
29056
|
-
uniqueKeys: [["asset_identifier"]],
|
|
29057
|
-
},
|
|
29058
|
-
});
|
|
29059
|
-
|
|
29060
|
-
export default defineSubgraph({
|
|
29061
|
-
name: "${name}",
|
|
29062
|
-
version: "1.0.0",
|
|
29063
|
-
description: "Per-token balance tracking for any SIP-010 asset",
|
|
29064
|
-
|
|
29065
|
-
sources: {
|
|
29066
|
-
transfer: { type: "ft_transfer" },
|
|
29067
|
-
mint: { type: "ft_mint" },
|
|
29068
|
-
burn: { type: "ft_burn" },
|
|
29069
|
-
},
|
|
29070
|
-
|
|
29071
|
-
schema,
|
|
29072
|
-
|
|
29073
|
-
handlers: {
|
|
29074
|
-
transfer: async (event, ctx) => {
|
|
29075
|
-
await labelToken(event.assetIdentifier, ctx);
|
|
29076
|
-
const amount = BigInt(event.amount ?? 0);
|
|
29077
|
-
if (event.sender) {
|
|
29078
|
-
ctx.increment("balances", { asset_identifier: event.assetIdentifier, holder: event.sender }, { amount: -amount });
|
|
29079
|
-
}
|
|
29080
|
-
if (event.recipient) {
|
|
29081
|
-
ctx.increment("balances", { asset_identifier: event.assetIdentifier, holder: event.recipient }, { amount });
|
|
29082
|
-
}
|
|
29083
|
-
},
|
|
29084
|
-
mint: (event, ctx) => {
|
|
29085
|
-
if (event.recipient) {
|
|
29086
|
-
ctx.increment("balances", { asset_identifier: event.assetIdentifier, holder: event.recipient }, { amount: BigInt(event.amount ?? 0) });
|
|
29087
|
-
}
|
|
29088
|
-
},
|
|
29089
|
-
burn: (event, ctx) => {
|
|
29090
|
-
if (event.sender) {
|
|
29091
|
-
ctx.increment("balances", { asset_identifier: event.assetIdentifier, holder: event.sender }, { amount: -BigInt(event.amount ?? 0) });
|
|
29092
|
-
}
|
|
29093
|
-
},
|
|
29094
|
-
},
|
|
29095
|
-
});
|
|
29096
|
-
|
|
29097
|
-
/** Resolve a token's symbol + decimals from its contract, once. */
|
|
29098
|
-
async function labelToken(
|
|
29099
|
-
assetIdentifier: string | undefined,
|
|
29100
|
-
ctx: TypedSubgraphContext<typeof schema>,
|
|
29101
|
-
) {
|
|
29102
|
-
if (!assetIdentifier || labelled.has(assetIdentifier)) return;
|
|
29103
|
-
labelled.add(assetIdentifier);
|
|
29104
|
-
const contractId = assetIdentifier.split("::")[0];
|
|
29105
|
-
const token = readContractAt(ctx, contractId, SIP010_ABI, { cache: "contract-constant" });
|
|
29106
|
-
const [decimals, symbol] = await Promise.all([
|
|
29107
|
-
token.read.getDecimals({}),
|
|
29108
|
-
token.read.getSymbol({}),
|
|
29109
|
-
]);
|
|
29110
|
-
ctx.upsert("tokens", { asset_identifier: assetIdentifier }, { asset_identifier: assetIdentifier, contract_id: contractId, symbol, decimals });
|
|
29111
|
-
}
|
|
29112
|
-
`;
|
|
29113
|
-
}
|
|
29114
|
-
function sbtcFlows(name) {
|
|
29115
|
-
return `import { defineSubgraph } from "@secondlayer/subgraphs";
|
|
29116
|
-
|
|
29117
|
-
/**
|
|
29118
|
-
* Track sBTC protocol flows: deposits, withdrawals, signer rotations,
|
|
29119
|
-
* governance updates — your own indexed view of the sbtc-registry contract.
|
|
29120
|
-
*
|
|
29121
|
-
* Source contract: sbtc-registry (mainnet).
|
|
29122
|
-
*
|
|
29123
|
-
* Query examples once deployed:
|
|
29124
|
-
* GET /v1/subgraphs/${name}/flows?topic=completed-deposit
|
|
29125
|
-
* GET /v1/subgraphs/${name}/flows?topic=withdrawal-create
|
|
29126
|
-
*/
|
|
29127
|
-
export default defineSubgraph({
|
|
29128
|
-
name: "${name}",
|
|
29129
|
-
version: "1.0.0",
|
|
29130
|
-
description: "sBTC deposits, withdrawals, signer rotations, governance",
|
|
29131
|
-
|
|
29132
|
-
// Skip pre-sBTC history. Raise this (e.g., to a recent block near tip) for
|
|
29133
|
-
// a smaller backfill, or lower it if you need every sBTC event from genesis.
|
|
29134
|
-
startBlock: 860000,
|
|
29135
|
-
|
|
29136
|
-
sources: {
|
|
29137
|
-
registry: {
|
|
29138
|
-
type: "print_event",
|
|
29139
|
-
contractId: "SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-registry",
|
|
29140
|
-
},
|
|
29141
|
-
},
|
|
29142
|
-
|
|
29143
|
-
schema: {
|
|
29144
|
-
flows: {
|
|
29145
|
-
columns: {
|
|
29146
|
-
topic: { type: "text", indexed: true, search: true },
|
|
29147
|
-
request_id: { type: "uint", nullable: true, indexed: true },
|
|
29148
|
-
amount: { type: "text", nullable: true },
|
|
29149
|
-
sender: { type: "principal", nullable: true, indexed: true },
|
|
29150
|
-
bitcoin_txid: { type: "text", nullable: true, search: true },
|
|
29151
|
-
burn_height: { type: "uint", nullable: true },
|
|
29152
|
-
},
|
|
29153
|
-
},
|
|
29154
|
-
},
|
|
29155
|
-
|
|
29156
|
-
handlers: {
|
|
29157
|
-
registry: (event, ctx) => {
|
|
29158
|
-
if (!event.topic) return;
|
|
29159
|
-
// event.data is untyped across topics — cast to the fields you read, or
|
|
29160
|
-
// declare \`prints\` per topic on the source for fully typed event.data.
|
|
29161
|
-
const data = event.data as {
|
|
29162
|
-
requestId?: bigint;
|
|
29163
|
-
amount?: bigint | string;
|
|
29164
|
-
sender?: string;
|
|
29165
|
-
bitcoinTxid?: string;
|
|
29166
|
-
burnHeight?: bigint;
|
|
29167
|
-
};
|
|
29168
|
-
ctx.insert("flows", {
|
|
29169
|
-
topic: event.topic,
|
|
29170
|
-
request_id: data.requestId ?? null,
|
|
29171
|
-
amount: data.amount != null ? String(data.amount) : null,
|
|
29172
|
-
sender: data.sender ?? null,
|
|
29173
|
-
bitcoin_txid: data.bitcoinTxid ?? null,
|
|
29174
|
-
burn_height: data.burnHeight ?? null,
|
|
29175
|
-
});
|
|
29176
|
-
},
|
|
29177
|
-
},
|
|
29178
|
-
});
|
|
29179
|
-
`;
|
|
29180
|
-
}
|
|
29181
|
-
function poxStacking(name) {
|
|
29182
|
-
return `import { defineSubgraph } from "@secondlayer/subgraphs";
|
|
29183
|
-
import { POX5_ABI } from "@secondlayer/stacks/pox5";
|
|
29184
|
-
|
|
29185
|
-
/**
|
|
29186
|
-
* Track PoX-5 \`stake\` calls with their arguments already decoded.
|
|
29187
|
-
*
|
|
29188
|
-
* pox-5 replaced pox-4 when Epoch 4.0 activated (Bitcoin block 960,230,
|
|
29189
|
-
* 2026-07-30). SIP-045 reshaped the API: stacking became bonds + staking, so
|
|
29190
|
-
* the entry point is \`stake\`, not \`stack-stx\`.
|
|
29191
|
-
*
|
|
29192
|
-
* The source pairs \`abi\` with \`functionName\`, which is what makes
|
|
29193
|
-
* \`event.input\` a typed, named argument set instead of positional
|
|
29194
|
-
* \`args[0] as bigint\`. Add one source per function you want to track.
|
|
29195
|
-
*
|
|
29196
|
-
* Query examples once deployed:
|
|
29197
|
-
* GET /v1/subgraphs/${name}/calls?caller=SP1...
|
|
29198
|
-
* GET /v1/subgraphs/${name}/calls?caller=SP1...
|
|
29199
|
-
*/
|
|
29200
|
-
export default defineSubgraph({
|
|
29201
|
-
name: "${name}",
|
|
29202
|
-
version: "1.0.0",
|
|
29203
|
-
description: "PoX-5 stake calls with decoded arguments",
|
|
29204
|
-
|
|
29205
|
-
sources: {
|
|
29206
|
-
pox: {
|
|
29207
|
-
type: "contract_call",
|
|
29208
|
-
// pox-5 is the live PoX contract since Epoch 4.0 activated at Bitcoin
|
|
29209
|
-
// block 960,230 (2026-07-30). Stacking history before that is pox-4 —
|
|
29210
|
-
// point a second source at \`.pox-4\` if you need it.
|
|
29211
|
-
contractId: "SP000000000000000000002Q6VF78.pox-5",
|
|
29212
|
-
// \`abi\` + \`functionName\` together are what type \`event.input\`: the
|
|
29213
|
-
// arguments cannot be typed without knowing which function was called.
|
|
29214
|
-
// POX5_ABI ships \`as const\` in @secondlayer/stacks, so there is no
|
|
29215
|
-
// hand-copied ABI to drift. Add a source per function you care about.
|
|
29216
|
-
functionName: "stake",
|
|
29217
|
-
abi: POX5_ABI,
|
|
29218
|
-
},
|
|
29219
|
-
},
|
|
29220
|
-
|
|
29221
|
-
schema: {
|
|
29222
|
-
calls: {
|
|
29223
|
-
columns: {
|
|
29224
|
-
function_name: { type: "text", indexed: true, search: true },
|
|
29225
|
-
caller: { type: "principal", indexed: true, search: true },
|
|
29226
|
-
amount_ustx: { type: "uint" },
|
|
29227
|
-
num_cycles: { type: "uint" },
|
|
29228
|
-
result_ok: { type: "boolean" },
|
|
29229
|
-
},
|
|
29230
|
-
},
|
|
29231
|
-
},
|
|
29232
|
-
|
|
29233
|
-
handlers: {
|
|
29234
|
-
pox: (event, ctx) => {
|
|
29235
|
-
// \`event.input\` is the decoded, named, typed argument set — no
|
|
29236
|
-
// \`args[0] as bigint\`. Hover \`amountUstx\` and it is a bigint.
|
|
29237
|
-
const resultHex = event.resultHex ?? "";
|
|
29238
|
-
ctx.insert("calls", {
|
|
29239
|
-
function_name: event.functionName || ctx.tx.functionName || "",
|
|
29240
|
-
caller: ctx.tx.sender,
|
|
29241
|
-
amount_ustx: event.input.amountUstx,
|
|
29242
|
-
num_cycles: event.input.numCycles,
|
|
29243
|
-
result_ok: resultHex.startsWith("0x07"), // 0x07 = response-ok type tag
|
|
29244
|
-
});
|
|
29245
|
-
},
|
|
29246
|
-
},
|
|
29247
|
-
});
|
|
29248
|
-
`;
|
|
29249
|
-
}
|
|
29250
|
-
function bnsNames(name) {
|
|
29251
|
-
return `import { defineSubgraph } from "@secondlayer/subgraphs";
|
|
29252
|
-
|
|
29253
|
-
/**
|
|
29254
|
-
* Track BNS-V2 name lifecycle events — registrations, transfers,
|
|
29255
|
-
* renewals, burns, airdrops.
|
|
29256
|
-
*
|
|
29257
|
-
* Source: BNS-V2 print events (topic-discriminated payloads).
|
|
29258
|
-
*
|
|
29259
|
-
* Query examples once deployed:
|
|
29260
|
-
* GET /v1/subgraphs/${name}/names?owner=SP1...
|
|
29261
|
-
* GET /v1/subgraphs/${name}/names?_search=alice
|
|
29262
|
-
*/
|
|
29263
|
-
export default defineSubgraph({
|
|
29264
|
-
name: "${name}",
|
|
29265
|
-
version: "1.0.0",
|
|
29266
|
-
description: "BNS-V2 name ownership and lifecycle",
|
|
29267
|
-
|
|
29268
|
-
sources: {
|
|
29269
|
-
bns: {
|
|
29270
|
-
type: "print_event",
|
|
29271
|
-
contractId: "SP2QEZ06AGJ3RKJPBV14SY1V5BBFNAW33D96YPGZF.BNS-V2",
|
|
29272
|
-
},
|
|
29273
|
-
},
|
|
29274
|
-
|
|
29275
|
-
schema: {
|
|
29276
|
-
names: {
|
|
29277
|
-
columns: {
|
|
29278
|
-
topic: { type: "text", indexed: true },
|
|
29279
|
-
namespace: { type: "text", indexed: true, search: true },
|
|
29280
|
-
name: { type: "text", indexed: true, search: true },
|
|
29281
|
-
fqn: { type: "text", indexed: true, search: true },
|
|
29282
|
-
owner: { type: "principal", nullable: true, indexed: true, search: true },
|
|
29283
|
-
},
|
|
29284
|
-
},
|
|
29285
|
-
},
|
|
29286
|
-
|
|
29287
|
-
handlers: {
|
|
29288
|
-
bns: (event, ctx) => {
|
|
29289
|
-
if (!event.topic) return;
|
|
29290
|
-
const data = event.data as { namespace?: unknown; name?: unknown; owner?: string };
|
|
29291
|
-
const namespace = decodeBuffUtf8(data.namespace);
|
|
29292
|
-
const nameLabel = decodeBuffUtf8(data.name);
|
|
29293
|
-
if (!namespace || !nameLabel) return;
|
|
29294
|
-
ctx.insert("names", {
|
|
29295
|
-
topic: event.topic,
|
|
29296
|
-
namespace,
|
|
29297
|
-
name: nameLabel,
|
|
29298
|
-
fqn: \`\${nameLabel}.\${namespace}\`,
|
|
29299
|
-
owner: event.topic === "burn-name" ? null : (data.owner ?? null),
|
|
29300
|
-
});
|
|
29301
|
-
},
|
|
29302
|
-
},
|
|
29303
|
-
});
|
|
29304
|
-
|
|
29305
|
-
function decodeBuffUtf8(value: unknown): string | null {
|
|
29306
|
-
if (typeof value !== "string") return null;
|
|
29307
|
-
const hex = value.startsWith("0x") ? value.slice(2) : value;
|
|
29308
|
-
if (hex.length === 0) return null;
|
|
29309
|
-
const bytes = new Uint8Array(hex.length / 2);
|
|
29310
|
-
for (let i = 0; i < bytes.length; i++) {
|
|
29311
|
-
bytes[i] = Number.parseInt(hex.slice(i * 2, i * 2 + 2), 16);
|
|
29312
|
-
}
|
|
29313
|
-
let end = bytes.length;
|
|
29314
|
-
while (end > 0 && bytes[end - 1] === 0) end -= 1;
|
|
29315
|
-
return new TextDecoder("utf-8").decode(bytes.subarray(0, end));
|
|
29316
|
-
}
|
|
29317
|
-
`;
|
|
29318
|
-
}
|
|
29319
|
-
var SUBGRAPH_TEMPLATE_SLUGS, SUBGRAPH_TEMPLATE_DESCRIPTIONS;
|
|
29320
|
-
var init_subgraph = __esm(() => {
|
|
29321
|
-
SUBGRAPH_TEMPLATE_SLUGS = [
|
|
29322
|
-
"basic",
|
|
29323
|
-
"sip-010-balances",
|
|
29324
|
-
"sbtc-flows",
|
|
29325
|
-
"pox-stacking",
|
|
29326
|
-
"bns-names"
|
|
29327
|
-
];
|
|
29328
|
-
SUBGRAPH_TEMPLATE_DESCRIPTIONS = {
|
|
29329
|
-
basic: "Empty starter — pick any source filter type",
|
|
29330
|
-
"sip-010-balances": "SIP-010 token balances (transfers + mints + burns)",
|
|
29331
|
-
"sbtc-flows": "sBTC protocol flows (deposits, withdrawals, signer rotations)",
|
|
29332
|
-
"pox-stacking": "PoX-4 stacking lifecycle calls",
|
|
29333
|
-
"bns-names": "BNS-V2 name ownership and lifecycle"
|
|
29334
|
-
};
|
|
29335
|
-
});
|
|
29336
28956
|
|
|
29337
28957
|
// src/commands/subgraph-test.ts
|
|
29338
28958
|
var exports_subgraph_test = {};
|
|
@@ -29587,7 +29207,6 @@ import {
|
|
|
29587
29207
|
generatePrintPayloadTypes,
|
|
29588
29208
|
generatePrintSchemaSubgraph
|
|
29589
29209
|
} from "@secondlayer/scaffold";
|
|
29590
|
-
import { ByoBreakingChangeError } from "@secondlayer/sdk";
|
|
29591
29210
|
import { TRAIT_STANDARDS } from "@secondlayer/stacks/clarity";
|
|
29592
29211
|
async function loadSubgraphWithDepCheck(absPath) {
|
|
29593
29212
|
try {
|
|
@@ -29940,27 +29559,6 @@ ${dim("Columns")}`);
|
|
|
29940
29559
|
const deployTarget = context.bundled ? "tenant API" : "local database";
|
|
29941
29560
|
info(`Dry run only. No ${deployTarget} changes were made.`);
|
|
29942
29561
|
}
|
|
29943
|
-
function printByoBreakingPlan(details) {
|
|
29944
|
-
error("Refusing breaking schema change on BYO subgraph (no data dropped).");
|
|
29945
|
-
console.log(`
|
|
29946
|
-
Breaking changes:`);
|
|
29947
|
-
for (const r2 of details.reasons)
|
|
29948
|
-
console.log(` ${red("✗")} ${r2}`);
|
|
29949
|
-
console.log(`
|
|
29950
|
-
To rebuild manually, run on YOUR database:`);
|
|
29951
|
-
console.log(` ${details.plan.dropStatement}`);
|
|
29952
|
-
console.log(` ${details.plan.statements.join(`;
|
|
29953
|
-
`)}`);
|
|
29954
|
-
console.log(`
|
|
29955
|
-
Grant (first deploy only):`);
|
|
29956
|
-
for (const line of details.plan.grantScript.split(`
|
|
29957
|
-
`)) {
|
|
29958
|
-
console.log(` ${dim(line)}`);
|
|
29959
|
-
}
|
|
29960
|
-
console.log(`
|
|
29961
|
-
${yellow("This DROPS all rows in that schema, then rebuilds it. Re-deploy after.")}`);
|
|
29962
|
-
console.log(dim("(--force destructive rebuild not yet supported — manual DROP required.)"));
|
|
29963
|
-
}
|
|
29964
29562
|
function formatDuration(seconds2) {
|
|
29965
29563
|
if (seconds2 < 90)
|
|
29966
29564
|
return `${Math.max(1, Math.round(seconds2))}s`;
|
|
@@ -30016,27 +29614,15 @@ function formatSubgraphSync(sync) {
|
|
|
30016
29614
|
}
|
|
30017
29615
|
function registerSubgraphsCommand(program2) {
|
|
30018
29616
|
const subgraphs = program2.command("subgraphs").description("Manage materialized subgraphs");
|
|
30019
|
-
subgraphs.command("create <name>").description("Create a new subgraph definition file").option("--
|
|
29617
|
+
subgraphs.command("create <name>").description("Create a new subgraph definition file").option("--from-contract <contractId>", "Generate sources/schema/handlers from the contract's observed print events (requires network)").option("--table-per-topic", "With --from-contract: one table per print topic instead of a single wide table").addHelpText("after", `
|
|
30020
29618
|
Examples:
|
|
30021
29619
|
$ secondlayer subgraphs create my-graph
|
|
30022
|
-
$ secondlayer subgraphs create token-balances --template sip-010-balances
|
|
30023
29620
|
$ secondlayer subgraphs create sbtc-flows --from-contract SM3VDXK3WZZSA84XXFKAFAF15NNZX32CTSG82JFQ4.sbtc-registry
|
|
30024
29621
|
$ secondlayer subgraphs create sbtc-flows --from-contract SM3....sbtc-registry --table-per-topic`).action(async (name, opts) => {
|
|
30025
|
-
if (opts.fromContract && opts.template) {
|
|
30026
|
-
error("--from-contract and --template are mutually exclusive");
|
|
30027
|
-
process.exit(1);
|
|
30028
|
-
}
|
|
30029
29622
|
if (opts.tablePerTopic && !opts.fromContract) {
|
|
30030
29623
|
error("--table-per-topic requires --from-contract");
|
|
30031
29624
|
process.exit(1);
|
|
30032
29625
|
}
|
|
30033
|
-
const slug = opts.template ?? "basic";
|
|
30034
|
-
if (!opts.fromContract && !SUBGRAPH_TEMPLATE_SLUGS.includes(slug)) {
|
|
30035
|
-
error(`Unknown template "${opts.template}". Available templates:
|
|
30036
|
-
${SUBGRAPH_TEMPLATE_SLUGS.map((s2) => ` ${s2.padEnd(20)} ${SUBGRAPH_TEMPLATE_DESCRIPTIONS[s2]}`).join(`
|
|
30037
|
-
`)}`);
|
|
30038
|
-
process.exit(1);
|
|
30039
|
-
}
|
|
30040
29626
|
const dir = resolve3("subgraphs");
|
|
30041
29627
|
const filePath = resolve3(dir, `${name}.ts`);
|
|
30042
29628
|
if (existsSync3(filePath)) {
|
|
@@ -30065,7 +29651,7 @@ Check the contract id, and that the contract has emitted print events (schemas a
|
|
|
30065
29651
|
sample: schema.sample
|
|
30066
29652
|
}));
|
|
30067
29653
|
} else {
|
|
30068
|
-
content =
|
|
29654
|
+
content = generateSubgraphStarter(name);
|
|
30069
29655
|
}
|
|
30070
29656
|
if (!existsSync3(dir)) {
|
|
30071
29657
|
mkdirSync2(dir, { recursive: true });
|
|
@@ -30074,8 +29660,6 @@ Check the contract id, and that the contract has emitted print events (schemas a
|
|
|
30074
29660
|
success(`Created ${filePath}`);
|
|
30075
29661
|
if (opts.fromContract) {
|
|
30076
29662
|
info(`Schema inferred from ${opts.fromContract} print events (${opts.tablePerTopic ? "table per topic" : "single wide table"})`);
|
|
30077
|
-
} else if (slug !== "basic") {
|
|
30078
|
-
info(`Template: ${slug} — ${SUBGRAPH_TEMPLATE_DESCRIPTIONS[slug]}`);
|
|
30079
29663
|
}
|
|
30080
29664
|
info(`Next: secondlayer subgraphs deploy subgraphs/${name}.ts`);
|
|
30081
29665
|
});
|
|
@@ -30140,7 +29724,7 @@ Stopped watching.`);
|
|
|
30140
29724
|
});
|
|
30141
29725
|
await new Promise(() => {});
|
|
30142
29726
|
});
|
|
30143
|
-
subgraphs.command("deploy <file>").alias("update").description("Deploy a subgraph definition file (alias: update — deploy is create-or-update)").option("--start-block <n>", "Override the subgraph definition startBlock for this deploy").option("--tip-first", "Go live at chain tip immediately; history backfills in the background (requires order-tolerant handlers — commutative or insert-only writes)").option("--dry-run", "Validate and preview deploy without writing changes").option("-y, --yes", "Skip the reindex confirmation prompt").option("--
|
|
29727
|
+
subgraphs.command("deploy <file>").alias("update").description("Deploy a subgraph definition file (alias: update — deploy is create-or-update)").option("--start-block <n>", "Override the subgraph definition startBlock for this deploy").option("--tip-first", "Go live at chain tip immediately; history backfills in the background (requires order-tolerant handlers — commutative or insert-only writes)").option("--dry-run", "Validate and preview deploy without writing changes").option("-y, --yes", "Skip the reindex confirmation prompt").option("--strict", "Run `tsc --noEmit` against the handler before deploy (slower; catches TS type errors)").addOption(new Option("--visibility <visibility>", "Hosted only. Public/private namespace.").hideHelp()).option("--allow-uncommitted", "Deploy even though the source file is not committed to git (the deployed definition will exist only in the database)").action(async (file, options3) => {
|
|
30144
29728
|
try {
|
|
30145
29729
|
const absPath = resolve3(file);
|
|
30146
29730
|
if (!options3.dryRun) {
|
|
@@ -30173,15 +29757,6 @@ Stopped watching.`);
|
|
|
30173
29757
|
}
|
|
30174
29758
|
const dryRun = options3.dryRun;
|
|
30175
29759
|
const visibility = parseVisibilityOption(options3.visibility);
|
|
30176
|
-
if (visibility === "public" && options3.databaseUrl && !options3.yes) {
|
|
30177
|
-
const confirmed = await confirm3({
|
|
30178
|
-
message: "⚠ Public BYO subgraph: anonymous reads will query YOUR database. Continue?"
|
|
30179
|
-
});
|
|
30180
|
-
if (!confirmed) {
|
|
30181
|
-
info("Aborted.");
|
|
30182
|
-
process.exit(0);
|
|
30183
|
-
}
|
|
30184
|
-
}
|
|
30185
29760
|
const startBlock = parseStartBlockOption(options3.startBlock);
|
|
30186
29761
|
if (startBlock !== undefined) {
|
|
30187
29762
|
warn(`--start-block ${startBlock} overrides the definition's startBlock for this deploy.`);
|
|
@@ -30246,9 +29821,6 @@ Stopped watching.`);
|
|
|
30246
29821
|
}
|
|
30247
29822
|
}
|
|
30248
29823
|
}
|
|
30249
|
-
if (options3.databaseUrl) {
|
|
30250
|
-
info("BYO data plane: schema + rows will live in your database. The server verifies the connection before deploying.");
|
|
30251
|
-
}
|
|
30252
29824
|
const result = await deploySubgraphApi({
|
|
30253
29825
|
name: effectiveDef.name,
|
|
30254
29826
|
version: undefined,
|
|
@@ -30258,12 +29830,8 @@ Stopped watching.`);
|
|
|
30258
29830
|
handlerCode,
|
|
30259
29831
|
sourceCode: source,
|
|
30260
29832
|
...deployStartBlock !== undefined ? { startBlock: deployStartBlock } : {},
|
|
30261
|
-
...options3.databaseUrl ? { databaseUrl: options3.databaseUrl } : {},
|
|
30262
29833
|
...visibility ? { visibility } : {}
|
|
30263
29834
|
});
|
|
30264
|
-
if (result.start_block_clamped) {
|
|
30265
|
-
info(` Free tier indexes forward from deploy (start block ${result.start_block}) — upgrade for genesis backfill.`);
|
|
30266
|
-
}
|
|
30267
29835
|
for (const w2 of result.warnings ?? [])
|
|
30268
29836
|
warn(w2);
|
|
30269
29837
|
const printDeployFooter = async () => {
|
|
@@ -30276,7 +29844,6 @@ Stopped watching.`);
|
|
|
30276
29844
|
info(` Share: ${apiUrl}/v1/subgraphs/${effectiveDef.name} (public — no key needed)`);
|
|
30277
29845
|
} else if (firstTable) {
|
|
30278
29846
|
info(` REST: ${apiUrl}/api/subgraphs/${effectiveDef.name}/${firstTable}`);
|
|
30279
|
-
info(` Publish: secondlayer subgraphs publish ${effectiveDef.name} (open anon /v1 reads)`);
|
|
30280
29847
|
}
|
|
30281
29848
|
if (result.reindexStarted) {
|
|
30282
29849
|
const denom = result.estimatedEvents != null ? ` ~${result.estimatedEvents.toLocaleString()} events —` : "";
|
|
@@ -30356,14 +29923,6 @@ Stopped watching.`);
|
|
|
30356
29923
|
await closeDb();
|
|
30357
29924
|
}
|
|
30358
29925
|
} catch (err) {
|
|
30359
|
-
if (err instanceof ByoBreakingChangeError) {
|
|
30360
|
-
printByoBreakingPlan(err.details);
|
|
30361
|
-
process.exit(1);
|
|
30362
|
-
}
|
|
30363
|
-
if (err && typeof err === "object" && err.code === "BYO_BREAKING_CHANGE" && err.details) {
|
|
30364
|
-
printByoBreakingPlan(err.details);
|
|
30365
|
-
process.exit(1);
|
|
30366
|
-
}
|
|
30367
29926
|
error(`Failed to deploy subgraph: ${err}`);
|
|
30368
29927
|
process.exit(1);
|
|
30369
29928
|
}
|
|
@@ -30700,33 +30259,6 @@ ${rows.length} row(s)`));
|
|
|
30700
30259
|
handleApiError(err, "delete subgraph");
|
|
30701
30260
|
}
|
|
30702
30261
|
});
|
|
30703
|
-
subgraphs.command("publish <name>", { hidden: true }).description("Make a subgraph publicly readable on /v1/subgraphs/<name> (claims the name globally, no key needed to read)").action(async (name) => {
|
|
30704
|
-
try {
|
|
30705
|
-
await requireAuth();
|
|
30706
|
-
const result = await publishSubgraphApi(name);
|
|
30707
|
-
success(`Subgraph "${name}" is now public`);
|
|
30708
|
-
try {
|
|
30709
|
-
const { apiUrl } = await resolveAuth();
|
|
30710
|
-
info(` Share: ${apiUrl}${result.url} (no key needed)`);
|
|
30711
|
-
} catch {}
|
|
30712
|
-
} catch (err) {
|
|
30713
|
-
const status = err?.status;
|
|
30714
|
-
if (status === 409) {
|
|
30715
|
-
error(`Public name "${name}" is already taken by another account. Rename the subgraph (redeploy under a new name) or keep it private.`);
|
|
30716
|
-
process.exit(1);
|
|
30717
|
-
}
|
|
30718
|
-
handleApiError(err, "publish subgraph");
|
|
30719
|
-
}
|
|
30720
|
-
});
|
|
30721
|
-
subgraphs.command("unpublish <name>", { hidden: true }).description("Make a subgraph private again — reads require your API key, the public name claim is released").action(async (name) => {
|
|
30722
|
-
try {
|
|
30723
|
-
await requireAuth();
|
|
30724
|
-
await unpublishSubgraphApi(name);
|
|
30725
|
-
success(`Subgraph "${name}" is now private (reads need your key)`);
|
|
30726
|
-
} catch (err) {
|
|
30727
|
-
handleApiError(err, "unpublish subgraph");
|
|
30728
|
-
}
|
|
30729
|
-
});
|
|
30730
30262
|
subgraphs.command("scaffold [contractAddress]").description("Scaffold a defineSubgraph() file from a contract or trait").option("-o, --output <path>", "Output file path (required)").option("-k, --api-key <key>", "Stacks node API key for direct RPC URLs").option("--functions <names>", "Comma-separated public functions to index as typed contract_call tables").option("--trait <std>", "Scaffold a trait-scoped source (sip-009|sip-010|sip-013) — no contract needed").option("--no-install", "Skip bun install after writing package.json").addHelpText("after", `
|
|
30731
30263
|
Examples:
|
|
30732
30264
|
$ secondlayer subgraphs scaffold SP3D6PV2ACBPEKYJTCMH7HEN02KP87QSP8KTEH335.megapont-ape-club-nft -o subgraphs/apes.ts
|
|
@@ -30887,7 +30419,6 @@ var init_subgraphs2 = __esm(() => {
|
|
|
30887
30419
|
init_resolve_auth();
|
|
30888
30420
|
init_resolve_auth();
|
|
30889
30421
|
init_clarity();
|
|
30890
|
-
init_subgraph();
|
|
30891
30422
|
init_api();
|
|
30892
30423
|
init_format();
|
|
30893
30424
|
ORM_ONLY_CODEGEN_FLAGS = {
|
|
@@ -34789,7 +34320,7 @@ init_esm();
|
|
|
34789
34320
|
// package.json
|
|
34790
34321
|
var package_default = {
|
|
34791
34322
|
name: "@secondlayer/cli",
|
|
34792
|
-
version: "
|
|
34323
|
+
version: "12.0.0",
|
|
34793
34324
|
repository: {
|
|
34794
34325
|
type: "git",
|
|
34795
34326
|
url: "git+https://github.com/ryanwaits/secondlayer.git",
|
|
@@ -34830,12 +34361,12 @@ var package_default = {
|
|
|
34830
34361
|
"@biomejs/wasm-nodejs": "^1.9.0",
|
|
34831
34362
|
"@dsnp/parquetjs": "^1.8.8",
|
|
34832
34363
|
"@inquirer/prompts": "^8.2.0",
|
|
34833
|
-
"@secondlayer/bundler": "^0.5.
|
|
34834
|
-
"@secondlayer/scaffold": "^1.5.
|
|
34835
|
-
"@secondlayer/sdk": "^
|
|
34836
|
-
"@secondlayer/shared": "^
|
|
34837
|
-
"@secondlayer/stacks": "^
|
|
34838
|
-
"@secondlayer/subgraphs": "^
|
|
34364
|
+
"@secondlayer/bundler": "^0.5.1",
|
|
34365
|
+
"@secondlayer/scaffold": "^1.5.4",
|
|
34366
|
+
"@secondlayer/sdk": "^8.0.0",
|
|
34367
|
+
"@secondlayer/shared": "^9.0.0",
|
|
34368
|
+
"@secondlayer/stacks": "^4.0.0",
|
|
34369
|
+
"@secondlayer/subgraphs": "^4.0.1",
|
|
34839
34370
|
esbuild: "^0.27.4",
|
|
34840
34371
|
"smol-toml": "^1.6.1",
|
|
34841
34372
|
zod: "^4.3.6"
|
|
@@ -34855,134 +34386,6 @@ var package_default = {
|
|
|
34855
34386
|
}
|
|
34856
34387
|
};
|
|
34857
34388
|
|
|
34858
|
-
// src/commands/account.ts
|
|
34859
|
-
init_api_client();
|
|
34860
|
-
init_output();
|
|
34861
|
-
|
|
34862
|
-
// src/commands/billing.ts
|
|
34863
|
-
init_http();
|
|
34864
|
-
init_output();
|
|
34865
|
-
async function runBillingStatus(json) {
|
|
34866
|
-
let res;
|
|
34867
|
-
try {
|
|
34868
|
-
res = await httpPlatform("/api/billing/status");
|
|
34869
|
-
} catch (err) {
|
|
34870
|
-
if (err instanceof CliHttpError) {
|
|
34871
|
-
error(err.message);
|
|
34872
|
-
process.exit(1);
|
|
34873
|
-
}
|
|
34874
|
-
throw err;
|
|
34875
|
-
}
|
|
34876
|
-
output({ json, data: res, human: () => renderBillingStatus(res) });
|
|
34877
|
-
}
|
|
34878
|
-
function addBillingCommand(parent) {
|
|
34879
|
-
parent.command("billing").description("Show archive credit balance (hidden; use secondlayer credits)").option("--json", "Output as JSON").action((options) => runBillingStatus(options.json));
|
|
34880
|
-
}
|
|
34881
|
-
function renderBillingStatus(res) {
|
|
34882
|
-
if (!res.subscription) {
|
|
34883
|
-
console.log(formatKeyValue([
|
|
34884
|
-
["Credits", dim("secondlayer credits balance")],
|
|
34885
|
-
[
|
|
34886
|
-
"Buy",
|
|
34887
|
-
dim("secondlayer credits buy --email you@example.com --pack 25")
|
|
34888
|
-
]
|
|
34889
|
-
]));
|
|
34890
|
-
return;
|
|
34891
|
-
}
|
|
34892
|
-
const rows = [];
|
|
34893
|
-
rows.push(["Plan", res.plan]);
|
|
34894
|
-
rows.push([
|
|
34895
|
-
"Customer",
|
|
34896
|
-
res.stripeCustomerId ?? dim("(none — no subscription yet)")
|
|
34897
|
-
]);
|
|
34898
|
-
const sub = res.subscription;
|
|
34899
|
-
rows.push(["Subscription", `${sub.id} (${sub.status})`]);
|
|
34900
|
-
if (sub.tier)
|
|
34901
|
-
rows.push(["Tier", sub.tier]);
|
|
34902
|
-
if (sub.amountCents !== null && sub.interval) {
|
|
34903
|
-
const dollars = (sub.amountCents / 100).toFixed(2);
|
|
34904
|
-
rows.push(["Price", `$${dollars} / ${sub.interval}`]);
|
|
34905
|
-
}
|
|
34906
|
-
if (sub.trialEnd) {
|
|
34907
|
-
const days = Math.max(0, Math.round((new Date(sub.trialEnd).getTime() - Date.now()) / 86400000));
|
|
34908
|
-
rows.push([
|
|
34909
|
-
"Trial ends",
|
|
34910
|
-
`${formatDate(sub.trialEnd)} (${days}d remaining)`
|
|
34911
|
-
]);
|
|
34912
|
-
}
|
|
34913
|
-
if (sub.currentPeriodEnd) {
|
|
34914
|
-
rows.push(["Renews", formatDate(sub.currentPeriodEnd)]);
|
|
34915
|
-
}
|
|
34916
|
-
if (sub.cancelAtPeriodEnd) {
|
|
34917
|
-
rows.push(["Cancels at period end", "yes"]);
|
|
34918
|
-
}
|
|
34919
|
-
if (sub.discount) {
|
|
34920
|
-
const off = sub.discount.percentOff !== null ? `${sub.discount.percentOff}% off` : sub.discount.amountOff !== null ? `$${(sub.discount.amountOff / 100).toFixed(2)} off` : "discount";
|
|
34921
|
-
const label = sub.discount.code ? `${sub.discount.code} (${sub.discount.name ?? "coupon"})` : sub.discount.name ?? "applied";
|
|
34922
|
-
rows.push(["Discount", `${label} — ${off}, ${sub.discount.duration}`]);
|
|
34923
|
-
}
|
|
34924
|
-
console.log(formatKeyValue(rows));
|
|
34925
|
-
}
|
|
34926
|
-
function formatDate(iso) {
|
|
34927
|
-
return new Date(iso).toISOString().slice(0, 10);
|
|
34928
|
-
}
|
|
34929
|
-
|
|
34930
|
-
// src/commands/account.ts
|
|
34931
|
-
async function showProfile(json) {
|
|
34932
|
-
const profile = await getAccountProfile();
|
|
34933
|
-
output({
|
|
34934
|
-
json,
|
|
34935
|
-
data: profile,
|
|
34936
|
-
human: () => console.log(formatKeyValue([
|
|
34937
|
-
["Email", profile.email],
|
|
34938
|
-
["Plan", profile.plan],
|
|
34939
|
-
["Display Name", profile.displayName ?? dim("—")],
|
|
34940
|
-
["Bio", profile.bio ?? dim("—")],
|
|
34941
|
-
["Slug", profile.slug ?? dim("—")],
|
|
34942
|
-
[
|
|
34943
|
-
"Notify on reindex complete",
|
|
34944
|
-
profile.notifyReindexComplete ? "on" : "off"
|
|
34945
|
-
]
|
|
34946
|
-
]))
|
|
34947
|
-
});
|
|
34948
|
-
}
|
|
34949
|
-
async function applyProfileUpdate(options, getOptionValueSource) {
|
|
34950
|
-
const data = {};
|
|
34951
|
-
if (options.name)
|
|
34952
|
-
data.display_name = options.name;
|
|
34953
|
-
if (options.bio)
|
|
34954
|
-
data.bio = options.bio;
|
|
34955
|
-
if (options.slug)
|
|
34956
|
-
data.slug = options.slug;
|
|
34957
|
-
if (getOptionValueSource("notifyReindexComplete") === "cli") {
|
|
34958
|
-
data.notify_reindex_complete = options.notifyReindexComplete;
|
|
34959
|
-
}
|
|
34960
|
-
const updated = await updateAccountProfile(data);
|
|
34961
|
-
output({
|
|
34962
|
-
json: options.json,
|
|
34963
|
-
data: updated,
|
|
34964
|
-
human: () => {
|
|
34965
|
-
success("Profile updated");
|
|
34966
|
-
console.log(formatKeyValue([
|
|
34967
|
-
["Display Name", updated.displayName ?? dim("—")],
|
|
34968
|
-
["Bio", updated.bio ?? dim("—")],
|
|
34969
|
-
["Slug", updated.slug ?? dim("—")],
|
|
34970
|
-
[
|
|
34971
|
-
"Notify on reindex complete",
|
|
34972
|
-
updated.notifyReindexComplete ? "on" : "off"
|
|
34973
|
-
]
|
|
34974
|
-
]));
|
|
34975
|
-
}
|
|
34976
|
-
});
|
|
34977
|
-
}
|
|
34978
|
-
function registerAccountCommand(program2) {
|
|
34979
|
-
const account = program2.command("account", { hidden: true }).description("Manage your account profile");
|
|
34980
|
-
addBillingCommand(account);
|
|
34981
|
-
account.command("get").description("Show your account profile").option("--json", "Output as JSON").action(withErrorHandling(async (options) => showProfile(options.json), { action: "show profile" }));
|
|
34982
|
-
account.command("update").description("Update your public profile").option("--name <name>", "Set display name").option("--bio <bio>", "Set bio").option("--slug <slug>", "Set public URL slug").option("--notify-reindex-complete", "Email me when a subgraph reindex finishes").option("--no-notify-reindex-complete", "Don't email me when a subgraph reindex finishes").option("--json", "Output as JSON").action(withErrorHandling(async (options, command) => applyProfileUpdate(options, (key) => command.getOptionValueSource(key)), {
|
|
34983
|
-
action: "update profile"
|
|
34984
|
-
}));
|
|
34985
|
-
}
|
|
34986
34389
|
// src/commands/credits.ts
|
|
34987
34390
|
init_http();
|
|
34988
34391
|
init_output();
|
|
@@ -37409,46 +36812,6 @@ Examples:
|
|
|
37409
36812
|
// src/commands/index.ts
|
|
37410
36813
|
init_index_api();
|
|
37411
36814
|
|
|
37412
|
-
// src/commands/keys.ts
|
|
37413
|
-
init_output();
|
|
37414
|
-
init_resolve_auth();
|
|
37415
|
-
import { SecondLayer as SecondLayer4 } from "@secondlayer/sdk";
|
|
37416
|
-
function registerKeysCommand(program2) {
|
|
37417
|
-
const keys = program2.command("keys", { hidden: true }).description("Manage API keys");
|
|
37418
|
-
keys.command("create").description("Mint a scoped streams/index read key (requires an account-level key)").option("--product <product>", "Key scope: streams or index", "streams").option("--name <name>", "Optional label for the key").option("--json", "Print the full response as JSON").action(async (o3) => {
|
|
37419
|
-
if (o3.product && o3.product !== "streams" && o3.product !== "index") {
|
|
37420
|
-
error("--product must be 'streams' or 'index'");
|
|
37421
|
-
process.exit(1);
|
|
37422
|
-
}
|
|
37423
|
-
const apiKey = resolveEnvKey();
|
|
37424
|
-
if (!apiKey) {
|
|
37425
|
-
error("No API key set. Export SL_API_KEY with an account-level key (issue one at https://www.secondlayer.tools/platform/api-keys).");
|
|
37426
|
-
process.exit(1);
|
|
37427
|
-
}
|
|
37428
|
-
try {
|
|
37429
|
-
const sl = new SecondLayer4({
|
|
37430
|
-
baseUrl: resolveApiUrl(),
|
|
37431
|
-
apiKey
|
|
37432
|
-
});
|
|
37433
|
-
const minted = await sl.apiKeys.create({
|
|
37434
|
-
product: o3.product === "index" ? "index" : "streams",
|
|
37435
|
-
name: o3.name
|
|
37436
|
-
});
|
|
37437
|
-
output({
|
|
37438
|
-
json: o3.json,
|
|
37439
|
-
data: minted,
|
|
37440
|
-
human: () => {
|
|
37441
|
-
success(`Created ${minted.product} key (${minted.prefix}…)`);
|
|
37442
|
-
note("This key is shown once — store it now:");
|
|
37443
|
-
writeData(minted.key);
|
|
37444
|
-
}
|
|
37445
|
-
});
|
|
37446
|
-
} catch (err) {
|
|
37447
|
-
error(err instanceof Error ? err.message : String(err));
|
|
37448
|
-
process.exit(1);
|
|
37449
|
-
}
|
|
37450
|
-
});
|
|
37451
|
-
}
|
|
37452
36815
|
// src/commands/doctor.ts
|
|
37453
36816
|
init_config2();
|
|
37454
36817
|
|
|
@@ -38662,14 +38025,6 @@ async function readActiveProject(cwd, globalDefault) {
|
|
|
38662
38025
|
}
|
|
38663
38026
|
return null;
|
|
38664
38027
|
}
|
|
38665
|
-
async function writeActiveProject(slug, cwd) {
|
|
38666
|
-
const dir = join13(resolve11(cwd), DIRNAME);
|
|
38667
|
-
await mkdir4(dir, { recursive: true });
|
|
38668
|
-
const file = join13(dir, FILENAME);
|
|
38669
|
-
await writeFile4(file, `${JSON.stringify({ slug }, null, 2)}
|
|
38670
|
-
`, "utf8");
|
|
38671
|
-
return file;
|
|
38672
|
-
}
|
|
38673
38028
|
|
|
38674
38029
|
// src/commands/whoami.ts
|
|
38675
38030
|
init_resolve_auth();
|
|
@@ -38699,7 +38054,6 @@ function registerWhoamiCommand(program2) {
|
|
|
38699
38054
|
json: options3.json,
|
|
38700
38055
|
data: {
|
|
38701
38056
|
email: account.email,
|
|
38702
|
-
plan: account.plan,
|
|
38703
38057
|
apiUrl: auth.apiUrl,
|
|
38704
38058
|
authSource,
|
|
38705
38059
|
project: active ? { slug: active.slug, source: active.resolvedFrom } : null
|
|
@@ -38707,7 +38061,6 @@ function registerWhoamiCommand(program2) {
|
|
|
38707
38061
|
human: () => {
|
|
38708
38062
|
const rows = [];
|
|
38709
38063
|
rows.push(["Email", account.email]);
|
|
38710
|
-
rows.push(["Plan", account.plan]);
|
|
38711
38064
|
rows.push(["API URL", auth.apiUrl]);
|
|
38712
38065
|
rows.push(["Auth", dim(authSource)]);
|
|
38713
38066
|
if (active) {
|
|
@@ -38750,85 +38103,6 @@ function registerLogoutCommand(program2) {
|
|
|
38750
38103
|
success("Logged out.");
|
|
38751
38104
|
});
|
|
38752
38105
|
}
|
|
38753
|
-
// src/commands/project.ts
|
|
38754
|
-
init_config2();
|
|
38755
|
-
init_http();
|
|
38756
|
-
init_output();
|
|
38757
|
-
function registerProjectCommand(program2) {
|
|
38758
|
-
const project = program2.command("projects", { hidden: true }).alias("project").description("Manage your Secondlayer project");
|
|
38759
|
-
project.command("list").alias("ls").description("List projects in your account").option("--json", "Output as JSON").action(async (options3) => {
|
|
38760
|
-
try {
|
|
38761
|
-
const res = await httpPlatform("/api/projects");
|
|
38762
|
-
const active = await readActiveProject(process.cwd(), (await loadConfig2()).defaultProject);
|
|
38763
|
-
output({
|
|
38764
|
-
json: options3.json,
|
|
38765
|
-
data: { projects: res.projects, active: active?.slug ?? null },
|
|
38766
|
-
human: () => {
|
|
38767
|
-
if (res.projects.length === 0) {
|
|
38768
|
-
info("No project found.");
|
|
38769
|
-
return;
|
|
38770
|
-
}
|
|
38771
|
-
const rows = res.projects.map((p2) => [
|
|
38772
|
-
p2.slug === active?.slug ? `* ${p2.slug}` : ` ${p2.slug}`,
|
|
38773
|
-
p2.name,
|
|
38774
|
-
p2.network,
|
|
38775
|
-
new Date(p2.createdAt).toLocaleDateString()
|
|
38776
|
-
]);
|
|
38777
|
-
console.log(formatTable(["", "Name", "Network", "Created"], rows));
|
|
38778
|
-
if (active) {
|
|
38779
|
-
console.log(dim(`Active: ${active.slug} (from ${active.resolvedFrom})`));
|
|
38780
|
-
}
|
|
38781
|
-
}
|
|
38782
|
-
});
|
|
38783
|
-
} catch (err) {
|
|
38784
|
-
handleProjectError(err);
|
|
38785
|
-
}
|
|
38786
|
-
});
|
|
38787
|
-
project.command("use <slug>").description("Bind this directory to a project (writes ./.secondlayer/project)").action(async (slug) => {
|
|
38788
|
-
try {
|
|
38789
|
-
await httpPlatform(`/api/projects/${encodeURIComponent(slug)}`);
|
|
38790
|
-
} catch (err) {
|
|
38791
|
-
if (err instanceof CliHttpError && err.status === 404) {
|
|
38792
|
-
error(`Project "${slug}" not found — run 'secondlayer project list' to see available projects`);
|
|
38793
|
-
process.exit(1);
|
|
38794
|
-
}
|
|
38795
|
-
handleProjectError(err);
|
|
38796
|
-
}
|
|
38797
|
-
const path11 = await writeActiveProject(slug, process.cwd());
|
|
38798
|
-
success(`Bound to project "${slug}"`);
|
|
38799
|
-
info(dim(`Written to ${path11}`));
|
|
38800
|
-
info(dim("Tip: add `.secondlayer/` to .gitignore — it's account-personal."));
|
|
38801
|
-
});
|
|
38802
|
-
project.command("get").description("Show the active project for this directory").option("--json", "Output as JSON").action(async (options3) => {
|
|
38803
|
-
const config = await loadConfig2();
|
|
38804
|
-
const active = await readActiveProject(process.cwd(), config.defaultProject);
|
|
38805
|
-
output({
|
|
38806
|
-
json: options3.json,
|
|
38807
|
-
data: active ? { slug: active.slug, source: active.resolvedFrom } : null,
|
|
38808
|
-
human: () => {
|
|
38809
|
-
if (!active) {
|
|
38810
|
-
info("No active project.");
|
|
38811
|
-
info(dim("Run 'secondlayer project use <slug>' to bind this directory."));
|
|
38812
|
-
return;
|
|
38813
|
-
}
|
|
38814
|
-
console.log(active.slug);
|
|
38815
|
-
console.log(dim(`(from ${active.resolvedFrom})`));
|
|
38816
|
-
}
|
|
38817
|
-
});
|
|
38818
|
-
});
|
|
38819
|
-
}
|
|
38820
|
-
function handleProjectError(err) {
|
|
38821
|
-
if (err instanceof CliHttpError) {
|
|
38822
|
-
if (err.code === "SESSION_EXPIRED") {
|
|
38823
|
-
error("Session expired. Run: secondlayer login");
|
|
38824
|
-
process.exit(1);
|
|
38825
|
-
}
|
|
38826
|
-
error(err.message);
|
|
38827
|
-
process.exit(1);
|
|
38828
|
-
}
|
|
38829
|
-
error(err instanceof Error ? err.message : String(err));
|
|
38830
|
-
process.exit(1);
|
|
38831
|
-
}
|
|
38832
38106
|
// src/commands/verify.ts
|
|
38833
38107
|
import {
|
|
38834
38108
|
compareRangeDigests,
|
|
@@ -39419,6 +38693,7 @@ Exit codes:
|
|
|
39419
38693
|
});
|
|
39420
38694
|
}
|
|
39421
38695
|
// src/commands/bootstrap.ts
|
|
38696
|
+
import { createHash as createHash3 } from "node:crypto";
|
|
39422
38697
|
import { unlink as unlink3, writeFile as writeFile6 } from "node:fs/promises";
|
|
39423
38698
|
import { tmpdir as tmpdir3 } from "node:os";
|
|
39424
38699
|
import { join as join16 } from "node:path";
|
|
@@ -39430,6 +38705,7 @@ import {
|
|
|
39430
38705
|
} from "@secondlayer/shared/archive/copy-loader";
|
|
39431
38706
|
import { computeRangeDigest as computeRangeDigest3 } from "@secondlayer/shared/archive/range-digest";
|
|
39432
38707
|
import { getDb as getDb4, getRawClient, sql as sql3 } from "@secondlayer/shared/db";
|
|
38708
|
+
import { upsertSyncScope } from "@secondlayer/shared/db/queries/sync-scope";
|
|
39433
38709
|
|
|
39434
38710
|
// src/lib/bootstrap-resume.ts
|
|
39435
38711
|
function planTornImport(input4) {
|
|
@@ -39466,6 +38742,18 @@ var BOOTSTRAP_EXIT = {
|
|
|
39466
38742
|
};
|
|
39467
38743
|
var BLOCKS_PER_DAY_ESTIMATE = 5400;
|
|
39468
38744
|
var RESTORE_ROWS_PER_SECOND = 30000;
|
|
38745
|
+
function archiveScopeBounds(partitions) {
|
|
38746
|
+
if (partitions.length === 0)
|
|
38747
|
+
return null;
|
|
38748
|
+
return {
|
|
38749
|
+
start_height: Math.min(...partitions.map((p2) => p2.from_block)),
|
|
38750
|
+
tip_height: Math.max(...partitions.map((p2) => p2.to_block))
|
|
38751
|
+
};
|
|
38752
|
+
}
|
|
38753
|
+
function manifestDigest(manifest) {
|
|
38754
|
+
const { signature: _signature, key_id: _keyId, ...payload } = manifest;
|
|
38755
|
+
return createHash3("sha256").update(JSON.stringify(payload)).digest("hex");
|
|
38756
|
+
}
|
|
39469
38757
|
async function readNodeTip() {
|
|
39470
38758
|
const url = process.env.STACKS_NODE_RPC_URL ?? "http://localhost:20443";
|
|
39471
38759
|
try {
|
|
@@ -39520,10 +38808,11 @@ async function loadPartition(reference, partition, rawClient) {
|
|
|
39520
38808
|
return written;
|
|
39521
38809
|
}
|
|
39522
38810
|
function attachBootstrapCommand(cmd) {
|
|
39523
|
-
return cmd.requiredOption("--against <manifest>", "archive manifest: an https URL or a local file path").option("--to-block <n>", "stop at this height instead of the archive tip").option("--public-key <pem>", "pin the signing key instead of fetching it").option("-y, --yes", "skip the confirmation prompt").option("--json", "Output as JSON").addHelpText("after", `
|
|
38811
|
+
return cmd.requiredOption("--against <manifest>", "archive manifest: an https URL or a local file path").option("--to-block <n>", "stop at this height instead of the archive tip").option("--from-block <n>", "forward-only: restore from this height instead of genesis; earlier history is declared out of scope").option("--public-key <pem>", "pin the signing key instead of fetching it").option("-y, --yes", "skip the confirmation prompt").option("--json", "Output as JSON").addHelpText("after", `
|
|
39524
38812
|
Examples:
|
|
39525
38813
|
$ secondlayer bootstrap --against https://archive.secondlayer.tools/.../snapshots/<digest>.json
|
|
39526
38814
|
$ secondlayer bootstrap --against ./snapshot.json --to-block 4000000 --yes
|
|
38815
|
+
$ secondlayer bootstrap --against ./snapshot.json --from-block 8000000 --yes
|
|
39527
38816
|
|
|
39528
38817
|
Exit codes:
|
|
39529
38818
|
0 restored and verified
|
|
@@ -39548,7 +38837,8 @@ Exit codes:
|
|
|
39548
38837
|
const maxHeightRow = await db.selectFrom("blocks").select(({ fn }) => fn.max("height").as("max")).executeTakeFirst();
|
|
39549
38838
|
const maxBlockHeight = maxHeightRow?.max === null || maxHeightRow?.max === undefined ? null : Number(maxHeightRow.max);
|
|
39550
38839
|
const toBlock = opts.toBlock === undefined ? undefined : Number(opts.toBlock);
|
|
39551
|
-
const
|
|
38840
|
+
const fromBlock = opts.fromBlock === undefined ? undefined : Number(opts.fromBlock);
|
|
38841
|
+
const declared = (reference.manifest.partitions ?? []).filter((p2) => (toBlock === undefined || p2.to_block <= toBlock) && (fromBlock === undefined || p2.from_block >= fromBlock));
|
|
39552
38842
|
const resume = planTornImport({
|
|
39553
38843
|
hasIndexProgress: !!progress,
|
|
39554
38844
|
maxBlockHeight,
|
|
@@ -39575,6 +38865,8 @@ Exit codes:
|
|
|
39575
38865
|
});
|
|
39576
38866
|
process.exit(BOOTSTRAP_EXIT.REFUSED);
|
|
39577
38867
|
}
|
|
38868
|
+
const scopeBounds = archiveScopeBounds(declared);
|
|
38869
|
+
const startHeight = scopeBounds?.start_height ?? 0;
|
|
39578
38870
|
const totalRows = partitions.reduce((sum, p2) => sum + p2.row_count, 0);
|
|
39579
38871
|
const totalBytes = partitions.reduce((sum, p2) => sum + p2.byte_size, 0);
|
|
39580
38872
|
const tipHeight = Math.max(...partitions.map((p2) => p2.to_block));
|
|
@@ -39584,7 +38876,10 @@ Exit codes:
|
|
|
39584
38876
|
console.error("");
|
|
39585
38877
|
console.error(formatKeyValue([
|
|
39586
38878
|
["archive", reference.origin],
|
|
39587
|
-
[
|
|
38879
|
+
[
|
|
38880
|
+
"coverage",
|
|
38881
|
+
`${startHeight === 0 ? "genesis" : startHeight.toLocaleString()} → ${tipHeight.toLocaleString()}`
|
|
38882
|
+
],
|
|
39588
38883
|
["rows", totalRows.toLocaleString()],
|
|
39589
38884
|
["download", `${(totalBytes / 1e9).toFixed(1)} GB`],
|
|
39590
38885
|
["signature", "verified"]
|
|
@@ -39642,6 +38937,17 @@ Exit codes:
|
|
|
39642
38937
|
highest_seen_block = GREATEST(index_progress.highest_seen_block, EXCLUDED.highest_seen_block),
|
|
39643
38938
|
updated_at = NOW()
|
|
39644
38939
|
`.execute(db);
|
|
38940
|
+
const genesisRow = startHeight === 0 ? await db.selectFrom("blocks").select("hash").where("canonical", "=", true).where("height", "=", 0).executeTakeFirst() : undefined;
|
|
38941
|
+
await upsertSyncScope(db, {
|
|
38942
|
+
network,
|
|
38943
|
+
start_height: startHeight,
|
|
38944
|
+
target_height: null,
|
|
38945
|
+
bootstrap: {
|
|
38946
|
+
source: "archive",
|
|
38947
|
+
manifest_digest: manifestDigest(reference.manifest),
|
|
38948
|
+
genesis_hash: genesisRow?.hash ?? null
|
|
38949
|
+
}
|
|
38950
|
+
});
|
|
39645
38951
|
const seam = nodeTipAtStart !== null && nodeTipAtStart > tipHeight ? {
|
|
39646
38952
|
nodeTip: nodeTipAtStart,
|
|
39647
38953
|
gap: nodeTipAtStart - tipHeight
|
|
@@ -39650,6 +38956,7 @@ Exit codes:
|
|
|
39650
38956
|
const report = {
|
|
39651
38957
|
status: divergent === 0 ? "restored" : "divergent",
|
|
39652
38958
|
archive: reference.origin,
|
|
38959
|
+
start_height: startHeight,
|
|
39653
38960
|
tip_height: tipHeight,
|
|
39654
38961
|
rows: loaded,
|
|
39655
38962
|
ranges_verified: referenceDigests.length,
|
|
@@ -39672,6 +38979,9 @@ Exit codes:
|
|
|
39672
38979
|
}
|
|
39673
38980
|
console.error("");
|
|
39674
38981
|
console.error(dim(`Your instance holds history through ${tipHeight.toLocaleString()} and will resume at ${(tipHeight + 1).toLocaleString()}.`));
|
|
38982
|
+
if (startHeight > 0) {
|
|
38983
|
+
console.error(dim(` Scope starts at ${startHeight.toLocaleString()} — earlier history is declared out of scope, not missing.`));
|
|
38984
|
+
}
|
|
39675
38985
|
if (seam) {
|
|
39676
38986
|
console.error(dim(`The chain advanced to ${seam.nodeTip.toLocaleString()} while restoring — ${seam.gap.toLocaleString()} blocks to catch up.`));
|
|
39677
38987
|
console.error(dim(" Start the indexer; it backfills that range before following the tip."));
|
|
@@ -39696,10 +39006,265 @@ Exit codes:
|
|
|
39696
39006
|
function registerBootstrapCommand(program2) {
|
|
39697
39007
|
attachBootstrapCommand(program2.command("bootstrap").description("Restore chain history from a verified archive instead of syncing from genesis"));
|
|
39698
39008
|
}
|
|
39009
|
+
// src/commands/backup.ts
|
|
39010
|
+
init_output();
|
|
39011
|
+
import { createHash as createHash4 } from "node:crypto";
|
|
39012
|
+
import {
|
|
39013
|
+
chmodSync,
|
|
39014
|
+
existsSync as existsSync8,
|
|
39015
|
+
mkdirSync as mkdirSync6,
|
|
39016
|
+
readFileSync as readFileSync7,
|
|
39017
|
+
statSync as statSync3,
|
|
39018
|
+
writeFileSync as writeFileSync7
|
|
39019
|
+
} from "node:fs";
|
|
39020
|
+
import { join as join17, resolve as resolve13 } from "node:path";
|
|
39021
|
+
import { getDb as getDb5, sql as sql4 } from "@secondlayer/shared/db";
|
|
39022
|
+
import {
|
|
39023
|
+
decryptBundle,
|
|
39024
|
+
encryptBundle,
|
|
39025
|
+
keyMatchesCanary,
|
|
39026
|
+
planBackup,
|
|
39027
|
+
precheckRestore,
|
|
39028
|
+
sealKeyCanary
|
|
39029
|
+
} from "@secondlayer/shared/runtime";
|
|
39030
|
+
var BACKUP_EXIT = { OK: 0, FAILED: 1, REFUSED: 2 };
|
|
39031
|
+
var MANIFEST_FILE = "manifest.json";
|
|
39032
|
+
var DB_FILE = "db.dump";
|
|
39033
|
+
var SECRETS_FILE = "secrets.enc";
|
|
39034
|
+
var SECRET_ENV_KEYS = [
|
|
39035
|
+
"INSTANCE_TOKEN",
|
|
39036
|
+
"SECONDLAYER_SECRETS_KEY",
|
|
39037
|
+
"STREAMS_SIGNING_PRIVATE_KEY",
|
|
39038
|
+
"SECONDLAYER_WEBHOOK_SIGNING_PRIVATE_KEY",
|
|
39039
|
+
"SL_API_KEY",
|
|
39040
|
+
"ARCHIVE_SIGNING_PUBLIC_KEY"
|
|
39041
|
+
];
|
|
39042
|
+
function readSecretsKey() {
|
|
39043
|
+
const key = process.env.SECONDLAYER_SECRETS_KEY?.trim();
|
|
39044
|
+
return key ? key : undefined;
|
|
39045
|
+
}
|
|
39046
|
+
function sha256File(path11) {
|
|
39047
|
+
return createHash4("sha256").update(readFileSync7(path11)).digest("hex");
|
|
39048
|
+
}
|
|
39049
|
+
async function readScope() {
|
|
39050
|
+
const db = getDb5();
|
|
39051
|
+
const bounds = await sql4`SELECT MIN(height)::text AS min, MAX(height)::text AS max FROM blocks WHERE canonical = true`.execute(db);
|
|
39052
|
+
const row = bounds.rows[0];
|
|
39053
|
+
const network = process.env.STACKS_NETWORK ?? "mainnet";
|
|
39054
|
+
return {
|
|
39055
|
+
network,
|
|
39056
|
+
fromHeight: row?.min ? Number(row.min) : 0,
|
|
39057
|
+
toHeight: row?.max ? Number(row.max) : null
|
|
39058
|
+
};
|
|
39059
|
+
}
|
|
39060
|
+
function requireDatabaseUrl() {
|
|
39061
|
+
const url = process.env.DATABASE_URL?.trim();
|
|
39062
|
+
if (!url)
|
|
39063
|
+
throw new Error("DATABASE_URL is required");
|
|
39064
|
+
return url;
|
|
39065
|
+
}
|
|
39066
|
+
async function runProcess(cmd, label) {
|
|
39067
|
+
const proc = Bun.spawn(cmd, { stdout: "pipe", stderr: "pipe" });
|
|
39068
|
+
const stderr = await new Response(proc.stderr).text();
|
|
39069
|
+
const code = await proc.exited;
|
|
39070
|
+
if (code !== 0) {
|
|
39071
|
+
return { ok: false, stderr: `${label} exited ${code}: ${stderr.trim()}` };
|
|
39072
|
+
}
|
|
39073
|
+
return { ok: true, stderr };
|
|
39074
|
+
}
|
|
39075
|
+
function attachBackupCommand(cmd) {
|
|
39076
|
+
return cmd.requiredOption("--out <dir>", "directory to write the bundle into").option("--passphrase <passphrase>", "encrypt bundled secrets (or set SECONDLAYER_BACKUP_PASSPHRASE)").option("--no-secrets", "omit keys; the bundle restores data only").option("--json", "Output as JSON").action(async (opts) => {
|
|
39077
|
+
try {
|
|
39078
|
+
await runBackup(opts);
|
|
39079
|
+
} catch (error2) {
|
|
39080
|
+
printError(error2 instanceof Error ? error2.message : String(error2));
|
|
39081
|
+
process.exit(BACKUP_EXIT.FAILED);
|
|
39082
|
+
}
|
|
39083
|
+
});
|
|
39084
|
+
}
|
|
39085
|
+
async function runBackup(opts) {
|
|
39086
|
+
const databaseUrl = requireDatabaseUrl();
|
|
39087
|
+
const includeSecrets = opts.secrets !== false;
|
|
39088
|
+
const passphrase = opts.passphrase ?? process.env.SECONDLAYER_BACKUP_PASSPHRASE ?? "";
|
|
39089
|
+
const secretsKey = readSecretsKey();
|
|
39090
|
+
if (includeSecrets && !secretsKey) {
|
|
39091
|
+
printError("SECONDLAYER_SECRETS_KEY is not set, so this backup cannot carry the keys that decrypt your data. Set it, or pass --no-secrets to take a data-only bundle.");
|
|
39092
|
+
process.exit(BACKUP_EXIT.REFUSED);
|
|
39093
|
+
}
|
|
39094
|
+
const scope = await readScope();
|
|
39095
|
+
const plan = planBackup({
|
|
39096
|
+
network: scope.network,
|
|
39097
|
+
fromHeight: scope.fromHeight,
|
|
39098
|
+
toHeight: scope.toHeight,
|
|
39099
|
+
includeSecrets,
|
|
39100
|
+
secretsEncrypted: includeSecrets && passphrase.length > 0,
|
|
39101
|
+
canary: secretsKey ? sealKeyCanary(secretsKey) : undefined
|
|
39102
|
+
});
|
|
39103
|
+
if (!plan.ok) {
|
|
39104
|
+
printError(plan.reason);
|
|
39105
|
+
process.exit(BACKUP_EXIT.REFUSED);
|
|
39106
|
+
}
|
|
39107
|
+
const outDir = resolve13(opts.out);
|
|
39108
|
+
mkdirSync6(outDir, { recursive: true });
|
|
39109
|
+
console.error("dumping database…");
|
|
39110
|
+
const dumpPath = join17(outDir, DB_FILE);
|
|
39111
|
+
const dump = await runProcess(["pg_dump", "-Fc", "--no-owner", "--no-acl", "-f", dumpPath, databaseUrl], "pg_dump");
|
|
39112
|
+
if (!dump.ok) {
|
|
39113
|
+
printError(dump.stderr);
|
|
39114
|
+
process.exit(BACKUP_EXIT.FAILED);
|
|
39115
|
+
}
|
|
39116
|
+
const manifest = {
|
|
39117
|
+
...plan.manifest,
|
|
39118
|
+
db: {
|
|
39119
|
+
file: DB_FILE,
|
|
39120
|
+
sha256: sha256File(dumpPath),
|
|
39121
|
+
bytes: statSync3(dumpPath).size,
|
|
39122
|
+
format: "pg_dump-custom"
|
|
39123
|
+
}
|
|
39124
|
+
};
|
|
39125
|
+
if (includeSecrets && secretsKey) {
|
|
39126
|
+
const lines2 = SECRET_ENV_KEYS.filter((k2) => process.env[k2]).map((k2) => `${k2}=${process.env[k2]}`);
|
|
39127
|
+
const sealed = encryptBundle(lines2.join(`
|
|
39128
|
+
`), passphrase);
|
|
39129
|
+
const secretsPath = join17(outDir, SECRETS_FILE);
|
|
39130
|
+
writeFileSync7(secretsPath, sealed);
|
|
39131
|
+
chmodSync(secretsPath, 384);
|
|
39132
|
+
manifest.secrets = {
|
|
39133
|
+
file: SECRETS_FILE,
|
|
39134
|
+
encrypted: true,
|
|
39135
|
+
canary: sealKeyCanary(secretsKey)
|
|
39136
|
+
};
|
|
39137
|
+
}
|
|
39138
|
+
writeFileSync7(join17(outDir, MANIFEST_FILE), `${JSON.stringify(manifest, null, 2)}
|
|
39139
|
+
`);
|
|
39140
|
+
output({
|
|
39141
|
+
json: opts.json,
|
|
39142
|
+
data: { out: outDir, manifest },
|
|
39143
|
+
human: () => {
|
|
39144
|
+
success(`backup written to ${outDir}`);
|
|
39145
|
+
note(`blocks ${manifest.scope.from_height}–${manifest.scope.to_height ?? "?"} · ${(manifest.db?.bytes ?? 0) / 1024 ** 2 | 0} MB`);
|
|
39146
|
+
if (!manifest.secrets) {
|
|
39147
|
+
warn("no keys in this bundle — it can restore data, but not a working instance");
|
|
39148
|
+
} else {
|
|
39149
|
+
note("keys are encrypted; keep the passphrase somewhere else");
|
|
39150
|
+
}
|
|
39151
|
+
}
|
|
39152
|
+
});
|
|
39153
|
+
}
|
|
39154
|
+
function attachRestoreCommand(cmd) {
|
|
39155
|
+
return cmd.requiredOption("--from <dir>", "bundle directory to restore from").option("--passphrase <passphrase>", "decrypt bundled secrets (or set SECONDLAYER_BACKUP_PASSPHRASE)").option("--apply", "actually restore (default is a dry run)").option("--force", "restore over a database that already holds chain data").option("--json", "Output as JSON").action(async (opts) => {
|
|
39156
|
+
try {
|
|
39157
|
+
await runRestore(opts);
|
|
39158
|
+
} catch (error2) {
|
|
39159
|
+
printError(error2 instanceof Error ? error2.message : String(error2));
|
|
39160
|
+
process.exit(BACKUP_EXIT.FAILED);
|
|
39161
|
+
}
|
|
39162
|
+
});
|
|
39163
|
+
}
|
|
39164
|
+
async function runRestore(opts) {
|
|
39165
|
+
const databaseUrl = requireDatabaseUrl();
|
|
39166
|
+
const dir = resolve13(opts.from);
|
|
39167
|
+
const manifestPath = join17(dir, MANIFEST_FILE);
|
|
39168
|
+
if (!existsSync8(manifestPath)) {
|
|
39169
|
+
printError(`no ${MANIFEST_FILE} in ${dir}`);
|
|
39170
|
+
process.exit(BACKUP_EXIT.REFUSED);
|
|
39171
|
+
}
|
|
39172
|
+
const manifest = JSON.parse(readFileSync7(manifestPath, "utf8"));
|
|
39173
|
+
const dumpPath = join17(dir, manifest.db?.file ?? DB_FILE);
|
|
39174
|
+
if (manifest.db && sha256File(dumpPath) !== manifest.db.sha256) {
|
|
39175
|
+
printError("the database dump does not match the digest in the manifest; this bundle is corrupt or truncated");
|
|
39176
|
+
process.exit(BACKUP_EXIT.REFUSED);
|
|
39177
|
+
}
|
|
39178
|
+
const envKey = readSecretsKey();
|
|
39179
|
+
let bundleKey;
|
|
39180
|
+
if (manifest.secrets) {
|
|
39181
|
+
const passphrase = opts.passphrase ?? process.env.SECONDLAYER_BACKUP_PASSPHRASE ?? "";
|
|
39182
|
+
if (!passphrase) {
|
|
39183
|
+
printError("this bundle carries encrypted keys; supply --passphrase to read them");
|
|
39184
|
+
process.exit(BACKUP_EXIT.REFUSED);
|
|
39185
|
+
}
|
|
39186
|
+
try {
|
|
39187
|
+
const env2 = decryptBundle(readFileSync7(join17(dir, manifest.secrets.file)), passphrase);
|
|
39188
|
+
bundleKey = env2.split(`
|
|
39189
|
+
`).find((l2) => l2.startsWith("SECONDLAYER_SECRETS_KEY="))?.slice("SECONDLAYER_SECRETS_KEY=".length).trim();
|
|
39190
|
+
} catch {
|
|
39191
|
+
printError("could not decrypt the secrets bundle — wrong passphrase?");
|
|
39192
|
+
process.exit(BACKUP_EXIT.REFUSED);
|
|
39193
|
+
}
|
|
39194
|
+
}
|
|
39195
|
+
if (envKey && manifest.secrets && !keyMatchesCanary(manifest.secrets.canary, envKey)) {
|
|
39196
|
+
printError("SECONDLAYER_SECRETS_KEY in this environment is NOT the key this backup was taken with. Restoring would leave every encrypted column unreadable. Unset it to install the bundle's key, or point this restore at the matching instance.");
|
|
39197
|
+
process.exit(BACKUP_EXIT.REFUSED);
|
|
39198
|
+
}
|
|
39199
|
+
const db = getDb5();
|
|
39200
|
+
const present = await sql4`SELECT to_regclass('public.blocks') IS NOT NULL AS present`.execute(db);
|
|
39201
|
+
let targetIsEmpty = true;
|
|
39202
|
+
if (present.rows[0]?.present) {
|
|
39203
|
+
const existing = await sql4`SELECT COUNT(*)::text AS count FROM blocks`.execute(db);
|
|
39204
|
+
targetIsEmpty = (existing.rows[0]?.count ?? "0") === "0";
|
|
39205
|
+
}
|
|
39206
|
+
const check = precheckRestore({
|
|
39207
|
+
manifest,
|
|
39208
|
+
targetNetwork: process.env.STACKS_NETWORK ?? "mainnet",
|
|
39209
|
+
targetIsEmpty,
|
|
39210
|
+
force: opts.force === true,
|
|
39211
|
+
secretsKeyHex: envKey ?? bundleKey
|
|
39212
|
+
});
|
|
39213
|
+
if (!check.ok) {
|
|
39214
|
+
printError(check.reason);
|
|
39215
|
+
process.exit(BACKUP_EXIT.REFUSED);
|
|
39216
|
+
}
|
|
39217
|
+
if (!opts.apply) {
|
|
39218
|
+
output({
|
|
39219
|
+
json: opts.json,
|
|
39220
|
+
data: { dryRun: true, manifest, targetIsEmpty },
|
|
39221
|
+
human: () => {
|
|
39222
|
+
note("dry run — nothing written. Pass --apply to restore.");
|
|
39223
|
+
note(`would restore ${manifest.network} blocks ${manifest.scope.from_height}–${manifest.scope.to_height ?? "?"}`);
|
|
39224
|
+
if (bundleKey && !envKey) {
|
|
39225
|
+
note("after restoring, install the bundle's keys before starting the runtime");
|
|
39226
|
+
}
|
|
39227
|
+
}
|
|
39228
|
+
});
|
|
39229
|
+
return;
|
|
39230
|
+
}
|
|
39231
|
+
console.error("restoring database…");
|
|
39232
|
+
const restore = await runProcess([
|
|
39233
|
+
"pg_restore",
|
|
39234
|
+
"--no-owner",
|
|
39235
|
+
"--no-acl",
|
|
39236
|
+
"--clean",
|
|
39237
|
+
"--if-exists",
|
|
39238
|
+
"-d",
|
|
39239
|
+
databaseUrl,
|
|
39240
|
+
dumpPath
|
|
39241
|
+
], "pg_restore");
|
|
39242
|
+
if (!restore.ok) {
|
|
39243
|
+
printError(restore.stderr);
|
|
39244
|
+
process.exit(BACKUP_EXIT.FAILED);
|
|
39245
|
+
}
|
|
39246
|
+
output({
|
|
39247
|
+
json: opts.json,
|
|
39248
|
+
data: { restored: true, manifest },
|
|
39249
|
+
human: () => {
|
|
39250
|
+
success("restore complete");
|
|
39251
|
+
if (bundleKey && !envKey) {
|
|
39252
|
+
warn("set SECONDLAYER_SECRETS_KEY from the bundle before starting the runtime, or it will generate a new one and the restored data will not decrypt");
|
|
39253
|
+
}
|
|
39254
|
+
note("verify with: secondlayer verify all --against <manifest>");
|
|
39255
|
+
}
|
|
39256
|
+
});
|
|
39257
|
+
}
|
|
39258
|
+
function registerBackupCommand(program2) {
|
|
39259
|
+
attachBackupCommand(program2.command("backup").description("Write a restorable bundle: index, keys, and scope"));
|
|
39260
|
+
}
|
|
39261
|
+
function registerRestoreCommand(program2) {
|
|
39262
|
+
attachRestoreCommand(program2.command("restore").description("Restore an instance from a backup bundle"));
|
|
39263
|
+
}
|
|
39699
39264
|
// src/lib/instance-init.ts
|
|
39700
39265
|
import { randomBytes as randomBytes3 } from "node:crypto";
|
|
39701
|
-
import { existsSync as
|
|
39702
|
-
import { resolve as
|
|
39266
|
+
import { existsSync as existsSync9, readFileSync as readFileSync8, writeFileSync as writeFileSync8 } from "node:fs";
|
|
39267
|
+
import { resolve as resolve14 } from "node:path";
|
|
39703
39268
|
import { generateEd25519KeyPair } from "@secondlayer/shared/crypto/ed25519";
|
|
39704
39269
|
import { INSTANCE_NETWORKS } from "@secondlayer/shared/db/queries/instance";
|
|
39705
39270
|
var INSTANCE_ENV_FILE = ".env.local";
|
|
@@ -39733,9 +39298,9 @@ function unescapeEnvValue(raw) {
|
|
|
39733
39298
|
`).replace(/\\"/g, '"').replace(/\\\\/g, "\\");
|
|
39734
39299
|
}
|
|
39735
39300
|
function readEnvValue(envPath, key) {
|
|
39736
|
-
if (!
|
|
39301
|
+
if (!existsSync9(envPath))
|
|
39737
39302
|
return null;
|
|
39738
|
-
const match =
|
|
39303
|
+
const match = readFileSync8(envPath, "utf8").match(new RegExp(`^${key}=(.*)$`, "m"));
|
|
39739
39304
|
return match ? unescapeEnvValue(match[1]) : null;
|
|
39740
39305
|
}
|
|
39741
39306
|
function buildInstanceEnv(input4) {
|
|
@@ -39778,12 +39343,12 @@ function renderInstanceEnv(env2) {
|
|
|
39778
39343
|
`);
|
|
39779
39344
|
}
|
|
39780
39345
|
function writeInstanceEnv(cwd, env2, file = INSTANCE_ENV_FILE) {
|
|
39781
|
-
const path11 =
|
|
39782
|
-
|
|
39346
|
+
const path11 = resolve14(cwd, file);
|
|
39347
|
+
writeFileSync8(path11, renderInstanceEnv(env2), { mode: 384 });
|
|
39783
39348
|
return path11;
|
|
39784
39349
|
}
|
|
39785
39350
|
function loadExistingInstanceEnv(cwd, file = INSTANCE_ENV_FILE) {
|
|
39786
|
-
const path11 =
|
|
39351
|
+
const path11 = resolve14(cwd, file);
|
|
39787
39352
|
return {
|
|
39788
39353
|
INSTANCE_TOKEN: readEnvValue(path11, "INSTANCE_TOKEN"),
|
|
39789
39354
|
SECONDLAYER_SECRETS_KEY: readEnvValue(path11, "SECONDLAYER_SECRETS_KEY"),
|
|
@@ -39969,7 +39534,6 @@ registerSubscriptionsCommand(program);
|
|
|
39969
39534
|
registerStreamsCommand(program);
|
|
39970
39535
|
registerIndexCommand(program);
|
|
39971
39536
|
program.commandsGroup("Project & codegen:");
|
|
39972
|
-
registerProjectCommand(program);
|
|
39973
39537
|
registerCodegenCommand(program);
|
|
39974
39538
|
var configureGenerate = (cmd) => cmd.option("-c, --config <path>", "Path to config file").option("-o, --output <path>", "Output file path (required when using direct files)").option("-k, --api-key <key>", "Stacks node API key for direct RPC URLs").option("-w, --watch", "Watch for changes").action(async (files, options3) => {
|
|
39975
39539
|
const { deprecatedCodegenNotice: deprecatedCodegenNotice2 } = await Promise.resolve().then(() => (init_codegen(), exports_codegen));
|
|
@@ -39994,11 +39558,11 @@ registerConfigCommand(program);
|
|
|
39994
39558
|
registerContextCommand(program);
|
|
39995
39559
|
registerVerifyCommand(program);
|
|
39996
39560
|
registerRepairCommand(program);
|
|
39561
|
+
registerBackupCommand(program);
|
|
39562
|
+
registerRestoreCommand(program);
|
|
39997
39563
|
program.commandsGroup("Account:");
|
|
39998
39564
|
registerCreditsCommand(program);
|
|
39999
|
-
registerAccountCommand(program);
|
|
40000
|
-
registerKeysCommand(program);
|
|
40001
39565
|
program.parse();
|
|
40002
39566
|
|
|
40003
|
-
//# debugId=
|
|
39567
|
+
//# debugId=D0F621CCA5D6F2E664756E2164756E21
|
|
40004
39568
|
//# sourceMappingURL=cli.js.map
|