agentic-wallet-mcp 0.4.2 → 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,7 +23,7 @@ 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? }` |
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
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/`
@@ -38,6 +38,15 @@ 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
51
  > way to provision a pre-chosen address. It always checks first whether an account is already
43
52
  > active for this session; if so, it returns `{ alreadyExists: true, existing }` and creates
@@ -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
  },
@@ -12547,7 +12547,7 @@ var import_node_path3 = require("node:path");
12547
12547
  // package.json
12548
12548
  var package_default = {
12549
12549
  name: "agentic-wallet-mcp",
12550
- version: "0.4.2",
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
@@ -22035,6 +22092,36 @@ function extractValidUntil(vc, fallback) {
22035
22092
  return fallback;
22036
22093
  }
22037
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
+
22038
22125
  // src/orchestrator/subscribe.ts
22039
22126
  async function subscribeAndIssue(deps, opts) {
22040
22127
  if (!/^did:zid:/.test(opts.templateId)) {
@@ -22058,13 +22145,15 @@ async function subscribeAndIssue(deps, opts) {
22058
22145
  const { agentDid, ...rest } = opts.attributes ?? {};
22059
22146
  const shouldAutoFillAgentDid = !agentDid && fields !== null && fields.allKeys.includes("agentDid");
22060
22147
  const attributes = shouldAutoFillAgentDid ? { agentDid: deps.holderDid, ...rest } : opts.attributes ?? {};
22061
- 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) {
22062
22150
  const attrs = attributes;
22063
22151
  const missing = fields.required.filter((k) => attrs[k] === void 0 || attrs[k] === null || attrs[k] === "");
22064
22152
  if (missing.length > 0) {
22065
22153
  return {
22066
22154
  issued: false,
22067
- 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 } : {}
22068
22157
  };
22069
22158
  }
22070
22159
  }
@@ -22073,7 +22162,15 @@ async function subscribeAndIssue(deps, opts) {
22073
22162
  const { signBlob: signData, publicKey } = await deps.sign(blob);
22074
22163
  const body = { data, signData, publicKey };
22075
22164
  if (opts.expirationDate) body.expirationDate = opts.expirationDate;
22076
- 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
+ }
22077
22174
  if (challenge.issued) {
22078
22175
  const issued2 = challenge.issued;
22079
22176
  if (deps.cache) {
@@ -22095,11 +22192,12 @@ async function subscribeAndIssue(deps, opts) {
22095
22192
  txHash: issued2.txHash,
22096
22193
  paidAsset: "none",
22097
22194
  amountPaid: "0",
22195
+ ...schema ? { schema } : {},
22098
22196
  ...opts.dryRun ? { reason: "this template requires no payment \u2014 MBI issues synchronously at phase 1, so dryRun could not prevent this issuance" } : {}
22099
22197
  };
22100
22198
  }
22101
22199
  const accept = challenge.accepts[0];
22102
- 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 } : {} };
22103
22201
  if (opts.dryRun) {
22104
22202
  const quotedRaw = String(accept.asset ?? "");
22105
22203
  const quotedAsset = deps.resolveSymbol ? await deps.resolveSymbol(quotedRaw) : quotedRaw;
@@ -22109,13 +22207,29 @@ async function subscribeAndIssue(deps, opts) {
22109
22207
  quote: {
22110
22208
  asset: quotedAsset,
22111
22209
  maxAmountRequired: accept.maxAmountRequired,
22112
- payTo: accept.payTo,
22113
- ...fields ? { requiredAttributes: fields.required } : {}
22114
- }
22210
+ payTo: accept.payTo
22211
+ },
22212
+ ...schema ? { schema } : {}
22115
22213
  };
22116
22214
  }
22117
- const xPayment = await deps.pay(accept);
22118
- 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
+ }
22119
22233
  const rawAsset = String(accept.asset ?? "");
22120
22234
  const paidAsset = deps.resolveSymbol ? await deps.resolveSymbol(rawAsset) : rawAsset;
22121
22235
  const amountPaid = String(accept.maxAmountRequired ?? "");
@@ -22137,10 +22251,23 @@ async function subscribeAndIssue(deps, opts) {
22137
22251
  vc: issued.verifiableCredential,
22138
22252
  txHash: issued.txHash,
22139
22253
  paidAsset,
22140
- amountPaid
22254
+ amountPaid,
22255
+ ...schema ? { schema } : {}
22141
22256
  };
22142
22257
  }
22143
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
+
22144
22271
  // src/orchestrator/onboard.ts
22145
22272
  function deriveHolderDid(publicKeyHex) {
22146
22273
  const hex = publicKeyHex.trim();
@@ -22158,31 +22285,383 @@ async function createHolderAccount(deps, input) {
22158
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.`
22159
22286
  };
22160
22287
  }
22161
- const { zetrixAddress, publicKeyHex } = await deps.create(input.password, input.label, input.purpose);
22288
+ const { zetrixAddress, publicKeyHex, activated } = await deps.create(input.password, input.label, input.purpose);
22162
22289
  const holderDid = deriveHolderDid(publicKeyHex);
22163
22290
  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 };
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);
22166
22595
  }
22167
22596
 
22168
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
+ }
22169
22612
  var TEMPLATE_ALIASES = [
22170
22613
  {
22171
22614
  match: "birthcert",
22172
- testnet: "did:zid:d6b783559acf6ba0f7ef6e1365bdaf0774d622d8d22728ca6323677f49ee94f8",
22173
- mainnet: "did:zid:032cb99be3577beccfc6252783c49c83673af38f8456d73462043654d7764e83"
22615
+ testnet: "did:zid:3c0fb79adff08e14e06dcd6e3243205010dd65f533434a3d96c55575d1d3d959",
22616
+ mainnet: "did:zid:19091d19049abb8869b4b8e2f4a887bd1d1d86e5f5ebd0c8297000255f67765b",
22617
+ deriveAttributes: { id: "agentUsername" },
22618
+ validateAttributes: { dob: validateDob, countryOfOrigin: validateCountryOfOrigin }
22174
22619
  }
22175
22620
  ];
22176
- function normalize(s) {
22621
+ function normalize2(s) {
22177
22622
  return s.toLowerCase().replace(/[^a-z0-9]/g, "");
22178
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
+ }
22179
22632
  function resolveTemplateAlias(input, network) {
22180
22633
  if (/^did:zid:/.test(input)) return void 0;
22181
- const normalized = normalize(input);
22182
- const entry = TEMPLATE_ALIASES.find((e) => normalized.includes(e.match));
22634
+ const entry = findEntry(input, network);
22183
22635
  if (!entry) return void 0;
22184
22636
  return network.includes("testnet") ? entry.testnet : entry.mainnet;
22185
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
+ }
22186
22665
 
22187
22666
  // src/mcp-tools.ts
22188
22667
  async function loadValidCachedCredentials(cache) {
@@ -22194,13 +22673,15 @@ function createTools(deps) {
22194
22673
  return {
22195
22674
  async wallet_status(input = {}) {
22196
22675
  const balances = deps.getBalances ? await deps.getBalances() : void 0;
22676
+ const tokenBalance = input.token && deps.queryTokenBalance ? await deps.queryTokenBalance(input.token) : void 0;
22197
22677
  const credentials = input.heldCredentials ?? (await loadValidCachedCredentials(deps.cache)).map((entry) => entry.vc);
22198
22678
  return {
22199
22679
  holderDid: deps.config.holderDid,
22200
22680
  zetrixAddress: deps.config.zetrixAddress,
22201
22681
  network: deps.config.network,
22202
22682
  credentials,
22203
- ...balances !== void 0 ? { balances } : {}
22683
+ ...balances !== void 0 ? { balances } : {},
22684
+ ...tokenBalance !== void 0 ? { tokenBalance } : {}
22204
22685
  };
22205
22686
  },
22206
22687
  async prove_identity(input) {
@@ -22226,16 +22707,36 @@ function createTools(deps) {
22226
22707
  pay_and_fetch(input) {
22227
22708
  return payAndFetch(deps.payer, input);
22228
22709
  },
22229
- subscribe_and_issue(input) {
22710
+ async subscribe_and_issue(input) {
22230
22711
  const resolved = resolveTemplateAlias(input.templateId, deps.config.network);
22231
- 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);
22232
22731
  },
22233
22732
  create_holder_account(input) {
22234
22733
  return createHolderAccount(
22235
22734
  {
22236
22735
  create: deps.createAccount,
22237
22736
  getExistingAccount: () => Promise.resolve(deps.config.zetrixAddress ? { zetrixAddress: deps.config.zetrixAddress, holderDid: deps.config.holderDid } : null),
22238
- saveAccount: deps.saveAccount
22737
+ saveAccount: deps.saveAccount,
22738
+ checkActivationStatus: deps.checkActivationStatus,
22739
+ sleep: deps.sleep
22239
22740
  },
22240
22741
  input
22241
22742
  );
@@ -22246,8 +22747,16 @@ function createTools(deps) {
22246
22747
  // src/orchestrator/resolve-holder.ts
22247
22748
  async function resolveHolder(deps, input) {
22248
22749
  if (!input.zetrixAddress) {
22249
- const { zetrixAddress, publicKeyHex } = await deps.createAccount(input.hsmPassword);
22250
- 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 };
22251
22760
  }
22252
22761
  const { publicKey } = await deps.signMessage(input.zetrixAddress, input.zetrixAddress, input.hsmPassword);
22253
22762
  const derivedDid = deriveHolderDid(publicKey);
@@ -22289,7 +22798,8 @@ function buildToolList() {
22289
22798
  inputSchema: {
22290
22799
  type: "object",
22291
22800
  properties: {
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." }
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.' }
22293
22803
  }
22294
22804
  }
22295
22805
  },
@@ -22328,9 +22838,22 @@ function buildToolList() {
22328
22838
  required: ["url"]
22329
22839
  }
22330
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
+ },
22331
22854
  {
22332
22855
  name: "subscribe_and_issue",
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.",
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.",
22334
22857
  inputSchema: {
22335
22858
  type: "object",
22336
22859
  properties: {
@@ -22345,7 +22868,7 @@ function buildToolList() {
22345
22868
  expirationDate: { type: "string" },
22346
22869
  dryRun: {
22347
22870
  type: "boolean",
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."
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."
22349
22872
  },
22350
22873
  forceReissue: {
22351
22874
  type: "boolean",
@@ -22388,10 +22911,13 @@ async function main() {
22388
22911
  const config2 = loadConfig(env);
22389
22912
  const hsmPassword = config2.hsmPassword;
22390
22913
  const be = new WalletBeClient(config2.walletBeUrl);
22391
- 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(
22392
22916
  {
22393
22917
  createAccount: (password) => be.createAccount(password),
22394
- 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
22395
22921
  },
22396
22922
  { zetrixAddress: config2.zetrixAddress, holderDid: config2.holderDid, hsmPassword }
22397
22923
  );
@@ -22410,6 +22936,12 @@ async function main() {
22410
22936
  if (didMismatch) {
22411
22937
  process.stderr.write(
22412
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.
22413
22945
  `
22414
22946
  );
22415
22947
  }
@@ -22420,6 +22952,25 @@ async function main() {
22420
22952
  const sdk = new import_zetrix_sdk_nodejs.default({ host: config2.nodeHost, port: config2.nodePort });
22421
22953
  const contractQuery = (a) => sdk.contract.call(a);
22422
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
+ };
22423
22974
  const nodeBaseUrl = `https://${config2.nodeHost}${config2.nodePort ? `:${config2.nodePort}` : ""}`;
22424
22975
  const nodeMetaQuery = (url) => fetch(url, { headers: { Accept: "application/json" } }).then((r) => r.json());
22425
22976
  const resolveTemplateFields = (templateId) => fetchTemplateFields(templateId, config2.templateRegistryAddress, nodeBaseUrl, nodeMetaQuery);
@@ -22427,7 +22978,10 @@ async function main() {
22427
22978
  const vcCache = createFsVcCache((0, import_node_path3.join)((0, import_node_os.homedir)(), ".agentic-wallet-mcp", "vc-cache", cacheScope));
22428
22979
  const pay = (accept) => {
22429
22980
  assertWithinPaymentCap(accept, config2.maxPaymentAmount);
22430
- 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
+ );
22431
22985
  };
22432
22986
  const payer = async (req) => {
22433
22987
  const init = { method: req.method ?? "GET", headers: req.headers, body: req.body };
@@ -22438,7 +22992,15 @@ async function main() {
22438
22992
  const parsed = await res.json();
22439
22993
  const accept = parsed.accepts?.[0];
22440
22994
  if (!accept) throw new Error("pay_and_fetch: 402 had no accepts[]");
22441
- 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
+ }
22442
23004
  const retry = await fetch(req.url, { ...init, headers: { ...req.headers ?? {}, "x-payment": xPayment } });
22443
23005
  const asset = await resolveSymbol(String(accept.asset ?? ""));
22444
23006
  return {
@@ -22468,8 +23030,12 @@ async function main() {
22468
23030
  makeWallet,
22469
23031
  payer,
22470
23032
  subscribeDeps: { mbi, sign: subscribeSign, pay, resolveSymbol, holderDid, resolveTemplateFields, cache: vcCache },
23033
+ queryContract: (input) => queryContract(input, contractQuery),
23034
+ queryTokenBalance,
22471
23035
  createAccount: (password, label, purpose) => be.createAccount(password, label, purpose),
22472
23036
  saveAccount: (account) => accountStore.set({ ...account, createdAt: (/* @__PURE__ */ new Date()).toISOString() }),
23037
+ checkActivationStatus: (address) => be.checkActivationStatus(address),
23038
+ sleep,
22473
23039
  cache: vcCache
22474
23040
  };
22475
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.2",
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",