sorokeep 1.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/CONTRIBUTING.md +296 -0
- package/LICENSE +21 -0
- package/README.md +677 -0
- package/dist/alerts/alerts.d.ts +21 -0
- package/dist/alerts/alerts.d.ts.map +1 -0
- package/dist/alerts/alerts.js +18 -0
- package/dist/alerts/alerts.js.map +1 -0
- package/dist/alerts/discord.d.ts +3 -0
- package/dist/alerts/discord.d.ts.map +1 -0
- package/dist/alerts/discord.js +199 -0
- package/dist/alerts/discord.js.map +1 -0
- package/dist/alerts/dispatcher.d.ts +13 -0
- package/dist/alerts/dispatcher.d.ts.map +1 -0
- package/dist/alerts/dispatcher.js +91 -0
- package/dist/alerts/dispatcher.js.map +1 -0
- package/dist/alerts/keys.d.ts +24 -0
- package/dist/alerts/keys.d.ts.map +1 -0
- package/dist/alerts/keys.js +44 -0
- package/dist/alerts/keys.js.map +1 -0
- package/dist/alerts/pagerduty.d.ts +8 -0
- package/dist/alerts/pagerduty.d.ts.map +1 -0
- package/dist/alerts/pagerduty.js +164 -0
- package/dist/alerts/pagerduty.js.map +1 -0
- package/dist/alerts/resource.d.ts +23 -0
- package/dist/alerts/resource.d.ts.map +1 -0
- package/dist/alerts/resource.js +167 -0
- package/dist/alerts/resource.js.map +1 -0
- package/dist/alerts/slack.d.ts +7 -0
- package/dist/alerts/slack.d.ts.map +1 -0
- package/dist/alerts/slack.js +184 -0
- package/dist/alerts/slack.js.map +1 -0
- package/dist/alerts/telegram.d.ts +9 -0
- package/dist/alerts/telegram.d.ts.map +1 -0
- package/dist/alerts/telegram.js +133 -0
- package/dist/alerts/telegram.js.map +1 -0
- package/dist/alerts/templates.d.ts +109 -0
- package/dist/alerts/templates.d.ts.map +1 -0
- package/dist/alerts/templates.js +139 -0
- package/dist/alerts/templates.js.map +1 -0
- package/dist/alerts/types.d.ts +113 -0
- package/dist/alerts/types.d.ts.map +1 -0
- package/dist/alerts/types.js +87 -0
- package/dist/alerts/types.js.map +1 -0
- package/dist/alerts/webhook.d.ts +13 -0
- package/dist/alerts/webhook.d.ts.map +1 -0
- package/dist/alerts/webhook.js +56 -0
- package/dist/alerts/webhook.js.map +1 -0
- package/dist/cli/schemaFormatter.d.ts +11 -0
- package/dist/cli/schemaFormatter.d.ts.map +1 -0
- package/dist/cli/schemaFormatter.js +74 -0
- package/dist/cli/schemaFormatter.js.map +1 -0
- package/dist/commands/alerts.d.ts +3 -0
- package/dist/commands/alerts.d.ts.map +1 -0
- package/dist/commands/alerts.js +256 -0
- package/dist/commands/alerts.js.map +1 -0
- package/dist/commands/budget.d.ts +3 -0
- package/dist/commands/budget.d.ts.map +1 -0
- package/dist/commands/budget.js +47 -0
- package/dist/commands/budget.js.map +1 -0
- package/dist/commands/channels.d.ts +3 -0
- package/dist/commands/channels.d.ts.map +1 -0
- package/dist/commands/channels.js +84 -0
- package/dist/commands/channels.js.map +1 -0
- package/dist/commands/check.d.ts +3 -0
- package/dist/commands/check.d.ts.map +1 -0
- package/dist/commands/check.js +58 -0
- package/dist/commands/check.js.map +1 -0
- package/dist/commands/completion.d.ts +3 -0
- package/dist/commands/completion.d.ts.map +1 -0
- package/dist/commands/completion.js +36 -0
- package/dist/commands/completion.js.map +1 -0
- package/dist/commands/costs.d.ts +15 -0
- package/dist/commands/costs.d.ts.map +1 -0
- package/dist/commands/costs.js +280 -0
- package/dist/commands/costs.js.map +1 -0
- package/dist/commands/daemon.d.ts +3 -0
- package/dist/commands/daemon.d.ts.map +1 -0
- package/dist/commands/daemon.js +93 -0
- package/dist/commands/daemon.js.map +1 -0
- package/dist/commands/db.d.ts +3 -0
- package/dist/commands/db.d.ts.map +1 -0
- package/dist/commands/db.js +140 -0
- package/dist/commands/db.js.map +1 -0
- package/dist/commands/guard.d.ts +3 -0
- package/dist/commands/guard.d.ts.map +1 -0
- package/dist/commands/guard.js +181 -0
- package/dist/commands/guard.js.map +1 -0
- package/dist/commands/history.d.ts +3 -0
- package/dist/commands/history.d.ts.map +1 -0
- package/dist/commands/history.js +57 -0
- package/dist/commands/history.js.map +1 -0
- package/dist/commands/inspect.d.ts +3 -0
- package/dist/commands/inspect.d.ts.map +1 -0
- package/dist/commands/inspect.js +82 -0
- package/dist/commands/inspect.js.map +1 -0
- package/dist/commands/mcp.d.ts +3 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +20 -0
- package/dist/commands/mcp.js.map +1 -0
- package/dist/commands/pause.d.ts +3 -0
- package/dist/commands/pause.d.ts.map +1 -0
- package/dist/commands/pause.js +32 -0
- package/dist/commands/pause.js.map +1 -0
- package/dist/commands/resources.d.ts +3 -0
- package/dist/commands/resources.d.ts.map +1 -0
- package/dist/commands/resources.js +95 -0
- package/dist/commands/resources.js.map +1 -0
- package/dist/commands/restore.d.ts +3 -0
- package/dist/commands/restore.d.ts.map +1 -0
- package/dist/commands/restore.js +92 -0
- package/dist/commands/restore.js.map +1 -0
- package/dist/commands/resume.d.ts +3 -0
- package/dist/commands/resume.d.ts.map +1 -0
- package/dist/commands/resume.js +31 -0
- package/dist/commands/resume.js.map +1 -0
- package/dist/commands/status.d.ts +3 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +63 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/watch.d.ts +3 -0
- package/dist/commands/watch.d.ts.map +1 -0
- package/dist/commands/watch.js +163 -0
- package/dist/commands/watch.js.map +1 -0
- package/dist/core/aws_secrets.d.ts +13 -0
- package/dist/core/aws_secrets.d.ts.map +1 -0
- package/dist/core/aws_secrets.js +34 -0
- package/dist/core/aws_secrets.js.map +1 -0
- package/dist/core/budget.d.ts +10 -0
- package/dist/core/budget.d.ts.map +1 -0
- package/dist/core/budget.js +31 -0
- package/dist/core/budget.js.map +1 -0
- package/dist/core/channels.d.ts +67 -0
- package/dist/core/channels.d.ts.map +1 -0
- package/dist/core/channels.js +136 -0
- package/dist/core/channels.js.map +1 -0
- package/dist/core/check.d.ts +25 -0
- package/dist/core/check.d.ts.map +1 -0
- package/dist/core/check.js +85 -0
- package/dist/core/check.js.map +1 -0
- package/dist/core/completion.d.ts +9 -0
- package/dist/core/completion.d.ts.map +1 -0
- package/dist/core/completion.js +68 -0
- package/dist/core/completion.js.map +1 -0
- package/dist/core/costs.d.ts +90 -0
- package/dist/core/costs.d.ts.map +1 -0
- package/dist/core/costs.js +147 -0
- package/dist/core/costs.js.map +1 -0
- package/dist/core/decoder.d.ts +7 -0
- package/dist/core/decoder.d.ts.map +1 -0
- package/dist/core/decoder.js +38 -0
- package/dist/core/decoder.js.map +1 -0
- package/dist/core/discovery.d.ts +38 -0
- package/dist/core/discovery.d.ts.map +1 -0
- package/dist/core/discovery.js +208 -0
- package/dist/core/discovery.js.map +1 -0
- package/dist/core/extension.d.ts +83 -0
- package/dist/core/extension.d.ts.map +1 -0
- package/dist/core/extension.js +429 -0
- package/dist/core/extension.js.map +1 -0
- package/dist/core/inspect.d.ts +62 -0
- package/dist/core/inspect.d.ts.map +1 -0
- package/dist/core/inspect.js +303 -0
- package/dist/core/inspect.js.map +1 -0
- package/dist/core/introspection.d.ts +8 -0
- package/dist/core/introspection.d.ts.map +1 -0
- package/dist/core/introspection.js +8 -0
- package/dist/core/introspection.js.map +1 -0
- package/dist/core/monitor.d.ts +42 -0
- package/dist/core/monitor.d.ts.map +1 -0
- package/dist/core/monitor.js +188 -0
- package/dist/core/monitor.js.map +1 -0
- package/dist/core/rent_projection.d.ts +153 -0
- package/dist/core/rent_projection.d.ts.map +1 -0
- package/dist/core/rent_projection.js +146 -0
- package/dist/core/rent_projection.js.map +1 -0
- package/dist/core/scvalTranslator.d.ts +11 -0
- package/dist/core/scvalTranslator.d.ts.map +1 -0
- package/dist/core/scvalTranslator.js +36 -0
- package/dist/core/scvalTranslator.js.map +1 -0
- package/dist/core/state_diff.d.ts +45 -0
- package/dist/core/state_diff.d.ts.map +1 -0
- package/dist/core/state_diff.js +112 -0
- package/dist/core/state_diff.js.map +1 -0
- package/dist/core/status.d.ts +24 -0
- package/dist/core/status.d.ts.map +1 -0
- package/dist/core/status.js +57 -0
- package/dist/core/status.js.map +1 -0
- package/dist/core/vault.d.ts +37 -0
- package/dist/core/vault.d.ts.map +1 -0
- package/dist/core/vault.js +179 -0
- package/dist/core/vault.js.map +1 -0
- package/dist/core/watch.d.ts +36 -0
- package/dist/core/watch.d.ts.map +1 -0
- package/dist/core/watch.js +176 -0
- package/dist/core/watch.js.map +1 -0
- package/dist/daemon/loop.d.ts +38 -0
- package/dist/daemon/loop.d.ts.map +1 -0
- package/dist/daemon/loop.js +169 -0
- package/dist/daemon/loop.js.map +1 -0
- package/dist/db/backup.d.ts +12 -0
- package/dist/db/backup.d.ts.map +1 -0
- package/dist/db/backup.js +132 -0
- package/dist/db/backup.js.map +1 -0
- package/dist/db/budget.d.ts +17 -0
- package/dist/db/budget.d.ts.map +1 -0
- package/dist/db/budget.js +31 -0
- package/dist/db/budget.js.map +1 -0
- package/dist/db/database.d.ts +6 -0
- package/dist/db/database.d.ts.map +1 -0
- package/dist/db/database.js +134 -0
- package/dist/db/database.js.map +1 -0
- package/dist/db/migrations/.gitkeep +1 -0
- package/dist/db/migrations/001_resource_usage_logs.sql +28 -0
- package/dist/db/migrator.d.ts +28 -0
- package/dist/db/migrator.d.ts.map +1 -0
- package/dist/db/migrator.js +75 -0
- package/dist/db/migrator.js.map +1 -0
- package/dist/db/repositories.d.ts +474 -0
- package/dist/db/repositories.d.ts.map +1 -0
- package/dist/db/repositories.js +809 -0
- package/dist/db/repositories.js.map +1 -0
- package/dist/db/schema.sql +207 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/lib.d.ts +20 -0
- package/dist/lib.d.ts.map +1 -0
- package/dist/lib.js +16 -0
- package/dist/lib.js.map +1 -0
- package/dist/logging/index.d.ts +22 -0
- package/dist/logging/index.d.ts.map +1 -0
- package/dist/logging/index.js +34 -0
- package/dist/logging/index.js.map +1 -0
- package/dist/logging/logger.d.ts +14 -0
- package/dist/logging/logger.d.ts.map +1 -0
- package/dist/logging/logger.js +135 -0
- package/dist/logging/logger.js.map +1 -0
- package/dist/logging/types.d.ts +10 -0
- package/dist/logging/types.d.ts.map +1 -0
- package/dist/logging/types.js +2 -0
- package/dist/logging/types.js.map +1 -0
- package/dist/mcp/index.d.ts +3 -0
- package/dist/mcp/index.d.ts.map +1 -0
- package/dist/mcp/index.js +14 -0
- package/dist/mcp/index.js.map +1 -0
- package/dist/mcp/server.d.ts +10 -0
- package/dist/mcp/server.d.ts.map +1 -0
- package/dist/mcp/server.js +51 -0
- package/dist/mcp/server.js.map +1 -0
- package/dist/mcp/tools/get-extension-costs.d.ts +4 -0
- package/dist/mcp/tools/get-extension-costs.d.ts.map +1 -0
- package/dist/mcp/tools/get-extension-costs.js +33 -0
- package/dist/mcp/tools/get-extension-costs.js.map +1 -0
- package/dist/mcp/tools/get_contract_status.d.ts +8 -0
- package/dist/mcp/tools/get_contract_status.d.ts.map +1 -0
- package/dist/mcp/tools/get_contract_status.js +33 -0
- package/dist/mcp/tools/get_contract_status.js.map +1 -0
- package/dist/rpc/client.d.ts +154 -0
- package/dist/rpc/client.d.ts.map +1 -0
- package/dist/rpc/client.js +766 -0
- package/dist/rpc/client.js.map +1 -0
- package/dist/utils/config.d.ts +51 -0
- package/dist/utils/config.d.ts.map +1 -0
- package/dist/utils/config.js +86 -0
- package/dist/utils/config.js.map +1 -0
- package/dist/utils/formatting.d.ts +10 -0
- package/dist/utils/formatting.d.ts.map +1 -0
- package/dist/utils/formatting.js +66 -0
- package/dist/utils/formatting.js.map +1 -0
- package/dist/utils/watch-config.d.ts +13 -0
- package/dist/utils/watch-config.d.ts.map +1 -0
- package/dist/utils/watch-config.js +32 -0
- package/dist/utils/watch-config.js.map +1 -0
- package/package.json +73 -0
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
export interface VaultConfig {
|
|
2
|
+
url: string;
|
|
3
|
+
token: string;
|
|
4
|
+
namespace?: string;
|
|
5
|
+
}
|
|
6
|
+
export declare class VaultAuthError extends Error {
|
|
7
|
+
constructor(message?: string);
|
|
8
|
+
}
|
|
9
|
+
export declare class VaultSecretNotFoundError extends Error {
|
|
10
|
+
constructor(secretPath: string);
|
|
11
|
+
}
|
|
12
|
+
export declare class VaultSecretError extends Error {
|
|
13
|
+
constructor(message: string);
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* VaultResolver - Retrieve Stellar secret keys dynamically from HashiCorp Vault
|
|
17
|
+
*
|
|
18
|
+
* Supports KV v1 and KV v2 secret engines.
|
|
19
|
+
*
|
|
20
|
+
* Secret lookup order for field names:
|
|
21
|
+
* secret_key, private_key, secret, value, stellar_secret, key
|
|
22
|
+
*
|
|
23
|
+
* You can also specify a custom field with a fragment: vault:secret/data/foo#my_field
|
|
24
|
+
*/
|
|
25
|
+
export declare class VaultResolver {
|
|
26
|
+
private url;
|
|
27
|
+
private token;
|
|
28
|
+
private namespace?;
|
|
29
|
+
constructor(config: VaultConfig);
|
|
30
|
+
/**
|
|
31
|
+
* Fetch a Stellar secret key from Vault
|
|
32
|
+
* @param secretPath Vault secret path, optionally with #field suffix
|
|
33
|
+
* @returns Stellar secret key string (starts with S)
|
|
34
|
+
*/
|
|
35
|
+
getSecret(secretPath: string): Promise<string>;
|
|
36
|
+
}
|
|
37
|
+
//# sourceMappingURL=vault.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault.d.ts","sourceRoot":"","sources":["../../src/core/vault.ts"],"names":[],"mappings":"AAKA,MAAM,WAAW,WAAW;IACxB,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,EAAE,MAAM,CAAC;IACd,SAAS,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,qBAAa,cAAe,SAAQ,KAAK;gBACzB,OAAO,GAAE,MAAsC;CAI9D;AAED,qBAAa,wBAAyB,SAAQ,KAAK;gBACnC,UAAU,EAAE,MAAM;CAIjC;AAED,qBAAa,gBAAiB,SAAQ,KAAK;gBAC3B,OAAO,EAAE,MAAM;CAI9B;AAED;;;;;;;;;GASG;AACH,qBAAa,aAAa;IACtB,OAAO,CAAC,GAAG,CAAS;IACpB,OAAO,CAAC,KAAK,CAAS;IACtB,OAAO,CAAC,SAAS,CAAC,CAAS;gBAEf,MAAM,EAAE,WAAW;IAa/B;;;;OAIG;IACG,SAAS,CAAC,UAAU,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC;CAmIvD"}
|
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-unused-vars */
|
|
2
|
+
import { getLogger } from "../logging/index.js";
|
|
3
|
+
const logger = getLogger().child({ component: "VaultResolver" });
|
|
4
|
+
export class VaultAuthError extends Error {
|
|
5
|
+
constructor(message = "Vault authentication failed") {
|
|
6
|
+
super(message);
|
|
7
|
+
this.name = "VaultAuthError";
|
|
8
|
+
}
|
|
9
|
+
}
|
|
10
|
+
export class VaultSecretNotFoundError extends Error {
|
|
11
|
+
constructor(secretPath) {
|
|
12
|
+
super(`Secret not found in Vault at path: ${secretPath}`);
|
|
13
|
+
this.name = "VaultSecretNotFoundError";
|
|
14
|
+
}
|
|
15
|
+
}
|
|
16
|
+
export class VaultSecretError extends Error {
|
|
17
|
+
constructor(message) {
|
|
18
|
+
super(message);
|
|
19
|
+
this.name = "VaultSecretError";
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
/**
|
|
23
|
+
* VaultResolver - Retrieve Stellar secret keys dynamically from HashiCorp Vault
|
|
24
|
+
*
|
|
25
|
+
* Supports KV v1 and KV v2 secret engines.
|
|
26
|
+
*
|
|
27
|
+
* Secret lookup order for field names:
|
|
28
|
+
* secret_key, private_key, secret, value, stellar_secret, key
|
|
29
|
+
*
|
|
30
|
+
* You can also specify a custom field with a fragment: vault:secret/data/foo#my_field
|
|
31
|
+
*/
|
|
32
|
+
export class VaultResolver {
|
|
33
|
+
url;
|
|
34
|
+
token;
|
|
35
|
+
namespace;
|
|
36
|
+
constructor(config) {
|
|
37
|
+
if (!config.url) {
|
|
38
|
+
throw new VaultSecretError("Vault URL is required");
|
|
39
|
+
}
|
|
40
|
+
if (!config.token) {
|
|
41
|
+
throw new VaultAuthError("Vault token is required");
|
|
42
|
+
}
|
|
43
|
+
// Normalize URL (strip trailing slash)
|
|
44
|
+
this.url = config.url.replace(/\/$/, "");
|
|
45
|
+
this.token = config.token;
|
|
46
|
+
this.namespace = config.namespace;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Fetch a Stellar secret key from Vault
|
|
50
|
+
* @param secretPath Vault secret path, optionally with #field suffix
|
|
51
|
+
* @returns Stellar secret key string (starts with S)
|
|
52
|
+
*/
|
|
53
|
+
async getSecret(secretPath) {
|
|
54
|
+
// Parse field fragment
|
|
55
|
+
let path = secretPath;
|
|
56
|
+
let fieldName = null;
|
|
57
|
+
const hashIdx = secretPath.indexOf("#");
|
|
58
|
+
if (hashIdx !== -1) {
|
|
59
|
+
path = secretPath.slice(0, hashIdx);
|
|
60
|
+
fieldName = secretPath.slice(hashIdx + 1);
|
|
61
|
+
}
|
|
62
|
+
// Strip leading slash
|
|
63
|
+
if (path.startsWith("/")) {
|
|
64
|
+
path = path.slice(1);
|
|
65
|
+
}
|
|
66
|
+
const url = `${this.url}/v1/${path}`;
|
|
67
|
+
const headers = {
|
|
68
|
+
"X-Vault-Token": this.token,
|
|
69
|
+
"Accept": "application/json",
|
|
70
|
+
};
|
|
71
|
+
if (this.namespace) {
|
|
72
|
+
headers["X-Vault-Namespace"] = this.namespace;
|
|
73
|
+
}
|
|
74
|
+
let res;
|
|
75
|
+
try {
|
|
76
|
+
res = await fetch(url, { headers });
|
|
77
|
+
}
|
|
78
|
+
catch (err) {
|
|
79
|
+
const message = err instanceof Error ? err.message : String(err);
|
|
80
|
+
logger.error(`Vault request failed: ${message}`);
|
|
81
|
+
throw new VaultSecretError(`Vault request failed: ${message}`);
|
|
82
|
+
}
|
|
83
|
+
if (!res.ok) {
|
|
84
|
+
if (res.status === 403 || res.status === 401) {
|
|
85
|
+
throw new VaultAuthError(`Vault authentication failed (${res.status})`);
|
|
86
|
+
}
|
|
87
|
+
if (res.status === 404) {
|
|
88
|
+
throw new VaultSecretNotFoundError(path);
|
|
89
|
+
}
|
|
90
|
+
let body = "";
|
|
91
|
+
try {
|
|
92
|
+
body = await res.text();
|
|
93
|
+
}
|
|
94
|
+
catch { /* ignore */ }
|
|
95
|
+
throw new VaultSecretError(`Vault request failed with status ${res.status}: ${body}`);
|
|
96
|
+
}
|
|
97
|
+
let json;
|
|
98
|
+
try {
|
|
99
|
+
json = await res.json();
|
|
100
|
+
}
|
|
101
|
+
catch (err) {
|
|
102
|
+
throw new VaultSecretError("Failed to parse Vault response as JSON");
|
|
103
|
+
}
|
|
104
|
+
// KV v2: data.data.{field}
|
|
105
|
+
// KV v1: data.{field}
|
|
106
|
+
let secretData;
|
|
107
|
+
if (json?.data?.data && typeof json.data.data === "object") {
|
|
108
|
+
secretData = json.data.data;
|
|
109
|
+
}
|
|
110
|
+
else if (json?.data && typeof json.data === "object") {
|
|
111
|
+
secretData = json.data;
|
|
112
|
+
}
|
|
113
|
+
if (!secretData) {
|
|
114
|
+
throw new VaultSecretError(`No secret data found at Vault path: ${path}`);
|
|
115
|
+
}
|
|
116
|
+
let secretValue;
|
|
117
|
+
if (fieldName) {
|
|
118
|
+
secretValue = secretData[fieldName];
|
|
119
|
+
if (secretValue === undefined) {
|
|
120
|
+
throw new VaultSecretError(`Field '${fieldName}' not found in Vault secret at path: ${path}`);
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
else {
|
|
124
|
+
// Try common field names in order
|
|
125
|
+
const candidates = [
|
|
126
|
+
"secret_key",
|
|
127
|
+
"private_key",
|
|
128
|
+
"secret",
|
|
129
|
+
"value",
|
|
130
|
+
"stellar_secret",
|
|
131
|
+
"key",
|
|
132
|
+
];
|
|
133
|
+
for (const key of candidates) {
|
|
134
|
+
if (typeof secretData[key] === "string") {
|
|
135
|
+
secretValue = secretData[key];
|
|
136
|
+
break;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
// Fallback: if there's exactly one string field that looks like a Stellar secret, use it
|
|
140
|
+
if (!secretValue) {
|
|
141
|
+
const stringEntries = Object.entries(secretData).filter(([, v]) => typeof v === "string");
|
|
142
|
+
if (stringEntries.length === 1) {
|
|
143
|
+
const first = stringEntries[0];
|
|
144
|
+
if (first)
|
|
145
|
+
secretValue = first[1];
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
// Try to find any Stellar-looking secret
|
|
149
|
+
const stellarEntry = stringEntries.find(([, v]) => isStellarSecret(v));
|
|
150
|
+
if (stellarEntry) {
|
|
151
|
+
secretValue = stellarEntry[1];
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
if (!secretValue || typeof secretValue !== "string") {
|
|
157
|
+
throw new VaultSecretError(`No valid secret key found in Vault response at path: ${path}. ` +
|
|
158
|
+
`Expected one of: secret_key, private_key, secret, value, stellar_secret, key`);
|
|
159
|
+
}
|
|
160
|
+
if (!isStellarSecret(secretValue)) {
|
|
161
|
+
throw new VaultSecretError(`Secret retrieved from Vault is not a valid Stellar secret key format. ` +
|
|
162
|
+
`Expected 56-character string starting with 'S', got: ${maskSecret(secretValue)}`);
|
|
163
|
+
}
|
|
164
|
+
logger.debug(`Successfully resolved secret from Vault path: ${path}`);
|
|
165
|
+
return secretValue;
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
/**
|
|
169
|
+
* Check if a string looks like a Stellar secret key
|
|
170
|
+
*/
|
|
171
|
+
function isStellarSecret(value) {
|
|
172
|
+
return typeof value === "string" && value.startsWith("S") && value.length === 56;
|
|
173
|
+
}
|
|
174
|
+
function maskSecret(secret) {
|
|
175
|
+
if (secret.length <= 8)
|
|
176
|
+
return "***";
|
|
177
|
+
return `${secret.slice(0, 4)}...${secret.slice(-4)}`;
|
|
178
|
+
}
|
|
179
|
+
//# sourceMappingURL=vault.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"vault.js","sourceRoot":"","sources":["../../src/core/vault.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
import { ContractInstanceResult, SorokeepLedgerEntryResult } from "../rpc/client.js";
|
|
3
|
+
export interface WatchOptions {
|
|
4
|
+
contractId: string;
|
|
5
|
+
network: string;
|
|
6
|
+
name?: string;
|
|
7
|
+
rpcUrl?: string;
|
|
8
|
+
storageKeys?: string[];
|
|
9
|
+
pollIntervalSeconds?: number;
|
|
10
|
+
/**
|
|
11
|
+
* When true, skip the introspection cache and always fetch fresh data from
|
|
12
|
+
* the RPC. Useful for manual `watch` commands where the user expects an
|
|
13
|
+
* immediate refresh. Defaults to false.
|
|
14
|
+
*/
|
|
15
|
+
forceRefresh?: boolean;
|
|
16
|
+
noIntrospection?: boolean;
|
|
17
|
+
}
|
|
18
|
+
export type WatchResult = {
|
|
19
|
+
success: true;
|
|
20
|
+
contractId: string;
|
|
21
|
+
instance: ContractInstanceResult;
|
|
22
|
+
wasm: SorokeepLedgerEntryResult | null;
|
|
23
|
+
wasmWarning?: string;
|
|
24
|
+
/** True when the result was served from the introspection cache (no RPC call). */
|
|
25
|
+
fromCache?: boolean;
|
|
26
|
+
} | {
|
|
27
|
+
success: false;
|
|
28
|
+
contractId: string;
|
|
29
|
+
error: string;
|
|
30
|
+
};
|
|
31
|
+
/**
|
|
32
|
+
* Core logic to discover and register a contract and its key entries.
|
|
33
|
+
* - This is `Layer 1` of the architecture.
|
|
34
|
+
*/
|
|
35
|
+
export declare function watchContract(db: Database.Database, options: WatchOptions): Promise<WatchResult>;
|
|
36
|
+
//# sourceMappingURL=watch.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.d.ts","sourceRoot":"","sources":["../../src/core/watch.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAoB,sBAAsB,EAAE,yBAAyB,EAAE,MAAM,kBAAkB,CAAC;AAavG,MAAM,WAAW,YAAY;IACzB,UAAU,EAAE,MAAM,CAAC;IACnB,OAAO,EAAE,MAAM,CAAC;IAChB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,MAAM,EAAE,CAAC;IACvB,mBAAmB,CAAC,EAAE,MAAM,CAAC;IAC7B;;;;OAIG;IACH,YAAY,CAAC,EAAE,OAAO,CAAC;IACvB,eAAe,CAAC,EAAE,OAAO,CAAC;CAC7B;AAED,MAAM,MAAM,WAAW,GACjB;IACF,OAAO,EAAE,IAAI,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,sBAAsB,CAAC;IACjC,IAAI,EAAE,yBAAyB,GAAG,IAAI,CAAC;IACvC,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kFAAkF;IAClF,SAAS,CAAC,EAAE,OAAO,CAAC;CACvB,GAAG;IACA,OAAO,EAAE,KAAK,CAAC;IACf,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,MAAM,CAAC;CACjB,CAAC;AAEF;;;GAGG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,QAAQ,CAAC,QAAQ,EAAE,OAAO,EAAE,YAAY,GAAG,OAAO,CAAC,WAAW,CAAC,CAuLtG"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
import { StellarRpcClient } from "../rpc/client.js";
|
|
2
|
+
import { insertContract, upsertEntry, updateLastCheckedLedger, getContract, updateLastIntrospectedAt, isIntrospectionCacheValid, } from "../db/repositories.js";
|
|
3
|
+
import { getLogger } from "../logging/index.js";
|
|
4
|
+
const logger = getLogger();
|
|
5
|
+
/**
|
|
6
|
+
* Core logic to discover and register a contract and its key entries.
|
|
7
|
+
* - This is `Layer 1` of the architecture.
|
|
8
|
+
*/
|
|
9
|
+
export async function watchContract(db, options) {
|
|
10
|
+
logger.debug(`Watching contract ${options.contractId} on ${options.network}`);
|
|
11
|
+
const { contractId, network, name, rpcUrl, storageKeys, forceRefresh = false, pollIntervalSeconds } = options;
|
|
12
|
+
// Basic Validation
|
|
13
|
+
if (!contractId.startsWith("C") || contractId.length !== 56) {
|
|
14
|
+
logger.error(`Invalid Contract ID format: ${contractId}`);
|
|
15
|
+
return {
|
|
16
|
+
success: false,
|
|
17
|
+
contractId,
|
|
18
|
+
error: "Invalid Contract ID format. Must be a 56-character string starting with 'C'.",
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
const client = new StellarRpcClient(network, rpcUrl);
|
|
22
|
+
try {
|
|
23
|
+
// 0. Check if already registered on a different network
|
|
24
|
+
const existing = getContract(db, contractId);
|
|
25
|
+
if (existing && existing.network !== network) {
|
|
26
|
+
logger.warn(`Contract ${contractId} is already registered on ${existing.network}. To watch on ${network}, unwatch it first.`);
|
|
27
|
+
return {
|
|
28
|
+
success: false,
|
|
29
|
+
contractId,
|
|
30
|
+
error: `Contract ${contractId} is already registered on ${existing.network}. To watch on ${network}, unwatch it first.`,
|
|
31
|
+
};
|
|
32
|
+
}
|
|
33
|
+
// ── Introspection cache check ─────────────────────────────────────────
|
|
34
|
+
// If the contract has been introspected recently (within 24 h) and the
|
|
35
|
+
// caller did not request a forced refresh, skip the RPC calls entirely
|
|
36
|
+
// and return the cached result.
|
|
37
|
+
if (!forceRefresh && isIntrospectionCacheValid(db, contractId)) {
|
|
38
|
+
logger.debug(`Contract ${contractId}: introspection cache is valid — skipping RPC calls`);
|
|
39
|
+
// Build a minimal success result from cached DB data.
|
|
40
|
+
// We return success=true without real instance/wasm objects so that
|
|
41
|
+
// callers (CLI, daemon) know the operation succeeded. The actual
|
|
42
|
+
// entry data remains accurate in the DB from the last introspection.
|
|
43
|
+
return {
|
|
44
|
+
success: true,
|
|
45
|
+
contractId,
|
|
46
|
+
// Provide stub values — callers that need live RPC data should
|
|
47
|
+
// use forceRefresh=true.
|
|
48
|
+
instance: {
|
|
49
|
+
entryKeyXdr: "",
|
|
50
|
+
latestLedger: 0,
|
|
51
|
+
liveUntilLedgerSeq: 0,
|
|
52
|
+
lastModifiedLedgerSeq: 0,
|
|
53
|
+
remainingTTL: 0,
|
|
54
|
+
executableType: "cached",
|
|
55
|
+
wasmHash: existing?.wasm_hash ?? null,
|
|
56
|
+
},
|
|
57
|
+
wasm: null,
|
|
58
|
+
fromCache: true,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
// 1. Fetch Contract Instance Entry
|
|
62
|
+
const instanceEntry = await client.getContractInstanceEntry(contractId);
|
|
63
|
+
if (!instanceEntry) {
|
|
64
|
+
return {
|
|
65
|
+
success: false,
|
|
66
|
+
contractId,
|
|
67
|
+
error: `Contract ${contractId} not found on ${network}.`,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
// 2. Fetch WASM code entry if applicable
|
|
71
|
+
let wasmEntry = null;
|
|
72
|
+
let wasmWarning;
|
|
73
|
+
if (instanceEntry.wasmHash && !options.noIntrospection) {
|
|
74
|
+
wasmEntry = await client.getWasmCodeEntry(instanceEntry.wasmHash);
|
|
75
|
+
if (!wasmEntry) {
|
|
76
|
+
wasmWarning = `WASM entry for hash ${instanceEntry.wasmHash} not found. It might be archived.`;
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
// 3. Fetch Manual Storage Keys if provided
|
|
80
|
+
const extraEntries = [];
|
|
81
|
+
if (storageKeys && storageKeys.length > 0) {
|
|
82
|
+
const ttls = await client.getEntryTTLs(storageKeys);
|
|
83
|
+
extraEntries.push(...ttls.entries);
|
|
84
|
+
}
|
|
85
|
+
// 3.5. Introspection: Fetch monitored keys if contract supports it
|
|
86
|
+
const introspectedEntries = [];
|
|
87
|
+
if (!options.noIntrospection) {
|
|
88
|
+
try {
|
|
89
|
+
const keys = await client.getMonitoredKeys(contractId);
|
|
90
|
+
if (keys.length > 0) {
|
|
91
|
+
const ttls = await client.getEntryTTLs(keys);
|
|
92
|
+
introspectedEntries.push(...ttls.entries);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
catch (error) {
|
|
96
|
+
logger.debug(`Introspection skipped for ${contractId}: ${error}`);
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
// 4. Store in Database
|
|
100
|
+
// Note: insertContract uses ON CONFLICT(id) DO UPDATE
|
|
101
|
+
insertContract(db, {
|
|
102
|
+
id: contractId,
|
|
103
|
+
name: name,
|
|
104
|
+
network: network,
|
|
105
|
+
wasm_hash: instanceEntry.wasmHash ?? undefined,
|
|
106
|
+
poll_interval_seconds: pollIntervalSeconds ?? null,
|
|
107
|
+
});
|
|
108
|
+
// Store Instance Entry
|
|
109
|
+
upsertEntry(db, {
|
|
110
|
+
contract_id: contractId,
|
|
111
|
+
entry_key_xdr: instanceEntry.entryKeyXdr,
|
|
112
|
+
entry_type: "instance",
|
|
113
|
+
label: "Contract Instance",
|
|
114
|
+
live_until_ledger: instanceEntry.liveUntilLedgerSeq,
|
|
115
|
+
last_modified_ledger: instanceEntry.lastModifiedLedgerSeq,
|
|
116
|
+
discovery_source: "deterministic",
|
|
117
|
+
});
|
|
118
|
+
// Store WASM Entry if found
|
|
119
|
+
if (wasmEntry) {
|
|
120
|
+
upsertEntry(db, {
|
|
121
|
+
contract_id: contractId,
|
|
122
|
+
entry_key_xdr: wasmEntry.entryKeyXdr,
|
|
123
|
+
entry_type: "wasm",
|
|
124
|
+
label: "WASM Code",
|
|
125
|
+
live_until_ledger: wasmEntry.liveUntilLedgerSeq,
|
|
126
|
+
last_modified_ledger: wasmEntry.lastModifiedLedgerSeq,
|
|
127
|
+
discovery_source: "deterministic",
|
|
128
|
+
});
|
|
129
|
+
}
|
|
130
|
+
// Store Manual Storage Entries
|
|
131
|
+
for (const entry of extraEntries) {
|
|
132
|
+
upsertEntry(db, {
|
|
133
|
+
contract_id: contractId,
|
|
134
|
+
entry_key_xdr: entry.entryKeyXdr,
|
|
135
|
+
entry_type: "persistent", // Defaulting to persistent for manual keys
|
|
136
|
+
label: "Manual Storage Entry",
|
|
137
|
+
live_until_ledger: entry.liveUntilLedgerSeq,
|
|
138
|
+
last_modified_ledger: entry.lastModifiedLedgerSeq,
|
|
139
|
+
discovery_source: "manual",
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
// Store Introspected Entries
|
|
143
|
+
for (const entry of introspectedEntries) {
|
|
144
|
+
upsertEntry(db, {
|
|
145
|
+
contract_id: contractId,
|
|
146
|
+
entry_key_xdr: entry.entryKeyXdr,
|
|
147
|
+
entry_type: "persistent", // Defaulting to persistent for introspected keys
|
|
148
|
+
label: "Introspected Storage Entry",
|
|
149
|
+
live_until_ledger: entry.liveUntilLedgerSeq,
|
|
150
|
+
last_modified_ledger: entry.lastModifiedLedgerSeq,
|
|
151
|
+
discovery_source: "introspection",
|
|
152
|
+
});
|
|
153
|
+
}
|
|
154
|
+
// Update last checked ledger
|
|
155
|
+
updateLastCheckedLedger(db, contractId, instanceEntry.latestLedger);
|
|
156
|
+
// ── Record successful introspection in the cache ───────────────────────
|
|
157
|
+
updateLastIntrospectedAt(db, contractId, new Date().toISOString());
|
|
158
|
+
return {
|
|
159
|
+
success: true,
|
|
160
|
+
contractId,
|
|
161
|
+
instance: instanceEntry,
|
|
162
|
+
wasm: wasmEntry,
|
|
163
|
+
wasmWarning,
|
|
164
|
+
fromCache: false,
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
catch (error) {
|
|
168
|
+
logger.error(`Error watching contract ${contractId}: ${error.message}`, error);
|
|
169
|
+
return {
|
|
170
|
+
success: false,
|
|
171
|
+
contractId,
|
|
172
|
+
error: error.message,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=watch.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"watch.js","sourceRoot":"","sources":["../../src/core/watch.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import type Database from "better-sqlite3";
|
|
2
|
+
import { type MonitorCycleResult } from "../core/monitor.js";
|
|
3
|
+
export interface DaemonOptions {
|
|
4
|
+
/** Polling interval in milliseconds. Defaults to 300000 (5 minutes). */
|
|
5
|
+
intervalMs?: number;
|
|
6
|
+
/** Optional RPC endpoint URL override. */
|
|
7
|
+
rpcUrl?: string;
|
|
8
|
+
/** Optional sponsor secret key for auto-extensions */
|
|
9
|
+
feeSponsorSecret?: string;
|
|
10
|
+
/** How frequently to run vacuum maintenance. Defaults to 24 hours. */
|
|
11
|
+
vacuumIntervalMs?: number;
|
|
12
|
+
/** Called after every cycle with the result (or null + error on failure). */
|
|
13
|
+
onCycle?: (result: MonitorCycleResult | null, error?: Error) => void;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Start the monitoring daemon.
|
|
17
|
+
*
|
|
18
|
+
* Runs one cycle immediately, then schedules repeating cycles at the
|
|
19
|
+
* configured interval. Never rejects — errors from individual cycles
|
|
20
|
+
* are caught, logged, and forwarded to the optional `onCycle` callback.
|
|
21
|
+
*
|
|
22
|
+
* Calling `startDaemon` while a daemon is already running will stop the
|
|
23
|
+
* previous loop first (kills the old timer), then start fresh.
|
|
24
|
+
*
|
|
25
|
+
* Re-entrance guard: if a cycle is still in-flight when the next interval
|
|
26
|
+
* fires, that tick is skipped silently.
|
|
27
|
+
*/
|
|
28
|
+
export declare function startDaemon(db: Database.Database, network: string, options?: DaemonOptions): Promise<void>;
|
|
29
|
+
/**
|
|
30
|
+
* Stop the monitoring daemon.
|
|
31
|
+
*
|
|
32
|
+
* Clears the interval timer so no further cycles are scheduled.
|
|
33
|
+
* Idempotent — safe to call multiple times or before `startDaemon`.
|
|
34
|
+
* Does NOT abort a cycle that is currently in-flight; it will finish
|
|
35
|
+
* naturally, but no new cycle will be scheduled after it.
|
|
36
|
+
*/
|
|
37
|
+
export declare function stopDaemon(): void;
|
|
38
|
+
//# sourceMappingURL=loop.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"loop.d.ts","sourceRoot":"","sources":["../../src/daemon/loop.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,QAAQ,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EAAmB,KAAK,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAiB9E,MAAM,WAAW,aAAa;IAC1B,wEAAwE;IACxE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,0CAA0C;IAC1C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,sDAAsD;IACtD,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,sEAAsE;IACtE,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,6EAA6E;IAC7E,OAAO,CAAC,EAAE,CAAC,MAAM,EAAE,kBAAkB,GAAG,IAAI,EAAE,KAAK,CAAC,EAAE,KAAK,KAAK,IAAI,CAAC;CACxE;AAcD;;;;;;;;;;;;GAYG;AACH,wBAAsB,WAAW,CAC7B,EAAE,EAAE,QAAQ,CAAC,QAAQ,EACrB,OAAO,EAAE,MAAM,EACf,OAAO,CAAC,EAAE,aAAa,GACxB,OAAO,CAAC,IAAI,CAAC,CAoBf;AAED;;;;;;;GAOG;AACH,wBAAgB,UAAU,IAAI,IAAI,CAUjC"}
|
|
@@ -0,0 +1,169 @@
|
|
|
1
|
+
import { runMonitorCycle } from "../core/monitor.js";
|
|
2
|
+
import { deliverPendingAlerts } from "../alerts/dispatcher.js";
|
|
3
|
+
import { vacuumDatabase } from "../db/database.js";
|
|
4
|
+
import { aggregateDailyCostSnapshots, getAllContracts } from "../db/repositories.js";
|
|
5
|
+
import { getLogger } from "../logging/index.js";
|
|
6
|
+
// Resolve the child logger lazily so that a runtime reconfiguration of the
|
|
7
|
+
// global logger (e.g. the daemon command's `--log-format json`) is in effect
|
|
8
|
+
// by the time the loop emits its first line.
|
|
9
|
+
let loopLogger = null;
|
|
10
|
+
function logger() {
|
|
11
|
+
return (loopLogger ??= getLogger().child({ component: "DaemonLoop" }));
|
|
12
|
+
}
|
|
13
|
+
// ─── Module-level state ───────────────────────────────────────────────────────
|
|
14
|
+
const DEFAULT_INTERVAL_MS = 300_000; // 5 minutes
|
|
15
|
+
const DEFAULT_VACUUM_INTERVAL_MS = 24 * 60 * 60 * 1000; // 24 hours
|
|
16
|
+
let intervalHandle = null;
|
|
17
|
+
let cycleInFlight = false;
|
|
18
|
+
let vacuumIntervalMs = DEFAULT_VACUUM_INTERVAL_MS;
|
|
19
|
+
let lastVacuumAt = 0;
|
|
20
|
+
// ─── Core API ─────────────────────────────────────────────────────────────────
|
|
21
|
+
/**
|
|
22
|
+
* Start the monitoring daemon.
|
|
23
|
+
*
|
|
24
|
+
* Runs one cycle immediately, then schedules repeating cycles at the
|
|
25
|
+
* configured interval. Never rejects — errors from individual cycles
|
|
26
|
+
* are caught, logged, and forwarded to the optional `onCycle` callback.
|
|
27
|
+
*
|
|
28
|
+
* Calling `startDaemon` while a daemon is already running will stop the
|
|
29
|
+
* previous loop first (kills the old timer), then start fresh.
|
|
30
|
+
*
|
|
31
|
+
* Re-entrance guard: if a cycle is still in-flight when the next interval
|
|
32
|
+
* fires, that tick is skipped silently.
|
|
33
|
+
*/
|
|
34
|
+
export async function startDaemon(db, network, options) {
|
|
35
|
+
// Kill any existing loop before starting a new one.
|
|
36
|
+
stopDaemon();
|
|
37
|
+
const intervalMs = options?.intervalMs ?? DEFAULT_INTERVAL_MS;
|
|
38
|
+
vacuumIntervalMs = options?.vacuumIntervalMs ?? DEFAULT_VACUUM_INTERVAL_MS;
|
|
39
|
+
const rpcUrl = options?.rpcUrl;
|
|
40
|
+
const onCycle = options?.onCycle;
|
|
41
|
+
const effectiveIntervalMs = resolvePollIntervalMs(db, network, intervalMs);
|
|
42
|
+
lastVacuumAt = Date.now();
|
|
43
|
+
logger().info(`Daemon starting — network: ${network}, interval: ${intervalMs}ms`);
|
|
44
|
+
// Run the initial cycle immediately.
|
|
45
|
+
await executeCycle(db, network, rpcUrl, options?.feeSponsorSecret, onCycle);
|
|
46
|
+
// Schedule repeating cycles.
|
|
47
|
+
intervalHandle = setInterval(() => {
|
|
48
|
+
void scheduledTick(db, network, rpcUrl, options?.feeSponsorSecret, onCycle);
|
|
49
|
+
}, effectiveIntervalMs);
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Stop the monitoring daemon.
|
|
53
|
+
*
|
|
54
|
+
* Clears the interval timer so no further cycles are scheduled.
|
|
55
|
+
* Idempotent — safe to call multiple times or before `startDaemon`.
|
|
56
|
+
* Does NOT abort a cycle that is currently in-flight; it will finish
|
|
57
|
+
* naturally, but no new cycle will be scheduled after it.
|
|
58
|
+
*/
|
|
59
|
+
export function stopDaemon() {
|
|
60
|
+
if (intervalHandle !== null) {
|
|
61
|
+
clearInterval(intervalHandle);
|
|
62
|
+
intervalHandle = null;
|
|
63
|
+
logger().info("Daemon stopped");
|
|
64
|
+
}
|
|
65
|
+
// Do NOT reset cycleInFlight here — let executeCycle's finally-block
|
|
66
|
+
// handle it when the in-flight cycle completes. Resetting it early
|
|
67
|
+
// breaks the re-entrance guard if startDaemon() is called before the
|
|
68
|
+
// old cycle finishes.
|
|
69
|
+
}
|
|
70
|
+
// ─── Private ──────────────────────────────────────────────────────────────────
|
|
71
|
+
/**
|
|
72
|
+
* Execute a single monitoring cycle with full error isolation.
|
|
73
|
+
*
|
|
74
|
+
* Sets the `cycleInFlight` flag to prevent re-entrance, runs the cycle,
|
|
75
|
+
* invokes the `onCycle` callback, and guarantees that no thrown error
|
|
76
|
+
* (from the cycle or the callback) can kill the daemon.
|
|
77
|
+
*/
|
|
78
|
+
async function executeCycle(db, network, rpcUrl, feeSponsorSecret, onCycle) {
|
|
79
|
+
cycleInFlight = true;
|
|
80
|
+
try {
|
|
81
|
+
const result = await runMonitorCycle(db, network, rpcUrl, feeSponsorSecret);
|
|
82
|
+
logger().debug(`Cycle complete — checked: ${result.contractsChecked}, ` +
|
|
83
|
+
`updated: ${result.entriesUpdated}, ` +
|
|
84
|
+
`crossed: ${result.thresholdsCrossed}, ` +
|
|
85
|
+
`resolved: ${result.alertsResolved}, ` +
|
|
86
|
+
`extended: ${result.extensionsTriggered}, ` +
|
|
87
|
+
`errors: ${result.errors.length}`);
|
|
88
|
+
// Step 2: deliver any pending alerts that accumulated during detection.
|
|
89
|
+
// Errors here are isolated — they must NOT kill the cycle or surface to onCycle.
|
|
90
|
+
try {
|
|
91
|
+
const delivery = await deliverPendingAlerts(db, network);
|
|
92
|
+
if (delivery.attempted > 0) {
|
|
93
|
+
logger().info(`Delivery — attempted: ${delivery.attempted}, ` +
|
|
94
|
+
`delivered: ${delivery.delivered}, failed: ${delivery.failed}`);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
catch (deliveryErr) {
|
|
98
|
+
// This should never happen (deliverPendingAlerts never throws),
|
|
99
|
+
// but guard defensively.
|
|
100
|
+
logger().error("deliverPendingAlerts threw unexpectedly", deliveryErr);
|
|
101
|
+
}
|
|
102
|
+
// Step 3: aggregate daily cost snapshots for past extension history.
|
|
103
|
+
try {
|
|
104
|
+
aggregateDailyCostSnapshots(db);
|
|
105
|
+
}
|
|
106
|
+
catch (snapshotErr) {
|
|
107
|
+
logger().error("aggregateDailyCostSnapshots threw unexpectedly", snapshotErr);
|
|
108
|
+
}
|
|
109
|
+
safeOnCycle(onCycle, result, undefined);
|
|
110
|
+
}
|
|
111
|
+
catch (err) {
|
|
112
|
+
const error = err instanceof Error ? err : new Error(String(err));
|
|
113
|
+
logger().error(`Cycle failed: ${error.message}`, err);
|
|
114
|
+
safeOnCycle(onCycle, null, error);
|
|
115
|
+
}
|
|
116
|
+
finally {
|
|
117
|
+
cycleInFlight = false;
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
async function scheduledTick(db, network, rpcUrl, feeSponsorSecret, onCycle) {
|
|
121
|
+
if (cycleInFlight) {
|
|
122
|
+
logger().debug("Skipping tick — previous cycle still in flight");
|
|
123
|
+
return;
|
|
124
|
+
}
|
|
125
|
+
await runScheduledVacuum(db);
|
|
126
|
+
await executeCycle(db, network, rpcUrl, feeSponsorSecret, onCycle);
|
|
127
|
+
}
|
|
128
|
+
async function runScheduledVacuum(db) {
|
|
129
|
+
if (Date.now() - lastVacuumAt < vacuumIntervalMs) {
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (db.inTransaction) {
|
|
133
|
+
logger().info("Skipping scheduled vacuum — database has an active transaction");
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
logger().info("Scheduled maintenance: starting database vacuum");
|
|
137
|
+
const vacuumed = vacuumDatabase(db);
|
|
138
|
+
if (vacuumed) {
|
|
139
|
+
lastVacuumAt = Date.now();
|
|
140
|
+
logger().info("Scheduled maintenance: database vacuum completed");
|
|
141
|
+
}
|
|
142
|
+
else {
|
|
143
|
+
logger().info("Scheduled maintenance: database vacuum skipped due to busy database");
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Invoke the onCycle callback without letting it kill the daemon.
|
|
148
|
+
*/
|
|
149
|
+
function safeOnCycle(onCycle, result, error) {
|
|
150
|
+
if (!onCycle)
|
|
151
|
+
return;
|
|
152
|
+
try {
|
|
153
|
+
onCycle(result, error);
|
|
154
|
+
}
|
|
155
|
+
catch (cbErr) {
|
|
156
|
+
logger().error("onCycle callback threw — ignoring", cbErr);
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
function resolvePollIntervalMs(db, network, fallbackIntervalMs) {
|
|
160
|
+
const contracts = getAllContracts(db).filter((contract) => contract.network === network);
|
|
161
|
+
const overrides = contracts
|
|
162
|
+
.map((contract) => contract.poll_interval_seconds)
|
|
163
|
+
.filter((value) => typeof value === "number" && value > 0);
|
|
164
|
+
if (overrides.length === 0) {
|
|
165
|
+
return fallbackIntervalMs;
|
|
166
|
+
}
|
|
167
|
+
return Math.min(...overrides) * 1000;
|
|
168
|
+
}
|
|
169
|
+
//# sourceMappingURL=loop.js.map
|