agentic-wallet-mcp 0.3.1 → 0.4.2

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/README.md CHANGED
@@ -20,20 +20,34 @@ VC issuance → identity proof → pay-per-use).
20
20
 
21
21
  | Tool | Does | Input | Output (shape) |
22
22
  |---|---|---|---|
23
- | `wallet_status` | Report holder DID/address/network + client-supplied held VCs | `{ heldCredentials? }` | `{ holderDid, zetrixAddress, network, credentials }` |
24
- | `prove_identity` | Answer an x401 `PROOF-REQUEST` → return the `PROOF-RESPONSE` header to replay | `{ proofRequest, vc, revealAttribute?, issuerKeys? }` | `{ proofResponseHeader, verified, presentationId }` |
23
+ | `wallet_status` | Report holder DID/address/network + held VCs (client-supplied, or the local cache) | `{ heldCredentials? }` | `{ holderDid, zetrixAddress, network, credentials }` |
24
+ | `prove_identity` | Answer an x401 `PROOF-REQUEST` → return the `PROOF-RESPONSE` header to replay | `{ proofRequest, vc?, revealAttribute?, issuerKeys? }` | `{ proofResponseHeader, verified, presentationId }` |
25
25
  | `pay_and_fetch` | Fetch a URL, auto-pay with x402 (self-pay via Wallet BE) on `402` | `{ url, method?, headers?, body? }` | `{ status, body, paymentMade, amountPaid, amountPaidHuman, asset }` |
26
- | `subscribe_and_issue` | Build the signed payload pay x402 → MBI issues → return the VC | `{ templateId, attributes, expirationDate? }` | `{ issued, vcId, vc, txHash }` |
27
- | `create_holder_account` | Onboarding: manually mint an additional/replacement HSM account (the MCP already auto-creates one at startup if `ZETRIX_ADDRESS` is omitted — see Environment below) | `{ password, label?, purpose? }` | `{ zetrixAddress, holderDid, publicKeyHex, message }` |
28
-
29
- > The wallet **never persists VCs** the client holds them and passes them in (e.g. `vc` on
30
- > `prove_identity`, `heldCredentials` on `wallet_status`). All Ed25519 signing goes through
31
- > Wallet BE HSM; no plaintext private keys.
26
+ | `subscribe_and_issue` | Reuse a cached VC if still valid, else pay x402 → MBI issues → return the VC | `{ templateId, attributes, expirationDate?, dryRun?, forceReissue? }` | `{ issued, vcId, vc, txHash, fromCache? }` |
27
+ | `create_holder_account` | Onboarding: mint an HSM account (the MCP already auto-creates one at startup if `ZETRIX_ADDRESS` is omitted — see Environment below). Always checks for an existing account first — if one is active for this session, returns `{ alreadyExists: true, existing }` without creating anything; pass `confirmNew: true` (after asking the user) to mint a new one anyway | `{ password, label?, purpose?, confirmNew? }` | `{ created, alreadyExists, existing?, zetrixAddress?, holderDid?, publicKeyHex?, message }` |
28
+
29
+ > **VCs are cached locally**, keyed by `templateId`, under `~/.agentic-wallet-mcp/vc-cache/`
30
+ > (scoped per network + holder different identities or networks never share a cache).
31
+ > `subscribe_and_issue` checks the cache before paying: a still-valid cached VC is returned
32
+ > immediately with `fromCache: true` and **no payment made**; pass `forceReissue: true` to pay
33
+ > and issue fresh regardless. `wallet_status`/`prove_identity` fall back to the cache
34
+ > automatically when you don't pass `heldCredentials`/`vc` explicitly — `prove_identity` only
35
+ > auto-selects when there's exactly one valid cached VC; with zero or several, it errors and
36
+ > asks you to pass `vc` explicitly. Explicitly passing `vc`/`heldCredentials` (including `[]`)
37
+ > always overrides the cache. Validity is read from the VC's own `validUntil` field, falling
38
+ > back to the `expirationDate` requested at issuance; a VC with neither is cached indefinitely.
39
+ > All Ed25519 signing still goes through Wallet BE HSM; no plaintext private keys.
32
40
 
33
41
  > `create_holder_account` mints a **brand-new** keypair — Wallet BE's `/account/create` has no
34
- > way to provision a pre-chosen address. It returns the new `zetrixAddress`/`holderDid` for you
35
- > to paste into `ZETRIX_ADDRESS`/`HSM_PASSWORD` yourself (`HOLDER_DID` is optional see
36
- > Environment below); the tool never writes your MCP config or restarts the server for you.
42
+ > way to provision a pre-chosen address. It always checks first whether an account is already
43
+ > active for this session; if so, it returns `{ alreadyExists: true, existing }` and creates
44
+ > nothing ask the user whether to keep the existing account or replace it, then call again
45
+ > with `confirmNew: true` only if they want a new one. A freshly minted account (address, DID,
46
+ > **and** password) is saved to this MCP's own local account store
47
+ > (`~/.agentic-wallet-mcp/account.json`, owner-only) and reused automatically on the next
48
+ > restart — no manual config edit needed. An explicit `ZETRIX_ADDRESS`/`HSM_PASSWORD` still set
49
+ > in your MCP config always overrides the saved account (see Environment below); the tool never
50
+ > writes the MCP host's own config file or restarts the server for you.
37
51
 
38
52
  > `revealAttribute` on `prove_identity` is optional and usually should stay that way. Omitted,
39
53
  > it's derived automatically from the challenge's DCQL `credential_requirements` — each claim path
@@ -92,9 +106,11 @@ startup, in one of two ways:
92
106
 
93
107
  1. **First-time user — only `HSM_PASSWORD` set.** The MCP creates a brand-new HSM account on
94
108
  Wallet BE (`POST /wallet/hsm/account/create`) and derives the DID from the returned public
95
- key. It logs the new `ZETRIX_ADDRESS` (and `HOLDER_DID`) to stderr on startup copy it into
96
- your MCP config for next time, since nothing is persisted to disk between runs (env vars only
97
- load once, at process start).
109
+ key. It logs the new `ZETRIX_ADDRESS` (and `HOLDER_DID`) to stderr on startup, and saves the
110
+ address, DID, and password to a local account store
111
+ (`~/.agentic-wallet-mcp/account.json`, owner-only) it's reused automatically next run, no
112
+ config edit required. An explicit `ZETRIX_ADDRESS`/`HSM_PASSWORD` set later in your MCP
113
+ config still overrides the saved account.
98
114
  2. **Existing user — `ZETRIX_ADDRESS` + `HSM_PASSWORD` set, `HOLDER_DID` optional.** The MCP
99
115
  always self-signs the address via the existing `POST /wallet/hsm/sign-message` call and
100
116
  derives the DID from the `publicKey` the response carries — no separate lookup endpoint
@@ -126,8 +142,9 @@ network:
126
142
 
127
143
  Only set any of the four explicitly if you run your own instance of that service instead of the
128
144
  default one — an explicit value always wins over the network default. The `*.myegdev.com`
129
- (testnet) hosts are internalunreachable without VPN; see "Network reachability &
130
- troubleshooting" below if any of them time out.
145
+ (testnet) hosts are public endpoints no VPN needed; if you're on a corporate VPN and one of
146
+ them times out, disconnecting it is more likely to fix that than connecting it. See "Network
147
+ reachability & troubleshooting" below.
131
148
 
132
149
  That's the complete list — no VC-MCP subprocess, no BaaS gateway key, no manually-configured
133
150
  BBS+ key to set up.
@@ -162,8 +179,9 @@ the `<...>` placeholders (don't commit a filled copy; `mcp.local.json` is gitign
162
179
  ```
163
180
 
164
181
  > First run, no account yet? Omit `ZETRIX_ADDRESS` (and `HOLDER_DID`) entirely — the MCP creates
165
- > one for you at startup and logs it to stderr; copy it back into `env` for next time. See
166
- > "Onboarding" under Environment above.
182
+ > one for you at startup, logs it to stderr, and saves it (address, DID, password) to
183
+ > `~/.agentic-wallet-mcp/account.json` for automatic reuse next run. See "Onboarding" under
184
+ > Environment above.
167
185
 
168
186
  > Working on this repo locally instead of the published package? Point `command`/`args` at the
169
187
  > local build directly: `"command": "node"`, `"args": ["/absolute/path/to/zetrix-agentic-wallet/dist/server-bundle.cjs"]`.
@@ -181,7 +199,7 @@ the `<...>` placeholders (don't commit a filled copy; `mcp.local.json` is gitign
181
199
  - *"I got a 401 with this PROOF-REQUEST header — prove my identity and give me the PROOF-RESPONSE to replay."* → `prove_identity`
182
200
  - *"Fetch `https://api.example/data` and pay automatically if it asks."* → `pay_and_fetch`
183
201
  - *"Apply for the agent-identity credential with these attributes and pay for it."* → `subscribe_and_issue`
184
- - *"My wallet_status call is failing — I don't have a holder account yet. Set one up."* → `create_holder_account` (asks you for a password, then returns the new address/DID to save)
202
+ - *"My wallet_status call is failing — I don't have a holder account yet. Set one up."* → `create_holder_account` (asks you for a password; if an account already exists it reports that instead of creating — confirm with the user, then re-call with `confirmNew: true` to replace it)
185
203
 
186
204
  For the full ordered script (onboarding → check → issue → prove → pay), see [`docs/USAGE_FLOW.md`](docs/USAGE_FLOW.md).
187
205
 
@@ -191,9 +209,12 @@ Full narrative version with example prompts: [`docs/USAGE_FLOW.md`](docs/USAGE_F
191
209
 
192
210
  **Step 0 — onboarding (once).** Only if `ZETRIX_ADDRESS` isn't set yet: the MCP creates an HSM
193
211
  account automatically at startup from `HSM_PASSWORD` alone (see "Onboarding" under Environment
194
- above) copy the logged `zetrixAddress` into your MCP config for next time. Alternatively, call
195
- `create_holder_account { password }` manually and paste the returned `zetrixAddress` in yourself;
196
- either way, restart the server afterward (env vars load once, at process start).
212
+ above) and saves it locally for automatic reuse. Alternatively, call `create_holder_account
213
+ { password }` manually it always checks for an existing account first and reports it instead
214
+ of creating (pass `confirmNew: true`, after asking the user, to replace it anyway). Either way,
215
+ the account is saved to `~/.agentic-wallet-mcp/account.json` and picked up automatically on the
216
+ next restart; no manual config edit needed unless your MCP config also sets `ZETRIX_ADDRESS`/
217
+ `HSM_PASSWORD` via env, in which case those still take precedence and should be updated too.
197
218
 
198
219
  **Phase 1 — `wallet_status` — pre-check.** Pass any VCs the caller already holds via
199
220
  `heldCredentials`; the response tells you whether the agent-identity credential you need is
@@ -220,13 +241,13 @@ Independent of Phases 2/3 — no VC or identity proof involved, just a fresh pay
220
241
 
221
242
  ## Network reachability & troubleshooting
222
243
 
223
- Several dependencies sit behind a corporate VPN or a CDN edge. When something that worked before
224
- suddenly times out or 403s, check this table before assuming a code regression in every case
225
- observed so far, the code was correct and the network/edge state had changed.
244
+ The `*.myegdev.com` testnet endpoints are public — no VPN needed to reach them. One dependency
245
+ sits behind a CDN edge (see the ZID resolver row below). When something that worked before
246
+ suddenly times out or 403s, check this table before assuming a code regression.
226
247
 
227
248
  | Symptom | Cause | Fix / status |
228
249
  |---|---|---|
229
- | `Wallet BE /wallet/hsm/sign-blob request failed <- fetch failed <- UND_ERR_CONNECT_TIMEOUT` (or same for `mbi-vc.myegdev.com`) | `*.myegdev.com` hosts are internal unreachable without VPN | Connect/reconnect your VPN, then retry. Verify first: `curl -I https://wallet-api.myegdev.com/server` |
250
+ | `Wallet BE /wallet/hsm/sign-blob request failed <- fetch failed <- UND_ERR_CONNECT_TIMEOUT` (or same for `mbi-vc.myegdev.com`) | Transient network issue, or (if you're on a corporate VPN) the VPN routing away from the public internet | If you're on a VPN, try disconnecting it and retrying. Verify reachability directly: `curl -I https://wallet-api.myegdev.com/server` |
230
251
  | `VP derivation failed <- ZID resolver HTTP 403 ... cf-mitigated: challenge` | ZID resolver (`zid-resolver-sandbox.zetrix.com`) sitting behind a Cloudflare **managed challenge** that blocks plain server-to-server `fetch` | Server-to-server access to the resolver must be allowlisted so it returns `200` directly. If the challenge is active, `prove_identity`'s `issuerKeys` input is the fallback — fetch the DID document via a real browser (it clears the JS challenge) and pass its `verificationMethod` entries' `publicKeyMultibase` (BBS+) / `publicKeyHex` (Ed25519) directly. |
231
252
  | `OID4VP backend returned a malformed presentation definition` | Historical SDK bug: the live sandbox's `GET /v1/presentation/{id}` response has no `expires_at` field, but the SDK guard required one | **Fixed** in `x401-zetrix-client` — `expiresAt` is now optional on `PresentationDefinition`. If you see this, you're on a stale cached `npx` install — clear it (`npx clear-npx-cache` or bump the version) to pick up the current published `agentic-wallet-mcp`. |
232
253
  | `SUBMIT_FAILED: OID4VP backend returned 401 ... Missing X-Wallet-Public-Key header` | Historical SDK gap: `POST /v1/presentation/submit` requires wallet-auth headers (`X-Wallet-Public-Key` / `X-Wallet-Signed-Data`, holder signs their own address) that the SDK didn't send | **Fixed** — `X401Wallet` now accepts an injected `submitAuth` provider and the wallet wires it automatically; nothing to configure. |
@@ -2233,8 +2233,8 @@ var require_resolve = __commonJS({
2233
2233
  }
2234
2234
  return count;
2235
2235
  }
2236
- function getFullPath(resolver, id = "", normalize) {
2237
- if (normalize !== false)
2236
+ function getFullPath(resolver, id = "", normalize2) {
2237
+ if (normalize2 !== false)
2238
2238
  id = normalizeId(id);
2239
2239
  const p = resolver.parse(id);
2240
2240
  return _getFullPath(resolver, p);
@@ -3630,7 +3630,7 @@ var require_fast_uri = __commonJS({
3630
3630
  "use strict";
3631
3631
  var { normalizeIPv6, removeDotSegments, recomposeAuthority, normalizePercentEncoding, normalizePathEncoding, escapePreservingEscapes, reescapeHostDelimiters, isIPv4, nonSimpleDomain } = require_utils();
3632
3632
  var { SCHEMES, getSchemeHandler } = require_schemes();
3633
- function normalize(uri, options) {
3633
+ function normalize2(uri, options) {
3634
3634
  if (typeof uri === "string") {
3635
3635
  uri = /** @type {T} */
3636
3636
  normalizeString(uri, options);
@@ -3897,7 +3897,7 @@ var require_fast_uri = __commonJS({
3897
3897
  }
3898
3898
  var fastUri = {
3899
3899
  SCHEMES,
3900
- normalize,
3900
+ normalize: normalize2,
3901
3901
  resolve,
3902
3902
  resolveComponent,
3903
3903
  equal,
@@ -8541,13 +8541,13 @@ var require_path = __commonJS({
8541
8541
  return /^(?:\/|\w+:)/.test(path2);
8542
8542
  }
8543
8543
  );
8544
- var normalize = (
8544
+ var normalize2 = (
8545
8545
  /**
8546
8546
  * Normalizes the specified path.
8547
8547
  * @param {string} path Path to normalize
8548
8548
  * @returns {string} Normalized path
8549
8549
  */
8550
- path.normalize = function normalize2(path2) {
8550
+ path.normalize = function normalize3(path2) {
8551
8551
  path2 = path2.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
8552
8552
  var parts = path2.split("/"), absolute = isAbsolute(path2), prefix = "";
8553
8553
  if (absolute)
@@ -8570,12 +8570,12 @@ var require_path = __commonJS({
8570
8570
  );
8571
8571
  path.resolve = function resolve(originPath, includePath, alreadyNormalized) {
8572
8572
  if (!alreadyNormalized)
8573
- includePath = normalize(includePath);
8573
+ includePath = normalize2(includePath);
8574
8574
  if (isAbsolute(includePath))
8575
8575
  return includePath;
8576
8576
  if (!alreadyNormalized)
8577
- originPath = normalize(originPath);
8578
- return (originPath = originPath.replace(/(?:\/|^)[^/]+$/, "")).length ? normalize(originPath + "/" + includePath) : includePath;
8577
+ originPath = normalize2(originPath);
8578
+ return (originPath = originPath.replace(/(?:\/|^)[^/]+$/, "")).length ? normalize2(originPath + "/" + includePath) : includePath;
8579
8579
  };
8580
8580
  }
8581
8581
  });
@@ -12540,11 +12540,14 @@ __export(index_exports, {
12540
12540
  buildToolList: () => buildToolList
12541
12541
  });
12542
12542
  module.exports = __toCommonJS(index_exports);
12543
+ var import_node_crypto2 = require("node:crypto");
12544
+ var import_node_os = require("node:os");
12545
+ var import_node_path3 = require("node:path");
12543
12546
 
12544
12547
  // package.json
12545
12548
  var package_default = {
12546
12549
  name: "agentic-wallet-mcp",
12547
- version: "0.3.1",
12550
+ version: "0.4.2",
12548
12551
  description: "Agent-facing MCP wallet for Zetrix \u2014 orchestrates x401 identity proof, x402 payment, and MBI VC issuance",
12549
12552
  keywords: ["mcp", "model-context-protocol", "zetrix", "wallet", "x401", "x402", "blockchain"],
12550
12553
  license: "MIT",
@@ -21607,7 +21610,7 @@ async function resolveAssetSymbol(asset, query) {
21607
21610
  }
21608
21611
 
21609
21612
  // src/clients/template-info-client.ts
21610
- async function fetchTemplateRequiredFields(templateId, registryAddress, nodeBaseUrl, query) {
21613
+ async function fetchTemplateFields(templateId, registryAddress, nodeBaseUrl, query) {
21611
21614
  const key = `template__${templateId}`;
21612
21615
  const url = `${nodeBaseUrl.replace(/\/+$/, "")}/getAccountMetaData?address=${encodeURIComponent(registryAddress)}&key=${encodeURIComponent(key)}`;
21613
21616
  let res;
@@ -21621,10 +21624,16 @@ async function fetchTemplateRequiredFields(templateId, registryAddress, nodeBase
21621
21624
  if (!value) return null;
21622
21625
  try {
21623
21626
  const parsed = JSON.parse(value);
21624
- if (parsed.applyFormat === void 0 || parsed.applyFormat === null) return [];
21627
+ if (parsed.applyFormat === void 0 || parsed.applyFormat === null) return { required: [], allKeys: [] };
21625
21628
  const format = typeof parsed.applyFormat === "string" ? JSON.parse(parsed.applyFormat) : parsed.applyFormat;
21626
21629
  if (!Array.isArray(format)) return null;
21627
- return format.filter((e) => e && e.mandatory === 1 && typeof e.key === "string" && e.key !== "").map((e) => e.key);
21630
+ const entries = format.filter(
21631
+ (e) => !!e && typeof e.key === "string" && e.key !== ""
21632
+ );
21633
+ return {
21634
+ required: entries.filter((e) => e.mandatory === 1).map((e) => e.key),
21635
+ allKeys: entries.map((e) => e.key)
21636
+ };
21628
21637
  } catch {
21629
21638
  return null;
21630
21639
  }
@@ -21721,6 +21730,10 @@ function findLeafPath(obj, key) {
21721
21730
  }
21722
21731
  return null;
21723
21732
  }
21733
+ function flattenLeafPaths(obj, prefix = []) {
21734
+ if (!isRecord2(obj)) return prefix.length ? [prefix.join(".")] : [];
21735
+ return Object.entries(obj).flatMap(([k, v]) => flattenLeafPaths(v, [...prefix, k]));
21736
+ }
21724
21737
  function dcqlToRevealAttributes(credentialQuery, vc) {
21725
21738
  if (!isRecord2(credentialQuery) || !Array.isArray(credentialQuery.credentials)) return [];
21726
21739
  const subject = isRecord2(vc) && isRecord2(vc.credentialSubject) ? vc.credentialSubject : void 0;
@@ -21743,7 +21756,9 @@ function dcqlToRevealAttributes(credentialQuery, vc) {
21743
21756
  }
21744
21757
  }
21745
21758
  }
21746
- return out;
21759
+ if (!subject) return out;
21760
+ const canonicalIndex = new Map(flattenLeafPaths(subject).map((path, i) => [path, i]));
21761
+ return out.map((path, i) => ({ path, key: canonicalIndex.get(path) ?? Number.MAX_SAFE_INTEGER, i })).sort((a, b) => a.key - b.key || a.i - b.i).map((entry) => entry.path);
21747
21762
  }
21748
21763
  var MbiVpAdapter = class {
21749
21764
  constructor(mbi, signHexBlob, signMessage, holderAddress, resolveIssuerKeys, present) {
@@ -21794,14 +21809,22 @@ var MbiError = class extends Error {
21794
21809
  this.httpStatus = httpStatus;
21795
21810
  }
21796
21811
  };
21797
- var MbiClient = class {
21812
+ var MbiClient = class _MbiClient {
21798
21813
  baseUrl;
21799
21814
  constructor(baseUrl) {
21800
21815
  this.baseUrl = baseUrl.replace(/\/+$/, "");
21801
21816
  }
21802
- /** Phase 1 — POST /v1/vc/pay/apply without X-PAYMENT; expects the 402 challenge. */
21817
+ /**
21818
+ * Phase 1 — POST /v1/vc/pay/apply without X-PAYMENT; expects the 402 challenge.
21819
+ * A free template short-circuits this: MBI issues the VC synchronously and returns
21820
+ * 200 instead, with no phase-2 settle to follow — surfaced via the `issued` field.
21821
+ */
21803
21822
  async applyChallenge(body) {
21804
21823
  const res = await this.fetch("POST", "/v1/vc/pay/apply", body);
21824
+ if (res.status === 200) {
21825
+ const issued = await this.unwrap(res);
21826
+ return { x402Version: 1, accepts: [], issued };
21827
+ }
21805
21828
  if (res.status !== 402) {
21806
21829
  throw await this.error(res, "apply (phase 1) expected 402");
21807
21830
  }
@@ -21849,12 +21872,14 @@ var MbiClient = class {
21849
21872
  const body = await res.json();
21850
21873
  return body.data;
21851
21874
  }
21875
+ static ERROR_BODY_MAX_LEN = 500;
21852
21876
  async error(res, context) {
21853
21877
  const text = await res.text().catch(() => "");
21854
21878
  let msg = text;
21855
21879
  try {
21856
21880
  const j = JSON.parse(text);
21857
- msg = j.message ?? j.error ?? text;
21881
+ const truncated = text.length > _MbiClient.ERROR_BODY_MAX_LEN ? `${text.slice(0, _MbiClient.ERROR_BODY_MAX_LEN)}\u2026 (truncated, ${text.length} bytes total)` : text;
21882
+ msg = `${j.message ?? j.error ?? text} | full body: ${truncated}`;
21858
21883
  } catch {
21859
21884
  }
21860
21885
  return new MbiError(`MBI ${context} \u2014 HTTP ${res.status}: ${msg}`, res.status);
@@ -21950,17 +21975,92 @@ function zetrixHexStringToBytes(s) {
21950
21975
  return out;
21951
21976
  }
21952
21977
 
21978
+ // src/clients/vc-cache.ts
21979
+ var import_node_crypto = require("node:crypto");
21980
+ var import_promises = require("node:fs/promises");
21981
+ var import_node_path = require("node:path");
21982
+ function cacheFileName(templateId) {
21983
+ return `${(0, import_node_crypto.createHash)("sha256").update(templateId).digest("hex")}.json`;
21984
+ }
21985
+ function isCachedVcShape(value) {
21986
+ return typeof value === "object" && value !== null && typeof value.templateId === "string" && "vc" in value && typeof value.issuedAt === "string";
21987
+ }
21988
+ function createFsVcCache(baseDir) {
21989
+ return {
21990
+ async get(templateId) {
21991
+ try {
21992
+ const raw = await (0, import_promises.readFile)((0, import_node_path.join)(baseDir, cacheFileName(templateId)), "utf8");
21993
+ const parsed = JSON.parse(raw);
21994
+ return isCachedVcShape(parsed) ? parsed : null;
21995
+ } catch {
21996
+ return null;
21997
+ }
21998
+ },
21999
+ async set(templateId, entry) {
22000
+ await (0, import_promises.mkdir)(baseDir, { recursive: true, mode: 448 });
22001
+ await (0, import_promises.writeFile)((0, import_node_path.join)(baseDir, cacheFileName(templateId)), JSON.stringify(entry), { encoding: "utf8", mode: 384 });
22002
+ },
22003
+ async list() {
22004
+ let files;
22005
+ try {
22006
+ files = await (0, import_promises.readdir)(baseDir);
22007
+ } catch {
22008
+ return [];
22009
+ }
22010
+ const entries = await Promise.all(
22011
+ files.filter((f) => f.endsWith(".json")).map(async (f) => {
22012
+ try {
22013
+ const parsed = JSON.parse(await (0, import_promises.readFile)((0, import_node_path.join)(baseDir, f), "utf8"));
22014
+ return isCachedVcShape(parsed) ? parsed : null;
22015
+ } catch {
22016
+ return null;
22017
+ }
22018
+ })
22019
+ );
22020
+ return entries.filter((e) => e !== null);
22021
+ }
22022
+ };
22023
+ }
22024
+ function isVcValid(entry, now = /* @__PURE__ */ new Date()) {
22025
+ if (!entry.validUntil) return true;
22026
+ const expiry = new Date(entry.validUntil);
22027
+ if (Number.isNaN(expiry.getTime())) return false;
22028
+ return expiry.getTime() > now.getTime();
22029
+ }
22030
+ function extractValidUntil(vc, fallback) {
22031
+ if (typeof vc === "object" && vc !== null && "validUntil" in vc) {
22032
+ const v = vc.validUntil;
22033
+ if (typeof v === "string") return v;
22034
+ }
22035
+ return fallback;
22036
+ }
22037
+
21953
22038
  // src/orchestrator/subscribe.ts
21954
22039
  async function subscribeAndIssue(deps, opts) {
21955
22040
  if (!/^did:zid:/.test(opts.templateId)) {
21956
22041
  return { issued: false, reason: `templateId must be a did:zid:... credential-definition id, got "${opts.templateId}"` };
21957
22042
  }
22043
+ if (deps.cache && !opts.forceReissue && !opts.dryRun) {
22044
+ const cached2 = await deps.cache.get(opts.templateId);
22045
+ if (cached2 && isVcValid(cached2)) {
22046
+ return {
22047
+ issued: true,
22048
+ vcId: cached2.vcId,
22049
+ vc: cached2.vc,
22050
+ txHash: cached2.txHash,
22051
+ paidAsset: cached2.paidAsset,
22052
+ amountPaid: cached2.amountPaid,
22053
+ fromCache: true
22054
+ };
22055
+ }
22056
+ }
22057
+ const fields = deps.resolveTemplateFields ? await deps.resolveTemplateFields(opts.templateId) : null;
21958
22058
  const { agentDid, ...rest } = opts.attributes ?? {};
21959
- const attributes = agentDid ? opts.attributes : { agentDid: deps.holderDid, ...rest };
21960
- const required2 = deps.resolveRequiredFields ? await deps.resolveRequiredFields(opts.templateId) : null;
21961
- if (!opts.dryRun && required2) {
22059
+ const shouldAutoFillAgentDid = !agentDid && fields !== null && fields.allKeys.includes("agentDid");
22060
+ const attributes = shouldAutoFillAgentDid ? { agentDid: deps.holderDid, ...rest } : opts.attributes ?? {};
22061
+ if (!opts.dryRun && fields) {
21962
22062
  const attrs = attributes;
21963
- const missing = required2.filter((k) => attrs[k] === void 0 || attrs[k] === null || attrs[k] === "");
22063
+ const missing = fields.required.filter((k) => attrs[k] === void 0 || attrs[k] === null || attrs[k] === "");
21964
22064
  if (missing.length > 0) {
21965
22065
  return {
21966
22066
  issued: false,
@@ -21974,6 +22074,30 @@ async function subscribeAndIssue(deps, opts) {
21974
22074
  const body = { data, signData, publicKey };
21975
22075
  if (opts.expirationDate) body.expirationDate = opts.expirationDate;
21976
22076
  const challenge = await deps.mbi.applyChallenge(body);
22077
+ if (challenge.issued) {
22078
+ const issued2 = challenge.issued;
22079
+ if (deps.cache) {
22080
+ await deps.cache.set(opts.templateId, {
22081
+ templateId: opts.templateId,
22082
+ vc: issued2.verifiableCredential,
22083
+ vcId: issued2.vcId,
22084
+ txHash: issued2.txHash,
22085
+ paidAsset: "none",
22086
+ amountPaid: "0",
22087
+ issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
22088
+ validUntil: extractValidUntil(issued2.verifiableCredential, opts.expirationDate)
22089
+ });
22090
+ }
22091
+ return {
22092
+ issued: true,
22093
+ vcId: issued2.vcId,
22094
+ vc: issued2.verifiableCredential,
22095
+ txHash: issued2.txHash,
22096
+ paidAsset: "none",
22097
+ amountPaid: "0",
22098
+ ...opts.dryRun ? { reason: "this template requires no payment \u2014 MBI issues synchronously at phase 1, so dryRun could not prevent this issuance" } : {}
22099
+ };
22100
+ }
21977
22101
  const accept = challenge.accepts[0];
21978
22102
  if (!accept) return { issued: false, reason: "MBI 402 returned no payment options" };
21979
22103
  if (opts.dryRun) {
@@ -21986,7 +22110,7 @@ async function subscribeAndIssue(deps, opts) {
21986
22110
  asset: quotedAsset,
21987
22111
  maxAmountRequired: accept.maxAmountRequired,
21988
22112
  payTo: accept.payTo,
21989
- ...required2 ? { requiredAttributes: required2 } : {}
22113
+ ...fields ? { requiredAttributes: fields.required } : {}
21990
22114
  }
21991
22115
  };
21992
22116
  }
@@ -21994,13 +22118,26 @@ async function subscribeAndIssue(deps, opts) {
21994
22118
  const issued = await deps.mbi.applySettle({ ...body, paymentId: challenge.paymentId }, xPayment);
21995
22119
  const rawAsset = String(accept.asset ?? "");
21996
22120
  const paidAsset = deps.resolveSymbol ? await deps.resolveSymbol(rawAsset) : rawAsset;
22121
+ const amountPaid = String(accept.maxAmountRequired ?? "");
22122
+ if (deps.cache) {
22123
+ await deps.cache.set(opts.templateId, {
22124
+ templateId: opts.templateId,
22125
+ vc: issued.verifiableCredential,
22126
+ vcId: issued.vcId,
22127
+ txHash: issued.txHash,
22128
+ paidAsset,
22129
+ amountPaid,
22130
+ issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
22131
+ validUntil: extractValidUntil(issued.verifiableCredential, opts.expirationDate)
22132
+ });
22133
+ }
21997
22134
  return {
21998
22135
  issued: true,
21999
22136
  vcId: issued.vcId,
22000
22137
  vc: issued.verifiableCredential,
22001
22138
  txHash: issued.txHash,
22002
22139
  paidAsset,
22003
- amountPaid: String(accept.maxAmountRequired ?? "")
22140
+ amountPaid
22004
22141
  };
22005
22142
  }
22006
22143
 
@@ -22011,29 +22148,76 @@ function deriveHolderDid(publicKeyHex) {
22011
22148
  if (hex.length === 76 && hex.slice(0, 4).toLowerCase() === "b001") return `did:zid:${hex.slice(4, 68)}`;
22012
22149
  throw new Error(`onboard: unrecognized public key hex format (length ${hex.length})`);
22013
22150
  }
22014
- async function createHolderAccount(create, input) {
22015
- const { zetrixAddress, publicKeyHex } = await create(input.password, input.label, input.purpose);
22151
+ async function createHolderAccount(deps, input) {
22152
+ const existing = await deps.getExistingAccount();
22153
+ if (existing && !input.confirmNew) {
22154
+ return {
22155
+ created: false,
22156
+ alreadyExists: true,
22157
+ existing,
22158
+ message: `An account already exists for this wallet (zetrixAddress=${existing.zetrixAddress}, holderDid=${existing.holderDid}). Ask the user whether to keep using it or create a brand-new one \u2014 call create_holder_account again with confirmNew:true to mint a new account.`
22159
+ };
22160
+ }
22161
+ const { zetrixAddress, publicKeyHex } = await deps.create(input.password, input.label, input.purpose);
22016
22162
  const holderDid = deriveHolderDid(publicKeyHex);
22017
- const message = `New holder HSM account created. Update your MCP config and restart the server: ZETRIX_ADDRESS=${zetrixAddress}, HSM_PASSWORD=<the password you just provided>. (HOLDER_DID=${holderDid} is optional \u2014 omit it and the MCP re-derives it from the account's public key at startup.)`;
22018
- return { zetrixAddress, holderDid, publicKeyHex, message };
22163
+ await deps.saveAccount({ zetrixAddress, holderDid, hsmPassword: input.password, label: input.label, purpose: input.purpose });
22164
+ const message = `New holder HSM account created \u2014 address, DID, and password saved to the wallet's local account store. Both will be used automatically on the next server restart; no manual config edit needed. If your MCP config also sets ZETRIX_ADDRESS/HSM_PASSWORD via environment variables, update or remove those too: an explicit env ZETRIX_ADDRESS/HSM_PASSWORD always takes precedence over the saved account. ZETRIX_ADDRESS=${zetrixAddress} (HOLDER_DID=${holderDid} is optional \u2014 it re-derives automatically).`;
22165
+ return { created: true, alreadyExists: Boolean(existing), zetrixAddress, holderDid, publicKeyHex, message };
22166
+ }
22167
+
22168
+ // src/template-aliases.ts
22169
+ var TEMPLATE_ALIASES = [
22170
+ {
22171
+ match: "birthcert",
22172
+ testnet: "did:zid:d6b783559acf6ba0f7ef6e1365bdaf0774d622d8d22728ca6323677f49ee94f8",
22173
+ mainnet: "did:zid:032cb99be3577beccfc6252783c49c83673af38f8456d73462043654d7764e83"
22174
+ }
22175
+ ];
22176
+ function normalize(s) {
22177
+ return s.toLowerCase().replace(/[^a-z0-9]/g, "");
22178
+ }
22179
+ function resolveTemplateAlias(input, network) {
22180
+ if (/^did:zid:/.test(input)) return void 0;
22181
+ const normalized = normalize(input);
22182
+ const entry = TEMPLATE_ALIASES.find((e) => normalized.includes(e.match));
22183
+ if (!entry) return void 0;
22184
+ return network.includes("testnet") ? entry.testnet : entry.mainnet;
22019
22185
  }
22020
22186
 
22021
22187
  // src/mcp-tools.ts
22188
+ async function loadValidCachedCredentials(cache) {
22189
+ if (!cache) return [];
22190
+ const all = await cache.list();
22191
+ return all.filter((entry) => isVcValid(entry));
22192
+ }
22022
22193
  function createTools(deps) {
22023
22194
  return {
22024
22195
  async wallet_status(input = {}) {
22025
22196
  const balances = deps.getBalances ? await deps.getBalances() : void 0;
22197
+ const credentials = input.heldCredentials ?? (await loadValidCachedCredentials(deps.cache)).map((entry) => entry.vc);
22026
22198
  return {
22027
22199
  holderDid: deps.config.holderDid,
22028
22200
  zetrixAddress: deps.config.zetrixAddress,
22029
22201
  network: deps.config.network,
22030
- credentials: input.heldCredentials ?? [],
22202
+ credentials,
22031
22203
  ...balances !== void 0 ? { balances } : {}
22032
22204
  };
22033
22205
  },
22034
- prove_identity(input) {
22206
+ async prove_identity(input) {
22207
+ let vc = input.vc;
22208
+ if (vc === void 0) {
22209
+ const cached2 = await loadValidCachedCredentials(deps.cache);
22210
+ if (cached2.length === 0) {
22211
+ throw new Error("prove_identity: no vc supplied and no valid credential is cached \u2014 call subscribe_and_issue first, or pass vc explicitly.");
22212
+ }
22213
+ if (cached2.length > 1) {
22214
+ const ids = cached2.map((entry) => entry.templateId).join(", ");
22215
+ throw new Error(`prove_identity: no vc supplied and multiple credentials are cached (templateIds: ${ids}) \u2014 pass vc explicitly to select one.`);
22216
+ }
22217
+ vc = cached2[0].vc;
22218
+ }
22035
22219
  const wallet = deps.makeWallet({
22036
- vc: input.vc,
22220
+ vc,
22037
22221
  revealAttribute: input.revealAttribute,
22038
22222
  issuerKeys: input.issuerKeys
22039
22223
  });
@@ -22043,10 +22227,18 @@ function createTools(deps) {
22043
22227
  return payAndFetch(deps.payer, input);
22044
22228
  },
22045
22229
  subscribe_and_issue(input) {
22046
- return subscribeAndIssue(deps.subscribeDeps, input);
22230
+ const resolved = resolveTemplateAlias(input.templateId, deps.config.network);
22231
+ return subscribeAndIssue(deps.subscribeDeps, resolved ? { ...input, templateId: resolved } : input);
22047
22232
  },
22048
22233
  create_holder_account(input) {
22049
- return createHolderAccount(deps.createAccount, input);
22234
+ return createHolderAccount(
22235
+ {
22236
+ create: deps.createAccount,
22237
+ getExistingAccount: () => Promise.resolve(deps.config.zetrixAddress ? { zetrixAddress: deps.config.zetrixAddress, holderDid: deps.config.holderDid } : null),
22238
+ saveAccount: deps.saveAccount
22239
+ },
22240
+ input
22241
+ );
22050
22242
  }
22051
22243
  };
22052
22244
  }
@@ -22063,6 +22255,30 @@ async function resolveHolder(deps, input) {
22063
22255
  return { zetrixAddress: input.zetrixAddress, holderDid: derivedDid, created: false, didMismatch };
22064
22256
  }
22065
22257
 
22258
+ // src/clients/account-store.ts
22259
+ var import_promises2 = require("node:fs/promises");
22260
+ var import_node_path2 = require("node:path");
22261
+ function isStoredAccountShape(value) {
22262
+ return typeof value === "object" && value !== null && typeof value.zetrixAddress === "string" && typeof value.holderDid === "string" && typeof value.hsmPassword === "string";
22263
+ }
22264
+ function createFsAccountStore(filePath) {
22265
+ return {
22266
+ async get() {
22267
+ try {
22268
+ const raw = await (0, import_promises2.readFile)(filePath, "utf8");
22269
+ const parsed = JSON.parse(raw);
22270
+ return isStoredAccountShape(parsed) ? parsed : null;
22271
+ } catch {
22272
+ return null;
22273
+ }
22274
+ },
22275
+ async set(account) {
22276
+ await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(filePath), { recursive: true, mode: 448 });
22277
+ await (0, import_promises2.writeFile)(filePath, JSON.stringify(account, null, 2), { encoding: "utf8", mode: 384 });
22278
+ }
22279
+ };
22280
+ }
22281
+
22066
22282
  // src/index.ts
22067
22283
  var packageVersion = package_default.version;
22068
22284
  function buildToolList() {
@@ -22073,7 +22289,7 @@ function buildToolList() {
22073
22289
  inputSchema: {
22074
22290
  type: "object",
22075
22291
  properties: {
22076
- heldCredentials: { type: "array", items: { type: "object" }, description: "VCs the client holds (the wallet does not persist them)." }
22292
+ heldCredentials: { type: "array", items: { type: "object" }, description: "VCs the client holds. Omit to report whatever the wallet has cached locally from prior subscribe_and_issue calls instead." }
22077
22293
  }
22078
22294
  }
22079
22295
  },
@@ -22084,7 +22300,7 @@ function buildToolList() {
22084
22300
  type: "object",
22085
22301
  properties: {
22086
22302
  proofRequest: { type: "string", description: "The PROOF-REQUEST header value from the 401 challenge." },
22087
- vc: { type: "object", description: "The client-held VerifiableCredential to present." },
22303
+ vc: { type: "object", description: "The VerifiableCredential to present. Omit to use the wallet's single locally-cached credential, if there is exactly one \u2014 the call fails with a clear error if none or several are cached." },
22088
22304
  revealAttribute: { type: "array", items: { type: "string" }, description: "Dotted disclosure paths to reveal. Omit to reveal exactly the claims the challenge (DCQL) requests; a challenge naming no claims reveals all." },
22089
22305
  issuerKeys: {
22090
22306
  type: "object",
@@ -22095,7 +22311,7 @@ function buildToolList() {
22095
22311
  description: "Optional issuer verification keys to bypass the ZID resolver when it is unreachable (e.g. Cloudflare-gated). When set, resolution is skipped."
22096
22312
  }
22097
22313
  },
22098
- required: ["proofRequest", "vc"]
22314
+ required: ["proofRequest"]
22099
22315
  }
22100
22316
  },
22101
22317
  {
@@ -22114,13 +22330,13 @@ function buildToolList() {
22114
22330
  },
22115
22331
  {
22116
22332
  name: "subscribe_and_issue",
22117
- description: "Obtain a VC from MBI: build the signed payload, pay x402, and return the issued credential. Payment is asset-agnostic \u2014 MBI's 402 challenge may quote the native ZETRIX token or a ZTP20 token (e.g. JMYR); pass dryRun:true first to see the quoted asset/amount for free before committing to pay.",
22333
+ description: "Obtain a VC from MBI: build the signed payload, pay x402, and return the issued credential. If a still-valid credential for this templateId is already cached locally, it is returned directly with no payment (fromCache: true) \u2014 pass forceReissue:true to pay and issue fresh regardless. Payment is asset-agnostic \u2014 MBI's 402 challenge may quote the native ZETRIX token or a ZTP20 token (e.g. JMYR); pass dryRun:true first to see the quoted asset/amount for free before committing to pay.",
22118
22334
  inputSchema: {
22119
22335
  type: "object",
22120
22336
  properties: {
22121
22337
  templateId: {
22122
22338
  type: "string",
22123
- description: `The MBI credential-definition id to issue, e.g. "did:zid:...". Take this from the x401 challenge's credential_requirements.query.credentials[].id \u2014 NOT from requirementsId (that's just a label for the requirement set, e.g. "agent-identity").`
22339
+ description: `The MBI credential-definition id to issue, e.g. "did:zid:...". Take this from the x401 challenge's credential_requirements.query.credentials[].id \u2014 NOT from requirementsId (that's just a label for the requirement set, e.g. "agent-identity"). A known template's natural-language name (e.g. "AI Birthcert") is also accepted and resolved to the right did:zid:... for the configured network.`
22124
22340
  },
22125
22341
  attributes: {
22126
22342
  type: "object",
@@ -22130,6 +22346,10 @@ function buildToolList() {
22130
22346
  dryRun: {
22131
22347
  type: "boolean",
22132
22348
  description: "Stop after MBI's free phase-1 quote and return { quote: { asset, maxAmountRequired, payTo, requiredAttributes? } } without paying or issuing \u2014 use this to check the payment requirement AND the template's required attributes (read from chain) before spending funds."
22349
+ },
22350
+ forceReissue: {
22351
+ type: "boolean",
22352
+ description: "Skip the local cache and pay + issue a fresh credential regardless of what is already cached."
22133
22353
  }
22134
22354
  },
22135
22355
  required: ["templateId", "attributes"]
@@ -22137,13 +22357,17 @@ function buildToolList() {
22137
22357
  },
22138
22358
  {
22139
22359
  name: "create_holder_account",
22140
- description: "Create a new holder HSM account on Wallet BE (onboarding, when ZETRIX_ADDRESS is not yet provisioned). Ask the user for a password first \u2014 never invent one. Returns the new address/DID for the user to save into their MCP config and restart the server; this tool does not persist anything itself.",
22360
+ description: "Create a new holder HSM account on Wallet BE (onboarding). Ask the user for a password first \u2014 never invent one. ALWAYS check first: if an account already exists for this session, this returns { alreadyExists: true, existing: {...} } WITHOUT creating anything \u2014 ask the user whether to keep using the existing account or create a new one, then call again with confirmNew:true only if they choose new. A freshly created account is saved to this MCP's local account store and reused automatically on the next restart; an explicit ZETRIX_ADDRESS in the MCP config still overrides it.",
22141
22361
  inputSchema: {
22142
22362
  type: "object",
22143
22363
  properties: {
22144
22364
  password: { type: "string", description: "HSM password to protect the new account. Must come from the user." },
22145
22365
  label: { type: "string" },
22146
- purpose: { type: "string" }
22366
+ purpose: { type: "string" },
22367
+ confirmNew: {
22368
+ type: "boolean",
22369
+ description: "Set true to mint a new account even though one already exists for this session \u2014 only after the user has confirmed they want a new one."
22370
+ }
22147
22371
  },
22148
22372
  required: ["password"]
22149
22373
  }
@@ -22155,7 +22379,13 @@ function asPayRequest(accept) {
22155
22379
  return { ...accept, extra: { gasModel: "client", ...extra } };
22156
22380
  }
22157
22381
  async function main() {
22158
- const config2 = loadConfig(process.env);
22382
+ const accountStore = createFsAccountStore((0, import_node_path3.join)((0, import_node_os.homedir)(), ".agentic-wallet-mcp", "account.json"));
22383
+ const storedAccount = process.env.ZETRIX_ADDRESS ? null : await accountStore.get();
22384
+ const env = { ...process.env };
22385
+ if (!env.ZETRIX_ADDRESS && storedAccount) env.ZETRIX_ADDRESS = storedAccount.zetrixAddress;
22386
+ if (!env.HOLDER_DID && storedAccount) env.HOLDER_DID = storedAccount.holderDid;
22387
+ if (!env.HSM_PASSWORD && storedAccount) env.HSM_PASSWORD = storedAccount.hsmPassword;
22388
+ const config2 = loadConfig(env);
22159
22389
  const hsmPassword = config2.hsmPassword;
22160
22390
  const be = new WalletBeClient(config2.walletBeUrl);
22161
22391
  const { zetrixAddress, holderDid, created, didMismatch } = await resolveHolder(
@@ -22166,8 +22396,14 @@ async function main() {
22166
22396
  { zetrixAddress: config2.zetrixAddress, holderDid: config2.holderDid, hsmPassword }
22167
22397
  );
22168
22398
  if (created) {
22399
+ await accountStore.set({ zetrixAddress, holderDid, hsmPassword, createdAt: (/* @__PURE__ */ new Date()).toISOString() });
22400
+ process.stderr.write(
22401
+ `agentic-wallet-mcp: no ZETRIX_ADDRESS was set \u2014 created a new HSM account and saved it (address, DID, and password) to ~/.agentic-wallet-mcp/account.json; it will be reused automatically next run. ZETRIX_ADDRESS=${zetrixAddress} (HOLDER_DID=${holderDid} is optional; it re-derives automatically).
22402
+ `
22403
+ );
22404
+ } else if (storedAccount && config2.zetrixAddress === storedAccount.zetrixAddress) {
22169
22405
  process.stderr.write(
22170
- `agentic-wallet-mcp: no ZETRIX_ADDRESS was set \u2014 created a new HSM account. Save this for next run: ZETRIX_ADDRESS=${zetrixAddress} (HOLDER_DID=${holderDid} is optional; it re-derives automatically).
22406
+ `agentic-wallet-mcp: using the holder account saved in ~/.agentic-wallet-mcp/account.json (ZETRIX_ADDRESS=${zetrixAddress}) \u2014 no ZETRIX_ADDRESS/HSM_PASSWORD was set in the MCP config.
22171
22407
  `
22172
22408
  );
22173
22409
  }
@@ -22186,7 +22422,9 @@ async function main() {
22186
22422
  const resolveSymbol = (asset) => resolveAssetSymbol(asset, contractQuery);
22187
22423
  const nodeBaseUrl = `https://${config2.nodeHost}${config2.nodePort ? `:${config2.nodePort}` : ""}`;
22188
22424
  const nodeMetaQuery = (url) => fetch(url, { headers: { Accept: "application/json" } }).then((r) => r.json());
22189
- const resolveRequiredFields = (templateId) => fetchTemplateRequiredFields(templateId, config2.templateRegistryAddress, nodeBaseUrl, nodeMetaQuery);
22425
+ const resolveTemplateFields = (templateId) => fetchTemplateFields(templateId, config2.templateRegistryAddress, nodeBaseUrl, nodeMetaQuery);
22426
+ const cacheScope = (0, import_node_crypto2.createHash)("sha256").update(`${config2.network}:${zetrixAddress}`).digest("hex");
22427
+ const vcCache = createFsVcCache((0, import_node_path3.join)((0, import_node_os.homedir)(), ".agentic-wallet-mcp", "vc-cache", cacheScope));
22190
22428
  const pay = (accept) => {
22191
22429
  assertWithinPaymentCap(accept, config2.maxPaymentAmount);
22192
22430
  return import_x402_zetrix_client.PaymentEngine.pay(asPayRequest(accept), walletCfg, node, {}, walletBeSignerFn);
@@ -22229,8 +22467,10 @@ async function main() {
22229
22467
  config: { holderDid, zetrixAddress, network: config2.network },
22230
22468
  makeWallet,
22231
22469
  payer,
22232
- subscribeDeps: { mbi, sign: subscribeSign, pay, resolveSymbol, holderDid, resolveRequiredFields },
22233
- createAccount: (password, label, purpose) => be.createAccount(password, label, purpose)
22470
+ subscribeDeps: { mbi, sign: subscribeSign, pay, resolveSymbol, holderDid, resolveTemplateFields, cache: vcCache },
22471
+ createAccount: (password, label, purpose) => be.createAccount(password, label, purpose),
22472
+ saveAccount: (account) => accountStore.set({ ...account, createdAt: (/* @__PURE__ */ new Date()).toISOString() }),
22473
+ cache: vcCache
22234
22474
  };
22235
22475
  const tools = createTools(deps);
22236
22476
  const server = new Server({ name: "agentic-wallet-mcp", version: packageVersion }, { capabilities: { tools: {} } });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-wallet-mcp",
3
- "version": "0.3.1",
3
+ "version": "0.4.2",
4
4
  "description": "Agent-facing MCP wallet for Zetrix — orchestrates x401 identity proof, x402 payment, and MBI VC issuance",
5
5
  "keywords": ["mcp", "model-context-protocol", "zetrix", "wallet", "x401", "x402", "blockchain"],
6
6
  "license": "MIT",