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 +16 -0
- package/dist/server-bundle.cjs +198 -60
- package/package.json +1 -1
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
|
package/dist/server-bundle.cjs
CHANGED
|
@@ -12542,15 +12542,16 @@ __export(index_exports, {
|
|
|
12542
12542
|
buildToolList: () => buildToolList
|
|
12543
12543
|
});
|
|
12544
12544
|
module.exports = __toCommonJS(index_exports);
|
|
12545
|
-
var
|
|
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
|
|
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.
|
|
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([
|
|
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 ${
|
|
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
|
|
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,
|
|
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 =
|
|
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
|
|
23507
|
+
var import_node_crypto3 = require("node:crypto");
|
|
23429
23508
|
function generateHsmPassword() {
|
|
23430
|
-
return (0,
|
|
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
|
|
23468
|
-
var
|
|
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,
|
|
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,
|
|
23485
|
-
await (0,
|
|
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
|
|
23593
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
23614
|
-
await (0,
|
|
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
|
|
23621
|
-
var
|
|
23622
|
-
var
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
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,
|
|
23649
|
-
await (0,
|
|
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
|
|
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,
|
|
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
|
|
24020
|
-
const
|
|
24021
|
-
|
|
24022
|
-
|
|
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
|
-
|
|
24135
|
+
downloaded = await deps.mbi.downloadVcs({ address: deps.address }, auth);
|
|
24028
24136
|
} catch (err) {
|
|
24029
|
-
return {
|
|
24137
|
+
return { error: `failed to fetch credential from MBI: ${err instanceof Error ? err.message : String(err)}` };
|
|
24030
24138
|
}
|
|
24031
|
-
await deps.quarantine.set({ vcId
|
|
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,
|
|
24257
|
-
const accountStore = createFsAccountStore((0,
|
|
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,
|
|
24331
|
-
const vcCache = createFsVcCache((0,
|
|
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,
|
|
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: {
|
|
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:
|
|
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;
|