@verusidx/shared 0.1.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/LICENSE +21 -0
- package/build/audit-logger.d.ts +9 -0
- package/build/audit-logger.d.ts.map +1 -0
- package/build/audit-logger.js +48 -0
- package/build/audit-logger.js.map +1 -0
- package/build/conf-parser.d.ts +9 -0
- package/build/conf-parser.d.ts.map +1 -0
- package/build/conf-parser.js +43 -0
- package/build/conf-parser.js.map +1 -0
- package/build/errors.d.ts +29 -0
- package/build/errors.d.ts.map +1 -0
- package/build/errors.js +96 -0
- package/build/errors.js.map +1 -0
- package/build/index.d.ts +11 -0
- package/build/index.d.ts.map +1 -0
- package/build/index.js +18 -0
- package/build/index.js.map +1 -0
- package/build/platform.d.ts +19 -0
- package/build/platform.d.ts.map +1 -0
- package/build/platform.js +99 -0
- package/build/platform.js.map +1 -0
- package/build/read-only-guard.d.ts +14 -0
- package/build/read-only-guard.d.ts.map +1 -0
- package/build/read-only-guard.js +21 -0
- package/build/read-only-guard.js.map +1 -0
- package/build/registry.d.ts +43 -0
- package/build/registry.d.ts.map +1 -0
- package/build/registry.js +111 -0
- package/build/registry.js.map +1 -0
- package/build/rpc-client.d.ts +24 -0
- package/build/rpc-client.d.ts.map +1 -0
- package/build/rpc-client.js +144 -0
- package/build/rpc-client.js.map +1 -0
- package/build/spending-limits.d.ts +35 -0
- package/build/spending-limits.d.ts.map +1 -0
- package/build/spending-limits.js +118 -0
- package/build/spending-limits.js.map +1 -0
- package/build/types.d.ts +82 -0
- package/build/types.d.ts.map +1 -0
- package/build/types.js +11 -0
- package/build/types.js.map +1 -0
- package/package.json +24 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 VerusIDX Contributors
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { AuditEntry } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Log a write operation to the audit trail.
|
|
4
|
+
*
|
|
5
|
+
* Called by MCP server tool handlers after a write operation completes
|
|
6
|
+
* (success or failure). Read-only operations are not logged.
|
|
7
|
+
*/
|
|
8
|
+
export declare function auditLog(entry: Omit<AuditEntry, 'timestamp'>): void;
|
|
9
|
+
//# sourceMappingURL=audit-logger.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-logger.d.ts","sourceRoot":"","sources":["../src/audit-logger.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAwB7C;;;;;GAKG;AACH,wBAAgB,QAAQ,CAAC,KAAK,EAAE,IAAI,CAAC,UAAU,EAAE,WAAW,CAAC,GAAG,IAAI,CAqBnE"}
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
import { appendFileSync, mkdirSync } from 'node:fs';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { getAuditDir } from './platform.js';
|
|
4
|
+
/**
|
|
5
|
+
* Date-stamped append-only audit logger for write operations.
|
|
6
|
+
*
|
|
7
|
+
* Each day gets a new JSONL file (e.g., 2026-03-11.jsonl).
|
|
8
|
+
* Files are created with 0600 permissions since params may contain
|
|
9
|
+
* addresses/amounts. Retention is the operator's responsibility.
|
|
10
|
+
*
|
|
11
|
+
* Disabled with VERUSIDX_AUDIT_LOG=false.
|
|
12
|
+
*/
|
|
13
|
+
function isAuditEnabled() {
|
|
14
|
+
return process.env.VERUSIDX_AUDIT_LOG !== 'false';
|
|
15
|
+
}
|
|
16
|
+
function getTodayFilename() {
|
|
17
|
+
const now = new Date();
|
|
18
|
+
const yyyy = now.getFullYear();
|
|
19
|
+
const mm = String(now.getMonth() + 1).padStart(2, '0');
|
|
20
|
+
const dd = String(now.getDate()).padStart(2, '0');
|
|
21
|
+
return `${yyyy}-${mm}-${dd}.jsonl`;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* Log a write operation to the audit trail.
|
|
25
|
+
*
|
|
26
|
+
* Called by MCP server tool handlers after a write operation completes
|
|
27
|
+
* (success or failure). Read-only operations are not logged.
|
|
28
|
+
*/
|
|
29
|
+
export function auditLog(entry) {
|
|
30
|
+
if (!isAuditEnabled())
|
|
31
|
+
return;
|
|
32
|
+
const auditDir = getAuditDir();
|
|
33
|
+
const filePath = join(auditDir, getTodayFilename());
|
|
34
|
+
const fullEntry = {
|
|
35
|
+
timestamp: new Date().toISOString(),
|
|
36
|
+
...entry,
|
|
37
|
+
};
|
|
38
|
+
try {
|
|
39
|
+
mkdirSync(auditDir, { recursive: true, mode: 0o700 });
|
|
40
|
+
appendFileSync(filePath, JSON.stringify(fullEntry) + '\n', { mode: 0o600 });
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
// Audit logging is best-effort — don't let a logging failure
|
|
44
|
+
// break the actual operation. Write to stderr so it's visible.
|
|
45
|
+
process.stderr.write(`verusidx-mcp: audit log write failed for ${filePath}\n`);
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
//# sourceMappingURL=audit-logger.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"audit-logger.js","sourceRoot":"","sources":["../src/audit-logger.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AACpD,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AACjC,OAAO,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAG5C;;;;;;;;GAQG;AAEH,SAAS,cAAc;IACrB,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,OAAO,CAAC;AACpD,CAAC;AAED,SAAS,gBAAgB;IACvB,MAAM,GAAG,GAAG,IAAI,IAAI,EAAE,CAAC;IACvB,MAAM,IAAI,GAAG,GAAG,CAAC,WAAW,EAAE,CAAC;IAC/B,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,QAAQ,EAAE,GAAG,CAAC,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IACvD,MAAM,EAAE,GAAG,MAAM,CAAC,GAAG,CAAC,OAAO,EAAE,CAAC,CAAC,QAAQ,CAAC,CAAC,EAAE,GAAG,CAAC,CAAC;IAClD,OAAO,GAAG,IAAI,IAAI,EAAE,IAAI,EAAE,QAAQ,CAAC;AACrC,CAAC;AAED;;;;;GAKG;AACH,MAAM,UAAU,QAAQ,CAAC,KAAoC;IAC3D,IAAI,CAAC,cAAc,EAAE;QAAE,OAAO;IAE9B,MAAM,QAAQ,GAAG,WAAW,EAAE,CAAC;IAC/B,MAAM,QAAQ,GAAG,IAAI,CAAC,QAAQ,EAAE,gBAAgB,EAAE,CAAC,CAAC;IAEpD,MAAM,SAAS,GAAe;QAC5B,SAAS,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE;QACnC,GAAG,KAAK;KACT,CAAC;IAEF,IAAI,CAAC;QACH,SAAS,CAAC,QAAQ,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;QACtD,cAAc,CAAC,QAAQ,EAAE,IAAI,CAAC,SAAS,CAAC,SAAS,CAAC,GAAG,IAAI,EAAE,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IAC9E,CAAC;IAAC,MAAM,CAAC;QACP,6DAA6D;QAC7D,+DAA+D;QAC/D,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,4CAA4C,QAAQ,IAAI,CACzD,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import type { ConfValues } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a Verus/Komodo .conf file for RPC credentials and port.
|
|
4
|
+
*
|
|
5
|
+
* Format: key=value lines, # comments, no sections.
|
|
6
|
+
* Returns null if the file cannot be read.
|
|
7
|
+
*/
|
|
8
|
+
export declare function parseConfFile(confPath: string): ConfValues | null;
|
|
9
|
+
//# sourceMappingURL=conf-parser.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conf-parser.d.ts","sourceRoot":"","sources":["../src/conf-parser.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,YAAY,CAAC;AAE7C;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,MAAM,GAAG,UAAU,GAAG,IAAI,CAqCjE"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
/**
|
|
3
|
+
* Parse a Verus/Komodo .conf file for RPC credentials and port.
|
|
4
|
+
*
|
|
5
|
+
* Format: key=value lines, # comments, no sections.
|
|
6
|
+
* Returns null if the file cannot be read.
|
|
7
|
+
*/
|
|
8
|
+
export function parseConfFile(confPath) {
|
|
9
|
+
let content;
|
|
10
|
+
try {
|
|
11
|
+
content = readFileSync(confPath, 'utf-8');
|
|
12
|
+
}
|
|
13
|
+
catch {
|
|
14
|
+
return null;
|
|
15
|
+
}
|
|
16
|
+
const values = {};
|
|
17
|
+
for (const line of content.split('\n')) {
|
|
18
|
+
const trimmed = line.trim();
|
|
19
|
+
if (!trimmed || trimmed.startsWith('#'))
|
|
20
|
+
continue;
|
|
21
|
+
const eqIndex = trimmed.indexOf('=');
|
|
22
|
+
if (eqIndex === -1)
|
|
23
|
+
continue;
|
|
24
|
+
const key = trimmed.slice(0, eqIndex).trim();
|
|
25
|
+
const value = trimmed.slice(eqIndex + 1).trim();
|
|
26
|
+
switch (key) {
|
|
27
|
+
case 'rpcuser':
|
|
28
|
+
values.rpcuser = value;
|
|
29
|
+
break;
|
|
30
|
+
case 'rpcpassword':
|
|
31
|
+
values.rpcpassword = value;
|
|
32
|
+
break;
|
|
33
|
+
case 'rpcport':
|
|
34
|
+
values.rpcport = value;
|
|
35
|
+
break;
|
|
36
|
+
case 'rpchost':
|
|
37
|
+
values.rpchost = value;
|
|
38
|
+
break;
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return values;
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=conf-parser.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"conf-parser.js","sourceRoot":"","sources":["../src/conf-parser.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AAGvC;;;;;GAKG;AACH,MAAM,UAAU,aAAa,CAAC,QAAgB;IAC5C,IAAI,OAAe,CAAC;IACpB,IAAI,CAAC;QACH,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,MAAM,GAAe,EAAE,CAAC;IAE9B,KAAK,MAAM,IAAI,IAAI,OAAO,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC;QACvC,MAAM,OAAO,GAAG,IAAI,CAAC,IAAI,EAAE,CAAC;QAC5B,IAAI,CAAC,OAAO,IAAI,OAAO,CAAC,UAAU,CAAC,GAAG,CAAC;YAAE,SAAS;QAElD,MAAM,OAAO,GAAG,OAAO,CAAC,OAAO,CAAC,GAAG,CAAC,CAAC;QACrC,IAAI,OAAO,KAAK,CAAC,CAAC;YAAE,SAAS;QAE7B,MAAM,GAAG,GAAG,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;QAC7C,MAAM,KAAK,GAAG,OAAO,CAAC,KAAK,CAAC,OAAO,GAAG,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC;QAEhD,QAAQ,GAAG,EAAE,CAAC;YACZ,KAAK,SAAS;gBACZ,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC;gBACvB,MAAM;YACR,KAAK,aAAa;gBAChB,MAAM,CAAC,WAAW,GAAG,KAAK,CAAC;gBAC3B,MAAM;YACR,KAAK,SAAS;gBACZ,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC;gBACvB,MAAM;YACR,KAAK,SAAS;gBACZ,MAAM,CAAC,OAAO,GAAG,KAAK,CAAC;gBACvB,MAAM;QACV,CAAC;IACH,CAAC;IAED,OAAO,MAAM,CAAC;AAChB,CAAC"}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import type { ErrorCategory } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Normalized error from Verus daemon RPC calls.
|
|
4
|
+
*
|
|
5
|
+
* Every error from the daemon (or from pre-RPC guards like spending limits)
|
|
6
|
+
* is wrapped in this class with a machine-readable category.
|
|
7
|
+
*/
|
|
8
|
+
export declare class VerusError extends Error {
|
|
9
|
+
readonly category: ErrorCategory;
|
|
10
|
+
readonly code: number | undefined;
|
|
11
|
+
readonly retryable: boolean;
|
|
12
|
+
constructor(category: ErrorCategory, message: string, code?: number);
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Normalize a daemon RPC error into a VerusError.
|
|
16
|
+
*
|
|
17
|
+
* @param code - The JSON-RPC error code from the daemon
|
|
18
|
+
* @param message - The error message from the daemon
|
|
19
|
+
*/
|
|
20
|
+
export declare function normalizeRpcError(code: number, message: string): VerusError;
|
|
21
|
+
/**
|
|
22
|
+
* Create a CONNECTION_FAILED error from a network-level failure.
|
|
23
|
+
*/
|
|
24
|
+
export declare function connectionError(chain: string, cause?: Error): VerusError;
|
|
25
|
+
/**
|
|
26
|
+
* Create an AUTH_FAILED error.
|
|
27
|
+
*/
|
|
28
|
+
export declare function authError(chain: string): VerusError;
|
|
29
|
+
//# sourceMappingURL=errors.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.d.ts","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEhD;;;;;GAKG;AACH,qBAAa,UAAW,SAAQ,KAAK;IACnC,QAAQ,CAAC,QAAQ,EAAE,aAAa,CAAC;IACjC,QAAQ,CAAC,IAAI,EAAE,MAAM,GAAG,SAAS,CAAC;IAClC,QAAQ,CAAC,SAAS,EAAE,OAAO,CAAC;gBAEhB,QAAQ,EAAE,aAAa,EAAE,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,MAAM;CAOpE;AAqCD;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,MAAM,GAAG,UAAU,CA4B3E;AAED;;GAEG;AACH,wBAAgB,eAAe,CAAC,KAAK,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,KAAK,GAAG,UAAU,CAMxE;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,MAAM,GAAG,UAAU,CAKnD"}
|
package/build/errors.js
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Normalized error from Verus daemon RPC calls.
|
|
3
|
+
*
|
|
4
|
+
* Every error from the daemon (or from pre-RPC guards like spending limits)
|
|
5
|
+
* is wrapped in this class with a machine-readable category.
|
|
6
|
+
*/
|
|
7
|
+
export class VerusError extends Error {
|
|
8
|
+
category;
|
|
9
|
+
code;
|
|
10
|
+
retryable;
|
|
11
|
+
constructor(category, message, code) {
|
|
12
|
+
super(message);
|
|
13
|
+
this.name = 'VerusError';
|
|
14
|
+
this.category = category;
|
|
15
|
+
this.code = code;
|
|
16
|
+
this.retryable = RETRYABLE_CATEGORIES.has(category);
|
|
17
|
+
}
|
|
18
|
+
}
|
|
19
|
+
const RETRYABLE_CATEGORIES = new Set([
|
|
20
|
+
'CONNECTION_FAILED',
|
|
21
|
+
]);
|
|
22
|
+
/**
|
|
23
|
+
* Known daemon error codes mapped to categories.
|
|
24
|
+
*
|
|
25
|
+
* Bitcoin/Zcash/Verus JSON-RPC error codes:
|
|
26
|
+
* -1: general error
|
|
27
|
+
* -5: invalid address or key not found (identity/currency not found)
|
|
28
|
+
* -6: insufficient funds
|
|
29
|
+
* -8: invalid parameter
|
|
30
|
+
* -32601: method not found
|
|
31
|
+
*/
|
|
32
|
+
const CODE_TO_CATEGORY = {
|
|
33
|
+
[-32601]: 'METHOD_NOT_FOUND',
|
|
34
|
+
[-8]: 'INVALID_PARAMS',
|
|
35
|
+
[-6]: 'INSUFFICIENT_FUNDS',
|
|
36
|
+
};
|
|
37
|
+
/**
|
|
38
|
+
* Message patterns for errors where the code alone isn't specific enough.
|
|
39
|
+
* Checked in order — first match wins.
|
|
40
|
+
*/
|
|
41
|
+
const MESSAGE_PATTERNS = [
|
|
42
|
+
{ pattern: /identity.*not found/i, category: 'IDENTITY_NOT_FOUND' },
|
|
43
|
+
{ pattern: /cannot find.*identity/i, category: 'IDENTITY_NOT_FOUND' },
|
|
44
|
+
{ pattern: /no such identity/i, category: 'IDENTITY_NOT_FOUND' },
|
|
45
|
+
{ pattern: /currency.*not found/i, category: 'CURRENCY_NOT_FOUND' },
|
|
46
|
+
{ pattern: /cannot find.*currency/i, category: 'CURRENCY_NOT_FOUND' },
|
|
47
|
+
{ pattern: /no such currency/i, category: 'CURRENCY_NOT_FOUND' },
|
|
48
|
+
{ pattern: /insufficient funds/i, category: 'INSUFFICIENT_FUNDS' },
|
|
49
|
+
{ pattern: /invalid.*param/i, category: 'INVALID_PARAMS' },
|
|
50
|
+
];
|
|
51
|
+
/**
|
|
52
|
+
* Normalize a daemon RPC error into a VerusError.
|
|
53
|
+
*
|
|
54
|
+
* @param code - The JSON-RPC error code from the daemon
|
|
55
|
+
* @param message - The error message from the daemon
|
|
56
|
+
*/
|
|
57
|
+
export function normalizeRpcError(code, message) {
|
|
58
|
+
// Check code-based mapping first
|
|
59
|
+
const codeCategory = CODE_TO_CATEGORY[code];
|
|
60
|
+
if (codeCategory) {
|
|
61
|
+
return new VerusError(codeCategory, message, code);
|
|
62
|
+
}
|
|
63
|
+
// Identity not found uses code -5 in some daemon versions
|
|
64
|
+
if (code === -5) {
|
|
65
|
+
// -5 can mean identity not found OR currency not found — check message
|
|
66
|
+
for (const { pattern, category } of MESSAGE_PATTERNS) {
|
|
67
|
+
if (pattern.test(message)) {
|
|
68
|
+
return new VerusError(category, message, code);
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
// Default -5 to identity not found (most common case)
|
|
72
|
+
return new VerusError('IDENTITY_NOT_FOUND', message, code);
|
|
73
|
+
}
|
|
74
|
+
// Check message patterns for any code
|
|
75
|
+
for (const { pattern, category } of MESSAGE_PATTERNS) {
|
|
76
|
+
if (pattern.test(message)) {
|
|
77
|
+
return new VerusError(category, message, code);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
// Fallback — preserve original message
|
|
81
|
+
return new VerusError('RPC_ERROR', message, code);
|
|
82
|
+
}
|
|
83
|
+
/**
|
|
84
|
+
* Create a CONNECTION_FAILED error from a network-level failure.
|
|
85
|
+
*/
|
|
86
|
+
export function connectionError(chain, cause) {
|
|
87
|
+
const detail = cause ? `: ${cause.message}` : '';
|
|
88
|
+
return new VerusError('CONNECTION_FAILED', `Cannot connect to ${chain} daemon${detail}`);
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Create an AUTH_FAILED error.
|
|
92
|
+
*/
|
|
93
|
+
export function authError(chain) {
|
|
94
|
+
return new VerusError('AUTH_FAILED', `Authentication failed for ${chain} — check rpcuser/rpcpassword in .conf file`);
|
|
95
|
+
}
|
|
96
|
+
//# sourceMappingURL=errors.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"errors.js","sourceRoot":"","sources":["../src/errors.ts"],"names":[],"mappings":"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"}
|
package/build/index.d.ts
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
export type { LocalChainEntry, RemoteChainEntry, ChainEntry, ChainRegistry, RpcCredentials, ConfValues, ErrorCategory, SpendingLimitsConfig, AuditEntry, RpcRequest, RpcResponse, } from './types.js';
|
|
2
|
+
export { isLocalChain, isRemoteChain } from './types.js';
|
|
3
|
+
export { getChainDataDir, getPbaasDir, getChainConfPath, getConfigDir, getRegistryPath, getSpendingLimitsPath, getAuditDir, getCommitmentsDir, getVerusdDefaultPaths, } from './platform.js';
|
|
4
|
+
export { parseConfFile } from './conf-parser.js';
|
|
5
|
+
export { VerusError, normalizeRpcError, connectionError, authError } from './errors.js';
|
|
6
|
+
export { RegistryReader, writeRegistry } from './registry.js';
|
|
7
|
+
export { rpcCall, setRegistryReader, clearCredentialCache } from './rpc-client.js';
|
|
8
|
+
export { auditLog } from './audit-logger.js';
|
|
9
|
+
export { getSpendingLimit, checkSpendingLimits, sumOutputAmounts, ensureSpendingLimitsFile, clearSpendingLimitsCache, } from './spending-limits.js';
|
|
10
|
+
export { isReadOnly, assertWriteEnabled } from './read-only-guard.js';
|
|
11
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AACA,YAAY,EACV,eAAe,EACf,gBAAgB,EAChB,UAAU,EACV,aAAa,EACb,cAAc,EACd,UAAU,EACV,aAAa,EACb,oBAAoB,EACpB,UAAU,EACV,UAAU,EACV,WAAW,GACZ,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAGzD,OAAO,EACL,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,qBAAqB,EACrB,WAAW,EACX,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AAGvB,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAGjD,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxF,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAG9D,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAGnF,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAG7C,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAG9B,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
|
package/build/index.js
ADDED
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
export { isLocalChain, isRemoteChain } from './types.js';
|
|
2
|
+
// Platform
|
|
3
|
+
export { getChainDataDir, getPbaasDir, getChainConfPath, getConfigDir, getRegistryPath, getSpendingLimitsPath, getAuditDir, getCommitmentsDir, getVerusdDefaultPaths, } from './platform.js';
|
|
4
|
+
// Conf parser
|
|
5
|
+
export { parseConfFile } from './conf-parser.js';
|
|
6
|
+
// Errors
|
|
7
|
+
export { VerusError, normalizeRpcError, connectionError, authError } from './errors.js';
|
|
8
|
+
// Registry
|
|
9
|
+
export { RegistryReader, writeRegistry } from './registry.js';
|
|
10
|
+
// RPC client
|
|
11
|
+
export { rpcCall, setRegistryReader, clearCredentialCache } from './rpc-client.js';
|
|
12
|
+
// Audit logger
|
|
13
|
+
export { auditLog } from './audit-logger.js';
|
|
14
|
+
// Spending limits
|
|
15
|
+
export { getSpendingLimit, checkSpendingLimits, sumOutputAmounts, ensureSpendingLimitsFile, clearSpendingLimitsCache, } from './spending-limits.js';
|
|
16
|
+
// Read-only guard
|
|
17
|
+
export { isReadOnly, assertWriteEnabled } from './read-only-guard.js';
|
|
18
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAcA,OAAO,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,YAAY,CAAC;AAEzD,WAAW;AACX,OAAO,EACL,eAAe,EACf,WAAW,EACX,gBAAgB,EAChB,YAAY,EACZ,eAAe,EACf,qBAAqB,EACrB,WAAW,EACX,iBAAiB,EACjB,qBAAqB,GACtB,MAAM,eAAe,CAAC;AAEvB,cAAc;AACd,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AAEjD,SAAS;AACT,OAAO,EAAE,UAAU,EAAE,iBAAiB,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExF,WAAW;AACX,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,eAAe,CAAC;AAE9D,aAAa;AACb,OAAO,EAAE,OAAO,EAAE,iBAAiB,EAAE,oBAAoB,EAAE,MAAM,iBAAiB,CAAC;AAEnF,eAAe;AACf,OAAO,EAAE,QAAQ,EAAE,MAAM,mBAAmB,CAAC;AAE7C,kBAAkB;AAClB,OAAO,EACL,gBAAgB,EAChB,mBAAmB,EACnB,gBAAgB,EAChB,wBAAwB,EACxB,wBAAwB,GACzB,MAAM,sBAAsB,CAAC;AAE9B,kBAAkB;AAClB,OAAO,EAAE,UAAU,EAAE,kBAAkB,EAAE,MAAM,sBAAsB,CAAC"}
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Primary chain data directory (Komodo-style). */
|
|
2
|
+
export declare function getChainDataDir(): string;
|
|
3
|
+
/** PBaaS data directory (Verus-style). */
|
|
4
|
+
export declare function getPbaasDir(): string;
|
|
5
|
+
/** Get the expected .conf file path for a chain in the Komodo data dir. */
|
|
6
|
+
export declare function getChainConfPath(chainName: string): string;
|
|
7
|
+
/** Root config directory for verusidx-mcp. */
|
|
8
|
+
export declare function getConfigDir(): string;
|
|
9
|
+
/** Path to the chain registry file. */
|
|
10
|
+
export declare function getRegistryPath(): string;
|
|
11
|
+
/** Path to the spending limits config file. */
|
|
12
|
+
export declare function getSpendingLimitsPath(): string;
|
|
13
|
+
/** Audit log directory. */
|
|
14
|
+
export declare function getAuditDir(): string;
|
|
15
|
+
/** Commitment storage directory for identity registration. */
|
|
16
|
+
export declare function getCommitmentsDir(): string;
|
|
17
|
+
/** Default locations to search for the verusd binary, after env var and PATH. */
|
|
18
|
+
export declare function getVerusdDefaultPaths(): string[];
|
|
19
|
+
//# sourceMappingURL=platform.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.d.ts","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"AA0BA,mDAAmD;AACnD,wBAAgB,eAAe,IAAI,MAAM,CAUxC;AAED,0CAA0C;AAC1C,wBAAgB,WAAW,IAAI,MAAM,CAUpC;AAED,2EAA2E;AAC3E,wBAAgB,gBAAgB,CAAC,SAAS,EAAE,MAAM,GAAG,MAAM,CAE1D;AAID,8CAA8C;AAC9C,wBAAgB,YAAY,IAAI,MAAM,CAUrC;AAED,uCAAuC;AACvC,wBAAgB,eAAe,IAAI,MAAM,CAExC;AAED,+CAA+C;AAC/C,wBAAgB,qBAAqB,IAAI,MAAM,CAI9C;AAED,2BAA2B;AAC3B,wBAAgB,WAAW,IAAI,MAAM,CAIpC;AAED,8DAA8D;AAC9D,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAID,iFAAiF;AACjF,wBAAgB,qBAAqB,IAAI,MAAM,EAAE,CAoBhD"}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
import { homedir, platform } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
function getPlatform() {
|
|
4
|
+
const p = platform();
|
|
5
|
+
if (p === 'darwin' || p === 'win32')
|
|
6
|
+
return p;
|
|
7
|
+
return 'linux'; // treat all non-mac/win as linux
|
|
8
|
+
}
|
|
9
|
+
function getAppData() {
|
|
10
|
+
return process.env.APPDATA || join(homedir(), 'AppData', 'Roaming');
|
|
11
|
+
}
|
|
12
|
+
// --- Chain data directories ---
|
|
13
|
+
/** Primary chain data directory (Komodo-style). */
|
|
14
|
+
export function getChainDataDir() {
|
|
15
|
+
const p = getPlatform();
|
|
16
|
+
switch (p) {
|
|
17
|
+
case 'darwin':
|
|
18
|
+
return join(homedir(), 'Library', 'Application Support', 'Komodo');
|
|
19
|
+
case 'win32':
|
|
20
|
+
return join(getAppData(), 'Komodo');
|
|
21
|
+
default:
|
|
22
|
+
return join(homedir(), '.komodo');
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
/** PBaaS data directory (Verus-style). */
|
|
26
|
+
export function getPbaasDir() {
|
|
27
|
+
const p = getPlatform();
|
|
28
|
+
switch (p) {
|
|
29
|
+
case 'darwin':
|
|
30
|
+
return join(homedir(), 'Library', 'Application Support', 'Verus', 'pbaas');
|
|
31
|
+
case 'win32':
|
|
32
|
+
return join(getAppData(), 'Verus', 'pbaas');
|
|
33
|
+
default:
|
|
34
|
+
return join(homedir(), '.verus', 'pbaas');
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
/** Get the expected .conf file path for a chain in the Komodo data dir. */
|
|
38
|
+
export function getChainConfPath(chainName) {
|
|
39
|
+
return join(getChainDataDir(), chainName, `${chainName}.conf`);
|
|
40
|
+
}
|
|
41
|
+
// --- verusidx-mcp config directory ---
|
|
42
|
+
/** Root config directory for verusidx-mcp. */
|
|
43
|
+
export function getConfigDir() {
|
|
44
|
+
const p = getPlatform();
|
|
45
|
+
switch (p) {
|
|
46
|
+
case 'darwin':
|
|
47
|
+
return join(homedir(), 'Library', 'Application Support', 'verusidx-mcp');
|
|
48
|
+
case 'win32':
|
|
49
|
+
return join(getAppData(), 'verusidx-mcp');
|
|
50
|
+
default:
|
|
51
|
+
return join(homedir(), '.config', 'verusidx-mcp');
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** Path to the chain registry file. */
|
|
55
|
+
export function getRegistryPath() {
|
|
56
|
+
return join(getConfigDir(), 'chains.json');
|
|
57
|
+
}
|
|
58
|
+
/** Path to the spending limits config file. */
|
|
59
|
+
export function getSpendingLimitsPath() {
|
|
60
|
+
const custom = process.env.VERUSIDX_SPENDING_LIMITS_PATH;
|
|
61
|
+
if (custom)
|
|
62
|
+
return custom;
|
|
63
|
+
return join(getConfigDir(), 'spending-limits.json');
|
|
64
|
+
}
|
|
65
|
+
/** Audit log directory. */
|
|
66
|
+
export function getAuditDir() {
|
|
67
|
+
const custom = process.env.VERUSIDX_AUDIT_DIR;
|
|
68
|
+
if (custom)
|
|
69
|
+
return custom;
|
|
70
|
+
return join(getConfigDir(), 'audit');
|
|
71
|
+
}
|
|
72
|
+
/** Commitment storage directory for identity registration. */
|
|
73
|
+
export function getCommitmentsDir() {
|
|
74
|
+
return join(homedir(), '.verusidx', 'commitments');
|
|
75
|
+
}
|
|
76
|
+
// --- verusd binary locations ---
|
|
77
|
+
/** Default locations to search for the verusd binary, after env var and PATH. */
|
|
78
|
+
export function getVerusdDefaultPaths() {
|
|
79
|
+
const p = getPlatform();
|
|
80
|
+
switch (p) {
|
|
81
|
+
case 'darwin':
|
|
82
|
+
return [
|
|
83
|
+
'/Applications/Verus-Desktop.app/Contents/Resources/verusd/verusd',
|
|
84
|
+
join(homedir(), 'Library', 'Application Support', 'verus-cli', 'verusd'),
|
|
85
|
+
join(homedir(), 'verus-cli', 'verusd'),
|
|
86
|
+
];
|
|
87
|
+
case 'win32':
|
|
88
|
+
return [
|
|
89
|
+
join(process.env.ProgramFiles || 'C:\\Program Files', 'VerusCoin', 'verusd.exe'),
|
|
90
|
+
join(homedir(), 'verus-cli', 'verusd.exe'),
|
|
91
|
+
];
|
|
92
|
+
default:
|
|
93
|
+
return [
|
|
94
|
+
'/opt/verus/verusd',
|
|
95
|
+
join(homedir(), 'verus-cli', 'verusd'),
|
|
96
|
+
];
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
//# sourceMappingURL=platform.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"platform.js","sourceRoot":"","sources":["../src/platform.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Check if the server is running in read-only mode.
|
|
3
|
+
*/
|
|
4
|
+
export declare function isReadOnly(): boolean;
|
|
5
|
+
/**
|
|
6
|
+
* Guard function for write tools. Call at the start of any write tool handler.
|
|
7
|
+
* Throws a WRITE_DISABLED VerusError if read-only mode is active.
|
|
8
|
+
*
|
|
9
|
+
* This catches stale client tool lists — if a client cached the tool list
|
|
10
|
+
* before read-only mode was enabled, it may try to call a write tool that
|
|
11
|
+
* should no longer be available.
|
|
12
|
+
*/
|
|
13
|
+
export declare function assertWriteEnabled(): void;
|
|
14
|
+
//# sourceMappingURL=read-only-guard.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-only-guard.d.ts","sourceRoot":"","sources":["../src/read-only-guard.ts"],"names":[],"mappings":"AAEA;;GAEG;AACH,wBAAgB,UAAU,IAAI,OAAO,CAEpC;AAED;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,IAAI,IAAI,CAOzC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { VerusError } from './errors.js';
|
|
2
|
+
/**
|
|
3
|
+
* Check if the server is running in read-only mode.
|
|
4
|
+
*/
|
|
5
|
+
export function isReadOnly() {
|
|
6
|
+
return process.env.VERUSIDX_READ_ONLY === 'true';
|
|
7
|
+
}
|
|
8
|
+
/**
|
|
9
|
+
* Guard function for write tools. Call at the start of any write tool handler.
|
|
10
|
+
* Throws a WRITE_DISABLED VerusError if read-only mode is active.
|
|
11
|
+
*
|
|
12
|
+
* This catches stale client tool lists — if a client cached the tool list
|
|
13
|
+
* before read-only mode was enabled, it may try to call a write tool that
|
|
14
|
+
* should no longer be available.
|
|
15
|
+
*/
|
|
16
|
+
export function assertWriteEnabled() {
|
|
17
|
+
if (isReadOnly()) {
|
|
18
|
+
throw new VerusError('WRITE_DISABLED', 'This server is running in read-only mode (VERUSIDX_READ_ONLY=true). Write tools are not available.');
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=read-only-guard.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"read-only-guard.js","sourceRoot":"","sources":["../src/read-only-guard.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAEzC;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,OAAO,OAAO,CAAC,GAAG,CAAC,kBAAkB,KAAK,MAAM,CAAC;AACnD,CAAC;AAED;;;;;;;GAOG;AACH,MAAM,UAAU,kBAAkB;IAChC,IAAI,UAAU,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,UAAU,CAClB,gBAAgB,EAChB,oGAAoG,CACrG,CAAC;IACJ,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import type { ChainRegistry, ChainEntry, RpcCredentials } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Chain registry reader with stat()-based invalidation and credential resolution.
|
|
4
|
+
*
|
|
5
|
+
* Usage:
|
|
6
|
+
* const reader = new RegistryReader();
|
|
7
|
+
* const chains = reader.getChains(); // all chains
|
|
8
|
+
* const creds = reader.getCredentials('VRSC'); // resolved credentials
|
|
9
|
+
*/
|
|
10
|
+
export declare class RegistryReader {
|
|
11
|
+
private registryPath;
|
|
12
|
+
private cached;
|
|
13
|
+
private cachedMtimeMs;
|
|
14
|
+
constructor(registryPath?: string);
|
|
15
|
+
/**
|
|
16
|
+
* Read the registry, re-reading from disk only if mtime changed.
|
|
17
|
+
* Returns null if the registry file doesn't exist.
|
|
18
|
+
*/
|
|
19
|
+
read(): ChainRegistry | null;
|
|
20
|
+
/** Force a re-read from disk on next access. */
|
|
21
|
+
invalidate(): void;
|
|
22
|
+
/** Get all chains from the registry. */
|
|
23
|
+
getChains(): Record<string, ChainEntry>;
|
|
24
|
+
/** Get a specific chain entry. */
|
|
25
|
+
getChain(chainName: string): ChainEntry | undefined;
|
|
26
|
+
/** Get the discoveredAt timestamp. */
|
|
27
|
+
getDiscoveredAt(): string | null;
|
|
28
|
+
/**
|
|
29
|
+
* Resolve RPC credentials for a chain.
|
|
30
|
+
*
|
|
31
|
+
* - Local chains: reads credentials from the .conf file on disk
|
|
32
|
+
* - Remote chains: uses credentials from the registry entry
|
|
33
|
+
*/
|
|
34
|
+
resolveCredentials(entry: ChainEntry): RpcCredentials | null;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Write a chain registry atomically.
|
|
38
|
+
*
|
|
39
|
+
* Writes to a temp file then renames — on POSIX, rename() is atomic
|
|
40
|
+
* at the filesystem level, so readers see either the old or new file.
|
|
41
|
+
*/
|
|
42
|
+
export declare function writeRegistry(registry: ChainRegistry, registryPath?: string): void;
|
|
43
|
+
//# sourceMappingURL=registry.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,aAAa,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5E;;;;;;;GAOG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,YAAY,CAAS;IAC7B,OAAO,CAAC,MAAM,CAA8B;IAC5C,OAAO,CAAC,aAAa,CAAK;gBAEd,YAAY,CAAC,EAAE,MAAM;IAIjC;;;OAGG;IACH,IAAI,IAAI,aAAa,GAAG,IAAI;IA2B5B,gDAAgD;IAChD,UAAU,IAAI,IAAI;IAKlB,wCAAwC;IACxC,SAAS,IAAI,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC;IAKvC,kCAAkC;IAClC,QAAQ,CAAC,SAAS,EAAE,MAAM,GAAG,UAAU,GAAG,SAAS;IAInD,sCAAsC;IACtC,eAAe,IAAI,MAAM,GAAG,IAAI;IAIhC;;;;;OAKG;IACH,kBAAkB,CAAC,KAAK,EAAE,UAAU,GAAG,cAAc,GAAG,IAAI;CAoB7D;AAED;;;;;GAKG;AACH,wBAAgB,aAAa,CAAC,QAAQ,EAAE,aAAa,EAAE,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAUlF"}
|
|
@@ -0,0 +1,111 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, renameSync, mkdirSync, statSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { parseConfFile } from './conf-parser.js';
|
|
4
|
+
import { getRegistryPath } from './platform.js';
|
|
5
|
+
import { isLocalChain } from './types.js';
|
|
6
|
+
/**
|
|
7
|
+
* Chain registry reader with stat()-based invalidation and credential resolution.
|
|
8
|
+
*
|
|
9
|
+
* Usage:
|
|
10
|
+
* const reader = new RegistryReader();
|
|
11
|
+
* const chains = reader.getChains(); // all chains
|
|
12
|
+
* const creds = reader.getCredentials('VRSC'); // resolved credentials
|
|
13
|
+
*/
|
|
14
|
+
export class RegistryReader {
|
|
15
|
+
registryPath;
|
|
16
|
+
cached = null;
|
|
17
|
+
cachedMtimeMs = 0;
|
|
18
|
+
constructor(registryPath) {
|
|
19
|
+
this.registryPath = registryPath ?? getRegistryPath();
|
|
20
|
+
}
|
|
21
|
+
/**
|
|
22
|
+
* Read the registry, re-reading from disk only if mtime changed.
|
|
23
|
+
* Returns null if the registry file doesn't exist.
|
|
24
|
+
*/
|
|
25
|
+
read() {
|
|
26
|
+
let stat;
|
|
27
|
+
try {
|
|
28
|
+
stat = statSync(this.registryPath);
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
// File doesn't exist
|
|
32
|
+
this.cached = null;
|
|
33
|
+
this.cachedMtimeMs = 0;
|
|
34
|
+
return null;
|
|
35
|
+
}
|
|
36
|
+
if (this.cached && stat.mtimeMs === this.cachedMtimeMs) {
|
|
37
|
+
return this.cached;
|
|
38
|
+
}
|
|
39
|
+
try {
|
|
40
|
+
const content = readFileSync(this.registryPath, 'utf-8');
|
|
41
|
+
this.cached = JSON.parse(content);
|
|
42
|
+
this.cachedMtimeMs = stat.mtimeMs;
|
|
43
|
+
return this.cached;
|
|
44
|
+
}
|
|
45
|
+
catch {
|
|
46
|
+
this.cached = null;
|
|
47
|
+
this.cachedMtimeMs = 0;
|
|
48
|
+
return null;
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
/** Force a re-read from disk on next access. */
|
|
52
|
+
invalidate() {
|
|
53
|
+
this.cached = null;
|
|
54
|
+
this.cachedMtimeMs = 0;
|
|
55
|
+
}
|
|
56
|
+
/** Get all chains from the registry. */
|
|
57
|
+
getChains() {
|
|
58
|
+
const registry = this.read();
|
|
59
|
+
return registry?.chains ?? {};
|
|
60
|
+
}
|
|
61
|
+
/** Get a specific chain entry. */
|
|
62
|
+
getChain(chainName) {
|
|
63
|
+
return this.getChains()[chainName];
|
|
64
|
+
}
|
|
65
|
+
/** Get the discoveredAt timestamp. */
|
|
66
|
+
getDiscoveredAt() {
|
|
67
|
+
return this.read()?.discoveredAt ?? null;
|
|
68
|
+
}
|
|
69
|
+
/**
|
|
70
|
+
* Resolve RPC credentials for a chain.
|
|
71
|
+
*
|
|
72
|
+
* - Local chains: reads credentials from the .conf file on disk
|
|
73
|
+
* - Remote chains: uses credentials from the registry entry
|
|
74
|
+
*/
|
|
75
|
+
resolveCredentials(entry) {
|
|
76
|
+
if (isLocalChain(entry)) {
|
|
77
|
+
const conf = parseConfFile(entry.confPath);
|
|
78
|
+
if (!conf?.rpcuser || !conf?.rpcpassword)
|
|
79
|
+
return null;
|
|
80
|
+
return {
|
|
81
|
+
host: entry.host,
|
|
82
|
+
port: entry.port,
|
|
83
|
+
user: conf.rpcuser,
|
|
84
|
+
password: conf.rpcpassword,
|
|
85
|
+
};
|
|
86
|
+
}
|
|
87
|
+
// Remote chain — credentials are in the registry
|
|
88
|
+
return {
|
|
89
|
+
host: entry.host,
|
|
90
|
+
port: entry.port,
|
|
91
|
+
user: entry.user,
|
|
92
|
+
password: entry.password,
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
}
|
|
96
|
+
/**
|
|
97
|
+
* Write a chain registry atomically.
|
|
98
|
+
*
|
|
99
|
+
* Writes to a temp file then renames — on POSIX, rename() is atomic
|
|
100
|
+
* at the filesystem level, so readers see either the old or new file.
|
|
101
|
+
*/
|
|
102
|
+
export function writeRegistry(registry, registryPath) {
|
|
103
|
+
const path = registryPath ?? getRegistryPath();
|
|
104
|
+
const dir = dirname(path);
|
|
105
|
+
const tmpPath = `${path}.tmp`;
|
|
106
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
107
|
+
const content = JSON.stringify(registry, null, 2) + '\n';
|
|
108
|
+
writeFileSync(tmpPath, content, { mode: 0o600 });
|
|
109
|
+
renameSync(tmpPath, path);
|
|
110
|
+
}
|
|
111
|
+
//# sourceMappingURL=registry.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"registry.js","sourceRoot":"","sources":["../src/registry.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { RegistryReader } from './registry.js';
|
|
2
|
+
/**
|
|
3
|
+
* Allow tests or custom setups to provide their own RegistryReader.
|
|
4
|
+
*/
|
|
5
|
+
export declare function setRegistryReader(reader: RegistryReader): void;
|
|
6
|
+
/**
|
|
7
|
+
* Make an RPC call to a chain's daemon.
|
|
8
|
+
*
|
|
9
|
+
* Handles:
|
|
10
|
+
* - Credential resolution from registry + .conf files
|
|
11
|
+
* - In-memory credential caching
|
|
12
|
+
* - Auth failure retry (invalidate cache, re-read .conf, retry once)
|
|
13
|
+
* - Error normalization
|
|
14
|
+
*
|
|
15
|
+
* @param chain - Chain name as it appears in the registry (e.g., "VRSC", "vrsctest")
|
|
16
|
+
* @param method - RPC method name (e.g., "getinfo", "sendcurrency")
|
|
17
|
+
* @param params - RPC method parameters
|
|
18
|
+
*/
|
|
19
|
+
export declare function rpcCall<T = unknown>(chain: string, method: string, params?: unknown[]): Promise<T | null>;
|
|
20
|
+
/**
|
|
21
|
+
* Clear the credential cache for a specific chain or all chains.
|
|
22
|
+
*/
|
|
23
|
+
export declare function clearCredentialCache(chain?: string): void;
|
|
24
|
+
//# sourceMappingURL=rpc-client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-client.d.ts","sourceRoot":"","sources":["../src/rpc-client.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAuB/C;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,MAAM,EAAE,cAAc,GAAG,IAAI,CAE9D;AA8FD;;;;;;;;;;;;GAYG;AACH,wBAAsB,OAAO,CAAC,CAAC,GAAG,OAAO,EAAE,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,GAAE,OAAO,EAAO,GAAG,OAAO,CAAC,CAAC,GAAG,IAAI,CAAC,CAuBnH;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,KAAK,CAAC,EAAE,MAAM,GAAG,IAAI,CAMzD"}
|
|
@@ -0,0 +1,144 @@
|
|
|
1
|
+
import { RegistryReader } from './registry.js';
|
|
2
|
+
import { VerusError, normalizeRpcError, connectionError, authError } from './errors.js';
|
|
3
|
+
const RPC_TIMEOUT = 30_000;
|
|
4
|
+
/**
|
|
5
|
+
* In-memory credential cache per chain.
|
|
6
|
+
* Credentials are read from .conf files (local) or registry (remote)
|
|
7
|
+
* and cached here. Invalidated on auth failure for one retry.
|
|
8
|
+
*/
|
|
9
|
+
const credentialCache = new Map();
|
|
10
|
+
/** Shared registry reader instance. */
|
|
11
|
+
let registryReader = null;
|
|
12
|
+
function getRegistry() {
|
|
13
|
+
if (!registryReader) {
|
|
14
|
+
registryReader = new RegistryReader();
|
|
15
|
+
}
|
|
16
|
+
return registryReader;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Allow tests or custom setups to provide their own RegistryReader.
|
|
20
|
+
*/
|
|
21
|
+
export function setRegistryReader(reader) {
|
|
22
|
+
registryReader = reader;
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Resolve credentials for a chain, using cache when available.
|
|
26
|
+
*/
|
|
27
|
+
function resolveCredentials(chain, skipCache = false) {
|
|
28
|
+
if (!skipCache) {
|
|
29
|
+
const cached = credentialCache.get(chain);
|
|
30
|
+
if (cached)
|
|
31
|
+
return cached;
|
|
32
|
+
}
|
|
33
|
+
const registry = getRegistry();
|
|
34
|
+
const entry = registry.getChain(chain);
|
|
35
|
+
if (!entry) {
|
|
36
|
+
throw new VerusError('CONNECTION_FAILED', `Chain "${chain}" not found in registry. Run refresh_chains to update.`);
|
|
37
|
+
}
|
|
38
|
+
const creds = registry.resolveCredentials(entry);
|
|
39
|
+
if (!creds) {
|
|
40
|
+
throw new VerusError('AUTH_FAILED', `Cannot resolve credentials for chain "${chain}". Check .conf file exists and contains rpcuser/rpcpassword.`);
|
|
41
|
+
}
|
|
42
|
+
credentialCache.set(chain, creds);
|
|
43
|
+
return creds;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Make a single HTTP JSON-RPC call to a daemon.
|
|
47
|
+
*/
|
|
48
|
+
async function rawRpcCall(creds, method, params) {
|
|
49
|
+
const url = `http://${creds.host}:${creds.port}`;
|
|
50
|
+
const credentials = Buffer.from(`${creds.user}:${creds.password}`).toString('base64');
|
|
51
|
+
const request = {
|
|
52
|
+
jsonrpc: '1.0',
|
|
53
|
+
id: `verusidx-${Date.now()}`,
|
|
54
|
+
method,
|
|
55
|
+
params,
|
|
56
|
+
};
|
|
57
|
+
let response;
|
|
58
|
+
try {
|
|
59
|
+
response = await fetch(url, {
|
|
60
|
+
method: 'POST',
|
|
61
|
+
headers: {
|
|
62
|
+
'Content-Type': 'text/plain',
|
|
63
|
+
'Authorization': `Basic ${credentials}`,
|
|
64
|
+
},
|
|
65
|
+
body: JSON.stringify(request),
|
|
66
|
+
signal: AbortSignal.timeout(RPC_TIMEOUT),
|
|
67
|
+
});
|
|
68
|
+
}
|
|
69
|
+
catch (err) {
|
|
70
|
+
// Network-level failure — timeout, ECONNREFUSED, etc.
|
|
71
|
+
throw connectionError(method, err instanceof Error ? err : undefined);
|
|
72
|
+
}
|
|
73
|
+
// HTTP 401/403 → auth failure
|
|
74
|
+
if (response.status === 401 || response.status === 403) {
|
|
75
|
+
throw authError(method);
|
|
76
|
+
}
|
|
77
|
+
// Verus daemon returns HTTP 500 for RPC errors but includes the error details
|
|
78
|
+
// in the JSON response body. Try to parse the body for all non-auth error responses.
|
|
79
|
+
let data;
|
|
80
|
+
try {
|
|
81
|
+
data = await response.json();
|
|
82
|
+
}
|
|
83
|
+
catch {
|
|
84
|
+
// If we can't parse JSON and the response wasn't OK, throw generic error
|
|
85
|
+
if (!response.ok) {
|
|
86
|
+
throw new VerusError('RPC_ERROR', `RPC HTTP error: ${response.status} ${response.statusText}`);
|
|
87
|
+
}
|
|
88
|
+
throw new VerusError('RPC_ERROR', `Failed to parse RPC response as JSON`);
|
|
89
|
+
}
|
|
90
|
+
if (data.error) {
|
|
91
|
+
throw normalizeRpcError(data.error.code, data.error.message);
|
|
92
|
+
}
|
|
93
|
+
if (data.result === undefined) {
|
|
94
|
+
throw new VerusError('RPC_ERROR', `RPC returned undefined result for ${method}`);
|
|
95
|
+
}
|
|
96
|
+
return data.result;
|
|
97
|
+
}
|
|
98
|
+
/**
|
|
99
|
+
* Make an RPC call to a chain's daemon.
|
|
100
|
+
*
|
|
101
|
+
* Handles:
|
|
102
|
+
* - Credential resolution from registry + .conf files
|
|
103
|
+
* - In-memory credential caching
|
|
104
|
+
* - Auth failure retry (invalidate cache, re-read .conf, retry once)
|
|
105
|
+
* - Error normalization
|
|
106
|
+
*
|
|
107
|
+
* @param chain - Chain name as it appears in the registry (e.g., "VRSC", "vrsctest")
|
|
108
|
+
* @param method - RPC method name (e.g., "getinfo", "sendcurrency")
|
|
109
|
+
* @param params - RPC method parameters
|
|
110
|
+
*/
|
|
111
|
+
export async function rpcCall(chain, method, params = []) {
|
|
112
|
+
const creds = resolveCredentials(chain);
|
|
113
|
+
try {
|
|
114
|
+
return await rawRpcCall(creds, method, params);
|
|
115
|
+
}
|
|
116
|
+
catch (err) {
|
|
117
|
+
if (err instanceof VerusError && err.category === 'AUTH_FAILED') {
|
|
118
|
+
// Auth failure — invalidate cache, re-read .conf, retry once
|
|
119
|
+
credentialCache.delete(chain);
|
|
120
|
+
getRegistry().invalidate();
|
|
121
|
+
const freshCreds = resolveCredentials(chain, true);
|
|
122
|
+
return rawRpcCall(freshCreds, method, params);
|
|
123
|
+
}
|
|
124
|
+
if (err instanceof VerusError && err.category === 'CONNECTION_FAILED') {
|
|
125
|
+
// Connection failure — invalidate registry cache so next call re-reads
|
|
126
|
+
// (the chain may have moved ports after a restart)
|
|
127
|
+
credentialCache.delete(chain);
|
|
128
|
+
getRegistry().invalidate();
|
|
129
|
+
}
|
|
130
|
+
throw err;
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
/**
|
|
134
|
+
* Clear the credential cache for a specific chain or all chains.
|
|
135
|
+
*/
|
|
136
|
+
export function clearCredentialCache(chain) {
|
|
137
|
+
if (chain) {
|
|
138
|
+
credentialCache.delete(chain);
|
|
139
|
+
}
|
|
140
|
+
else {
|
|
141
|
+
credentialCache.clear();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
//# sourceMappingURL=rpc-client.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rpc-client.js","sourceRoot":"","sources":["../src/rpc-client.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Get the configured spending limit for a currency.
|
|
3
|
+
* Returns undefined if no limit is configured (uncapped).
|
|
4
|
+
*/
|
|
5
|
+
export declare function getSpendingLimit(currency: string): number | undefined;
|
|
6
|
+
/**
|
|
7
|
+
* Check a set of currency amounts against spending limits.
|
|
8
|
+
* Throws SPENDING_LIMIT_EXCEEDED if any limit is exceeded.
|
|
9
|
+
*
|
|
10
|
+
* @param amounts - Map of currency name to total amount being spent/offered
|
|
11
|
+
*/
|
|
12
|
+
export declare function checkSpendingLimits(amounts: Map<string, number>): void;
|
|
13
|
+
/**
|
|
14
|
+
* Extract and sum currency amounts from sendcurrency outputs.
|
|
15
|
+
* Returns a map of currency name → total amount.
|
|
16
|
+
*
|
|
17
|
+
* Handles multi-output sends by summing per currency.
|
|
18
|
+
*/
|
|
19
|
+
export declare function sumOutputAmounts(outputs: Array<{
|
|
20
|
+
currency?: string;
|
|
21
|
+
amount?: number;
|
|
22
|
+
}>): Map<string, number>;
|
|
23
|
+
/**
|
|
24
|
+
* Ensure the spending limits file exists. If it doesn't, create it with
|
|
25
|
+
* safe defaults so users have a safety net out of the box.
|
|
26
|
+
*
|
|
27
|
+
* Call this on startup from any MCP server that enforces spending limits.
|
|
28
|
+
* Only writes if the file doesn't already exist — never overwrites user config.
|
|
29
|
+
*/
|
|
30
|
+
export declare function ensureSpendingLimitsFile(): void;
|
|
31
|
+
/**
|
|
32
|
+
* Clear the cached spending limits (for testing or config reload).
|
|
33
|
+
*/
|
|
34
|
+
export declare function clearSpendingLimitsCache(): void;
|
|
35
|
+
//# sourceMappingURL=spending-limits.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spending-limits.d.ts","sourceRoot":"","sources":["../src/spending-limits.ts"],"names":[],"mappings":"AAwDA;;;GAGG;AACH,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAGrE;AAED;;;;;GAKG;AACH,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,IAAI,CAatE;AAED;;;;;GAKG;AACH,wBAAgB,gBAAgB,CAC9B,OAAO,EAAE,KAAK,CAAC;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GACrD,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,CAarB;AAED;;;;;;GAMG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAW/C;AAED;;GAEG;AACH,wBAAgB,wBAAwB,IAAI,IAAI,CAG/C"}
|
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
import { readFileSync, writeFileSync, mkdirSync, existsSync } from 'node:fs';
|
|
2
|
+
import { dirname } from 'node:path';
|
|
3
|
+
import { getSpendingLimitsPath } from './platform.js';
|
|
4
|
+
import { VerusError } from './errors.js';
|
|
5
|
+
/**
|
|
6
|
+
* Spending limits — pre-RPC guard that prevents transactions exceeding
|
|
7
|
+
* configured per-currency limits.
|
|
8
|
+
*
|
|
9
|
+
* Config file: spending-limits.json (location OS-specific, or VERUSIDX_SPENDING_LIMITS_PATH)
|
|
10
|
+
* Format: { "VRSC": 50, "BTC": 0.01, "Bridge.vETH": 0.1 }
|
|
11
|
+
* Currency name matching is case-insensitive (Verus names are case-insensitive on-chain).
|
|
12
|
+
*
|
|
13
|
+
* If the file doesn't exist, ensureSpendingLimitsFile() creates it with safe
|
|
14
|
+
* defaults on first run. Users can edit or delete it to adjust limits.
|
|
15
|
+
*/
|
|
16
|
+
/** Default spending limits created on first run. */
|
|
17
|
+
const DEFAULT_LIMITS = {
|
|
18
|
+
VRSC: 10,
|
|
19
|
+
};
|
|
20
|
+
let cachedLimits = null;
|
|
21
|
+
let cachedPath = null;
|
|
22
|
+
/**
|
|
23
|
+
* Load spending limits from the config file.
|
|
24
|
+
* Returns an empty map if the file doesn't exist.
|
|
25
|
+
*/
|
|
26
|
+
function loadLimits() {
|
|
27
|
+
const path = getSpendingLimitsPath();
|
|
28
|
+
// Return cached if same path (limits don't change at runtime)
|
|
29
|
+
if (cachedLimits && cachedPath === path)
|
|
30
|
+
return cachedLimits;
|
|
31
|
+
const limits = new Map();
|
|
32
|
+
try {
|
|
33
|
+
const content = readFileSync(path, 'utf-8');
|
|
34
|
+
const config = JSON.parse(content);
|
|
35
|
+
for (const [currency, limit] of Object.entries(config)) {
|
|
36
|
+
if (typeof limit === 'number' && limit >= 0) {
|
|
37
|
+
limits.set(currency.toLowerCase(), limit);
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
// File doesn't exist or is invalid — no limits enforced
|
|
43
|
+
}
|
|
44
|
+
cachedLimits = limits;
|
|
45
|
+
cachedPath = path;
|
|
46
|
+
return limits;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Get the configured spending limit for a currency.
|
|
50
|
+
* Returns undefined if no limit is configured (uncapped).
|
|
51
|
+
*/
|
|
52
|
+
export function getSpendingLimit(currency) {
|
|
53
|
+
const limits = loadLimits();
|
|
54
|
+
return limits.get(currency.toLowerCase());
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Check a set of currency amounts against spending limits.
|
|
58
|
+
* Throws SPENDING_LIMIT_EXCEEDED if any limit is exceeded.
|
|
59
|
+
*
|
|
60
|
+
* @param amounts - Map of currency name to total amount being spent/offered
|
|
61
|
+
*/
|
|
62
|
+
export function checkSpendingLimits(amounts) {
|
|
63
|
+
const limits = loadLimits();
|
|
64
|
+
if (limits.size === 0)
|
|
65
|
+
return; // No limits configured
|
|
66
|
+
for (const [currency, amount] of amounts) {
|
|
67
|
+
const limit = limits.get(currency.toLowerCase());
|
|
68
|
+
if (limit !== undefined && amount > limit) {
|
|
69
|
+
throw new VerusError('SPENDING_LIMIT_EXCEEDED', `Amount ${amount} ${currency} exceeds configured spending limit of ${limit} ${currency}`);
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* Extract and sum currency amounts from sendcurrency outputs.
|
|
75
|
+
* Returns a map of currency name → total amount.
|
|
76
|
+
*
|
|
77
|
+
* Handles multi-output sends by summing per currency.
|
|
78
|
+
*/
|
|
79
|
+
export function sumOutputAmounts(outputs) {
|
|
80
|
+
const sums = new Map();
|
|
81
|
+
for (const output of outputs) {
|
|
82
|
+
const currency = output.currency;
|
|
83
|
+
const amount = output.amount;
|
|
84
|
+
if (!currency || typeof amount !== 'number' || amount <= 0)
|
|
85
|
+
continue;
|
|
86
|
+
const current = sums.get(currency) ?? 0;
|
|
87
|
+
sums.set(currency, current + amount);
|
|
88
|
+
}
|
|
89
|
+
return sums;
|
|
90
|
+
}
|
|
91
|
+
/**
|
|
92
|
+
* Ensure the spending limits file exists. If it doesn't, create it with
|
|
93
|
+
* safe defaults so users have a safety net out of the box.
|
|
94
|
+
*
|
|
95
|
+
* Call this on startup from any MCP server that enforces spending limits.
|
|
96
|
+
* Only writes if the file doesn't already exist — never overwrites user config.
|
|
97
|
+
*/
|
|
98
|
+
export function ensureSpendingLimitsFile() {
|
|
99
|
+
const limitsPath = getSpendingLimitsPath();
|
|
100
|
+
if (existsSync(limitsPath))
|
|
101
|
+
return;
|
|
102
|
+
try {
|
|
103
|
+
mkdirSync(dirname(limitsPath), { recursive: true });
|
|
104
|
+
const content = JSON.stringify(DEFAULT_LIMITS, null, 2) + '\n';
|
|
105
|
+
writeFileSync(limitsPath, content, { mode: 0o600 });
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
// Best-effort — don't break startup if we can't write
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
/**
|
|
112
|
+
* Clear the cached spending limits (for testing or config reload).
|
|
113
|
+
*/
|
|
114
|
+
export function clearSpendingLimitsCache() {
|
|
115
|
+
cachedLimits = null;
|
|
116
|
+
cachedPath = null;
|
|
117
|
+
}
|
|
118
|
+
//# sourceMappingURL=spending-limits.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"spending-limits.js","sourceRoot":"","sources":["../src/spending-limits.ts"],"names":[],"mappings":"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"}
|
package/build/types.d.ts
ADDED
|
@@ -0,0 +1,82 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain registry types — the JSON structure written by chain-mcp
|
|
3
|
+
* and read by all other MCPs.
|
|
4
|
+
*/
|
|
5
|
+
/** A local chain discovered from a .conf file on disk. */
|
|
6
|
+
export interface LocalChainEntry {
|
|
7
|
+
confPath: string;
|
|
8
|
+
host: string;
|
|
9
|
+
port: number;
|
|
10
|
+
}
|
|
11
|
+
/** A remote chain added via VERUSIDX_EXTRA_CHAINS env var. */
|
|
12
|
+
export interface RemoteChainEntry {
|
|
13
|
+
host: string;
|
|
14
|
+
port: number;
|
|
15
|
+
user: string;
|
|
16
|
+
password: string;
|
|
17
|
+
}
|
|
18
|
+
export type ChainEntry = LocalChainEntry | RemoteChainEntry;
|
|
19
|
+
export declare function isLocalChain(entry: ChainEntry): entry is LocalChainEntry;
|
|
20
|
+
export declare function isRemoteChain(entry: ChainEntry): entry is RemoteChainEntry;
|
|
21
|
+
/** The chain registry file written to disk as chains.json. */
|
|
22
|
+
export interface ChainRegistry {
|
|
23
|
+
version: number;
|
|
24
|
+
discoveredAt: string;
|
|
25
|
+
chains: Record<string, ChainEntry>;
|
|
26
|
+
}
|
|
27
|
+
/**
|
|
28
|
+
* Credentials resolved from a .conf file or remote entry.
|
|
29
|
+
*/
|
|
30
|
+
export interface RpcCredentials {
|
|
31
|
+
host: string;
|
|
32
|
+
port: number;
|
|
33
|
+
user: string;
|
|
34
|
+
password: string;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Parsed values from a Verus/Komodo .conf file.
|
|
38
|
+
*/
|
|
39
|
+
export interface ConfValues {
|
|
40
|
+
rpcuser?: string;
|
|
41
|
+
rpcpassword?: string;
|
|
42
|
+
rpcport?: string;
|
|
43
|
+
rpchost?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Error categories for normalized daemon errors.
|
|
47
|
+
*/
|
|
48
|
+
export type ErrorCategory = 'CONNECTION_FAILED' | 'AUTH_FAILED' | 'METHOD_NOT_FOUND' | 'INVALID_PARAMS' | 'INSUFFICIENT_FUNDS' | 'IDENTITY_NOT_FOUND' | 'CURRENCY_NOT_FOUND' | 'SPENDING_LIMIT_EXCEEDED' | 'WRITE_DISABLED' | 'RPC_ERROR';
|
|
49
|
+
/**
|
|
50
|
+
* Spending limits config — currency name (case-insensitive) to max amount.
|
|
51
|
+
*/
|
|
52
|
+
export type SpendingLimitsConfig = Record<string, number>;
|
|
53
|
+
/**
|
|
54
|
+
* Audit log entry for a write operation.
|
|
55
|
+
*/
|
|
56
|
+
export interface AuditEntry {
|
|
57
|
+
timestamp: string;
|
|
58
|
+
server: string;
|
|
59
|
+
tool: string;
|
|
60
|
+
chain: string;
|
|
61
|
+
params: unknown;
|
|
62
|
+
result: unknown;
|
|
63
|
+
success: boolean;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* JSON-RPC request/response types.
|
|
67
|
+
*/
|
|
68
|
+
export interface RpcRequest {
|
|
69
|
+
jsonrpc: '1.0';
|
|
70
|
+
id: string;
|
|
71
|
+
method: string;
|
|
72
|
+
params: unknown[];
|
|
73
|
+
}
|
|
74
|
+
export interface RpcResponse<T = unknown> {
|
|
75
|
+
result: T | null;
|
|
76
|
+
error: {
|
|
77
|
+
code: number;
|
|
78
|
+
message: string;
|
|
79
|
+
} | null;
|
|
80
|
+
id: string;
|
|
81
|
+
}
|
|
82
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,0DAA0D;AAC1D,MAAM,WAAW,eAAe;IAC9B,QAAQ,EAAE,MAAM,CAAC;IACjB,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;CACd;AAED,8DAA8D;AAC9D,MAAM,WAAW,gBAAgB;IAC/B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED,MAAM,MAAM,UAAU,GAAG,eAAe,GAAG,gBAAgB,CAAC;AAE5D,wBAAgB,YAAY,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,IAAI,eAAe,CAExE;AAED,wBAAgB,aAAa,CAAC,KAAK,EAAE,UAAU,GAAG,KAAK,IAAI,gBAAgB,CAE1E;AAED,8DAA8D;AAC9D,MAAM,WAAW,aAAa;IAC5B,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;CACpC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,MAAM,aAAa,GACrB,mBAAmB,GACnB,aAAa,GACb,kBAAkB,GAClB,gBAAgB,GAChB,oBAAoB,GACpB,oBAAoB,GACpB,oBAAoB,GACpB,yBAAyB,GACzB,gBAAgB,GAChB,WAAW,CAAC;AAEhB;;GAEG;AACH,MAAM,MAAM,oBAAoB,GAAG,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;AAE1D;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;IACf,IAAI,EAAE,MAAM,CAAC;IACb,KAAK,EAAE,MAAM,CAAC;IACd,MAAM,EAAE,OAAO,CAAC;IAChB,MAAM,EAAE,OAAO,CAAC;IAChB,OAAO,EAAE,OAAO,CAAC;CAClB;AAED;;GAEG;AACH,MAAM,WAAW,UAAU;IACzB,OAAO,EAAE,KAAK,CAAC;IACf,EAAE,EAAE,MAAM,CAAC;IACX,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,OAAO,EAAE,CAAC;CACnB;AAED,MAAM,WAAW,WAAW,CAAC,CAAC,GAAG,OAAO;IACtC,MAAM,EAAE,CAAC,GAAG,IAAI,CAAC;IACjB,KAAK,EAAE;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,OAAO,EAAE,MAAM,CAAA;KAAE,GAAG,IAAI,CAAC;IAChD,EAAE,EAAE,MAAM,CAAC;CACZ"}
|
package/build/types.js
ADDED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Chain registry types — the JSON structure written by chain-mcp
|
|
3
|
+
* and read by all other MCPs.
|
|
4
|
+
*/
|
|
5
|
+
export function isLocalChain(entry) {
|
|
6
|
+
return 'confPath' in entry;
|
|
7
|
+
}
|
|
8
|
+
export function isRemoteChain(entry) {
|
|
9
|
+
return !('confPath' in entry);
|
|
10
|
+
}
|
|
11
|
+
//# sourceMappingURL=types.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../src/types.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAmBH,MAAM,UAAU,YAAY,CAAC,KAAiB;IAC5C,OAAO,UAAU,IAAI,KAAK,CAAC;AAC7B,CAAC;AAED,MAAM,UAAU,aAAa,CAAC,KAAiB;IAC7C,OAAO,CAAC,CAAC,UAAU,IAAI,KAAK,CAAC,CAAC;AAChC,CAAC"}
|
package/package.json
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@verusidx/shared",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Shared library for verusidx MCP servers — registry reader, RPC client, error handling, audit logging",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./build/index.js",
|
|
7
|
+
"types": "./build/index.d.ts",
|
|
8
|
+
"files": [
|
|
9
|
+
"build"
|
|
10
|
+
],
|
|
11
|
+
"engines": {
|
|
12
|
+
"node": ">=18.0.0"
|
|
13
|
+
},
|
|
14
|
+
"license": "MIT",
|
|
15
|
+
"devDependencies": {
|
|
16
|
+
"@types/node": "^22.0.0",
|
|
17
|
+
"typescript": "^5.7.0",
|
|
18
|
+
"vitest": "^3.0.0"
|
|
19
|
+
},
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsc",
|
|
22
|
+
"test": "vitest run"
|
|
23
|
+
}
|
|
24
|
+
}
|