agentic-wallet-mcp 0.4.2 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md ADDED
@@ -0,0 +1,138 @@
1
+ # Changelog
2
+
3
+ All notable changes to `agentic-wallet-mcp` are documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ > Entries for 0.5.0 and earlier were reconstructed from commit history when this file was
9
+ > introduced in 0.6.0, so they summarise each release rather than being exhaustive.
10
+
11
+ ## [0.6.0] — 2026-07-28
12
+
13
+ This release is about the wallet never misstating what a call cost. Four reporting defects were
14
+ found during live use, in each case the wallet told the caller something untrue about money.
15
+
16
+ ### Changed — BREAKING
17
+
18
+ - **`amountPaid` is now `undefined` on a cache hit.** Previously a cache hit replayed the original
19
+ issuance's `txHash`/`paidAsset`/`amountPaid` at the top level, so a **free** call was
20
+ indistinguishable from a fresh charge and anything summing `amountPaid` across calls
21
+ double-counted. Those values now appear under `originalPayment: { txHash, asset, amount }`
22
+ instead, and are omitted entirely when the cached credential was issued free. Any consumer
23
+ reading top-level `amountPaid` to track spend needs updating.
24
+
25
+ ### Added
26
+
27
+ - **`get_template_schema` tool** — a free read of a credential template's declared attribute
28
+ schema (`{ required, optional }`), taking a `did:zid:...` id or a known template name. No
29
+ payment, no signing, no issuer call. Previously the only way to ask what a template required was
30
+ `subscribe_and_issue` with `dryRun` — a tool whose name reads as "this charges money" — so an
31
+ agent had no obvious reason to reach for it and would discover a newly-required attribute by
32
+ failing an issuance first. A template that cannot be read returns `{ error }` rather than an
33
+ empty schema, so "needs nothing" is never confused with "could not look it up".
34
+ - **`staleAttributes` on a cache hit** — `{ missing, dropped }` when a held credential no longer
35
+ matches the template it came from. Validity checks only ever asked whether a credential had
36
+ expired, never whether its fields still fit the template, so an issuer changing a template left
37
+ holders with a credential that looked valid and wasn't. The cached credential is still returned;
38
+ this reports, it does not re-issue or charge.
39
+ - **`decimals` on `wallet_status({ token })`** — the raw base-unit balance stays canonical (it is
40
+ the unit x402 quotes `maxAmountRequired` in, so cap checks and comparisons remain integer-only),
41
+ but callers no longer need a second contract call to know whether `"473999900"` means 474 or
42
+ 474 million.
43
+ - **`paymentAttempted: { asset, amount, paymentId }`** on any failure occurring after the x402
44
+ payment has settled on chain. The issuer's error body carries neither the amount nor a
45
+ transaction reference, so such a debit was previously invisible in the response and discoverable
46
+ only by comparing `balanceOf` before and after. `paymentId` is the handle the issuer's idempotent
47
+ recovery endpoint takes.
48
+ - **Indeterminate-settlement recovery.** The issuer distinguishes a definitive facilitator
49
+ rejection from an outcome that is *unknown* — where the payment may well have landed and the
50
+ record is deliberately left recoverable. On the indeterminate code the wallet now polls the
51
+ issuer's status endpoint (bounded) and reports `recovery: { status, txHash?, vcId?, polls }`,
52
+ rather than discarding a credential already paid for. `status: "ISSUED"` means it exists after
53
+ all; note the status endpoint returns only its id, not the credential body.
54
+ - **`MbiError.mbiStatus`** — the issuer's own numeric status code, parsed from the error body.
55
+ Previously only the HTTP status and an opaque message string were available, so the two
56
+ post-payment failures could be told apart only by substring-matching or by trusting an HTTP 502
57
+ that any gateway can emit.
58
+ - **Explicit HTTP deadline** on the issuer client (90s, overridable), above the issuer's own 60s
59
+ facilitator timeout. Previously the runtime default applied, which happened to be longer but was
60
+ not a deliberate choice — a deadline at or below the issuer's would abort a settlement still
61
+ legitimately in progress.
62
+
63
+ ### Fixed
64
+
65
+ - **Failed balance lookups no longer report a fabricated zero.** Any failure — non-zero
66
+ `errorCode`, missing field, malformed payload — previously collapsed to `{ balance: '0' }`,
67
+ making an unreachable node indistinguishable from an empty wallet. Worse, because the
68
+ underlying helpers returned *normally*, a caller's `try`/`catch` never fired. Now surfaced as
69
+ `{ error: 'query_failed' }`. Both the ZTP20 and the native ZTX path had the same defect; both
70
+ are fixed.
71
+ - `originalPayment` is omitted for a cached credential that was issued free, rather than reported
72
+ as `{ asset: 'none', amount: '0' }` — matching the documented behaviour.
73
+
74
+ ### Documentation
75
+
76
+ - `query_contract` documented for the first time. It shipped in 0.5.0 but was never added to the
77
+ tool list. The documentation now also states plainly that it is a pass-through with no ABI or
78
+ method list — the contract decides what it understands, and an unknown method returns the same
79
+ shape as a typo.
80
+ - The `subscribe_and_issue` description previously claimed `schema` is returned on *every*
81
+ response; the cache path returns before the chain lookup, so it never did. Corrected, and the
82
+ cache path now performs the lookup so the claim holds.
83
+
84
+ ## [0.5.0] — 2026-07-27
85
+
86
+ ### Added
87
+
88
+ - Wallet BE account-activation checking: `activated`/`activationTxHash` fields,
89
+ `checkActivationStatus`, a bounded `waitForActivation` polling helper, and polling wired into
90
+ both first-run account creation and `create_holder_account`.
91
+ - x402 payment readiness — `pay_and_fetch` and `subscribe_and_issue` surface an insufficient-funds
92
+ shortfall as a structured result instead of throwing.
93
+ - Per-network JMYR token registry (`resolveTokenAddress`) and token-balance lookup on
94
+ `wallet_status`.
95
+ - `query_contract` — general-purpose read-only contract/account query, exposed as an agent tool.
96
+ - Template attribute validation and derivation, with the full declared schema surfaced.
97
+
98
+ ### Fixed
99
+
100
+ - A `resolveHolder` polling failure degrades instead of crashing startup.
101
+ - The `hsmPassword` is persisted alongside address/DID on account override, not dropped.
102
+
103
+ ## [0.4.0] — 2026-07-24
104
+
105
+ ### Added
106
+
107
+ - Local cache of issued credentials, keyed by template, so `subscribe_and_issue` does not pay and
108
+ re-issue for a credential already held.
109
+ - Named-template alias resolution (e.g. `"AI Birthcert"`), and an `agentDid` auto-fill gated on the
110
+ template's declared schema.
111
+
112
+ ### Fixed
113
+
114
+ - `revealAttributes` ordering to match the credential's signed field order, which was breaking BBS+
115
+ presentation verification.
116
+ - Free-template synchronous issuance handled in the issuer's phase 1.
117
+
118
+ ## [0.3.0] — 0.3.1
119
+
120
+ ### Added
121
+
122
+ - Live x401 proof integration: OID4VP submit authentication, DCQL reveal mapping, and an issuer-key
123
+ override for when the resolver is unreachable.
124
+ - Integration guide, presentation-submission fix, and the switch to the published
125
+ `x401-zetrix-client` package.
126
+
127
+ ## [0.2.0]
128
+
129
+ ### Added
130
+
131
+ - Optional `ZETRIX_ADDRESS`/`HOLDER_DID` onboarding, with `HSM_PASSWORD` guaranteed present.
132
+ - x402 asset symbol resolved from a ZTP20 contract's `contractInfo`.
133
+
134
+ ## [0.1.0]
135
+
136
+ Initial release — the five agent-facing tools (`wallet_status`, `prove_identity`, `pay_and_fetch`,
137
+ `subscribe_and_issue`, `create_holder_account`) over x401, x402, and issuer-side credential
138
+ issuance, with all signing through Wallet BE's HSM.
package/README.md CHANGED
@@ -13,18 +13,20 @@ through Wallet BE's HSM.
13
13
  - **holder key custody + signing** → Wallet BE softHSM (`/wallet/hsm/*`)
14
14
  - **VC issuance** → MBI RS (`/v1/vc/pay/*`)
15
15
 
16
- [`docs/USAGE_FLOW.md`](docs/USAGE_FLOW.md) has a full end-to-end prompt script (onboarding →
16
+ [`docs/USAGE_FLOW.md`](https://github.com/Zetrix-Chain/zetrix-agentic-wallet/blob/main/docs/USAGE_FLOW.md) has a full end-to-end prompt script (onboarding →
17
17
  VC issuance → identity proof → pay-per-use).
18
18
 
19
19
  ## Tools
20
20
 
21
21
  | Tool | Does | Input | Output (shape) |
22
22
  |---|---|---|---|
23
- | `wallet_status` | Report holder DID/address/network + held VCs (client-supplied, or the local cache) | `{ heldCredentials? }` | `{ holderDid, zetrixAddress, network, credentials }` |
23
+ | `wallet_status` | Report holder DID/address/network + held VCs (client-supplied, or the local cache); optionally a token balance | `{ heldCredentials?, token? }` | `{ holderDid, zetrixAddress, network, credentials, tokenBalance? }` |
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?, originalPayment?, paymentAttempted?, recovery? }` |
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
+ | `get_template_schema` | **Free** read of a VC template's declared attribute schema. Call before `subscribe_and_issue` to learn which attributes it requires | `{ templateId }` | `{ templateId, schema: { required, optional } }` or `{ templateId, error }` |
29
+ | `query_contract` | Read-only query against any Zetrix contract — call an arbitrary method and return its raw result. No signing, no state change | `{ contractAddress, method, params? }` | `{ ok: true, result }` or `{ ok: false, error }` |
28
30
 
29
31
  > **VCs are cached locally**, keyed by `templateId`, under `~/.agentic-wallet-mcp/vc-cache/`
30
32
  > (scoped per network + holder — different identities or networks never share a cache).
@@ -38,6 +40,40 @@ VC issuance → identity proof → pay-per-use).
38
40
  > back to the `expirationDate` requested at issuance; a VC with neither is cached indefinitely.
39
41
  > All Ed25519 signing still goes through Wallet BE HSM; no plaintext private keys.
40
42
 
43
+ > `subscribe_and_issue` also returns `schema: { required, optional }` — the template's full
44
+ > declared attribute list, read from chain — on every outcome that reaches the chain (issued,
45
+ > dry-run quote, or a missing-attribute error), so you always see the complete field list rather
46
+ > than only what went wrong — **including on a cache hit**, so a caller holding a credential can
47
+ > still see what the template currently asks for.
48
+ > Attributes the wallet auto-fills for you (e.g. `agentDid`, or a template-declared derived
49
+ > key like the `AI Birthcert` template's `id` ← `agentUsername`) are omitted from `schema` since
50
+ > you never need to supply them. Some templates also declare format validators for optional
51
+ > attributes (e.g. `AI Birthcert`'s `dob` must be `YYYY-MM-DD`, `countryOfOrigin` must be a valid
52
+ > ISO 3166 code or name) — an invalid value is rejected locally before any payment or MBI call.
53
+
54
+ > **A held credential can go stale without expiring.** `isVcValid` only asks whether a VC is past
55
+ > its `validUntil` — a VC whose *fields* no longer match the template it came from still reads as
56
+ > valid. When an issuer changes a template (adds a required attribute, drops one), a cache hit now
57
+ > reports the delta:
58
+ >
59
+ > ```jsonc
60
+ > { "fromCache": true, "staleAttributes": { "missing": ["agentUsername"], "dropped": ["agentName"] } }
61
+ > ```
62
+ >
63
+ > `missing` is what a reissue would need you to supply; `dropped` is what the held VC carries that
64
+ > the template no longer declares. Absent when the held VC still satisfies the template. **The
65
+ > cached VC is still returned** — this reports, it does not re-issue or charge. Reported from live
66
+ > testing: without it, an agent reusing the attributes from a previously-issued credential only
67
+ > discovered a newly-required field by attempting an issuance and being rejected.
68
+
69
+ > **`get_template_schema` is the free way to ask what a template needs**, and is what an agent
70
+ > should reach for before building an issuance request. It takes a `did:zid:...` id *or* a known
71
+ > template name (e.g. `"AI Birthcert"`), performs no payment/signing/MBI call, and hides the
72
+ > attributes the wallet fills in itself. A template it cannot read returns `{ error }` rather than
73
+ > an empty schema, so *"needs nothing"* is never confused with *"couldn't look it up"*.
74
+ > (`subscribe_and_issue` with `dryRun: true` also returns the schema, alongside the price — use
75
+ > that when you want both.)
76
+
41
77
  > `create_holder_account` mints a **brand-new** keypair — Wallet BE's `/account/create` has no
42
78
  > way to provision a pre-chosen address. It always checks first whether an account is already
43
79
  > active for this session; if so, it returns `{ alreadyExists: true, existing }` and creates
@@ -49,6 +85,49 @@ VC issuance → identity proof → pay-per-use).
49
85
  > in your MCP config always overrides the saved account (see Environment below); the tool never
50
86
  > writes the MCP host's own config file or restarts the server for you.
51
87
 
88
+ > **What a call cost is reported precisely.** `paidAsset`/`amountPaid` are set **only when this
89
+ > call paid**. A cache hit reports the earlier charge under `originalPayment: { txHash, asset,
90
+ > amount }` — never as `amountPaid` — so summing spend across calls cannot double-count a free
91
+ > hit (omitted entirely when the cached VC was issued free). If issuance fails **after** the x402
92
+ > payment has already settled on chain (MBI phase 2), the response carries
93
+ > `paymentAttempted: { asset, amount, paymentId }`. MBI's own error body reports none of these, so
94
+ > this is the only in-band signal that you were charged — and `paymentId` is the handle MBI's
95
+ > recovery endpoint takes. It is never set for a phase-1 failure, which happens before anything is
96
+ > paid. **Never retry a post-payment failure: the funds may already be gone, and each attempt costs
97
+ > the full amount again — look the `paymentId` up instead.**
98
+
99
+ > **Two post-payment failures exist and they mean different things:**
100
+ > `4006` is a *definitive* facilitator rejection, while `4012` (HTTP 502) means the settle outcome
101
+ > is **indeterminate** — MBI stopped listening but the payment may well have landed, so it
102
+ > deliberately leaves the record recoverable rather than marking it failed. On `4012` the wallet
103
+ > polls `GET /v1/vc/pay/status/{paymentId}` for you and reports
104
+ > `recovery: { status, txHash?, vcId?, polls }`:
105
+ >
106
+ > | `recovery.status` | Means |
107
+ > |---|---|
108
+ > | `ISSUED` | The payment landed and the credential exists after all. `/status` returns only its `vcId`, **not** the VC body — so `vc` stays unset and you fetch it separately. |
109
+ > | `FAILED` | MBI's terminal verdict on the payment. |
110
+ > | `REQUIRED` / `SETTLED` | Still unresolved when the poll budget ran out — check again later with the `paymentId`. |
111
+ > | `UNKNOWN` | `/status` itself was unreachable. The charge still stands; retry the lookup, not the payment. |
112
+
113
+ > `wallet_status({ token })` returns `tokenBalance: { token, balance, decimals }` for `ZTX` or any
114
+ > registered ZTP20 symbol (e.g. `JMYR`). `balance` is in the asset's **raw base units** — the same
115
+ > unit x402 quotes `maxAmountRequired` in, so cap checks and quote comparisons stay integer-only.
116
+ > Divide by `10^decimals` to display it: `"473999900"` with `decimals: 6` is `473.9999 JMYR`.
117
+ > `decimals` is `null` if the token's `contractInfo` can't be read (the balance is still returned).
118
+ > A failed lookup reports `{ token, error: "query_failed" }` and an unregistered symbol
119
+ > `{ token, error: "unknown_token" }` — **never a zero balance**, so "0" always means you really
120
+ > hold nothing rather than that the node was unreachable.
121
+
122
+ > `query_contract` is a **pass-through**: whatever you put in `method` is sent to the contract
123
+ > as-is. It performs no validation and holds no ABI or method list — the contract decides what it
124
+ > understands, and an unknown method comes back as `{ ok: false, error: "query_contract: no result
125
+ > value returned" }`, the same shape as a typo. To discover what a token supports, start with
126
+ > `contractInfo` (returns `symbol`, `decimals`, `protocol`, `supply`, …); a `protocol: "ztp20"`
127
+ > contract implements at least `balanceOf({ address })`, `totalSupply()` and
128
+ > `allowance({ owner, spender })`. Read-only only (`optType: 2`) — state-changing methods like
129
+ > `transfer`/`approve` need signing and are not reachable through this tool.
130
+
52
131
  > `revealAttribute` on `prove_identity` is optional and usually should stay that way. Omitted,
53
132
  > it's derived automatically from the challenge's DCQL `credential_requirements` — each claim path
54
133
  > is resolved against the presented VC's `credentialSubject` (e.g. a DCQL leaf name `agentName`
@@ -196,16 +275,19 @@ the `<...>` placeholders (don't commit a filled copy; `mcp.local.json` is gitign
196
275
  ## Example prompts
197
276
 
198
277
  - *"Check my wallet status."* → `wallet_status`
278
+ - *"What's my JMYR balance?"* → `wallet_status({ token: "JMYR" })` (divide `balance` by `10^decimals` to display it)
199
279
  - *"I got a 401 with this PROOF-REQUEST header — prove my identity and give me the PROOF-RESPONSE to replay."* → `prove_identity`
200
280
  - *"Fetch `https://api.example/data` and pay automatically if it asks."* → `pay_and_fetch`
281
+ - *"What fields does the AI Birthcert template need?"* → `get_template_schema` (free — do this before issuing)
201
282
  - *"Apply for the agent-identity credential with these attributes and pay for it."* → `subscribe_and_issue`
202
283
  - *"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)
284
+ - *"What's the total supply of this token contract?"* → `query_contract({ contractAddress, method: "totalSupply" })`
203
285
 
204
- For the full ordered script (onboarding → check → issue → prove → pay), see [`docs/USAGE_FLOW.md`](docs/USAGE_FLOW.md).
286
+ For the full ordered script (onboarding → check → issue → prove → pay), see [`docs/USAGE_FLOW.md`](https://github.com/Zetrix-Chain/zetrix-agentic-wallet/blob/main/docs/USAGE_FLOW.md).
205
287
 
206
288
  ## End-to-end usage flow
207
289
 
208
- Full narrative version with example prompts: [`docs/USAGE_FLOW.md`](docs/USAGE_FLOW.md).
290
+ Full narrative version with example prompts: [`docs/USAGE_FLOW.md`](https://github.com/Zetrix-Chain/zetrix-agentic-wallet/blob/main/docs/USAGE_FLOW.md).
209
291
 
210
292
  **Step 0 — onboarding (once).** Only if `ZETRIX_ADDRESS` isn't set yet: the MCP creates an HSM
211
293
  account automatically at startup from `HSM_PASSWORD` alone (see "Onboarding" under Environment
@@ -232,7 +314,7 @@ via MBI (`/vp/ext/create` + `/vp/ext/submit`, `includeVp: true`) → resolve the
232
314
  verification keys via the ZID resolver → submit to the verifier with wallet-auth headers →
233
315
  package the signed result as a `PROOF-RESPONSE`. Replaying that header back to the original
234
316
  resource server happens outside this MCP, in whatever drove the conversation. See "The
235
- `PROOF-REQUEST` header" section in [`docs/USAGE_FLOW.md`](docs/USAGE_FLOW.md) for the exact wire
317
+ `PROOF-REQUEST` header" section in [`docs/USAGE_FLOW.md`](https://github.com/Zetrix-Chain/zetrix-agentic-wallet/blob/main/docs/USAGE_FLOW.md) for the exact wire
236
318
  structure and field semantics.
237
319
 
238
320
  **Phase 4 — `pay_and_fetch` — pay-per-use.**