mrmainspring 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.
Files changed (79) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +47 -0
  3. package/dist/audit/service.d.ts +7 -0
  4. package/dist/audit/service.js +98 -0
  5. package/dist/audit/store.d.ts +7 -0
  6. package/dist/audit/store.js +37 -0
  7. package/dist/audit/supabase-store.d.ts +9 -0
  8. package/dist/audit/supabase-store.js +22 -0
  9. package/dist/audit/types.d.ts +31 -0
  10. package/dist/audit/types.js +1 -0
  11. package/dist/casper/anchorClient.d.ts +99 -0
  12. package/dist/casper/anchorClient.js +412 -0
  13. package/dist/config.d.ts +51 -0
  14. package/dist/config.js +215 -0
  15. package/dist/env-file.d.ts +1 -0
  16. package/dist/env-file.js +51 -0
  17. package/dist/grimoire/service.d.ts +13 -0
  18. package/dist/grimoire/service.js +199 -0
  19. package/dist/grimoire/store.d.ts +10 -0
  20. package/dist/grimoire/store.js +64 -0
  21. package/dist/grimoire/supabase-store.d.ts +13 -0
  22. package/dist/grimoire/supabase-store.js +50 -0
  23. package/dist/grimoire/types.d.ts +60 -0
  24. package/dist/grimoire/types.js +1 -0
  25. package/dist/index.d.ts +2 -0
  26. package/dist/index.js +17 -0
  27. package/dist/mcp/auditTools.d.ts +3 -0
  28. package/dist/mcp/auditTools.js +13 -0
  29. package/dist/mcp/grimoireTools.d.ts +3 -0
  30. package/dist/mcp/grimoireTools.js +91 -0
  31. package/dist/mcp/jsonResult.d.ts +2 -0
  32. package/dist/mcp/jsonResult.js +10 -0
  33. package/dist/mcp/memoryTools.d.ts +3 -0
  34. package/dist/mcp/memoryTools.js +73 -0
  35. package/dist/mcp/paymentTools.d.ts +3 -0
  36. package/dist/mcp/paymentTools.js +33 -0
  37. package/dist/memory/canonical.d.ts +4 -0
  38. package/dist/memory/canonical.js +49 -0
  39. package/dist/memory/hash.d.ts +1 -0
  40. package/dist/memory/hash.js +4 -0
  41. package/dist/memory/service.d.ts +37 -0
  42. package/dist/memory/service.js +175 -0
  43. package/dist/memory/store.d.ts +8 -0
  44. package/dist/memory/store.js +49 -0
  45. package/dist/memory/supabase-store.d.ts +10 -0
  46. package/dist/memory/supabase-store.js +30 -0
  47. package/dist/memory/types.d.ts +56 -0
  48. package/dist/memory/types.js +7 -0
  49. package/dist/payments/service.d.ts +26 -0
  50. package/dist/payments/service.js +613 -0
  51. package/dist/payments/store.d.ts +10 -0
  52. package/dist/payments/store.js +64 -0
  53. package/dist/payments/supabase-store.d.ts +13 -0
  54. package/dist/payments/supabase-store.js +51 -0
  55. package/dist/payments/types.d.ts +101 -0
  56. package/dist/payments/types.js +1 -0
  57. package/dist/server.d.ts +5 -0
  58. package/dist/server.js +68 -0
  59. package/dist/storage/json-file-store.d.ts +17 -0
  60. package/dist/storage/json-file-store.js +87 -0
  61. package/dist/storage/store-factory.d.ts +12 -0
  62. package/dist/storage/store-factory.js +26 -0
  63. package/dist/storage/supabase-rest.d.ts +26 -0
  64. package/dist/storage/supabase-rest.js +85 -0
  65. package/dist/x402/client.d.ts +44 -0
  66. package/dist/x402/client.js +95 -0
  67. package/dist/x402/facilitator.d.ts +84 -0
  68. package/dist/x402/facilitator.js +800 -0
  69. package/dist/x402/readiness.d.ts +55 -0
  70. package/dist/x402/readiness.js +433 -0
  71. package/dist/x402/redaction.d.ts +1 -0
  72. package/dist/x402/redaction.js +30 -0
  73. package/dist/x402/resource.d.ts +69 -0
  74. package/dist/x402/resource.js +325 -0
  75. package/dist/x402/settlement.d.ts +176 -0
  76. package/dist/x402/settlement.js +1210 -0
  77. package/dist/x402/signer.d.ts +71 -0
  78. package/dist/x402/signer.js +616 -0
  79. package/package.json +61 -0
@@ -0,0 +1,412 @@
1
+ import { spawn } from "node:child_process";
2
+ import { sha256Hex } from "../memory/hash.js";
3
+ const HEX_64_PATTERN = /^[a-f0-9]{64}$/;
4
+ const CASPER_HASH_PATTERN = /^(hash-)?[a-f0-9]{64}$/i;
5
+ const CASPER_PACKAGE_HASH_PATTERN = /^(hash-|package-)?[a-f0-9]{64}$/i;
6
+ export const REAL_CASPER_ANCHOR_ENV_VARS = [
7
+ "CASPER_RPC_URL",
8
+ "CASPER_NETWORK_NAME",
9
+ "MEMORY_ANCHOR_CONTRACT_HASH",
10
+ "MEMORY_ANCHOR_PACKAGE_HASH",
11
+ "CASPER_ACCOUNT_KEY_PATH",
12
+ "CASPER_ENABLE_REAL_SUBMISSION"
13
+ ];
14
+ export class UnconfiguredCasperAnchorClient {
15
+ reason;
16
+ mode = "unconfigured";
17
+ submissions = [];
18
+ constructor(reason = "casper_contract_not_configured") {
19
+ this.reason = reason;
20
+ }
21
+ async anchorMemory(submission) {
22
+ assertValidAnchorSubmission(submission);
23
+ this.submissions.push(submission);
24
+ return createPendingAnchorResult(submission, this.reason);
25
+ }
26
+ }
27
+ export class ConfiguredCasperAnchorClient {
28
+ config;
29
+ commandRunner;
30
+ mode = "configured";
31
+ submissions = [];
32
+ constructor(config, commandRunner = runCasperCommand) {
33
+ this.config = config;
34
+ this.commandRunner = commandRunner;
35
+ }
36
+ async anchorMemory(submission) {
37
+ assertValidAnchorSubmission(submission);
38
+ this.submissions.push(submission);
39
+ if (!this.config.submissionEnabled) {
40
+ return createPendingAnchorResult(submission, "casper_transaction_submission_disabled");
41
+ }
42
+ const invocation = buildCasperAnchorCommand(this.config, submission);
43
+ let commandResult;
44
+ try {
45
+ commandResult = await this.commandRunner(invocation.command, invocation.args);
46
+ }
47
+ catch {
48
+ return createFailedAnchorResult(submission, "casper_transaction_submission_failed");
49
+ }
50
+ if (commandResult.exitCode !== 0) {
51
+ return createFailedAnchorResult(submission, "casper_transaction_submission_failed");
52
+ }
53
+ const transactionHash = extractCasperTransactionHash(commandResult);
54
+ if (!transactionHash) {
55
+ return createFailedAnchorResult(submission, "casper_transaction_hash_missing");
56
+ }
57
+ return createSubmittedAnchorResult(submission, transactionHash);
58
+ }
59
+ }
60
+ export class MockCasperAnchorClient extends UnconfiguredCasperAnchorClient {
61
+ constructor() {
62
+ super("casper_contract_not_configured");
63
+ }
64
+ }
65
+ export function createCasperAnchorClient(config, options = {}) {
66
+ const anchorConfig = validateCasperAnchorConfig(config);
67
+ if (!anchorConfig) {
68
+ return new UnconfiguredCasperAnchorClient();
69
+ }
70
+ return new ConfiguredCasperAnchorClient(anchorConfig, options.commandRunner);
71
+ }
72
+ export function validateCasperAnchorConfig(config) {
73
+ const contractHash = normalizeOptional(config.memoryAnchorContractHash);
74
+ if (!contractHash) {
75
+ return null;
76
+ }
77
+ assertCasperHash("MEMORY_ANCHOR_CONTRACT_HASH", contractHash);
78
+ const packageHash = requireNonEmpty(config.memoryAnchorPackageHash, "MEMORY_ANCHOR_PACKAGE_HASH when MEMORY_ANCHOR_CONTRACT_HASH is set");
79
+ assertCasperPackageHash("MEMORY_ANCHOR_PACKAGE_HASH", packageHash);
80
+ const networkName = requireNonEmpty(config.networkName, "CASPER_NETWORK_NAME");
81
+ const caip2ChainId = requireNonEmpty(config.caip2ChainId, "CASPER_CAIP2_CHAIN_ID");
82
+ const rpcUrl = requireNonEmpty(config.rpcUrl, "CASPER_RPC_URL when MEMORY_ANCHOR_CONTRACT_HASH is set");
83
+ const accountKeyPath = requireNonEmpty(config.accountKeyPath, "CASPER_ACCOUNT_KEY_PATH when MEMORY_ANCHOR_CONTRACT_HASH is set");
84
+ assertHttpUrl("CASPER_RPC_URL", rpcUrl);
85
+ assertUnsignedInteger("CASPER_GAS_PRICE_TOLERANCE", config.gasPriceTolerance ?? "10");
86
+ assertPricingMode("CASPER_PRICING_MODE", config.pricingMode ?? "classic");
87
+ assertUnsignedInteger("CASPER_ANCHOR_PAYMENT_AMOUNT_MOTES", config.anchorPaymentAmountMotes ?? "3000000000");
88
+ return {
89
+ networkName,
90
+ caip2ChainId,
91
+ rpcUrl,
92
+ accountKeyPath,
93
+ memoryAnchorContractHash: contractHash,
94
+ memoryAnchorPackageHash: packageHash,
95
+ submissionEnabled: config.submissionEnabled ?? false,
96
+ clientBin: requireNonEmpty(config.clientBin ?? "casper-client", "CASPER_CLIENT_BIN"),
97
+ clientWslDistro: normalizeOptional(config.clientWslDistro),
98
+ anchorSubmissionMode: config.anchorSubmissionMode ?? "transaction-package",
99
+ gasPriceTolerance: requireNonEmpty(config.gasPriceTolerance ?? "10", "CASPER_GAS_PRICE_TOLERANCE"),
100
+ pricingMode: requireNonEmpty(config.pricingMode ?? "classic", "CASPER_PRICING_MODE"),
101
+ anchorPaymentAmountMotes: requireNonEmpty(config.anchorPaymentAmountMotes ?? "3000000000", "CASPER_ANCHOR_PAYMENT_AMOUNT_MOTES")
102
+ };
103
+ }
104
+ export function createAnchorSubmission(input) {
105
+ assertNonEmpty("agent_id", input.agent_id);
106
+ assertNonEmpty("memory_id", input.memory_id);
107
+ assertHex64("content_hash", input.content_hash);
108
+ assertHex64("metadata_hash", input.metadata_hash);
109
+ if (input.prev_anchor_hash !== null) {
110
+ assertHex64("prev_anchor_hash", input.prev_anchor_hash);
111
+ }
112
+ return {
113
+ anchor_id: computeAnchorId(input),
114
+ agent_id_hash: sha256Hex(input.agent_id),
115
+ memory_id_hash: sha256Hex(input.memory_id),
116
+ content_hash: input.content_hash,
117
+ metadata_hash: input.metadata_hash,
118
+ prev_anchor_hash: input.prev_anchor_hash
119
+ };
120
+ }
121
+ export function createPendingAnchorResult(submission, reason) {
122
+ assertValidAnchorSubmission(submission);
123
+ return {
124
+ status: "pending",
125
+ anchor_id: submission.anchor_id,
126
+ casper_transaction_hash: null,
127
+ onchain_content_hash: null,
128
+ reason
129
+ };
130
+ }
131
+ export function createFailedAnchorResult(submission, reason) {
132
+ assertValidAnchorSubmission(submission);
133
+ return {
134
+ status: "failed",
135
+ anchor_id: submission.anchor_id,
136
+ casper_transaction_hash: null,
137
+ onchain_content_hash: null,
138
+ reason
139
+ };
140
+ }
141
+ export function createAnchoredAnchorResult(submission, casperTransactionHash) {
142
+ assertValidAnchorSubmission(submission);
143
+ assertHex64("casper_transaction_hash", casperTransactionHash);
144
+ return {
145
+ status: "anchored",
146
+ anchor_id: submission.anchor_id,
147
+ casper_transaction_hash: casperTransactionHash,
148
+ onchain_content_hash: submission.content_hash
149
+ };
150
+ }
151
+ export function createSubmittedAnchorResult(submission, casperTransactionHash) {
152
+ assertValidAnchorSubmission(submission);
153
+ assertHex64("casper_transaction_hash", casperTransactionHash);
154
+ return {
155
+ status: "pending",
156
+ anchor_id: submission.anchor_id,
157
+ casper_transaction_hash: casperTransactionHash,
158
+ onchain_content_hash: null
159
+ };
160
+ }
161
+ export function computeAnchorId(input) {
162
+ return sha256Hex([
163
+ input.agent_id,
164
+ input.memory_id,
165
+ input.content_hash,
166
+ input.prev_anchor_hash ?? ""
167
+ ].join(":"));
168
+ }
169
+ export function buildCasperAnchorCommand(config, submission) {
170
+ assertValidAnchorSubmission(submission);
171
+ const sessionArgs = [
172
+ "--session-args-json",
173
+ JSON.stringify([
174
+ sessionJsonArg("anchor_id", submission.anchor_id),
175
+ sessionJsonArg("agent_id_hash", submission.agent_id_hash),
176
+ sessionJsonArg("memory_id_hash", submission.memory_id_hash),
177
+ sessionJsonArg("content_hash", submission.content_hash),
178
+ sessionJsonArg("metadata_hash", submission.metadata_hash),
179
+ sessionJsonArg("prev_anchor_hash", submission.prev_anchor_hash ?? "")
180
+ ])
181
+ ];
182
+ if (config.anchorSubmissionMode === "deploy-contract-hash") {
183
+ return wrapCasperCommand(config, [
184
+ "put-deploy",
185
+ "--node-address",
186
+ config.rpcUrl,
187
+ "--chain-name",
188
+ config.networkName,
189
+ "--session-hash",
190
+ formatContractHash(config.memoryAnchorContractHash),
191
+ "--session-entry-point",
192
+ "anchor_memory",
193
+ "--payment-amount",
194
+ config.anchorPaymentAmountMotes,
195
+ "--secret-key",
196
+ config.accountKeyPath,
197
+ ...sessionArgs
198
+ ]);
199
+ }
200
+ return wrapCasperCommand(config, [
201
+ "put-transaction",
202
+ "package",
203
+ "--node-address",
204
+ config.rpcUrl,
205
+ "--chain-name",
206
+ config.networkName,
207
+ "--contract-package-hash",
208
+ formatContractPackageHash(config.memoryAnchorPackageHash),
209
+ "--session-entry-point",
210
+ "anchor_memory",
211
+ "--gas-price-tolerance",
212
+ config.gasPriceTolerance,
213
+ "--pricing-mode",
214
+ config.pricingMode,
215
+ "--payment-amount",
216
+ config.anchorPaymentAmountMotes,
217
+ "--standard-payment",
218
+ "true",
219
+ "--secret-key",
220
+ config.accountKeyPath,
221
+ ...sessionArgs
222
+ ]);
223
+ }
224
+ export function extractCasperTransactionHash(result) {
225
+ for (const output of [result.stdout, result.stderr]) {
226
+ const json = parseCasperClientJson(output);
227
+ const jsonHash = json ? findHashField(json) : null;
228
+ if (jsonHash) {
229
+ return jsonHash;
230
+ }
231
+ const labelledHash = findLabelledHash(output);
232
+ if (labelledHash) {
233
+ return labelledHash;
234
+ }
235
+ }
236
+ return null;
237
+ }
238
+ function assertValidAnchorSubmission(submission) {
239
+ assertHex64("anchor_id", submission.anchor_id);
240
+ assertHex64("agent_id_hash", submission.agent_id_hash);
241
+ assertHex64("memory_id_hash", submission.memory_id_hash);
242
+ assertHex64("content_hash", submission.content_hash);
243
+ assertHex64("metadata_hash", submission.metadata_hash);
244
+ if (submission.prev_anchor_hash !== null) {
245
+ assertHex64("prev_anchor_hash", submission.prev_anchor_hash);
246
+ }
247
+ }
248
+ function assertHex64(name, value) {
249
+ if (!HEX_64_PATTERN.test(value)) {
250
+ throw new Error(`${name} must be a lowercase 64-character hex string`);
251
+ }
252
+ }
253
+ function assertCasperHash(name, value) {
254
+ if (!CASPER_HASH_PATTERN.test(value)) {
255
+ throw new Error(`${name} must be a Casper hash value with optional hash- prefix`);
256
+ }
257
+ }
258
+ function assertCasperPackageHash(name, value) {
259
+ if (!CASPER_PACKAGE_HASH_PATTERN.test(value)) {
260
+ throw new Error(`${name} must be a Casper package hash value`);
261
+ }
262
+ }
263
+ function assertUnsignedInteger(name, value) {
264
+ if (!/^\d+$/.test(value)) {
265
+ throw new Error(`${name} must be an unsigned integer`);
266
+ }
267
+ }
268
+ function assertPricingMode(name, value) {
269
+ if (value !== "classic" && value !== "reserved" && value !== "fixed") {
270
+ throw new Error(`${name} must be classic, reserved, or fixed`);
271
+ }
272
+ }
273
+ function assertHttpUrl(name, value) {
274
+ const url = new URL(value);
275
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
276
+ throw new Error(`${name} must be an HTTP(S) URL`);
277
+ }
278
+ }
279
+ function requireNonEmpty(value, name) {
280
+ const normalized = normalizeOptional(value);
281
+ if (!normalized) {
282
+ throw new Error(`${name} is required`);
283
+ }
284
+ return normalized;
285
+ }
286
+ function assertNonEmpty(name, value) {
287
+ if (value.trim().length === 0) {
288
+ throw new Error(`${name} is required`);
289
+ }
290
+ }
291
+ function normalizeOptional(value) {
292
+ const normalized = value?.trim();
293
+ return normalized ? normalized : null;
294
+ }
295
+ function runCasperCommand(command, args) {
296
+ return new Promise((resolve, reject) => {
297
+ const child = spawn(command, args, {
298
+ shell: false,
299
+ windowsHide: true
300
+ });
301
+ let stdout = "";
302
+ let stderr = "";
303
+ child.stdout.setEncoding("utf8");
304
+ child.stderr.setEncoding("utf8");
305
+ child.stdout.on("data", (chunk) => {
306
+ stdout += chunk;
307
+ });
308
+ child.stderr.on("data", (chunk) => {
309
+ stderr += chunk;
310
+ });
311
+ child.on("error", reject);
312
+ child.on("close", (code) => {
313
+ resolve({
314
+ exitCode: code ?? 1,
315
+ stdout,
316
+ stderr
317
+ });
318
+ });
319
+ });
320
+ }
321
+ function wrapCasperCommand(config, args) {
322
+ if (!config.clientWslDistro) {
323
+ return {
324
+ command: config.clientBin,
325
+ args
326
+ };
327
+ }
328
+ return {
329
+ command: "wsl",
330
+ args: ["-d", config.clientWslDistro, "--", config.clientBin, ...args]
331
+ };
332
+ }
333
+ function sessionJsonArg(name, value) {
334
+ return { name, type: "String", value };
335
+ }
336
+ function formatContractPackageHash(value) {
337
+ const normalized = value.toLowerCase().replace(/^(hash-|package-)/, "");
338
+ return `hash-${normalized}`;
339
+ }
340
+ function formatContractHash(value) {
341
+ const normalized = value.toLowerCase().replace(/^hash-/, "");
342
+ return `hash-${normalized}`;
343
+ }
344
+ function parseCasperClientJson(output) {
345
+ const trimmed = output.trim();
346
+ if (!trimmed) {
347
+ return null;
348
+ }
349
+ for (const candidate of [trimmed, jsonObjectSlice(trimmed)]) {
350
+ if (!candidate) {
351
+ continue;
352
+ }
353
+ try {
354
+ return JSON.parse(candidate);
355
+ }
356
+ catch {
357
+ continue;
358
+ }
359
+ }
360
+ return null;
361
+ }
362
+ function jsonObjectSlice(output) {
363
+ const firstBrace = output.indexOf("{");
364
+ const lastBrace = output.lastIndexOf("}");
365
+ if (firstBrace === -1 || lastBrace === -1 || lastBrace <= firstBrace) {
366
+ return null;
367
+ }
368
+ return output.slice(firstBrace, lastBrace + 1);
369
+ }
370
+ function findHashField(value) {
371
+ if (typeof value === "string") {
372
+ return HEX_64_PATTERN.test(value.toLowerCase()) ? value.toLowerCase() : null;
373
+ }
374
+ if (!value || typeof value !== "object") {
375
+ return null;
376
+ }
377
+ const record = value;
378
+ for (const key of ["transaction_hash", "deploy_hash"]) {
379
+ const direct = record[key];
380
+ const hash = extractHashValue(direct);
381
+ if (hash) {
382
+ return hash;
383
+ }
384
+ }
385
+ for (const nested of Object.values(record)) {
386
+ const hash = findHashField(nested);
387
+ if (hash) {
388
+ return hash;
389
+ }
390
+ }
391
+ return null;
392
+ }
393
+ function extractHashValue(value) {
394
+ if (typeof value === "string") {
395
+ const normalized = value.toLowerCase();
396
+ return HEX_64_PATTERN.test(normalized) ? normalized : null;
397
+ }
398
+ if (!value || typeof value !== "object") {
399
+ return null;
400
+ }
401
+ for (const nested of Object.values(value)) {
402
+ const hash = extractHashValue(nested);
403
+ if (hash) {
404
+ return hash;
405
+ }
406
+ }
407
+ return null;
408
+ }
409
+ function findLabelledHash(output) {
410
+ const match = output.match(/(?:transaction_hash|deploy_hash)[\s"'=:{}A-Za-z0-9]*?([a-f0-9]{64})/i);
411
+ return match?.[1]?.toLowerCase() ?? null;
412
+ }
@@ -0,0 +1,51 @@
1
+ export type CasperConfig = {
2
+ networkName: string;
3
+ caip2ChainId: string;
4
+ rpcUrl: string | null;
5
+ accountKeyPath: string | null;
6
+ memoryAnchorContractHash: string | null;
7
+ memoryAnchorPackageHash: string | null;
8
+ submissionEnabled: boolean;
9
+ clientBin: string;
10
+ clientWslDistro: string | null;
11
+ anchorSubmissionMode: "transaction-package" | "deploy-contract-hash";
12
+ gasPriceTolerance: string;
13
+ pricingMode: string;
14
+ anchorPaymentAmountMotes: string;
15
+ };
16
+ export type X402Config = {
17
+ facilitatorUrl: string;
18
+ resourceDemoUrl: string;
19
+ assetPackage: string | null;
20
+ assetName: string | null;
21
+ settlementEnabled: boolean;
22
+ settlementMode: "resource-retry" | "facilitator" | "casper-cli";
23
+ signerUrl: string | null;
24
+ signerAuthToken: string | null;
25
+ signerTimeoutMs: number;
26
+ paymentHeaderName: string;
27
+ casperSettlementPaymentAmountMotes: string;
28
+ casperConfirmationPollIntervalMs: number;
29
+ casperConfirmationTimeoutMs: number;
30
+ };
31
+ export type StorageConfig = {
32
+ backend: "file";
33
+ } | {
34
+ backend: "supabase";
35
+ supabase: {
36
+ url: string;
37
+ key: string;
38
+ schema: string;
39
+ tablePrefix: string;
40
+ };
41
+ };
42
+ export type SigilConfig = {
43
+ dataDir: string;
44
+ grimoireMasterKey: Buffer;
45
+ serverName: string;
46
+ serverVersion: string;
47
+ storage: StorageConfig;
48
+ casper: CasperConfig;
49
+ x402: X402Config;
50
+ };
51
+ export declare function loadConfig(env?: NodeJS.ProcessEnv): SigilConfig;
package/dist/config.js ADDED
@@ -0,0 +1,215 @@
1
+ import { createHash } from "node:crypto";
2
+ import { dirname, isAbsolute, resolve } from "node:path";
3
+ import { fileURLToPath } from "node:url";
4
+ const CASPER_HASH_PATTERN = /^(hash-)?[a-f0-9]{64}$/i;
5
+ const CASPER_PACKAGE_HASH_PATTERN = /^(hash-|package-)?[a-f0-9]{64}$/i;
6
+ export function loadConfig(env = process.env) {
7
+ const casper = {
8
+ networkName: optionalEnv(env.CASPER_NETWORK_NAME) ?? "casper-test",
9
+ caip2ChainId: optionalEnv(env.CASPER_CAIP2_CHAIN_ID) ?? "casper:casper-test",
10
+ rpcUrl: optionalEnv(env.CASPER_RPC_URL),
11
+ accountKeyPath: normalizeCasperAccountKeyPath(optionalEnv(env.CASPER_ACCOUNT_KEY_PATH) ?? "./keys/backend.pem", optionalEnv(env.CASPER_CLIENT_WSL_DISTRO)),
12
+ memoryAnchorContractHash: optionalEnv(env.MEMORY_ANCHOR_CONTRACT_HASH),
13
+ memoryAnchorPackageHash: optionalEnv(env.MEMORY_ANCHOR_PACKAGE_HASH),
14
+ submissionEnabled: parseBoolean(env.CASPER_ENABLE_REAL_SUBMISSION),
15
+ clientBin: optionalEnv(env.CASPER_CLIENT_BIN) ?? "casper-client",
16
+ clientWslDistro: optionalEnv(env.CASPER_CLIENT_WSL_DISTRO),
17
+ anchorSubmissionMode: parseCasperAnchorSubmissionMode(env.CASPER_ANCHOR_SUBMISSION_MODE),
18
+ gasPriceTolerance: optionalEnv(env.CASPER_GAS_PRICE_TOLERANCE) ?? "10",
19
+ pricingMode: optionalEnv(env.CASPER_PRICING_MODE) ?? "classic",
20
+ anchorPaymentAmountMotes: optionalEnv(env.CASPER_ANCHOR_PAYMENT_AMOUNT_MOTES) ?? "3000000000"
21
+ };
22
+ validateCasperConfig(casper);
23
+ const x402 = {
24
+ facilitatorUrl: optionalEnv(env.X402_FACILITATOR_URL) ?? "http://localhost:4022",
25
+ resourceDemoUrl: optionalEnv(env.X402_RESOURCE_DEMO_URL) ?? "http://localhost:4021/weather",
26
+ assetPackage: optionalEnv(env.X402_ASSET_PACKAGE),
27
+ assetName: optionalEnv(env.X402_ASSET_NAME),
28
+ settlementEnabled: parseBoolean(env.X402_ENABLE_REAL_SETTLEMENT),
29
+ settlementMode: parseX402SettlementMode(env.X402_SETTLEMENT_MODE),
30
+ signerUrl: optionalEnv(env.X402_SIGNER_URL),
31
+ signerAuthToken: optionalEnv(env.X402_SIGNER_AUTH_TOKEN),
32
+ signerTimeoutMs: parsePositiveInteger(env.X402_SIGNER_TIMEOUT_MS, "X402_SIGNER_TIMEOUT_MS", 10_000),
33
+ paymentHeaderName: optionalEnv(env.X402_PAYMENT_HEADER_NAME) ?? "PAYMENT-SIGNATURE",
34
+ casperSettlementPaymentAmountMotes: optionalEnv(env.X402_CASPER_SETTLEMENT_PAYMENT_AMOUNT_MOTES) ?? "7000000000",
35
+ casperConfirmationPollIntervalMs: parsePositiveInteger(env.X402_CASPER_CONFIRMATION_POLL_INTERVAL_MS, "X402_CASPER_CONFIRMATION_POLL_INTERVAL_MS", 2_000),
36
+ casperConfirmationTimeoutMs: parsePositiveInteger(env.X402_CASPER_CONFIRMATION_TIMEOUT_MS, "X402_CASPER_CONFIRMATION_TIMEOUT_MS", 120_000)
37
+ };
38
+ validateX402Config(x402);
39
+ return {
40
+ dataDir: resolve(optionalEnv(env.SIGIL_DATA_DIR) ?? ".sigil"),
41
+ grimoireMasterKey: loadMasterKey(env.GRIMOIRE_MASTER_KEY),
42
+ serverName: optionalEnv(env.SIGIL_MCP_NAME) ?? "mr-mainspring",
43
+ serverVersion: optionalEnv(env.SIGIL_MCP_VERSION) ?? "0.1.0",
44
+ storage: loadStorageConfig(env),
45
+ casper,
46
+ x402
47
+ };
48
+ }
49
+ function loadStorageConfig(env) {
50
+ const backend = optionalEnv(env.SIGIL_STORAGE_BACKEND);
51
+ const supabaseUrl = optionalEnv(env.PROJECT_URL) ?? optionalEnv(env.SUPABASE_URL);
52
+ const supabaseKey = optionalEnv(env.SECRET_KEY) ??
53
+ optionalEnv(env.PUBLISHABLE_KEY) ??
54
+ optionalEnv(env.SUPABASE_SERVICE_ROLE_KEY) ??
55
+ optionalEnv(env.SUPABASE_ANON_KEY);
56
+ if (!backend && !supabaseUrl && !supabaseKey) {
57
+ return { backend: "file" };
58
+ }
59
+ if (backend && backend !== "file" && backend !== "supabase") {
60
+ throw new Error("SIGIL_STORAGE_BACKEND must be either file or supabase");
61
+ }
62
+ if (backend === "file") {
63
+ return { backend: "file" };
64
+ }
65
+ if (!supabaseUrl) {
66
+ throw new Error("PROJECT_URL is required when Supabase storage is enabled");
67
+ }
68
+ if (!supabaseKey) {
69
+ throw new Error("SECRET_KEY or PUBLISHABLE_KEY is required when Supabase storage is enabled");
70
+ }
71
+ assertHttpUrl("PROJECT_URL", supabaseUrl);
72
+ return {
73
+ backend: "supabase",
74
+ supabase: {
75
+ url: supabaseUrl,
76
+ key: supabaseKey,
77
+ schema: optionalEnv(env.SUPABASE_DB_SCHEMA) ?? "public",
78
+ tablePrefix: optionalEnv(env.SUPABASE_TABLE_PREFIX) ?? "sigil_"
79
+ }
80
+ };
81
+ }
82
+ function loadMasterKey(encodedKey) {
83
+ if (!encodedKey) {
84
+ return createHash("sha256").update("sigil-local-development-master-key").digest();
85
+ }
86
+ const key = Buffer.from(encodedKey, "base64");
87
+ if (key.byteLength !== 32) {
88
+ throw new Error("GRIMOIRE_MASTER_KEY must be a base64-encoded 32-byte key");
89
+ }
90
+ return key;
91
+ }
92
+ function validateCasperConfig(config) {
93
+ assertNonEmpty("CASPER_NETWORK_NAME", config.networkName);
94
+ assertNonEmpty("CASPER_CAIP2_CHAIN_ID", config.caip2ChainId);
95
+ if (config.memoryAnchorContractHash) {
96
+ assertCasperHash("MEMORY_ANCHOR_CONTRACT_HASH", config.memoryAnchorContractHash);
97
+ if (!config.memoryAnchorPackageHash) {
98
+ throw new Error("MEMORY_ANCHOR_PACKAGE_HASH is required when MEMORY_ANCHOR_CONTRACT_HASH is set");
99
+ }
100
+ if (!config.rpcUrl) {
101
+ throw new Error("CASPER_RPC_URL is required when MEMORY_ANCHOR_CONTRACT_HASH is set");
102
+ }
103
+ if (!config.accountKeyPath) {
104
+ throw new Error("CASPER_ACCOUNT_KEY_PATH is required when MEMORY_ANCHOR_CONTRACT_HASH is set");
105
+ }
106
+ }
107
+ if (config.memoryAnchorPackageHash) {
108
+ assertCasperPackageHash("MEMORY_ANCHOR_PACKAGE_HASH", config.memoryAnchorPackageHash);
109
+ }
110
+ if (config.rpcUrl) {
111
+ assertHttpUrl("CASPER_RPC_URL", config.rpcUrl);
112
+ }
113
+ assertNonEmpty("CASPER_CLIENT_BIN", config.clientBin);
114
+ if (config.clientWslDistro) {
115
+ assertNonEmpty("CASPER_CLIENT_WSL_DISTRO", config.clientWslDistro);
116
+ }
117
+ assertUnsignedInteger("CASPER_GAS_PRICE_TOLERANCE", config.gasPriceTolerance);
118
+ assertPricingMode("CASPER_PRICING_MODE", config.pricingMode);
119
+ assertUnsignedInteger("CASPER_ANCHOR_PAYMENT_AMOUNT_MOTES", config.anchorPaymentAmountMotes);
120
+ }
121
+ function validateX402Config(config) {
122
+ assertHttpUrl("X402_FACILITATOR_URL", config.facilitatorUrl);
123
+ assertHttpUrl("X402_RESOURCE_DEMO_URL", config.resourceDemoUrl);
124
+ if (config.signerUrl) {
125
+ assertHttpUrl("X402_SIGNER_URL", config.signerUrl);
126
+ }
127
+ assertNonEmpty("X402_PAYMENT_HEADER_NAME", config.paymentHeaderName);
128
+ assertUnsignedInteger("X402_CASPER_SETTLEMENT_PAYMENT_AMOUNT_MOTES", config.casperSettlementPaymentAmountMotes);
129
+ }
130
+ function assertNonEmpty(name, value) {
131
+ if (value.trim().length === 0) {
132
+ throw new Error(`${name} is required`);
133
+ }
134
+ }
135
+ function assertCasperHash(name, value) {
136
+ if (!CASPER_HASH_PATTERN.test(value)) {
137
+ throw new Error(`${name} must be a Casper hash value with optional hash- prefix`);
138
+ }
139
+ }
140
+ function assertCasperPackageHash(name, value) {
141
+ if (!CASPER_PACKAGE_HASH_PATTERN.test(value)) {
142
+ throw new Error(`${name} must be a Casper package hash value`);
143
+ }
144
+ }
145
+ function assertHttpUrl(name, value) {
146
+ const url = new URL(value);
147
+ if (url.protocol !== "http:" && url.protocol !== "https:") {
148
+ throw new Error(`${name} must be an HTTP(S) URL`);
149
+ }
150
+ }
151
+ function assertUnsignedInteger(name, value) {
152
+ if (!/^\d+$/.test(value)) {
153
+ throw new Error(`${name} must be an unsigned integer`);
154
+ }
155
+ }
156
+ function assertPricingMode(name, value) {
157
+ if (value !== "classic" && value !== "reserved" && value !== "fixed") {
158
+ throw new Error(`${name} must be classic, reserved, or fixed`);
159
+ }
160
+ }
161
+ function parseBoolean(value) {
162
+ const normalized = optionalEnv(value)?.toLowerCase();
163
+ return normalized === "1" || normalized === "true" || normalized === "yes";
164
+ }
165
+ function parseCasperAnchorSubmissionMode(value) {
166
+ const normalized = optionalEnv(value) ?? "transaction-package";
167
+ if (normalized === "transaction-package" || normalized === "deploy-contract-hash") {
168
+ return normalized;
169
+ }
170
+ throw new Error("CASPER_ANCHOR_SUBMISSION_MODE must be transaction-package or deploy-contract-hash");
171
+ }
172
+ function parseX402SettlementMode(value) {
173
+ const normalized = optionalEnv(value) ?? "resource-retry";
174
+ if (normalized === "resource-retry" ||
175
+ normalized === "facilitator" ||
176
+ normalized === "casper-cli") {
177
+ return normalized;
178
+ }
179
+ throw new Error("X402_SETTLEMENT_MODE must be resource-retry, facilitator, or casper-cli");
180
+ }
181
+ function parsePositiveInteger(value, name, defaultValue) {
182
+ const normalized = optionalEnv(value);
183
+ if (!normalized) {
184
+ return defaultValue;
185
+ }
186
+ if (!/^[1-9]\d*$/.test(normalized)) {
187
+ throw new Error(`${name} must be a positive integer`);
188
+ }
189
+ return Number(normalized);
190
+ }
191
+ function normalizeCasperAccountKeyPath(value, clientWslDistro) {
192
+ if (!clientWslDistro) {
193
+ return value;
194
+ }
195
+ if (value.startsWith("/")) {
196
+ return value;
197
+ }
198
+ const absolutePath = isAbsolute(value) ? value : resolve(repoRoot(), value);
199
+ return toWslPath(absolutePath);
200
+ }
201
+ function repoRoot() {
202
+ const backendRoot = dirname(dirname(fileURLToPath(import.meta.url)));
203
+ return dirname(backendRoot);
204
+ }
205
+ function toWslPath(value) {
206
+ const match = value.match(/^([A-Za-z]):[\\/](.*)$/);
207
+ if (!match) {
208
+ return value.replaceAll("\\", "/");
209
+ }
210
+ return `/mnt/${match[1].toLowerCase()}/${match[2].replaceAll("\\", "/")}`;
211
+ }
212
+ function optionalEnv(value) {
213
+ const normalized = value?.trim();
214
+ return normalized ? normalized : null;
215
+ }
@@ -0,0 +1 @@
1
+ export declare function loadLocalEnvFile(env?: NodeJS.ProcessEnv): string | null;