agentic-wallet-mcp 0.10.0 → 0.11.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/CHANGELOG.md CHANGED
@@ -8,6 +8,22 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  > Entries for 0.5.0 and earlier were reconstructed from commit history when this file was
9
9
  > introduced in 0.6.0, so they summarise each release rather than being exhaustive.
10
10
 
11
+ ## [0.11.0] — 17 September 2026
12
+
13
+ ### Added
14
+
15
+ - **VC pass-design images are now surfaced for basic and verified birthcert issuance.** When the
16
+ issuer returns a pass-design image alongside a credential, the wallet extracts it, writes it to
17
+ disk, and — since a remote MCP client has no filesystem access to read a local path back —
18
+ returns it as an inline image in the tool response too, so the pass is actually visible rather
19
+ than just referenced by path.
20
+
21
+ ### Fixed
22
+
23
+ - **The spending-cap blocker now shows human-readable amounts.** It previously showed the raw
24
+ base-unit number with no symbol or decimal conversion, while the balance/fee blocker in the same
25
+ response already showed the correctly formatted amount — the cap blocker now matches it.
26
+
11
27
  ## [0.10.0] — 14 September 2026
12
28
 
13
29
  ### Added
@@ -12542,15 +12542,16 @@ __export(index_exports, {
12542
12542
  buildToolList: () => buildToolList
12543
12543
  });
12544
12544
  module.exports = __toCommonJS(index_exports);
12545
- var import_node_crypto5 = require("node:crypto");
12545
+ var import_node_crypto6 = require("node:crypto");
12546
12546
  var import_node_fs = require("node:fs");
12547
+ var import_promises6 = require("node:fs/promises");
12547
12548
  var import_node_os2 = require("node:os");
12548
- var import_node_path6 = require("node:path");
12549
+ var import_node_path7 = require("node:path");
12549
12550
 
12550
12551
  // package.json
12551
12552
  var package_default = {
12552
12553
  name: "agentic-wallet-mcp",
12553
- version: "0.10.0",
12554
+ version: "0.11.0",
12554
12555
  description: "Agent-facing MCP wallet for Zetrix \u2014 orchestrates x401 identity proof, x402 payment, and MBI VC issuance",
12555
12556
  keywords: [
12556
12557
  "mcp",
@@ -22429,6 +22430,31 @@ async function payWithReadinessCheck(requestedAsset, rawPay, activated) {
22429
22430
  }
22430
22431
  }
22431
22432
 
22433
+ // src/clients/vc-pass-image.ts
22434
+ var import_node_crypto2 = require("node:crypto");
22435
+ var import_promises2 = require("node:fs/promises");
22436
+ var import_node_path3 = require("node:path");
22437
+ var MAX_PASS_IMAGE_PAGES = 10;
22438
+ var MAX_PASS_IMAGE_BASE64_LENGTH = 8e6;
22439
+ function extractVcPassBase64(extraData) {
22440
+ if (typeof extraData !== "object" || extraData === null) return void 0;
22441
+ const raw = extraData.vcPassBase64;
22442
+ if (!Array.isArray(raw)) return void 0;
22443
+ const strings = raw.filter((v) => typeof v === "string" && v.length > 0 && v.length <= MAX_PASS_IMAGE_BASE64_LENGTH).slice(0, MAX_PASS_IMAGE_PAGES);
22444
+ return strings.length > 0 ? strings : void 0;
22445
+ }
22446
+ async function writeVcPassImages(baseDir, vcId, base64Images) {
22447
+ await (0, import_promises2.mkdir)(baseDir, { recursive: true });
22448
+ const hash = (0, import_node_crypto2.createHash)("sha256").update(vcId).digest("hex");
22449
+ const paths = [];
22450
+ for (let i = 0; i < base64Images.length; i++) {
22451
+ const filePath = (0, import_node_path3.join)(baseDir, `${hash}-${i}.png`);
22452
+ await (0, import_promises2.writeFile)(filePath, Buffer.from(base64Images[i], "base64"));
22453
+ paths.push(filePath);
22454
+ }
22455
+ return paths;
22456
+ }
22457
+
22432
22458
  // src/orchestrator/subscribe.ts
22433
22459
  function diffAgainstTemplate(vc, fields) {
22434
22460
  const held = new Set(extractAttributeKeys(vc));
@@ -22458,6 +22484,36 @@ async function pollSettlementOutcome(deps, paymentId) {
22458
22484
  }
22459
22485
  return { ...last, polls: RECOVERY_MAX_POLLS };
22460
22486
  }
22487
+ async function resolveVcPassImagePaths(deps, vcId) {
22488
+ const downloadVcs = deps.mbi.downloadVcs;
22489
+ const auth = deps.auth;
22490
+ const address = deps.address;
22491
+ const quarantine = deps.quarantine;
22492
+ const passImagesDir = deps.passImagesDir;
22493
+ if (!downloadVcs || !auth || !address || !quarantine || !passImagesDir) return void 0;
22494
+ try {
22495
+ const entries = await quarantine.withLock(vcId, async () => {
22496
+ const quarantined = await quarantine.get(vcId);
22497
+ if (quarantined) return quarantined.entries;
22498
+ let downloaded;
22499
+ try {
22500
+ downloaded = await downloadVcs({ address }, await auth());
22501
+ } catch {
22502
+ return void 0;
22503
+ }
22504
+ await quarantine.set({ vcId, entries: downloaded, downloadedAt: (/* @__PURE__ */ new Date()).toISOString() });
22505
+ return downloaded;
22506
+ });
22507
+ if (!entries) return void 0;
22508
+ const match = entries.find((e) => typeof e.vc === "object" && e.vc !== null && e.vc.id === vcId);
22509
+ if (!match) return void 0;
22510
+ const base64Images = extractVcPassBase64(match.extraData);
22511
+ if (!base64Images) return void 0;
22512
+ return await writeVcPassImages(passImagesDir, vcId, base64Images);
22513
+ } catch {
22514
+ return void 0;
22515
+ }
22516
+ }
22461
22517
  async function subscribeAndIssue(deps, opts) {
22462
22518
  if (!/^did:zid:/.test(opts.templateId)) {
22463
22519
  return { issued: false, reason: `templateId must be a did:zid:... credential-definition id, got "${opts.templateId}"` };
@@ -22534,7 +22590,9 @@ async function subscribeAndIssue(deps, opts) {
22534
22590
  ...schema ? { schema } : {}
22535
22591
  };
22536
22592
  }
22537
- const data = JSON.stringify([{ templateId: opts.templateId, metadata: attributes }]);
22593
+ const data = JSON.stringify([
22594
+ deps.passDesignId ? { templateId: opts.templateId, passDesignId: deps.passDesignId, metadata: attributes } : { templateId: opts.templateId, metadata: attributes }
22595
+ ]);
22538
22596
  const blob = zetrixHexStringToBytes(data).toString("hex");
22539
22597
  const { signBlob: signData, publicKey } = await deps.sign(blob);
22540
22598
  const body = { data, signData, publicKey };
@@ -22550,6 +22608,7 @@ async function subscribeAndIssue(deps, opts) {
22550
22608
  }
22551
22609
  if (challenge.issued) {
22552
22610
  const issued2 = challenge.issued;
22611
+ const vcPassImagePaths2 = issued2.vcId ? await resolveVcPassImagePaths(deps, issued2.vcId) : void 0;
22553
22612
  if (deps.cache) {
22554
22613
  await deps.cache.set(opts.templateId, {
22555
22614
  templateId: opts.templateId,
@@ -22559,7 +22618,8 @@ async function subscribeAndIssue(deps, opts) {
22559
22618
  paidAsset: "none",
22560
22619
  amountPaid: "0",
22561
22620
  issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
22562
- validUntil: extractValidUntil(issued2.verifiableCredential, opts.expirationDate)
22621
+ validUntil: extractValidUntil(issued2.verifiableCredential, opts.expirationDate),
22622
+ ...vcPassImagePaths2 ? { vcPassImagePaths: vcPassImagePaths2 } : {}
22563
22623
  });
22564
22624
  }
22565
22625
  return {
@@ -22569,7 +22629,8 @@ async function subscribeAndIssue(deps, opts) {
22569
22629
  txHash: issued2.txHash,
22570
22630
  paidAsset: "none",
22571
22631
  amountPaid: "0",
22572
- ...schema ? { schema } : {}
22632
+ ...schema ? { schema } : {},
22633
+ ...vcPassImagePaths2 ? { vcPassImagePaths: vcPassImagePaths2 } : {}
22573
22634
  };
22574
22635
  }
22575
22636
  const accept = challenge.accepts[0];
@@ -22604,6 +22665,7 @@ async function subscribeAndIssue(deps, opts) {
22604
22665
  }
22605
22666
  throw err;
22606
22667
  }
22668
+ const vcPassImagePaths = issued.vcId ? await resolveVcPassImagePaths(deps, issued.vcId) : void 0;
22607
22669
  if (deps.cache) {
22608
22670
  await deps.cache.set(opts.templateId, {
22609
22671
  templateId: opts.templateId,
@@ -22613,7 +22675,8 @@ async function subscribeAndIssue(deps, opts) {
22613
22675
  paidAsset,
22614
22676
  amountPaid,
22615
22677
  issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
22616
- validUntil: extractValidUntil(issued.verifiableCredential, opts.expirationDate)
22678
+ validUntil: extractValidUntil(issued.verifiableCredential, opts.expirationDate),
22679
+ ...vcPassImagePaths ? { vcPassImagePaths } : {}
22617
22680
  });
22618
22681
  }
22619
22682
  return {
@@ -22623,7 +22686,8 @@ async function subscribeAndIssue(deps, opts) {
22623
22686
  txHash: issued.txHash,
22624
22687
  paidAsset,
22625
22688
  amountPaid,
22626
- ...schema ? { schema } : {}
22689
+ ...schema ? { schema } : {},
22690
+ ...vcPassImagePaths ? { vcPassImagePaths } : {}
22627
22691
  };
22628
22692
  }
22629
22693
 
@@ -22986,7 +23050,11 @@ var TEMPLATE_ALIASES = [
22986
23050
  testnet: "did:zid:3c0fb79adff08e14e06dcd6e3243205010dd65f533434a3d96c55575d1d3d959",
22987
23051
  mainnet: "did:zid:19091d19049abb8869b4b8e2f4a887bd1d1d86e5f5ebd0c8297000255f67765b",
22988
23052
  deriveAttributes: { id: "agentUsername" },
22989
- validateAttributes: { dob: validateDob, countryOfOrigin: validateCountryOfOrigin }
23053
+ validateAttributes: { dob: validateDob, countryOfOrigin: validateCountryOfOrigin },
23054
+ passDesignId: {
23055
+ testnet: "did:zid:992e1e18985ba36a09ba0fbfeb601ddeb449f5e4e882ed69d0d620085f399818",
23056
+ mainnet: "did:zid:915955cb71c6fd2a256d04344f57381084903cb6a85a1c9ddb71c746f08932ab"
23057
+ }
22990
23058
  }
22991
23059
  ];
22992
23060
  function normalize2(s) {
@@ -23033,6 +23101,11 @@ function validateTemplateAttributes(templateId, network, attributes) {
23033
23101
  }
23034
23102
  return errors;
23035
23103
  }
23104
+ function resolvePassDesignId(templateId, network) {
23105
+ const entry = findEntry(templateId, network);
23106
+ if (!entry?.passDesignId) return void 0;
23107
+ return network.includes("testnet") ? entry.passDesignId.testnet : entry.passDesignId.mainnet;
23108
+ }
23036
23109
 
23037
23110
  // src/orchestrator/preflight.ts
23038
23111
  var VERIFIED_AI_BIRTHCERT = "verified_ai_birthcert";
@@ -23080,7 +23153,7 @@ async function credentialPreflight(deps, input) {
23080
23153
  }
23081
23154
  }
23082
23155
  const cap = describePaymentCap(asset, requiredRaw, deps.caps);
23083
- if (!cap.wouldPass && !isFree) blockers.push(renderCapBlocker(cap, requiredRaw));
23156
+ if (!cap.wouldPass && !isFree) blockers.push(renderCapBlocker(cap, requiredRaw, feeBalance));
23084
23157
  return {
23085
23158
  credential: input.credential,
23086
23159
  ready: blockers.length === 0,
@@ -23106,12 +23179,14 @@ function renderAmount(raw, balance) {
23106
23179
  const frac = (BigInt(raw) % d).toString().padStart(balance.decimals, "0").replace(/0+$/, "");
23107
23180
  return `${whole}${frac ? `.${frac}` : ""} ${balance.token}`;
23108
23181
  }
23109
- function renderCapBlocker(cap, requiredRaw) {
23182
+ function renderCapBlocker(cap, requiredRaw, feeBalance) {
23183
+ const requiredHuman = renderAmount(requiredRaw, feeBalance);
23110
23184
  if (cap.capRaw === null) {
23111
23185
  return `No spending limit applies to ${cap.asset}, and limits are configured \u2014 so this payment would be refused. Set a limit keyed by "${cap.asset}".`;
23112
23186
  }
23187
+ const capHuman = renderAmount(cap.capRaw, feeBalance);
23113
23188
  const misKeyed = cap.matchedKey !== cap.asset;
23114
- return misKeyed ? `The spending limit that applies is ${cap.capRaw} (from the "*" fallback \u2014 no limit is set for ${cap.asset}), and this needs ${requiredRaw}.` : `The spending limit for ${cap.asset} is ${cap.capRaw}, and this needs ${requiredRaw}.`;
23189
+ return misKeyed ? `The spending limit that applies is ${capHuman} (from the "*" fallback \u2014 no limit is set for ${cap.asset}), and this needs ${requiredHuman}.` : `The spending limit for ${cap.asset} is ${capHuman}, and this needs ${requiredHuman}.`;
23115
23190
  }
23116
23191
  function uncheckable(isVerified, isFree = false) {
23117
23192
  const items = [
@@ -23189,7 +23264,11 @@ function createTools(deps) {
23189
23264
  if (errors.length > 0) {
23190
23265
  return { issued: false, reason: errors.join("; ") };
23191
23266
  }
23192
- const result = await subscribeAndIssue(deps.subscribeDeps, { ...input, templateId, attributes });
23267
+ const passDesignId = resolvePassDesignId(templateId, deps.config.network);
23268
+ const result = await subscribeAndIssue(
23269
+ { ...deps.subscribeDeps, passDesignId },
23270
+ { ...input, templateId, attributes }
23271
+ );
23193
23272
  if (!result.schema) return result;
23194
23273
  const hidden = /* @__PURE__ */ new Set(["agentDid", ...derivedAttributeKeys(templateId, deps.config.network)]);
23195
23274
  return {
@@ -23388,12 +23467,12 @@ function configPathFrom(argv) {
23388
23467
  if (!path || path.startsWith("--")) throw new Error("agentic-wallet-mcp: --config requires a file path");
23389
23468
  return path;
23390
23469
  }
23391
- function loadConfigFileEnv(argv, readFile5) {
23470
+ function loadConfigFileEnv(argv, readFile6) {
23392
23471
  const path = configPathFrom(argv);
23393
23472
  if (path === null) return {};
23394
23473
  let raw;
23395
23474
  try {
23396
- raw = readFile5(path);
23475
+ raw = readFile6(path);
23397
23476
  } catch (e) {
23398
23477
  throw new Error(`agentic-wallet-mcp: cannot read config file ${path}: ${e.message}`);
23399
23478
  }
@@ -23425,9 +23504,9 @@ function loadConfigFileEnv(argv, readFile5) {
23425
23504
  }
23426
23505
 
23427
23506
  // src/hsm-password.ts
23428
- var import_node_crypto2 = require("node:crypto");
23507
+ var import_node_crypto3 = require("node:crypto");
23429
23508
  function generateHsmPassword() {
23430
- return (0, import_node_crypto2.randomBytes)(24).toString("base64url");
23509
+ return (0, import_node_crypto3.randomBytes)(24).toString("base64url");
23431
23510
  }
23432
23511
 
23433
23512
  // src/export-credentials.ts
@@ -23464,8 +23543,8 @@ permanently inaccessible.
23464
23543
  }
23465
23544
 
23466
23545
  // src/clients/account-store.ts
23467
- var import_promises2 = require("node:fs/promises");
23468
- var import_node_path3 = require("node:path");
23546
+ var import_promises3 = require("node:fs/promises");
23547
+ var import_node_path4 = require("node:path");
23469
23548
  function isStoredAccountShape(value) {
23470
23549
  return typeof value === "object" && value !== null && typeof value.zetrixAddress === "string" && typeof value.holderDid === "string" && typeof value.hsmPassword === "string";
23471
23550
  }
@@ -23473,7 +23552,7 @@ function createFsAccountStore(filePath) {
23473
23552
  return {
23474
23553
  async get() {
23475
23554
  try {
23476
- const raw = await (0, import_promises2.readFile)(filePath, "utf8");
23555
+ const raw = await (0, import_promises3.readFile)(filePath, "utf8");
23477
23556
  const parsed = JSON.parse(raw);
23478
23557
  return isStoredAccountShape(parsed) ? parsed : null;
23479
23558
  } catch {
@@ -23481,12 +23560,31 @@ function createFsAccountStore(filePath) {
23481
23560
  }
23482
23561
  },
23483
23562
  async set(account) {
23484
- await (0, import_promises2.mkdir)((0, import_node_path3.dirname)(filePath), { recursive: true, mode: 448 });
23485
- await (0, import_promises2.writeFile)(filePath, JSON.stringify(account, null, 2), { encoding: "utf8", mode: 384 });
23563
+ await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(filePath), { recursive: true, mode: 448 });
23564
+ await (0, import_promises3.writeFile)(filePath, JSON.stringify(account, null, 2), { encoding: "utf8", mode: 384 });
23486
23565
  }
23487
23566
  };
23488
23567
  }
23489
23568
 
23569
+ // src/tool-result.ts
23570
+ function extractVcPassImagePaths(result) {
23571
+ if (typeof result !== "object" || result === null) return [];
23572
+ const paths = result.vcPassImagePaths;
23573
+ if (!Array.isArray(paths)) return [];
23574
+ return paths.filter((p) => typeof p === "string");
23575
+ }
23576
+ async function buildToolContent(result, readFile6) {
23577
+ const blocks = [{ type: "text", text: JSON.stringify(result) }];
23578
+ for (const path of extractVcPassImagePaths(result)) {
23579
+ try {
23580
+ const data = await readFile6(path);
23581
+ blocks.push({ type: "image", data: data.toString("base64"), mimeType: "image/png" });
23582
+ } catch {
23583
+ }
23584
+ }
23585
+ return blocks;
23586
+ }
23587
+
23490
23588
  // src/clients/ssivc-client.ts
23491
23589
  var SsivcError = class extends Error {
23492
23590
  httpStatus;
@@ -23589,8 +23687,8 @@ var SsivcClient = class {
23589
23687
  };
23590
23688
 
23591
23689
  // src/clients/ssivc-session-store.ts
23592
- var import_promises3 = require("node:fs/promises");
23593
- var import_node_path4 = require("node:path");
23690
+ var import_promises4 = require("node:fs/promises");
23691
+ var import_node_path5 = require("node:path");
23594
23692
  function isStoredSessionShape(value) {
23595
23693
  if (typeof value !== "object" || value === null) return false;
23596
23694
  const v = value;
@@ -23600,7 +23698,7 @@ function createFsSsivcSessionStore(filePath) {
23600
23698
  return {
23601
23699
  async get() {
23602
23700
  try {
23603
- const raw = await (0, import_promises3.readFile)(filePath, "utf8");
23701
+ const raw = await (0, import_promises4.readFile)(filePath, "utf8");
23604
23702
  const parsed = JSON.parse(raw);
23605
23703
  return isStoredSessionShape(parsed) ? parsed : null;
23606
23704
  } catch {
@@ -23608,20 +23706,20 @@ function createFsSsivcSessionStore(filePath) {
23608
23706
  }
23609
23707
  },
23610
23708
  async set(session) {
23611
- await (0, import_promises3.mkdir)((0, import_node_path4.dirname)(filePath), { recursive: true, mode: 448 });
23709
+ await (0, import_promises4.mkdir)((0, import_node_path5.dirname)(filePath), { recursive: true, mode: 448 });
23612
23710
  const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
23613
- await (0, import_promises3.writeFile)(tmpPath, JSON.stringify(session, null, 2), { encoding: "utf8", mode: 384 });
23614
- await (0, import_promises3.rename)(tmpPath, filePath);
23711
+ await (0, import_promises4.writeFile)(tmpPath, JSON.stringify(session, null, 2), { encoding: "utf8", mode: 384 });
23712
+ await (0, import_promises4.rename)(tmpPath, filePath);
23615
23713
  }
23616
23714
  };
23617
23715
  }
23618
23716
 
23619
23717
  // src/clients/ssivc-download-quarantine-store.ts
23620
- var import_node_crypto3 = require("node:crypto");
23621
- var import_promises4 = require("node:fs/promises");
23622
- var import_node_path5 = require("node:path");
23718
+ var import_node_crypto4 = require("node:crypto");
23719
+ var import_promises5 = require("node:fs/promises");
23720
+ var import_node_path6 = require("node:path");
23623
23721
  function quarantineFileName(vcId) {
23624
- return `${(0, import_node_crypto3.createHash)("sha256").update(vcId).digest("hex")}.json`;
23722
+ return `${(0, import_node_crypto4.createHash)("sha256").update(vcId).digest("hex")}.json`;
23625
23723
  }
23626
23724
  function isQuarantinedDownloadShape(value) {
23627
23725
  if (typeof value !== "object" || value === null) return false;
@@ -23629,12 +23727,22 @@ function isQuarantinedDownloadShape(value) {
23629
23727
  return typeof v.vcId === "string" && "entries" in v && typeof v.downloadedAt === "string";
23630
23728
  }
23631
23729
  function createFsDownloadQuarantineStore(baseDir) {
23632
- const pathFor = (vcId) => (0, import_node_path5.join)(baseDir, quarantineFileName(vcId));
23730
+ const pathFor = (vcId) => (0, import_node_path6.join)(baseDir, quarantineFileName(vcId));
23731
+ const locks = /* @__PURE__ */ new Map();
23633
23732
  return {
23634
23733
  filePathFor: pathFor,
23734
+ withLock(vcId, fn) {
23735
+ const previous = locks.get(vcId) ?? Promise.resolve();
23736
+ const next = previous.then(fn, fn);
23737
+ locks.set(
23738
+ vcId,
23739
+ next.catch(() => void 0)
23740
+ );
23741
+ return next;
23742
+ },
23635
23743
  async get(vcId) {
23636
23744
  try {
23637
- const raw = await (0, import_promises4.readFile)(pathFor(vcId), "utf8");
23745
+ const raw = await (0, import_promises5.readFile)(pathFor(vcId), "utf8");
23638
23746
  const parsed = JSON.parse(raw);
23639
23747
  return isQuarantinedDownloadShape(parsed) ? parsed : null;
23640
23748
  } catch {
@@ -23642,17 +23750,17 @@ function createFsDownloadQuarantineStore(baseDir) {
23642
23750
  }
23643
23751
  },
23644
23752
  async set(entry) {
23645
- await (0, import_promises4.mkdir)(baseDir, { recursive: true, mode: 448 });
23753
+ await (0, import_promises5.mkdir)(baseDir, { recursive: true, mode: 448 });
23646
23754
  const filePath = pathFor(entry.vcId);
23647
23755
  const tmpPath = `${filePath}.tmp-${process.pid}-${Date.now()}`;
23648
- await (0, import_promises4.writeFile)(tmpPath, JSON.stringify(entry, null, 2), { encoding: "utf8", mode: 384 });
23649
- await (0, import_promises4.rename)(tmpPath, filePath);
23756
+ await (0, import_promises5.writeFile)(tmpPath, JSON.stringify(entry, null, 2), { encoding: "utf8", mode: 384 });
23757
+ await (0, import_promises5.rename)(tmpPath, filePath);
23650
23758
  }
23651
23759
  };
23652
23760
  }
23653
23761
 
23654
23762
  // src/orchestrator/verify-ai-birthcert.ts
23655
- var import_node_crypto4 = require("node:crypto");
23763
+ var import_node_crypto5 = require("node:crypto");
23656
23764
 
23657
23765
  // src/canonical-json.ts
23658
23766
  function canonicalizeJson(value) {
@@ -23890,7 +23998,7 @@ async function buildSessionBody(deps, agentName, input) {
23890
23998
  if (input.evidenceAssuranceLevel) fields.evidenceAssuranceLevel = input.evidenceAssuranceLevel;
23891
23999
  if (input.ownerType) fields.ownerType = input.ownerType;
23892
24000
  if (input.ownerVerified) fields.ownerVerified = input.ownerVerified;
23893
- const digestHex = (0, import_node_crypto4.createHash)("sha256").update(canonicalizeJson(fields), "utf8").digest("hex");
24001
+ const digestHex = (0, import_node_crypto5.createHash)("sha256").update(canonicalizeJson(fields), "utf8").digest("hex");
23894
24002
  const { signBlob: signedData } = await deps.signHexBlob(digestHex);
23895
24003
  return { ...fields, signedData };
23896
24004
  }
@@ -24009,6 +24117,7 @@ async function checkAiBirthcertVerification(deps) {
24009
24117
  return { ...status, verificationUrl: stored.verificationUrl };
24010
24118
  }
24011
24119
  if (status.status !== "issued" || !status.vcId) return status;
24120
+ const vcId = status.vcId;
24012
24121
  if (deps.verifiedTemplateId && deps.cache) {
24013
24122
  const cached2 = await deps.cache.get(deps.verifiedTemplateId);
24014
24123
  if (cached2 && cached2.vcId === status.vcId && isVcValid(cached2)) return { ...status, vc: cached2.vc };
@@ -24016,20 +24125,22 @@ async function checkAiBirthcertVerification(deps) {
24016
24125
  if (!deps.verifiedTemplateId || !deps.cache) {
24017
24126
  return { ...status, cacheError: "AI Birthcert verified-template id is not configured \u2014 cannot fetch or cache the credential yet." };
24018
24127
  }
24019
- const quarantined = await deps.quarantine.get(status.vcId);
24020
- const quarantineFilePath = deps.quarantine.filePathFor(status.vcId);
24021
- let entries;
24022
- if (quarantined) {
24023
- entries = quarantined.entries;
24024
- } else {
24128
+ const quarantineFilePath = deps.quarantine.filePathFor(vcId);
24129
+ const locked = await deps.quarantine.withLock(vcId, async () => {
24130
+ const quarantined = await deps.quarantine.get(vcId);
24131
+ if (quarantined) return { entries: quarantined.entries };
24025
24132
  const auth = await deps.messageSigner(deps.address).then((r) => ({ signedData: r.signBlob, publicKey: r.publicKey }));
24133
+ let downloaded;
24026
24134
  try {
24027
- entries = await deps.mbi.downloadVcs({ address: deps.address }, auth);
24135
+ downloaded = await deps.mbi.downloadVcs({ address: deps.address }, auth);
24028
24136
  } catch (err) {
24029
- return { ...status, cacheError: `failed to fetch credential from MBI: ${err instanceof Error ? err.message : String(err)}` };
24137
+ return { error: `failed to fetch credential from MBI: ${err instanceof Error ? err.message : String(err)}` };
24030
24138
  }
24031
- await deps.quarantine.set({ vcId: status.vcId, entries, downloadedAt: (/* @__PURE__ */ new Date()).toISOString() });
24032
- }
24139
+ await deps.quarantine.set({ vcId, entries: downloaded, downloadedAt: (/* @__PURE__ */ new Date()).toISOString() });
24140
+ return { entries: downloaded };
24141
+ });
24142
+ if ("error" in locked) return { ...status, cacheError: locked.error };
24143
+ const entries = locked.entries;
24033
24144
  const match = entries.find((e) => typeof e.vc === "object" && e.vc !== null && e.vc.id === status.vcId);
24034
24145
  if (!match) {
24035
24146
  return {
@@ -24056,14 +24167,23 @@ async function checkAiBirthcertVerification(deps) {
24056
24167
  cacheError: `downloaded credential expired at ${validUntil} \u2014 refusing to cache or return; raw response preserved for recovery at ${quarantineFilePath}`
24057
24168
  };
24058
24169
  }
24170
+ const vcPassImagePaths = deps.passImagesDir ? await (async () => {
24171
+ try {
24172
+ const base64Images = extractVcPassBase64(match.extraData);
24173
+ return base64Images ? await writeVcPassImages(deps.passImagesDir, status.vcId, base64Images) : void 0;
24174
+ } catch {
24175
+ return void 0;
24176
+ }
24177
+ })() : void 0;
24059
24178
  await deps.cache.set(deps.verifiedTemplateId, {
24060
24179
  templateId: deps.verifiedTemplateId,
24061
24180
  vc: match.vc,
24062
24181
  vcId: status.vcId,
24063
24182
  issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
24064
- validUntil
24183
+ validUntil,
24184
+ ...vcPassImagePaths ? { vcPassImagePaths } : {}
24065
24185
  });
24066
- return { ...status, vc: match.vc };
24186
+ return { ...status, vc: match.vc, ...vcPassImagePaths ? { vcPassImagePaths } : {} };
24067
24187
  }
24068
24188
 
24069
24189
  // src/index.ts
@@ -24253,8 +24373,8 @@ function asPayRequest(accept) {
24253
24373
  }
24254
24374
  async function main() {
24255
24375
  const fileEnv = loadConfigFileEnv(process.argv, (p) => (0, import_node_fs.readFileSync)(p, "utf8"));
24256
- const stateDir = (process.env.ZETRIX_WALLET_STATE_DIR ?? fileEnv.ZETRIX_WALLET_STATE_DIR ?? (0, import_node_path6.join)((0, import_node_os2.homedir)(), ".agentic-wallet-mcp")).replace(/\/+$/, "");
24257
- const accountStore = createFsAccountStore((0, import_node_path6.join)(stateDir, "account.json"));
24376
+ const stateDir = (process.env.ZETRIX_WALLET_STATE_DIR ?? fileEnv.ZETRIX_WALLET_STATE_DIR ?? (0, import_node_path7.join)((0, import_node_os2.homedir)(), ".agentic-wallet-mcp")).replace(/\/+$/, "");
24377
+ const accountStore = createFsAccountStore((0, import_node_path7.join)(stateDir, "account.json"));
24258
24378
  if (process.argv[2] === "export-credentials") {
24259
24379
  const { exitCode } = await exportCredentials({
24260
24380
  getAccount: () => accountStore.get(),
@@ -24327,8 +24447,8 @@ async function main() {
24327
24447
  const nodeBaseUrl = `https://${config2.nodeHost}${config2.nodePort ? `:${config2.nodePort}` : ""}`;
24328
24448
  const nodeMetaQuery = (url) => fetch(url, { headers: { Accept: "application/json" } }).then((r) => r.json());
24329
24449
  const resolveTemplateFields = (templateId) => fetchTemplateFields(templateId, config2.templateRegistryAddress, nodeBaseUrl, nodeMetaQuery);
24330
- const cacheScope = (0, import_node_crypto5.createHash)("sha256").update(`${config2.network}:${zetrixAddress}`).digest("hex");
24331
- const vcCache = createFsVcCache((0, import_node_path6.join)(config2.stateDir, "vc-cache", cacheScope));
24450
+ const cacheScope = (0, import_node_crypto6.createHash)("sha256").update(`${config2.network}:${zetrixAddress}`).digest("hex");
24451
+ const vcCache = createFsVcCache((0, import_node_path7.join)(config2.stateDir, "vc-cache", cacheScope));
24332
24452
  const mbi = new MbiClient(config2.mbiBaseUrl);
24333
24453
  const messageSigner = (message) => be.signMessage(message, zetrixAddress, hsmPassword);
24334
24454
  const formatAssetAmount = async (asset, raw) => {
@@ -24384,9 +24504,14 @@ async function main() {
24384
24504
  }
24385
24505
  };
24386
24506
  const { pay, payForCredential, preflightCaps } = buildPayers(config2, makePay);
24507
+ const downloadQuarantine = createFsDownloadQuarantineStore((0, import_node_path7.join)(config2.stateDir, "ssivc-download-quarantine"));
24508
+ const passImagesDir = (0, import_node_path7.join)(config2.stateDir, "vc-pass-images");
24509
+ const mbiAuth = async () => {
24510
+ const { signBlob, publicKey } = await messageSigner(zetrixAddress);
24511
+ return { signedData: signBlob, publicKey };
24512
+ };
24387
24513
  const verifyAiBirthcert = config2.ssivcBaseUrl ? (() => {
24388
- const ssivcSessionStore = createFsSsivcSessionStore((0, import_node_path6.join)(config2.stateDir, "ssivc-session.json"));
24389
- const downloadQuarantine = createFsDownloadQuarantineStore((0, import_node_path6.join)(config2.stateDir, "ssivc-download-quarantine"));
24514
+ const ssivcSessionStore = createFsSsivcSessionStore((0, import_node_path7.join)(config2.stateDir, "ssivc-session.json"));
24390
24515
  const ssivc = new SsivcClient(config2.ssivcBaseUrl);
24391
24516
  const verifyAiBirthcertDeps = {
24392
24517
  ssivc,
@@ -24404,7 +24529,8 @@ async function main() {
24404
24529
  quarantine: downloadQuarantine,
24405
24530
  gasPreference: config2.gasPreference,
24406
24531
  maxSettlementAttempts: config2.maxSettlementAttempts,
24407
- formatAssetAmount
24532
+ formatAssetAmount,
24533
+ passImagesDir
24408
24534
  };
24409
24535
  return {
24410
24536
  request: (input) => requestAiBirthcertVerification(verifyAiBirthcertDeps, input),
@@ -24455,7 +24581,19 @@ async function main() {
24455
24581
  config: { holderDid, zetrixAddress, network: config2.network },
24456
24582
  makeWallet,
24457
24583
  payer,
24458
- subscribeDeps: { mbi, sign: subscribeSign, pay: payForCredential, resolveSymbol, holderDid, resolveTemplateFields, cache: vcCache },
24584
+ subscribeDeps: {
24585
+ mbi,
24586
+ sign: subscribeSign,
24587
+ pay: payForCredential,
24588
+ resolveSymbol,
24589
+ holderDid,
24590
+ resolveTemplateFields,
24591
+ cache: vcCache,
24592
+ auth: mbiAuth,
24593
+ address: zetrixAddress,
24594
+ quarantine: downloadQuarantine,
24595
+ passImagesDir
24596
+ },
24459
24597
  queryContract: (input) => queryContract(input, contractQuery),
24460
24598
  queryTokenBalance: queryTokenBalance2,
24461
24599
  // Read-only, for credential_preflight's cap headroom. Preflight only ever prices credentials,
@@ -24480,7 +24618,7 @@ async function main() {
24480
24618
  if (!fn) throw new Error(`Unknown tool: ${name}`);
24481
24619
  try {
24482
24620
  const result = await fn(args ?? {});
24483
- return { content: [{ type: "text", text: JSON.stringify(result) }] };
24621
+ return { content: await buildToolContent(result, (p) => (0, import_promises6.readFile)(p)) };
24484
24622
  } catch (err) {
24485
24623
  const chain = [];
24486
24624
  let e = err;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-wallet-mcp",
3
- "version": "0.10.0",
3
+ "version": "0.11.0",
4
4
  "description": "Agent-facing MCP wallet for Zetrix — orchestrates x401 identity proof, x402 payment, and MBI VC issuance",
5
5
  "keywords": [
6
6
  "mcp",