agentic-wallet-mcp 0.4.0 → 0.5.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/README.md CHANGED
@@ -23,8 +23,8 @@ VC issuance → identity proof → pay-per-use).
23
23
  | `wallet_status` | Report holder DID/address/network + held VCs (client-supplied, or the local cache) | `{ heldCredentials? }` | `{ holderDid, zetrixAddress, network, credentials }` |
24
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` | 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: 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 }` |
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?, schema? }` |
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
28
 
29
29
  > **VCs are cached locally**, keyed by `templateId`, under `~/.agentic-wallet-mcp/vc-cache/`
30
30
  > (scoped per network + holder — different identities or networks never share a cache).
@@ -38,10 +38,25 @@ VC issuance → identity proof → pay-per-use).
38
38
  > back to the `expirationDate` requested at issuance; a VC with neither is cached indefinitely.
39
39
  > All Ed25519 signing still goes through Wallet BE HSM; no plaintext private keys.
40
40
 
41
+ > `subscribe_and_issue` also returns `schema: { required, optional }` — the template's full
42
+ > declared attribute list, read from chain — on every outcome (issued, dry-run quote, or a
43
+ > missing-attribute error), so you always see the complete field list rather than only what went
44
+ > wrong. Attributes the wallet auto-fills for you (e.g. `agentDid`, or a template-declared derived
45
+ > key like the `AI Birthcert` template's `id` ← `agentUsername`) are omitted from `schema` since
46
+ > you never need to supply them. Some templates also declare format validators for optional
47
+ > attributes (e.g. `AI Birthcert`'s `dob` must be `YYYY-MM-DD`, `countryOfOrigin` must be a valid
48
+ > ISO 3166 code or name) — an invalid value is rejected locally before any payment or MBI call.
49
+
41
50
  > `create_holder_account` mints a **brand-new** keypair — Wallet BE's `/account/create` has no
42
- > way to provision a pre-chosen address. It returns the new `zetrixAddress`/`holderDid` for you
43
- > to paste into `ZETRIX_ADDRESS`/`HSM_PASSWORD` yourself (`HOLDER_DID` is optional see
44
- > Environment below); the tool never writes your MCP config or restarts the server for you.
51
+ > way to provision a pre-chosen address. It always checks first whether an account is already
52
+ > active for this session; if so, it returns `{ alreadyExists: true, existing }` and creates
53
+ > nothing ask the user whether to keep the existing account or replace it, then call again
54
+ > with `confirmNew: true` only if they want a new one. A freshly minted account (address, DID,
55
+ > **and** password) is saved to this MCP's own local account store
56
+ > (`~/.agentic-wallet-mcp/account.json`, owner-only) and reused automatically on the next
57
+ > restart — no manual config edit needed. An explicit `ZETRIX_ADDRESS`/`HSM_PASSWORD` still set
58
+ > in your MCP config always overrides the saved account (see Environment below); the tool never
59
+ > writes the MCP host's own config file or restarts the server for you.
45
60
 
46
61
  > `revealAttribute` on `prove_identity` is optional and usually should stay that way. Omitted,
47
62
  > it's derived automatically from the challenge's DCQL `credential_requirements` — each claim path
@@ -100,9 +115,11 @@ startup, in one of two ways:
100
115
 
101
116
  1. **First-time user — only `HSM_PASSWORD` set.** The MCP creates a brand-new HSM account on
102
117
  Wallet BE (`POST /wallet/hsm/account/create`) and derives the DID from the returned public
103
- key. It logs the new `ZETRIX_ADDRESS` (and `HOLDER_DID`) to stderr on startup copy it into
104
- your MCP config for next time, since nothing is persisted to disk between runs (env vars only
105
- load once, at process start).
118
+ key. It logs the new `ZETRIX_ADDRESS` (and `HOLDER_DID`) to stderr on startup, and saves the
119
+ address, DID, and password to a local account store
120
+ (`~/.agentic-wallet-mcp/account.json`, owner-only) it's reused automatically next run, no
121
+ config edit required. An explicit `ZETRIX_ADDRESS`/`HSM_PASSWORD` set later in your MCP
122
+ config still overrides the saved account.
106
123
  2. **Existing user — `ZETRIX_ADDRESS` + `HSM_PASSWORD` set, `HOLDER_DID` optional.** The MCP
107
124
  always self-signs the address via the existing `POST /wallet/hsm/sign-message` call and
108
125
  derives the DID from the `publicKey` the response carries — no separate lookup endpoint
@@ -171,8 +188,9 @@ the `<...>` placeholders (don't commit a filled copy; `mcp.local.json` is gitign
171
188
  ```
172
189
 
173
190
  > First run, no account yet? Omit `ZETRIX_ADDRESS` (and `HOLDER_DID`) entirely — the MCP creates
174
- > one for you at startup and logs it to stderr; copy it back into `env` for next time. See
175
- > "Onboarding" under Environment above.
191
+ > one for you at startup, logs it to stderr, and saves it (address, DID, password) to
192
+ > `~/.agentic-wallet-mcp/account.json` for automatic reuse next run. See "Onboarding" under
193
+ > Environment above.
176
194
 
177
195
  > Working on this repo locally instead of the published package? Point `command`/`args` at the
178
196
  > local build directly: `"command": "node"`, `"args": ["/absolute/path/to/zetrix-agentic-wallet/dist/server-bundle.cjs"]`.
@@ -190,7 +208,7 @@ the `<...>` placeholders (don't commit a filled copy; `mcp.local.json` is gitign
190
208
  - *"I got a 401 with this PROOF-REQUEST header — prove my identity and give me the PROOF-RESPONSE to replay."* → `prove_identity`
191
209
  - *"Fetch `https://api.example/data` and pay automatically if it asks."* → `pay_and_fetch`
192
210
  - *"Apply for the agent-identity credential with these attributes and pay for it."* → `subscribe_and_issue`
193
- - *"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)
211
+ - *"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)
194
212
 
195
213
  For the full ordered script (onboarding → check → issue → prove → pay), see [`docs/USAGE_FLOW.md`](docs/USAGE_FLOW.md).
196
214
 
@@ -200,9 +218,12 @@ Full narrative version with example prompts: [`docs/USAGE_FLOW.md`](docs/USAGE_F
200
218
 
201
219
  **Step 0 — onboarding (once).** Only if `ZETRIX_ADDRESS` isn't set yet: the MCP creates an HSM
202
220
  account automatically at startup from `HSM_PASSWORD` alone (see "Onboarding" under Environment
203
- above) copy the logged `zetrixAddress` into your MCP config for next time. Alternatively, call
204
- `create_holder_account { password }` manually and paste the returned `zetrixAddress` in yourself;
205
- either way, restart the server afterward (env vars load once, at process start).
221
+ above) and saves it locally for automatic reuse. Alternatively, call `create_holder_account
222
+ { password }` manually it always checks for an existing account first and reports it instead
223
+ of creating (pass `confirmNew: true`, after asking the user, to replace it anyway). Either way,
224
+ the account is saved to `~/.agentic-wallet-mcp/account.json` and picked up automatically on the
225
+ next restart; no manual config edit needed unless your MCP config also sets `ZETRIX_ADDRESS`/
226
+ `HSM_PASSWORD` via env, in which case those still take precedence and should be updated too.
206
227
 
207
228
  **Phase 1 — `wallet_status` — pre-check.** Pass any VCs the caller already holds via
208
229
  `heldCredentials`; the response tells you whether the agent-identity credential you need is
@@ -2233,8 +2233,8 @@ var require_resolve = __commonJS({
2233
2233
  }
2234
2234
  return count;
2235
2235
  }
2236
- function getFullPath(resolver, id = "", normalize2) {
2237
- if (normalize2 !== false)
2236
+ function getFullPath(resolver, id = "", normalize3) {
2237
+ if (normalize3 !== 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 normalize2(uri, options) {
3633
+ function normalize3(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: normalize2,
3900
+ normalize: normalize3,
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 normalize2 = (
8544
+ var normalize3 = (
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 normalize3(path2) {
8550
+ path.normalize = function normalize4(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 = normalize2(includePath);
8573
+ includePath = normalize3(includePath);
8574
8574
  if (isAbsolute(includePath))
8575
8575
  return includePath;
8576
8576
  if (!alreadyNormalized)
8577
- originPath = normalize2(originPath);
8578
- return (originPath = originPath.replace(/(?:\/|^)[^/]+$/, "")).length ? normalize2(originPath + "/" + includePath) : includePath;
8577
+ originPath = normalize3(originPath);
8578
+ return (originPath = originPath.replace(/(?:\/|^)[^/]+$/, "")).length ? normalize3(originPath + "/" + includePath) : includePath;
8579
8579
  };
8580
8580
  }
8581
8581
  });
@@ -12192,7 +12192,7 @@ var require_payment_engine = __commonJS({
12192
12192
  var blob_decoder_1 = require_blob_decoder();
12193
12193
  var prepare_client_1 = require_prepare_client();
12194
12194
  var _sdkCtor = require("zetrix-sdk-nodejs");
12195
- var InsufficientBalanceError = class _InsufficientBalanceError extends Error {
12195
+ var InsufficientBalanceError2 = class _InsufficientBalanceError extends Error {
12196
12196
  constructor(message, required2, available, asset) {
12197
12197
  super(message);
12198
12198
  this.required = required2;
@@ -12202,7 +12202,7 @@ var require_payment_engine = __commonJS({
12202
12202
  Object.setPrototypeOf(this, _InsufficientBalanceError.prototype);
12203
12203
  }
12204
12204
  };
12205
- exports2.InsufficientBalanceError = InsufficientBalanceError;
12205
+ exports2.InsufficientBalanceError = InsufficientBalanceError2;
12206
12206
  exports2.PaymentEngine = {
12207
12207
  /**
12208
12208
  * Create a Zetrix SDK instance for the given node config.
@@ -12295,20 +12295,20 @@ var require_payment_engine = __commonJS({
12295
12295
  const { balance } = await exports2.PaymentEngine.fetchAccountInfo(wallet.address, node);
12296
12296
  const required2 = amount + fee;
12297
12297
  if (BigInt(balance) < required2) {
12298
- throw new InsufficientBalanceError(`Insufficient ZTX: required ${required2}, available ${balance}`, String(required2), balance, "ZTX");
12298
+ throw new InsufficientBalanceError2(`Insufficient ZTX: required ${required2}, available ${balance}`, String(required2), balance, "ZTX");
12299
12299
  }
12300
12300
  return;
12301
12301
  }
12302
12302
  if (!opts.skipTokenCheck) {
12303
12303
  const { balance: tokenBal } = await exports2.PaymentEngine.fetchZTP20Balance(req.asset, wallet.address, node);
12304
12304
  if (BigInt(tokenBal) < amount) {
12305
- throw new InsufficientBalanceError(`Insufficient ${req.asset}: required ${req.maxAmountRequired}, available ${tokenBal}`, req.maxAmountRequired, tokenBal, req.asset);
12305
+ throw new InsufficientBalanceError2(`Insufficient ${req.asset}: required ${req.maxAmountRequired}, available ${tokenBal}`, req.maxAmountRequired, tokenBal, req.asset);
12306
12306
  }
12307
12307
  }
12308
12308
  if (fee > 0n) {
12309
12309
  const { balance: ztxBal } = await exports2.PaymentEngine.fetchAccountInfo(wallet.address, node);
12310
12310
  if (BigInt(ztxBal) < fee) {
12311
- throw new InsufficientBalanceError(`Insufficient ZTX for gas: required ${feeLimit}, available ${ztxBal}`, feeLimit, ztxBal, "ZTX");
12311
+ throw new InsufficientBalanceError2(`Insufficient ZTX for gas: required ${feeLimit}, available ${ztxBal}`, feeLimit, ztxBal, "ZTX");
12312
12312
  }
12313
12313
  }
12314
12314
  },
@@ -12542,12 +12542,12 @@ __export(index_exports, {
12542
12542
  module.exports = __toCommonJS(index_exports);
12543
12543
  var import_node_crypto2 = require("node:crypto");
12544
12544
  var import_node_os = require("node:os");
12545
- var import_node_path2 = require("node:path");
12545
+ var import_node_path3 = require("node:path");
12546
12546
 
12547
12547
  // package.json
12548
12548
  var package_default = {
12549
12549
  name: "agentic-wallet-mcp",
12550
- version: "0.4.0",
12550
+ version: "0.5.0",
12551
12551
  description: "Agent-facing MCP wallet for Zetrix \u2014 orchestrates x401 identity proof, x402 payment, and MBI VC issuance",
12552
12552
  keywords: ["mcp", "model-context-protocol", "zetrix", "wallet", "x401", "x402", "blockchain"],
12553
12553
  license: "MIT",
@@ -21159,7 +21159,7 @@ var StdioServerTransport = class {
21159
21159
  };
21160
21160
 
21161
21161
  // src/index.ts
21162
- var import_x402_zetrix_client = __toESM(require_dist2(), 1);
21162
+ var import_x402_zetrix_client2 = __toESM(require_dist2(), 1);
21163
21163
 
21164
21164
  // node_modules/x401-zetrix-client/dist/config.js
21165
21165
  var ZETRIX_OID4VP_URLS = {
@@ -21547,6 +21547,14 @@ function deriveMbiBaseUrl(network) {
21547
21547
  function deriveTemplateRegistryAddress(network) {
21548
21548
  return network.includes("testnet") ? "ZTX3JszqPgRUx743SAp7q7zURfjvkWuH2FMEz" : "ZTX3GqJM1U6ifMPonwD4fGvrgoTKJua7b2cKX";
21549
21549
  }
21550
+ var TOKEN_REGISTRY = {
21551
+ JMYR: { testnet: "ZTX3WeinXtt28YMyr4vUZ14ddTgEMGeuc1e6b", mainnet: "ZTX3NCkXBqbyJWjZZxciQez945Lu6tGAcjNJr" }
21552
+ };
21553
+ function resolveTokenAddress(symbol, network) {
21554
+ const entry = TOKEN_REGISTRY[symbol.toUpperCase()];
21555
+ if (!entry) return void 0;
21556
+ return network.includes("testnet") ? entry.testnet : entry.mainnet;
21557
+ }
21550
21558
  function loadConfig(env) {
21551
21559
  const req = (key, hint) => {
21552
21560
  const v = env[key];
@@ -21609,6 +21617,32 @@ async function resolveAssetSymbol(asset, query) {
21609
21617
  return info?.symbol ?? asset;
21610
21618
  }
21611
21619
 
21620
+ // src/clients/contract-query-client.ts
21621
+ async function queryContract(input, query) {
21622
+ let response;
21623
+ try {
21624
+ response = await query({
21625
+ contractAddress: input.contractAddress,
21626
+ input: JSON.stringify({ method: input.method, params: input.params ?? {} }),
21627
+ optType: 2
21628
+ });
21629
+ } catch (e) {
21630
+ return { ok: false, error: `query_contract: RPC call failed \u2014 ${e.message}` };
21631
+ }
21632
+ if (response.errorCode !== 0) {
21633
+ return { ok: false, error: `query_contract: contract call failed with errorCode ${response.errorCode}` };
21634
+ }
21635
+ const raw = response.result?.query_rets?.[0]?.result?.value;
21636
+ if (raw === void 0) {
21637
+ return { ok: false, error: "query_contract: no result value returned" };
21638
+ }
21639
+ try {
21640
+ return { ok: true, result: JSON.parse(raw) };
21641
+ } catch {
21642
+ return { ok: true, result: raw };
21643
+ }
21644
+ }
21645
+
21612
21646
  // src/clients/template-info-client.ts
21613
21647
  async function fetchTemplateFields(templateId, registryAddress, nodeBaseUrl, query) {
21614
21648
  const key = `template__${templateId}`;
@@ -21667,6 +21701,10 @@ var WalletBeClient = class {
21667
21701
  createAccount(password, label, purpose) {
21668
21702
  return this.post("/wallet/hsm/account/create", { password, label, purpose });
21669
21703
  }
21704
+ /** GET /wallet/hsm/account/activate/status — ground-truth on-chain activation check (no local state). */
21705
+ checkActivationStatus(address) {
21706
+ return this.get("/wallet/hsm/account/activate/status", { address });
21707
+ }
21670
21708
  async post(path, body) {
21671
21709
  const url = `${this.baseUrl}${path}`;
21672
21710
  let res;
@@ -21694,6 +21732,25 @@ var WalletBeClient = class {
21694
21732
  }
21695
21733
  return env.data;
21696
21734
  }
21735
+ async get(path, query) {
21736
+ const url = `${this.baseUrl}${path}?${new URLSearchParams(query).toString()}`;
21737
+ let res;
21738
+ try {
21739
+ res = await fetch(url, { headers: { Accept: "application/json" } });
21740
+ } catch (e) {
21741
+ throw new WalletBeError(`Wallet BE ${path} request failed`, void 0, e);
21742
+ }
21743
+ if (!res.ok) {
21744
+ const text = await res.text().catch(() => "");
21745
+ throw new WalletBeError(`Wallet BE ${path} HTTP ${res.status}: ${text}`);
21746
+ }
21747
+ const env = await res.json();
21748
+ if (env.errorCode !== 0) {
21749
+ const detail = env.data?.errorList?.length ? ` \u2014 ${env.data.errorList.join("; ")}` : "";
21750
+ throw new WalletBeError(`Wallet BE ${path} errorCode ${env.errorCode}: ${env.message ?? "error"}${detail}`, env.errorCode);
21751
+ }
21752
+ return env.data;
21753
+ }
21697
21754
  };
21698
21755
 
21699
21756
  // src/signer.ts
@@ -21809,14 +21866,22 @@ var MbiError = class extends Error {
21809
21866
  this.httpStatus = httpStatus;
21810
21867
  }
21811
21868
  };
21812
- var MbiClient = class {
21869
+ var MbiClient = class _MbiClient {
21813
21870
  baseUrl;
21814
21871
  constructor(baseUrl) {
21815
21872
  this.baseUrl = baseUrl.replace(/\/+$/, "");
21816
21873
  }
21817
- /** Phase 1 — POST /v1/vc/pay/apply without X-PAYMENT; expects the 402 challenge. */
21874
+ /**
21875
+ * Phase 1 — POST /v1/vc/pay/apply without X-PAYMENT; expects the 402 challenge.
21876
+ * A free template short-circuits this: MBI issues the VC synchronously and returns
21877
+ * 200 instead, with no phase-2 settle to follow — surfaced via the `issued` field.
21878
+ */
21818
21879
  async applyChallenge(body) {
21819
21880
  const res = await this.fetch("POST", "/v1/vc/pay/apply", body);
21881
+ if (res.status === 200) {
21882
+ const issued = await this.unwrap(res);
21883
+ return { x402Version: 1, accepts: [], issued };
21884
+ }
21820
21885
  if (res.status !== 402) {
21821
21886
  throw await this.error(res, "apply (phase 1) expected 402");
21822
21887
  }
@@ -21864,12 +21929,14 @@ var MbiClient = class {
21864
21929
  const body = await res.json();
21865
21930
  return body.data;
21866
21931
  }
21932
+ static ERROR_BODY_MAX_LEN = 500;
21867
21933
  async error(res, context) {
21868
21934
  const text = await res.text().catch(() => "");
21869
21935
  let msg = text;
21870
21936
  try {
21871
21937
  const j = JSON.parse(text);
21872
- msg = j.message ?? j.error ?? text;
21938
+ const truncated = text.length > _MbiClient.ERROR_BODY_MAX_LEN ? `${text.slice(0, _MbiClient.ERROR_BODY_MAX_LEN)}\u2026 (truncated, ${text.length} bytes total)` : text;
21939
+ msg = `${j.message ?? j.error ?? text} | full body: ${truncated}`;
21873
21940
  } catch {
21874
21941
  }
21875
21942
  return new MbiError(`MBI ${context} \u2014 HTTP ${res.status}: ${msg}`, res.status);
@@ -22025,6 +22092,36 @@ function extractValidUntil(vc, fallback) {
22025
22092
  return fallback;
22026
22093
  }
22027
22094
 
22095
+ // src/payment-readiness.ts
22096
+ var import_x402_zetrix_client = __toESM(require_dist2(), 1);
22097
+ var PaymentReadinessError = class extends Error {
22098
+ shortfall;
22099
+ constructor(message, shortfall) {
22100
+ super(message);
22101
+ this.name = "PaymentReadinessError";
22102
+ this.shortfall = shortfall;
22103
+ }
22104
+ };
22105
+ function toPaymentReadinessError(err, requestedAsset) {
22106
+ if (!(err instanceof import_x402_zetrix_client.InsufficientBalanceError)) return null;
22107
+ const reason = err.asset === "ZTX" && requestedAsset !== "ZTX" ? "gas" : "resource_payment";
22108
+ return new PaymentReadinessError(err.message, {
22109
+ asset: err.asset,
22110
+ required: err.required,
22111
+ available: err.available,
22112
+ reason
22113
+ });
22114
+ }
22115
+ async function payWithReadinessCheck(requestedAsset, rawPay) {
22116
+ try {
22117
+ return await rawPay();
22118
+ } catch (err) {
22119
+ const readinessError = toPaymentReadinessError(err, requestedAsset);
22120
+ if (readinessError) throw readinessError;
22121
+ throw err;
22122
+ }
22123
+ }
22124
+
22028
22125
  // src/orchestrator/subscribe.ts
22029
22126
  async function subscribeAndIssue(deps, opts) {
22030
22127
  if (!/^did:zid:/.test(opts.templateId)) {
@@ -22048,13 +22145,15 @@ async function subscribeAndIssue(deps, opts) {
22048
22145
  const { agentDid, ...rest } = opts.attributes ?? {};
22049
22146
  const shouldAutoFillAgentDid = !agentDid && fields !== null && fields.allKeys.includes("agentDid");
22050
22147
  const attributes = shouldAutoFillAgentDid ? { agentDid: deps.holderDid, ...rest } : opts.attributes ?? {};
22051
- if (!opts.dryRun && fields) {
22148
+ const schema = fields ? { required: fields.required, optional: fields.allKeys.filter((k) => !fields.required.includes(k)) } : void 0;
22149
+ if (fields) {
22052
22150
  const attrs = attributes;
22053
22151
  const missing = fields.required.filter((k) => attrs[k] === void 0 || attrs[k] === null || attrs[k] === "");
22054
22152
  if (missing.length > 0) {
22055
22153
  return {
22056
22154
  issued: false,
22057
- reason: `template requires attribute(s) not supplied: ${missing.join(", ")} \u2014 no payment made`
22155
+ reason: `template requires attribute(s) not supplied: ${missing.join(", ")} \u2014 no payment made`,
22156
+ ...schema ? { schema } : {}
22058
22157
  };
22059
22158
  }
22060
22159
  }
@@ -22063,9 +22162,42 @@ async function subscribeAndIssue(deps, opts) {
22063
22162
  const { signBlob: signData, publicKey } = await deps.sign(blob);
22064
22163
  const body = { data, signData, publicKey };
22065
22164
  if (opts.expirationDate) body.expirationDate = opts.expirationDate;
22066
- const challenge = await deps.mbi.applyChallenge(body);
22165
+ let challenge;
22166
+ try {
22167
+ challenge = await deps.mbi.applyChallenge(body);
22168
+ } catch (err) {
22169
+ if (err instanceof MbiError) {
22170
+ return { issued: false, reason: err.message, httpStatus: err.httpStatus, ...schema ? { schema } : {} };
22171
+ }
22172
+ throw err;
22173
+ }
22174
+ if (challenge.issued) {
22175
+ const issued2 = challenge.issued;
22176
+ if (deps.cache) {
22177
+ await deps.cache.set(opts.templateId, {
22178
+ templateId: opts.templateId,
22179
+ vc: issued2.verifiableCredential,
22180
+ vcId: issued2.vcId,
22181
+ txHash: issued2.txHash,
22182
+ paidAsset: "none",
22183
+ amountPaid: "0",
22184
+ issuedAt: (/* @__PURE__ */ new Date()).toISOString(),
22185
+ validUntil: extractValidUntil(issued2.verifiableCredential, opts.expirationDate)
22186
+ });
22187
+ }
22188
+ return {
22189
+ issued: true,
22190
+ vcId: issued2.vcId,
22191
+ vc: issued2.verifiableCredential,
22192
+ txHash: issued2.txHash,
22193
+ paidAsset: "none",
22194
+ amountPaid: "0",
22195
+ ...schema ? { schema } : {},
22196
+ ...opts.dryRun ? { reason: "this template requires no payment \u2014 MBI issues synchronously at phase 1, so dryRun could not prevent this issuance" } : {}
22197
+ };
22198
+ }
22067
22199
  const accept = challenge.accepts[0];
22068
- if (!accept) return { issued: false, reason: "MBI 402 returned no payment options" };
22200
+ if (!accept) return { issued: false, reason: "MBI 402 returned no payment options", ...schema ? { schema } : {} };
22069
22201
  if (opts.dryRun) {
22070
22202
  const quotedRaw = String(accept.asset ?? "");
22071
22203
  const quotedAsset = deps.resolveSymbol ? await deps.resolveSymbol(quotedRaw) : quotedRaw;
@@ -22075,13 +22207,29 @@ async function subscribeAndIssue(deps, opts) {
22075
22207
  quote: {
22076
22208
  asset: quotedAsset,
22077
22209
  maxAmountRequired: accept.maxAmountRequired,
22078
- payTo: accept.payTo,
22079
- ...fields ? { requiredAttributes: fields.required } : {}
22080
- }
22210
+ payTo: accept.payTo
22211
+ },
22212
+ ...schema ? { schema } : {}
22081
22213
  };
22082
22214
  }
22083
- const xPayment = await deps.pay(accept);
22084
- const issued = await deps.mbi.applySettle({ ...body, paymentId: challenge.paymentId }, xPayment);
22215
+ let xPayment;
22216
+ try {
22217
+ xPayment = await deps.pay(accept);
22218
+ } catch (err) {
22219
+ if (err instanceof PaymentReadinessError) {
22220
+ return { issued: false, reason: `insufficient funds: ${err.message}`, insufficientFunds: err.shortfall };
22221
+ }
22222
+ throw err;
22223
+ }
22224
+ let issued;
22225
+ try {
22226
+ issued = await deps.mbi.applySettle({ ...body, paymentId: challenge.paymentId }, xPayment);
22227
+ } catch (err) {
22228
+ if (err instanceof MbiError) {
22229
+ return { issued: false, reason: err.message, httpStatus: err.httpStatus, ...schema ? { schema } : {} };
22230
+ }
22231
+ throw err;
22232
+ }
22085
22233
  const rawAsset = String(accept.asset ?? "");
22086
22234
  const paidAsset = deps.resolveSymbol ? await deps.resolveSymbol(rawAsset) : rawAsset;
22087
22235
  const amountPaid = String(accept.maxAmountRequired ?? "");
@@ -22103,10 +22251,23 @@ async function subscribeAndIssue(deps, opts) {
22103
22251
  vc: issued.verifiableCredential,
22104
22252
  txHash: issued.txHash,
22105
22253
  paidAsset,
22106
- amountPaid
22254
+ amountPaid,
22255
+ ...schema ? { schema } : {}
22107
22256
  };
22108
22257
  }
22109
22258
 
22259
+ // src/orchestrator/wait-for-activation.ts
22260
+ async function waitForActivation(check, address, sleep, opts = {}) {
22261
+ const attempts = opts.attempts ?? 3;
22262
+ const delayMs = opts.delayMs ?? 3e3;
22263
+ for (let i = 0; i < attempts; i++) {
22264
+ await sleep(delayMs);
22265
+ const { activated } = await check(address);
22266
+ if (activated) return true;
22267
+ }
22268
+ return false;
22269
+ }
22270
+
22110
22271
  // src/orchestrator/onboard.ts
22111
22272
  function deriveHolderDid(publicKeyHex) {
22112
22273
  const hex = publicKeyHex.trim();
@@ -22114,31 +22275,393 @@ function deriveHolderDid(publicKeyHex) {
22114
22275
  if (hex.length === 76 && hex.slice(0, 4).toLowerCase() === "b001") return `did:zid:${hex.slice(4, 68)}`;
22115
22276
  throw new Error(`onboard: unrecognized public key hex format (length ${hex.length})`);
22116
22277
  }
22117
- async function createHolderAccount(create, input) {
22118
- const { zetrixAddress, publicKeyHex } = await create(input.password, input.label, input.purpose);
22278
+ async function createHolderAccount(deps, input) {
22279
+ const existing = await deps.getExistingAccount();
22280
+ if (existing && !input.confirmNew) {
22281
+ return {
22282
+ created: false,
22283
+ alreadyExists: true,
22284
+ existing,
22285
+ 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.`
22286
+ };
22287
+ }
22288
+ const { zetrixAddress, publicKeyHex, activated } = await deps.create(input.password, input.label, input.purpose);
22119
22289
  const holderDid = deriveHolderDid(publicKeyHex);
22120
- 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.)`;
22121
- return { zetrixAddress, holderDid, publicKeyHex, message };
22290
+ await deps.saveAccount({ zetrixAddress, holderDid, hsmPassword: input.password, label: input.label, purpose: input.purpose });
22291
+ const finalActivated = activated || await waitForActivation(deps.checkActivationStatus, zetrixAddress, deps.sleep);
22292
+ 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).` + (finalActivated ? "" : ` Note: on-chain activation has not completed yet \u2014 balance/on-chain calls for this address may fail until it does; check again later.`);
22293
+ return { created: true, alreadyExists: Boolean(existing), zetrixAddress, holderDid, publicKeyHex, activated: finalActivated, message };
22294
+ }
22295
+
22296
+ // src/iso3166.ts
22297
+ var COUNTRIES = [
22298
+ { alpha2: "AD", alpha3: "AND", name: "Andorra" },
22299
+ { alpha2: "AE", alpha3: "ARE", name: "United Arab Emirates" },
22300
+ { alpha2: "AF", alpha3: "AFG", name: "Afghanistan" },
22301
+ { alpha2: "AG", alpha3: "ATG", name: "Antigua and Barbuda" },
22302
+ { alpha2: "AI", alpha3: "AIA", name: "Anguilla" },
22303
+ { alpha2: "AL", alpha3: "ALB", name: "Albania" },
22304
+ { alpha2: "AM", alpha3: "ARM", name: "Armenia" },
22305
+ { alpha2: "AO", alpha3: "AGO", name: "Angola" },
22306
+ { alpha2: "AQ", alpha3: "ATA", name: "Antarctica" },
22307
+ { alpha2: "AR", alpha3: "ARG", name: "Argentina" },
22308
+ { alpha2: "AS", alpha3: "ASM", name: "American Samoa" },
22309
+ { alpha2: "AT", alpha3: "AUT", name: "Austria" },
22310
+ { alpha2: "AU", alpha3: "AUS", name: "Australia" },
22311
+ { alpha2: "AW", alpha3: "ABW", name: "Aruba" },
22312
+ { alpha2: "AX", alpha3: "ALA", name: "\xC5land Islands" },
22313
+ { alpha2: "AZ", alpha3: "AZE", name: "Azerbaijan" },
22314
+ { alpha2: "BA", alpha3: "BIH", name: "Bosnia and Herzegovina" },
22315
+ { alpha2: "BB", alpha3: "BRB", name: "Barbados" },
22316
+ { alpha2: "BD", alpha3: "BGD", name: "Bangladesh" },
22317
+ { alpha2: "BE", alpha3: "BEL", name: "Belgium" },
22318
+ { alpha2: "BF", alpha3: "BFA", name: "Burkina Faso" },
22319
+ { alpha2: "BG", alpha3: "BGR", name: "Bulgaria" },
22320
+ { alpha2: "BH", alpha3: "BHR", name: "Bahrain" },
22321
+ { alpha2: "BI", alpha3: "BDI", name: "Burundi" },
22322
+ { alpha2: "BJ", alpha3: "BEN", name: "Benin" },
22323
+ { alpha2: "BL", alpha3: "BLM", name: "Saint Barth\xE9lemy" },
22324
+ { alpha2: "BM", alpha3: "BMU", name: "Bermuda" },
22325
+ { alpha2: "BN", alpha3: "BRN", name: "Brunei Darussalam" },
22326
+ { alpha2: "BO", alpha3: "BOL", name: "Bolivia (Plurinational State of)" },
22327
+ { alpha2: "BQ", alpha3: "BES", name: "Bonaire, Sint Eustatius and Saba" },
22328
+ { alpha2: "BR", alpha3: "BRA", name: "Brazil" },
22329
+ { alpha2: "BS", alpha3: "BHS", name: "Bahamas" },
22330
+ { alpha2: "BT", alpha3: "BTN", name: "Bhutan" },
22331
+ { alpha2: "BV", alpha3: "BVT", name: "Bouvet Island" },
22332
+ { alpha2: "BW", alpha3: "BWA", name: "Botswana" },
22333
+ { alpha2: "BY", alpha3: "BLR", name: "Belarus" },
22334
+ { alpha2: "BZ", alpha3: "BLZ", name: "Belize" },
22335
+ { alpha2: "CA", alpha3: "CAN", name: "Canada" },
22336
+ { alpha2: "CC", alpha3: "CCK", name: "Cocos (Keeling) Islands" },
22337
+ { alpha2: "CD", alpha3: "COD", name: "Congo, Democratic Republic of the" },
22338
+ { alpha2: "CF", alpha3: "CAF", name: "Central African Republic" },
22339
+ { alpha2: "CG", alpha3: "COG", name: "Congo" },
22340
+ { alpha2: "CH", alpha3: "CHE", name: "Switzerland" },
22341
+ { alpha2: "CI", alpha3: "CIV", name: "C\xF4te d'Ivoire" },
22342
+ { alpha2: "CK", alpha3: "COK", name: "Cook Islands" },
22343
+ { alpha2: "CL", alpha3: "CHL", name: "Chile" },
22344
+ { alpha2: "CM", alpha3: "CMR", name: "Cameroon" },
22345
+ { alpha2: "CN", alpha3: "CHN", name: "China" },
22346
+ { alpha2: "CO", alpha3: "COL", name: "Colombia" },
22347
+ { alpha2: "CR", alpha3: "CRI", name: "Costa Rica" },
22348
+ { alpha2: "CU", alpha3: "CUB", name: "Cuba" },
22349
+ { alpha2: "CV", alpha3: "CPV", name: "Cabo Verde" },
22350
+ { alpha2: "CW", alpha3: "CUW", name: "Cura\xE7ao" },
22351
+ { alpha2: "CX", alpha3: "CXR", name: "Christmas Island" },
22352
+ { alpha2: "CY", alpha3: "CYP", name: "Cyprus" },
22353
+ { alpha2: "CZ", alpha3: "CZE", name: "Czechia" },
22354
+ { alpha2: "DE", alpha3: "DEU", name: "Germany" },
22355
+ { alpha2: "DJ", alpha3: "DJI", name: "Djibouti" },
22356
+ { alpha2: "DK", alpha3: "DNK", name: "Denmark" },
22357
+ { alpha2: "DM", alpha3: "DMA", name: "Dominica" },
22358
+ { alpha2: "DO", alpha3: "DOM", name: "Dominican Republic" },
22359
+ { alpha2: "DZ", alpha3: "DZA", name: "Algeria" },
22360
+ { alpha2: "EC", alpha3: "ECU", name: "Ecuador" },
22361
+ { alpha2: "EE", alpha3: "EST", name: "Estonia" },
22362
+ { alpha2: "EG", alpha3: "EGY", name: "Egypt" },
22363
+ { alpha2: "EH", alpha3: "ESH", name: "Western Sahara" },
22364
+ { alpha2: "ER", alpha3: "ERI", name: "Eritrea" },
22365
+ { alpha2: "ES", alpha3: "ESP", name: "Spain" },
22366
+ { alpha2: "ET", alpha3: "ETH", name: "Ethiopia" },
22367
+ { alpha2: "FI", alpha3: "FIN", name: "Finland" },
22368
+ { alpha2: "FJ", alpha3: "FJI", name: "Fiji" },
22369
+ { alpha2: "FK", alpha3: "FLK", name: "Falkland Islands (Malvinas)" },
22370
+ { alpha2: "FM", alpha3: "FSM", name: "Micronesia (Federated States of)" },
22371
+ { alpha2: "FO", alpha3: "FRO", name: "Faroe Islands" },
22372
+ { alpha2: "FR", alpha3: "FRA", name: "France" },
22373
+ { alpha2: "GA", alpha3: "GAB", name: "Gabon" },
22374
+ { alpha2: "GB", alpha3: "GBR", name: "United Kingdom of Great Britain and Northern Ireland" },
22375
+ { alpha2: "GD", alpha3: "GRD", name: "Grenada" },
22376
+ { alpha2: "GE", alpha3: "GEO", name: "Georgia" },
22377
+ { alpha2: "GF", alpha3: "GUF", name: "French Guiana" },
22378
+ { alpha2: "GG", alpha3: "GGY", name: "Guernsey" },
22379
+ { alpha2: "GH", alpha3: "GHA", name: "Ghana" },
22380
+ { alpha2: "GI", alpha3: "GIB", name: "Gibraltar" },
22381
+ { alpha2: "GL", alpha3: "GRL", name: "Greenland" },
22382
+ { alpha2: "GM", alpha3: "GMB", name: "Gambia" },
22383
+ { alpha2: "GN", alpha3: "GIN", name: "Guinea" },
22384
+ { alpha2: "GP", alpha3: "GLP", name: "Guadeloupe" },
22385
+ { alpha2: "GQ", alpha3: "GNQ", name: "Equatorial Guinea" },
22386
+ { alpha2: "GR", alpha3: "GRC", name: "Greece" },
22387
+ { alpha2: "GS", alpha3: "SGS", name: "South Georgia and the South Sandwich Islands" },
22388
+ { alpha2: "GT", alpha3: "GTM", name: "Guatemala" },
22389
+ { alpha2: "GU", alpha3: "GUM", name: "Guam" },
22390
+ { alpha2: "GW", alpha3: "GNB", name: "Guinea-Bissau" },
22391
+ { alpha2: "GY", alpha3: "GUY", name: "Guyana" },
22392
+ { alpha2: "HK", alpha3: "HKG", name: "Hong Kong" },
22393
+ { alpha2: "HM", alpha3: "HMD", name: "Heard Island and McDonald Islands" },
22394
+ { alpha2: "HN", alpha3: "HND", name: "Honduras" },
22395
+ { alpha2: "HR", alpha3: "HRV", name: "Croatia" },
22396
+ { alpha2: "HT", alpha3: "HTI", name: "Haiti" },
22397
+ { alpha2: "HU", alpha3: "HUN", name: "Hungary" },
22398
+ { alpha2: "ID", alpha3: "IDN", name: "Indonesia" },
22399
+ { alpha2: "IE", alpha3: "IRL", name: "Ireland" },
22400
+ { alpha2: "IL", alpha3: "ISR", name: "Israel" },
22401
+ { alpha2: "IM", alpha3: "IMN", name: "Isle of Man" },
22402
+ { alpha2: "IN", alpha3: "IND", name: "India" },
22403
+ { alpha2: "IO", alpha3: "IOT", name: "British Indian Ocean Territory" },
22404
+ { alpha2: "IQ", alpha3: "IRQ", name: "Iraq" },
22405
+ { alpha2: "IR", alpha3: "IRN", name: "Iran (Islamic Republic of)" },
22406
+ { alpha2: "IS", alpha3: "ISL", name: "Iceland" },
22407
+ { alpha2: "IT", alpha3: "ITA", name: "Italy" },
22408
+ { alpha2: "JE", alpha3: "JEY", name: "Jersey" },
22409
+ { alpha2: "JM", alpha3: "JAM", name: "Jamaica" },
22410
+ { alpha2: "JO", alpha3: "JOR", name: "Jordan" },
22411
+ { alpha2: "JP", alpha3: "JPN", name: "Japan" },
22412
+ { alpha2: "KE", alpha3: "KEN", name: "Kenya" },
22413
+ { alpha2: "KG", alpha3: "KGZ", name: "Kyrgyzstan" },
22414
+ { alpha2: "KH", alpha3: "KHM", name: "Cambodia" },
22415
+ { alpha2: "KI", alpha3: "KIR", name: "Kiribati" },
22416
+ { alpha2: "KM", alpha3: "COM", name: "Comoros" },
22417
+ { alpha2: "KN", alpha3: "KNA", name: "Saint Kitts and Nevis" },
22418
+ { alpha2: "KP", alpha3: "PRK", name: "Korea (Democratic People's Republic of)" },
22419
+ { alpha2: "KR", alpha3: "KOR", name: "Korea, Republic of" },
22420
+ { alpha2: "KW", alpha3: "KWT", name: "Kuwait" },
22421
+ { alpha2: "KY", alpha3: "CYM", name: "Cayman Islands" },
22422
+ { alpha2: "KZ", alpha3: "KAZ", name: "Kazakhstan" },
22423
+ { alpha2: "LA", alpha3: "LAO", name: "Lao People's Democratic Republic" },
22424
+ { alpha2: "LB", alpha3: "LBN", name: "Lebanon" },
22425
+ { alpha2: "LC", alpha3: "LCA", name: "Saint Lucia" },
22426
+ { alpha2: "LI", alpha3: "LIE", name: "Liechtenstein" },
22427
+ { alpha2: "LK", alpha3: "LKA", name: "Sri Lanka" },
22428
+ { alpha2: "LR", alpha3: "LBR", name: "Liberia" },
22429
+ { alpha2: "LS", alpha3: "LSO", name: "Lesotho" },
22430
+ { alpha2: "LT", alpha3: "LTU", name: "Lithuania" },
22431
+ { alpha2: "LU", alpha3: "LUX", name: "Luxembourg" },
22432
+ { alpha2: "LV", alpha3: "LVA", name: "Latvia" },
22433
+ { alpha2: "LY", alpha3: "LBY", name: "Libya" },
22434
+ { alpha2: "MA", alpha3: "MAR", name: "Morocco" },
22435
+ { alpha2: "MC", alpha3: "MCO", name: "Monaco" },
22436
+ { alpha2: "MD", alpha3: "MDA", name: "Moldova, Republic of" },
22437
+ { alpha2: "ME", alpha3: "MNE", name: "Montenegro" },
22438
+ { alpha2: "MF", alpha3: "MAF", name: "Saint Martin (French part)" },
22439
+ { alpha2: "MG", alpha3: "MDG", name: "Madagascar" },
22440
+ { alpha2: "MH", alpha3: "MHL", name: "Marshall Islands" },
22441
+ { alpha2: "MK", alpha3: "MKD", name: "North Macedonia" },
22442
+ { alpha2: "ML", alpha3: "MLI", name: "Mali" },
22443
+ { alpha2: "MM", alpha3: "MMR", name: "Myanmar" },
22444
+ { alpha2: "MN", alpha3: "MNG", name: "Mongolia" },
22445
+ { alpha2: "MO", alpha3: "MAC", name: "Macao" },
22446
+ { alpha2: "MP", alpha3: "MNP", name: "Northern Mariana Islands" },
22447
+ { alpha2: "MQ", alpha3: "MTQ", name: "Martinique" },
22448
+ { alpha2: "MR", alpha3: "MRT", name: "Mauritania" },
22449
+ { alpha2: "MS", alpha3: "MSR", name: "Montserrat" },
22450
+ { alpha2: "MT", alpha3: "MLT", name: "Malta" },
22451
+ { alpha2: "MU", alpha3: "MUS", name: "Mauritius" },
22452
+ { alpha2: "MV", alpha3: "MDV", name: "Maldives" },
22453
+ { alpha2: "MW", alpha3: "MWI", name: "Malawi" },
22454
+ { alpha2: "MX", alpha3: "MEX", name: "Mexico" },
22455
+ { alpha2: "MY", alpha3: "MYS", name: "Malaysia" },
22456
+ { alpha2: "MZ", alpha3: "MOZ", name: "Mozambique" },
22457
+ { alpha2: "NA", alpha3: "NAM", name: "Namibia" },
22458
+ { alpha2: "NC", alpha3: "NCL", name: "New Caledonia" },
22459
+ { alpha2: "NE", alpha3: "NER", name: "Niger" },
22460
+ { alpha2: "NF", alpha3: "NFK", name: "Norfolk Island" },
22461
+ { alpha2: "NG", alpha3: "NGA", name: "Nigeria" },
22462
+ { alpha2: "NI", alpha3: "NIC", name: "Nicaragua" },
22463
+ { alpha2: "NL", alpha3: "NLD", name: "Netherlands" },
22464
+ { alpha2: "NO", alpha3: "NOR", name: "Norway" },
22465
+ { alpha2: "NP", alpha3: "NPL", name: "Nepal" },
22466
+ { alpha2: "NR", alpha3: "NRU", name: "Nauru" },
22467
+ { alpha2: "NU", alpha3: "NIU", name: "Niue" },
22468
+ { alpha2: "NZ", alpha3: "NZL", name: "New Zealand" },
22469
+ { alpha2: "OM", alpha3: "OMN", name: "Oman" },
22470
+ { alpha2: "PA", alpha3: "PAN", name: "Panama" },
22471
+ { alpha2: "PE", alpha3: "PER", name: "Peru" },
22472
+ { alpha2: "PF", alpha3: "PYF", name: "French Polynesia" },
22473
+ { alpha2: "PG", alpha3: "PNG", name: "Papua New Guinea" },
22474
+ { alpha2: "PH", alpha3: "PHL", name: "Philippines" },
22475
+ { alpha2: "PK", alpha3: "PAK", name: "Pakistan" },
22476
+ { alpha2: "PL", alpha3: "POL", name: "Poland" },
22477
+ { alpha2: "PM", alpha3: "SPM", name: "Saint Pierre and Miquelon" },
22478
+ { alpha2: "PN", alpha3: "PCN", name: "Pitcairn" },
22479
+ { alpha2: "PR", alpha3: "PRI", name: "Puerto Rico" },
22480
+ { alpha2: "PS", alpha3: "PSE", name: "Palestine, State of" },
22481
+ { alpha2: "PT", alpha3: "PRT", name: "Portugal" },
22482
+ { alpha2: "PW", alpha3: "PLW", name: "Palau" },
22483
+ { alpha2: "PY", alpha3: "PRY", name: "Paraguay" },
22484
+ { alpha2: "QA", alpha3: "QAT", name: "Qatar" },
22485
+ { alpha2: "RE", alpha3: "REU", name: "R\xE9union" },
22486
+ { alpha2: "RO", alpha3: "ROU", name: "Romania" },
22487
+ { alpha2: "RS", alpha3: "SRB", name: "Serbia" },
22488
+ { alpha2: "RU", alpha3: "RUS", name: "Russian Federation" },
22489
+ { alpha2: "RW", alpha3: "RWA", name: "Rwanda" },
22490
+ { alpha2: "SA", alpha3: "SAU", name: "Saudi Arabia" },
22491
+ { alpha2: "SB", alpha3: "SLB", name: "Solomon Islands" },
22492
+ { alpha2: "SC", alpha3: "SYC", name: "Seychelles" },
22493
+ { alpha2: "SD", alpha3: "SDN", name: "Sudan" },
22494
+ { alpha2: "SE", alpha3: "SWE", name: "Sweden" },
22495
+ { alpha2: "SG", alpha3: "SGP", name: "Singapore" },
22496
+ { alpha2: "SH", alpha3: "SHN", name: "Saint Helena, Ascension and Tristan da Cunha" },
22497
+ { alpha2: "SI", alpha3: "SVN", name: "Slovenia" },
22498
+ { alpha2: "SJ", alpha3: "SJM", name: "Svalbard and Jan Mayen" },
22499
+ { alpha2: "SK", alpha3: "SVK", name: "Slovakia" },
22500
+ { alpha2: "SL", alpha3: "SLE", name: "Sierra Leone" },
22501
+ { alpha2: "SM", alpha3: "SMR", name: "San Marino" },
22502
+ { alpha2: "SN", alpha3: "SEN", name: "Senegal" },
22503
+ { alpha2: "SO", alpha3: "SOM", name: "Somalia" },
22504
+ { alpha2: "SR", alpha3: "SUR", name: "Suriname" },
22505
+ { alpha2: "SS", alpha3: "SSD", name: "South Sudan" },
22506
+ { alpha2: "ST", alpha3: "STP", name: "Sao Tome and Principe" },
22507
+ { alpha2: "SV", alpha3: "SLV", name: "El Salvador" },
22508
+ { alpha2: "SX", alpha3: "SXM", name: "Sint Maarten (Dutch part)" },
22509
+ { alpha2: "SY", alpha3: "SYR", name: "Syrian Arab Republic" },
22510
+ { alpha2: "SZ", alpha3: "SWZ", name: "Eswatini" },
22511
+ { alpha2: "TC", alpha3: "TCA", name: "Turks and Caicos Islands" },
22512
+ { alpha2: "TD", alpha3: "TCD", name: "Chad" },
22513
+ { alpha2: "TF", alpha3: "ATF", name: "French Southern Territories" },
22514
+ { alpha2: "TG", alpha3: "TGO", name: "Togo" },
22515
+ { alpha2: "TH", alpha3: "THA", name: "Thailand" },
22516
+ { alpha2: "TJ", alpha3: "TJK", name: "Tajikistan" },
22517
+ { alpha2: "TK", alpha3: "TKL", name: "Tokelau" },
22518
+ { alpha2: "TL", alpha3: "TLS", name: "Timor-Leste" },
22519
+ { alpha2: "TM", alpha3: "TKM", name: "Turkmenistan" },
22520
+ { alpha2: "TN", alpha3: "TUN", name: "Tunisia" },
22521
+ { alpha2: "TO", alpha3: "TON", name: "Tonga" },
22522
+ { alpha2: "TR", alpha3: "TUR", name: "T\xFCrkiye" },
22523
+ { alpha2: "TT", alpha3: "TTO", name: "Trinidad and Tobago" },
22524
+ { alpha2: "TV", alpha3: "TUV", name: "Tuvalu" },
22525
+ { alpha2: "TW", alpha3: "TWN", name: "Taiwan, Province of China" },
22526
+ { alpha2: "TZ", alpha3: "TZA", name: "Tanzania, United Republic of" },
22527
+ { alpha2: "UA", alpha3: "UKR", name: "Ukraine" },
22528
+ { alpha2: "UG", alpha3: "UGA", name: "Uganda" },
22529
+ { alpha2: "UM", alpha3: "UMI", name: "United States Minor Outlying Islands" },
22530
+ { alpha2: "US", alpha3: "USA", name: "United States of America" },
22531
+ { alpha2: "UY", alpha3: "URY", name: "Uruguay" },
22532
+ { alpha2: "UZ", alpha3: "UZB", name: "Uzbekistan" },
22533
+ { alpha2: "VA", alpha3: "VAT", name: "Holy See" },
22534
+ { alpha2: "VC", alpha3: "VCT", name: "Saint Vincent and the Grenadines" },
22535
+ { alpha2: "VE", alpha3: "VEN", name: "Venezuela (Bolivarian Republic of)" },
22536
+ { alpha2: "VG", alpha3: "VGB", name: "Virgin Islands (British)" },
22537
+ { alpha2: "VI", alpha3: "VIR", name: "Virgin Islands (U.S.)" },
22538
+ { alpha2: "VN", alpha3: "VNM", name: "Viet Nam" },
22539
+ { alpha2: "VU", alpha3: "VUT", name: "Vanuatu" },
22540
+ { alpha2: "WF", alpha3: "WLF", name: "Wallis and Futuna" },
22541
+ { alpha2: "WS", alpha3: "WSM", name: "Samoa" },
22542
+ { alpha2: "YE", alpha3: "YEM", name: "Yemen" },
22543
+ { alpha2: "YT", alpha3: "MYT", name: "Mayotte" },
22544
+ { alpha2: "ZA", alpha3: "ZAF", name: "South Africa" },
22545
+ { alpha2: "ZM", alpha3: "ZMB", name: "Zambia" },
22546
+ { alpha2: "ZW", alpha3: "ZWE", name: "Zimbabwe" }
22547
+ ];
22548
+ var COMMON_ALIASES = {
22549
+ usa: "US",
22550
+ america: "US",
22551
+ unitedstates: "US",
22552
+ uk: "GB",
22553
+ unitedkingdom: "GB",
22554
+ britain: "GB",
22555
+ greatbritain: "GB",
22556
+ southkorea: "KR",
22557
+ northkorea: "KP",
22558
+ russia: "RU",
22559
+ vietnam: "VN",
22560
+ laos: "LA",
22561
+ syria: "SY",
22562
+ iran: "IR",
22563
+ ivorycoast: "CI",
22564
+ czechrepublic: "CZ",
22565
+ swaziland: "SZ",
22566
+ burma: "MM",
22567
+ capeverde: "CV",
22568
+ easttimor: "TL",
22569
+ taiwan: "TW",
22570
+ macedonia: "MK",
22571
+ vatican: "VA",
22572
+ vaticancity: "VA",
22573
+ holland: "NL",
22574
+ moldova: "MD",
22575
+ bolivia: "BO",
22576
+ tanzania: "TZ",
22577
+ venezuela: "VE",
22578
+ palestine: "PS",
22579
+ brunei: "BN"
22580
+ };
22581
+ function normalize(s) {
22582
+ return s.toLowerCase().trim().replace(/[^a-z0-9]/g, "");
22583
+ }
22584
+ function isValidCountry(input) {
22585
+ const trimmed = input.trim();
22586
+ if (/^[A-Za-z]{2}$/.test(trimmed)) {
22587
+ return COUNTRIES.some((c) => c.alpha2 === trimmed.toUpperCase());
22588
+ }
22589
+ if (/^[A-Za-z]{3}$/.test(trimmed)) {
22590
+ return COUNTRIES.some((c) => c.alpha3 === trimmed.toUpperCase());
22591
+ }
22592
+ const normalized = normalize(trimmed);
22593
+ if (COMMON_ALIASES[normalized]) return true;
22594
+ return COUNTRIES.some((c) => normalize(c.name) === normalized);
22122
22595
  }
22123
22596
 
22124
22597
  // src/template-aliases.ts
22598
+ function validateDob(value) {
22599
+ if (typeof value !== "string") return "dob must be a string in YYYY-MM-DD format";
22600
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(value)) return `dob must match the format YYYY-MM-DD, got "${value}"`;
22601
+ const [year, month, day] = value.split("-").map(Number);
22602
+ const date3 = new Date(Date.UTC(year, month - 1, day));
22603
+ const isRealCalendarDate = date3.getUTCFullYear() === year && date3.getUTCMonth() === month - 1 && date3.getUTCDate() === day;
22604
+ if (!isRealCalendarDate) return `dob is not a valid calendar date: "${value}"`;
22605
+ return void 0;
22606
+ }
22607
+ function validateCountryOfOrigin(value) {
22608
+ if (typeof value !== "string" || !value.trim()) return "countryOfOrigin must be a non-empty string";
22609
+ if (!isValidCountry(value)) return `countryOfOrigin must be an ISO 3166 country code or name, got "${value}"`;
22610
+ return void 0;
22611
+ }
22125
22612
  var TEMPLATE_ALIASES = [
22126
22613
  {
22127
22614
  match: "birthcert",
22128
- testnet: "did:zid:d6b783559acf6ba0f7ef6e1365bdaf0774d622d8d22728ca6323677f49ee94f8",
22129
- mainnet: "did:zid:032cb99be3577beccfc6252783c49c83673af38f8456d73462043654d7764e83"
22615
+ testnet: "did:zid:3c0fb79adff08e14e06dcd6e3243205010dd65f533434a3d96c55575d1d3d959",
22616
+ mainnet: "did:zid:19091d19049abb8869b4b8e2f4a887bd1d1d86e5f5ebd0c8297000255f67765b",
22617
+ deriveAttributes: { id: "agentUsername" },
22618
+ validateAttributes: { dob: validateDob, countryOfOrigin: validateCountryOfOrigin }
22130
22619
  }
22131
22620
  ];
22132
- function normalize(s) {
22621
+ function normalize2(s) {
22133
22622
  return s.toLowerCase().replace(/[^a-z0-9]/g, "");
22134
22623
  }
22624
+ function findEntry(templateId, network) {
22625
+ if (/^did:zid:/.test(templateId)) {
22626
+ const key = network.includes("testnet") ? "testnet" : "mainnet";
22627
+ return TEMPLATE_ALIASES.find((e) => e[key] === templateId);
22628
+ }
22629
+ const normalized = normalize2(templateId);
22630
+ return TEMPLATE_ALIASES.find((e) => normalized.includes(e.match));
22631
+ }
22135
22632
  function resolveTemplateAlias(input, network) {
22136
22633
  if (/^did:zid:/.test(input)) return void 0;
22137
- const normalized = normalize(input);
22138
- const entry = TEMPLATE_ALIASES.find((e) => normalized.includes(e.match));
22634
+ const entry = findEntry(input, network);
22139
22635
  if (!entry) return void 0;
22140
22636
  return network.includes("testnet") ? entry.testnet : entry.mainnet;
22141
22637
  }
22638
+ function deriveTemplateAttributes(templateId, network, attributes) {
22639
+ const entry = findEntry(templateId, network);
22640
+ if (!entry?.deriveAttributes) return attributes;
22641
+ const result = { ...attributes };
22642
+ for (const [target, source] of Object.entries(entry.deriveAttributes)) {
22643
+ if ((result[target] === void 0 || result[target] === null || result[target] === "") && result[source] !== void 0) {
22644
+ result[target] = result[source];
22645
+ }
22646
+ }
22647
+ return result;
22648
+ }
22649
+ function derivedAttributeKeys(templateId, network) {
22650
+ const entry = findEntry(templateId, network);
22651
+ return entry?.deriveAttributes ? Object.keys(entry.deriveAttributes) : [];
22652
+ }
22653
+ function validateTemplateAttributes(templateId, network, attributes) {
22654
+ const entry = findEntry(templateId, network);
22655
+ if (!entry?.validateAttributes) return [];
22656
+ const errors = [];
22657
+ for (const [key, validate] of Object.entries(entry.validateAttributes)) {
22658
+ const value = attributes[key];
22659
+ if (value === void 0 || value === null || value === "") continue;
22660
+ const error2 = validate(value);
22661
+ if (error2) errors.push(error2);
22662
+ }
22663
+ return errors;
22664
+ }
22142
22665
 
22143
22666
  // src/mcp-tools.ts
22144
22667
  async function loadValidCachedCredentials(cache) {
@@ -22150,13 +22673,15 @@ function createTools(deps) {
22150
22673
  return {
22151
22674
  async wallet_status(input = {}) {
22152
22675
  const balances = deps.getBalances ? await deps.getBalances() : void 0;
22676
+ const tokenBalance = input.token && deps.queryTokenBalance ? await deps.queryTokenBalance(input.token) : void 0;
22153
22677
  const credentials = input.heldCredentials ?? (await loadValidCachedCredentials(deps.cache)).map((entry) => entry.vc);
22154
22678
  return {
22155
22679
  holderDid: deps.config.holderDid,
22156
22680
  zetrixAddress: deps.config.zetrixAddress,
22157
22681
  network: deps.config.network,
22158
22682
  credentials,
22159
- ...balances !== void 0 ? { balances } : {}
22683
+ ...balances !== void 0 ? { balances } : {},
22684
+ ...tokenBalance !== void 0 ? { tokenBalance } : {}
22160
22685
  };
22161
22686
  },
22162
22687
  async prove_identity(input) {
@@ -22182,12 +22707,39 @@ function createTools(deps) {
22182
22707
  pay_and_fetch(input) {
22183
22708
  return payAndFetch(deps.payer, input);
22184
22709
  },
22185
- subscribe_and_issue(input) {
22710
+ async subscribe_and_issue(input) {
22186
22711
  const resolved = resolveTemplateAlias(input.templateId, deps.config.network);
22187
- return subscribeAndIssue(deps.subscribeDeps, resolved ? { ...input, templateId: resolved } : input);
22712
+ const templateId = resolved ?? input.templateId;
22713
+ const attributes = deriveTemplateAttributes(templateId, deps.config.network, input.attributes ?? {});
22714
+ const errors = validateTemplateAttributes(templateId, deps.config.network, attributes);
22715
+ if (errors.length > 0) {
22716
+ return { issued: false, reason: errors.join("; ") };
22717
+ }
22718
+ const result = await subscribeAndIssue(deps.subscribeDeps, { ...input, templateId, attributes });
22719
+ if (!result.schema) return result;
22720
+ const hidden = /* @__PURE__ */ new Set(["agentDid", ...derivedAttributeKeys(templateId, deps.config.network)]);
22721
+ return {
22722
+ ...result,
22723
+ schema: {
22724
+ required: result.schema.required.filter((k) => !hidden.has(k)),
22725
+ optional: result.schema.optional.filter((k) => !hidden.has(k))
22726
+ }
22727
+ };
22728
+ },
22729
+ query_contract(input) {
22730
+ return deps.queryContract(input);
22188
22731
  },
22189
22732
  create_holder_account(input) {
22190
- return createHolderAccount(deps.createAccount, input);
22733
+ return createHolderAccount(
22734
+ {
22735
+ create: deps.createAccount,
22736
+ getExistingAccount: () => Promise.resolve(deps.config.zetrixAddress ? { zetrixAddress: deps.config.zetrixAddress, holderDid: deps.config.holderDid } : null),
22737
+ saveAccount: deps.saveAccount,
22738
+ checkActivationStatus: deps.checkActivationStatus,
22739
+ sleep: deps.sleep
22740
+ },
22741
+ input
22742
+ );
22191
22743
  }
22192
22744
  };
22193
22745
  }
@@ -22195,8 +22747,16 @@ function createTools(deps) {
22195
22747
  // src/orchestrator/resolve-holder.ts
22196
22748
  async function resolveHolder(deps, input) {
22197
22749
  if (!input.zetrixAddress) {
22198
- const { zetrixAddress, publicKeyHex } = await deps.createAccount(input.hsmPassword);
22199
- return { zetrixAddress, holderDid: deriveHolderDid(publicKeyHex), created: true, didMismatch: false };
22750
+ const { zetrixAddress, publicKeyHex, activated } = await deps.createAccount(input.hsmPassword);
22751
+ let finalActivated = activated;
22752
+ if (!finalActivated) {
22753
+ try {
22754
+ finalActivated = await waitForActivation(deps.checkActivationStatus, zetrixAddress, deps.sleep);
22755
+ } catch {
22756
+ finalActivated = false;
22757
+ }
22758
+ }
22759
+ return { zetrixAddress, holderDid: deriveHolderDid(publicKeyHex), created: true, didMismatch: false, activated: finalActivated };
22200
22760
  }
22201
22761
  const { publicKey } = await deps.signMessage(input.zetrixAddress, input.zetrixAddress, input.hsmPassword);
22202
22762
  const derivedDid = deriveHolderDid(publicKey);
@@ -22204,6 +22764,30 @@ async function resolveHolder(deps, input) {
22204
22764
  return { zetrixAddress: input.zetrixAddress, holderDid: derivedDid, created: false, didMismatch };
22205
22765
  }
22206
22766
 
22767
+ // src/clients/account-store.ts
22768
+ var import_promises2 = require("node:fs/promises");
22769
+ var import_node_path2 = require("node:path");
22770
+ function isStoredAccountShape(value) {
22771
+ return typeof value === "object" && value !== null && typeof value.zetrixAddress === "string" && typeof value.holderDid === "string" && typeof value.hsmPassword === "string";
22772
+ }
22773
+ function createFsAccountStore(filePath) {
22774
+ return {
22775
+ async get() {
22776
+ try {
22777
+ const raw = await (0, import_promises2.readFile)(filePath, "utf8");
22778
+ const parsed = JSON.parse(raw);
22779
+ return isStoredAccountShape(parsed) ? parsed : null;
22780
+ } catch {
22781
+ return null;
22782
+ }
22783
+ },
22784
+ async set(account) {
22785
+ await (0, import_promises2.mkdir)((0, import_node_path2.dirname)(filePath), { recursive: true, mode: 448 });
22786
+ await (0, import_promises2.writeFile)(filePath, JSON.stringify(account, null, 2), { encoding: "utf8", mode: 384 });
22787
+ }
22788
+ };
22789
+ }
22790
+
22207
22791
  // src/index.ts
22208
22792
  var packageVersion = package_default.version;
22209
22793
  function buildToolList() {
@@ -22214,7 +22798,8 @@ function buildToolList() {
22214
22798
  inputSchema: {
22215
22799
  type: "object",
22216
22800
  properties: {
22217
- 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." }
22801
+ 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." },
22802
+ token: { type: "string", description: 'Optional token symbol (e.g. "ZTX", "JMYR") to check its balance for the active network, alongside the usual status fields.' }
22218
22803
  }
22219
22804
  }
22220
22805
  },
@@ -22253,9 +22838,22 @@ function buildToolList() {
22253
22838
  required: ["url"]
22254
22839
  }
22255
22840
  },
22841
+ {
22842
+ name: "query_contract",
22843
+ description: 'Read-only query against a Zetrix contract or account \u2014 call an arbitrary contract method (e.g. "balanceOf", "contractInfo") and return its raw result. No signing, no state change.',
22844
+ inputSchema: {
22845
+ type: "object",
22846
+ properties: {
22847
+ contractAddress: { type: "string", description: "Zetrix contract address to query." },
22848
+ method: { type: "string", description: 'Contract method name, e.g. "balanceOf", "contractInfo".' },
22849
+ params: { type: "object", description: 'Method parameters, e.g. { "address": "ZTX..." } for balanceOf.' }
22850
+ },
22851
+ required: ["contractAddress", "method"]
22852
+ }
22853
+ },
22256
22854
  {
22257
22855
  name: "subscribe_and_issue",
22258
- 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.",
22856
+ 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. Every response (success, dry-run, or a missing-attribute error) also includes { schema: { required, optional } } \u2014 the template's full declared attribute schema read from chain \u2014 so you always see the complete field list, not just what went wrong.",
22259
22857
  inputSchema: {
22260
22858
  type: "object",
22261
22859
  properties: {
@@ -22270,7 +22868,7 @@ function buildToolList() {
22270
22868
  expirationDate: { type: "string" },
22271
22869
  dryRun: {
22272
22870
  type: "boolean",
22273
- 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."
22871
+ description: "Stop after MBI's free phase-1 quote and return { quote: { asset, maxAmountRequired, payTo }, schema: { required, optional } } without paying or issuing \u2014 use this to check the payment requirement AND the template's full attribute schema (read from chain) before spending funds. Still validates required attributes locally first \u2014 a missing one blocks before any MBI call."
22274
22872
  },
22275
22873
  forceReissue: {
22276
22874
  type: "boolean",
@@ -22282,13 +22880,17 @@ function buildToolList() {
22282
22880
  },
22283
22881
  {
22284
22882
  name: "create_holder_account",
22285
- 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.",
22883
+ 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.",
22286
22884
  inputSchema: {
22287
22885
  type: "object",
22288
22886
  properties: {
22289
22887
  password: { type: "string", description: "HSM password to protect the new account. Must come from the user." },
22290
22888
  label: { type: "string" },
22291
- purpose: { type: "string" }
22889
+ purpose: { type: "string" },
22890
+ confirmNew: {
22891
+ type: "boolean",
22892
+ 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."
22893
+ }
22292
22894
  },
22293
22895
  required: ["password"]
22294
22896
  }
@@ -22300,25 +22902,46 @@ function asPayRequest(accept) {
22300
22902
  return { ...accept, extra: { gasModel: "client", ...extra } };
22301
22903
  }
22302
22904
  async function main() {
22303
- const config2 = loadConfig(process.env);
22905
+ const accountStore = createFsAccountStore((0, import_node_path3.join)((0, import_node_os.homedir)(), ".agentic-wallet-mcp", "account.json"));
22906
+ const storedAccount = process.env.ZETRIX_ADDRESS ? null : await accountStore.get();
22907
+ const env = { ...process.env };
22908
+ if (!env.ZETRIX_ADDRESS && storedAccount) env.ZETRIX_ADDRESS = storedAccount.zetrixAddress;
22909
+ if (!env.HOLDER_DID && storedAccount) env.HOLDER_DID = storedAccount.holderDid;
22910
+ if (!env.HSM_PASSWORD && storedAccount) env.HSM_PASSWORD = storedAccount.hsmPassword;
22911
+ const config2 = loadConfig(env);
22304
22912
  const hsmPassword = config2.hsmPassword;
22305
22913
  const be = new WalletBeClient(config2.walletBeUrl);
22306
- const { zetrixAddress, holderDid, created, didMismatch } = await resolveHolder(
22914
+ const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
22915
+ const { zetrixAddress, holderDid, created, didMismatch, activated } = await resolveHolder(
22307
22916
  {
22308
22917
  createAccount: (password) => be.createAccount(password),
22309
- signMessage: (message, address, password) => be.signMessage(message, address, password)
22918
+ signMessage: (message, address, password) => be.signMessage(message, address, password),
22919
+ checkActivationStatus: (address) => be.checkActivationStatus(address),
22920
+ sleep
22310
22921
  },
22311
22922
  { zetrixAddress: config2.zetrixAddress, holderDid: config2.holderDid, hsmPassword }
22312
22923
  );
22313
22924
  if (created) {
22925
+ await accountStore.set({ zetrixAddress, holderDid, hsmPassword, createdAt: (/* @__PURE__ */ new Date()).toISOString() });
22926
+ process.stderr.write(
22927
+ `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).
22928
+ `
22929
+ );
22930
+ } else if (storedAccount && config2.zetrixAddress === storedAccount.zetrixAddress) {
22314
22931
  process.stderr.write(
22315
- `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).
22932
+ `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.
22316
22933
  `
22317
22934
  );
22318
22935
  }
22319
22936
  if (didMismatch) {
22320
22937
  process.stderr.write(
22321
22938
  `agentic-wallet-mcp: configured HOLDER_DID=${config2.holderDid} does not match the account's actual public key \u2014 using the derived HOLDER_DID=${holderDid} instead. Update your MCP config.
22939
+ `
22940
+ );
22941
+ }
22942
+ if (created && !activated) {
22943
+ process.stderr.write(
22944
+ `agentic-wallet-mcp: the newly created HSM account (ZETRIX_ADDRESS=${zetrixAddress}) has not completed on-chain activation yet \u2014 balance/on-chain calls for this address may fail until it does.
22322
22945
  `
22323
22946
  );
22324
22947
  }
@@ -22329,14 +22952,36 @@ async function main() {
22329
22952
  const sdk = new import_zetrix_sdk_nodejs.default({ host: config2.nodeHost, port: config2.nodePort });
22330
22953
  const contractQuery = (a) => sdk.contract.call(a);
22331
22954
  const resolveSymbol = (asset) => resolveAssetSymbol(asset, contractQuery);
22955
+ const queryTokenBalance = async (token) => {
22956
+ const symbol = token.toUpperCase();
22957
+ if (symbol === "ZTX") {
22958
+ try {
22959
+ const { balance } = await import_x402_zetrix_client2.PaymentEngine.fetchAccountInfo(zetrixAddress, node);
22960
+ return { token: symbol, balance };
22961
+ } catch {
22962
+ return { token: symbol, error: "query_failed" };
22963
+ }
22964
+ }
22965
+ const contractAddress = resolveTokenAddress(symbol, config2.network);
22966
+ if (!contractAddress) return { token: symbol, error: "unknown_token" };
22967
+ try {
22968
+ const { balance } = await import_x402_zetrix_client2.PaymentEngine.fetchZTP20Balance(contractAddress, zetrixAddress, node);
22969
+ return { token: symbol, balance };
22970
+ } catch {
22971
+ return { token: symbol, error: "query_failed" };
22972
+ }
22973
+ };
22332
22974
  const nodeBaseUrl = `https://${config2.nodeHost}${config2.nodePort ? `:${config2.nodePort}` : ""}`;
22333
22975
  const nodeMetaQuery = (url) => fetch(url, { headers: { Accept: "application/json" } }).then((r) => r.json());
22334
22976
  const resolveTemplateFields = (templateId) => fetchTemplateFields(templateId, config2.templateRegistryAddress, nodeBaseUrl, nodeMetaQuery);
22335
22977
  const cacheScope = (0, import_node_crypto2.createHash)("sha256").update(`${config2.network}:${zetrixAddress}`).digest("hex");
22336
- const vcCache = createFsVcCache((0, import_node_path2.join)((0, import_node_os.homedir)(), ".agentic-wallet-mcp", "vc-cache", cacheScope));
22978
+ const vcCache = createFsVcCache((0, import_node_path3.join)((0, import_node_os.homedir)(), ".agentic-wallet-mcp", "vc-cache", cacheScope));
22337
22979
  const pay = (accept) => {
22338
22980
  assertWithinPaymentCap(accept, config2.maxPaymentAmount);
22339
- return import_x402_zetrix_client.PaymentEngine.pay(asPayRequest(accept), walletCfg, node, {}, walletBeSignerFn);
22981
+ return payWithReadinessCheck(
22982
+ String(accept.asset ?? ""),
22983
+ () => import_x402_zetrix_client2.PaymentEngine.pay(asPayRequest(accept), walletCfg, node, {}, walletBeSignerFn)
22984
+ );
22340
22985
  };
22341
22986
  const payer = async (req) => {
22342
22987
  const init = { method: req.method ?? "GET", headers: req.headers, body: req.body };
@@ -22347,7 +22992,15 @@ async function main() {
22347
22992
  const parsed = await res.json();
22348
22993
  const accept = parsed.accepts?.[0];
22349
22994
  if (!accept) throw new Error("pay_and_fetch: 402 had no accepts[]");
22350
- const xPayment = await pay(accept);
22995
+ let xPayment;
22996
+ try {
22997
+ xPayment = await pay(accept);
22998
+ } catch (err) {
22999
+ if (err instanceof PaymentReadinessError) {
23000
+ return { status: 402, body: "", paymentMade: false, amountPaid: "", amountPaidHuman: "", asset: "", insufficientFunds: err.shortfall };
23001
+ }
23002
+ throw err;
23003
+ }
22351
23004
  const retry = await fetch(req.url, { ...init, headers: { ...req.headers ?? {}, "x-payment": xPayment } });
22352
23005
  const asset = await resolveSymbol(String(accept.asset ?? ""));
22353
23006
  return {
@@ -22377,7 +23030,12 @@ async function main() {
22377
23030
  makeWallet,
22378
23031
  payer,
22379
23032
  subscribeDeps: { mbi, sign: subscribeSign, pay, resolveSymbol, holderDid, resolveTemplateFields, cache: vcCache },
23033
+ queryContract: (input) => queryContract(input, contractQuery),
23034
+ queryTokenBalance,
22380
23035
  createAccount: (password, label, purpose) => be.createAccount(password, label, purpose),
23036
+ saveAccount: (account) => accountStore.set({ ...account, createdAt: (/* @__PURE__ */ new Date()).toISOString() }),
23037
+ checkActivationStatus: (address) => be.checkActivationStatus(address),
23038
+ sleep,
22381
23039
  cache: vcCache
22382
23040
  };
22383
23041
  const tools = createTools(deps);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-wallet-mcp",
3
- "version": "0.4.0",
3
+ "version": "0.5.0",
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",