agentic-wallet-mcp 0.11.0 → 0.12.1

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 CHANGED
@@ -8,6 +8,80 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
8
8
  > Entries for 0.5.0 and earlier were reconstructed from commit history when this file was
9
9
  > introduced in 0.6.0, so they summarise each release rather than being exhaustive.
10
10
 
11
+ ## [0.12.1] — 24 September 2026
12
+
13
+ ### Fixed
14
+
15
+ - **A refused receipt replay is now reported as a refusal, not as a pending settlement.** When the
16
+ credential service was reached and answered with a 4xx (for example a token failure on its side),
17
+ the wallet used to say "PAYMENT SENT — nothing has gone wrong", even when something had. It now
18
+ returns `issuerRejected: true` with the service's own message and status code, and
19
+ `paymentInvalid: true` when the service specifically ruled the payment invalid. Neither says
20
+ whether the fee was taken, in either direction. The receipt is still kept and the call stays
21
+ retryable through `check_ai_birthcert_verification`. Indeterminate, void and already-settled
22
+ answers are deliberately not treated as refusals.
23
+ - **A receipt the payment service has declared void is discarded automatically**, so the next
24
+ request pays fresh instead of replaying a dead receipt forever. The discarded receipt id is
25
+ returned so the lost payment stays traceable.
26
+ - **No message denies a payment this call itself made.** On a fresh purchase that then fails to
27
+ settle, the wallet no longer says "no new payment was made" or "nothing is lost".
28
+ - **The already-settled answer no longer invites a second charge.** It now says the fee was most
29
+ likely taken and asks for the user's explicit agreement before retrying.
30
+ - **`discardedPaymentReceipt` is returned on every result after a discard**, including errors and
31
+ pending results. If the same call also discarded a void receipt, both ids are given.
32
+ - **Tool descriptions no longer tell an agent an undetermined settlement is "progressing".** Both
33
+ `request_ai_birthcert_verification` and `check_ai_birthcert_verification` now separate a
34
+ confirmed-queued settlement from one whose outcome could not be determined.
35
+
36
+ ### Tests
37
+
38
+ - Every agent-facing tool and parameter description is now pinned by a reviewed snapshot plus
39
+ guards against false money claims and unsafe retry advice.
40
+
41
+ ## [0.12.0] — 22 September 2026
42
+
43
+ ### Added
44
+
45
+ - **Three free policy read tools**: `get_policy_template_schema` (which spending rules a template
46
+ allows), `get_my_policy` (the policies this owner has deployed on chain) and `policy_preflight`
47
+ (is a draft policy valid, and does it MEAN what the user thinks). All read-only: they sign
48
+ nothing, spend nothing and deploy nothing. Each reports a three-state result, so "this network has
49
+ no policy system" is never reported as "you have no policy".
50
+ - **`clear_stuck_payment_receipt`**: the supported way to discard a Verified AI Birthcert payment
51
+ receipt the wallet is holding and refusing to pay past — previously only possible by deleting a
52
+ file on the server, which a hosted subscriber cannot do. Two steps by construction: the first call
53
+ clears nothing and returns the receipt id, and clearing requires echoing that exact id back.
54
+ - **`request_ai_birthcert_verification` gains `discardStuckReceiptAndPayFresh`**: discard a stuck
55
+ receipt and pay again in one call. Takes the receipt id, never a boolean, so a receipt cannot be
56
+ discarded that was not first shown to the user. Refuses a mismatched id, a receipt belonging to a
57
+ live session, and combination with `dryRun` — spending nothing in each case.
58
+ - **`SETTLEMENT_STUCK_AFTER_MS`** (default `86400000`, 24h): how long an unconfirmed settlement may
59
+ stay unresolved before the wallet stops calling it "still settling" and calls it permanently stuck.
60
+
61
+ ### Changed
62
+
63
+ - **`check_ai_birthcert_verification` now advances a queued settlement** instead of only reporting
64
+ it. It replays the saved receipt — never a new payment — and returns the live session once it
65
+ settles, so "check back in a few minutes" genuinely progresses the flow.
66
+ - **A queued settlement no longer blocks the tool call for ~20 minutes.** It returns within a bounded
67
+ wall-clock budget (`SETTLEMENT_WAIT_BUDGET_MS`, default 90s) as
68
+ `{ settlementPending: true, paymentReceipt, message }` — the payment succeeded and is in flight,
69
+ which is not an error.
70
+ - **Settlement messages lead with their verdict** (`PAYMENT SENT` / `OUTCOME UNKNOWN` / `RECEIPT VOID`)
71
+ and carry `paymentReceipt` as its own field, so an assistant summarising the result cannot turn
72
+ "we do not know yet" into "it failed".
73
+ - **The three settlement verdicts are now told apart.** A receipt the service declares finished is
74
+ terminal and reported as `status: "receipt_void"`; an unresolved one is judged by the receipt's own
75
+ age — recent means "still settling, check back", older than `SETTLEMENT_STUCK_AFTER_MS` means
76
+ "this is not coming back". None of them claims the fee was refunded: a settlement can expire after
77
+ the money has already moved, so the receipt id is always handed back for support.
78
+
79
+ ### Fixed
80
+
81
+ - A stuck receipt no longer dead-ends. The wallet still refuses to buy a new credential while one is
82
+ held — that refusal is what prevents a second charge — but it now names the way out and states
83
+ plainly that starting over costs the fee a second time.
84
+
11
85
  ## [0.11.0] — 17 September 2026
12
86
 
13
87
  ### Added
package/README.md CHANGED
@@ -28,8 +28,9 @@ VC issuance → identity proof → pay-per-use).
28
28
  | `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 }` |
29
29
  | `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 }` |
30
30
  | `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 }` |
31
- | `request_ai_birthcert_verification` | Start a **Verified** AI Birthcert issuance session with myid (MyDigital ID owner verification) — distinct from `subscribe_and_issue`'s self-declared Basic AI Birthcert. Optional `gasPayer` (`"sponsored"` \| `"self"`) overrides the gas payer for this call only — precedence is per-call `gasPayer` > `GAS_PREFERENCE` config > `sponsored` hardcoded default. If a sponsored quote is refused before any money moved, the wallet automatically falls back to paying gas itself; it never falls back once payment is merely pending/indeterminate. `dryRun: true` quotes instead of paying: it stops after the 402 challenge, pays nothing, creates no session, and returns `{ quote }` — the asset, the amount and which side pays gas. Quote and paid request send byte-identical signed bodies | `{ agentName, agentPurpose?, evidenceAssuranceLevel?, ownerType?, ownerVerified?, gasPayer?, dryRun? }` | `{ sessionId, verificationUrl, expiresAt }` or `{ quote: { asset, maxAmountRequired, payTo?, gasModel } }` |
32
- | `check_ai_birthcert_verification` | **Free.** Poll the most recently requested Verified AI Birthcert session; on `status: "issued"`, also fetches, verifies, and caches the credential. While the session is still open it replays the stored `verificationUrl` alongside SSIVC's live `expiresAt` — SSIVC issues the link only once, at creation, so this store is the only place it survives. That makes *"where is my link?"* answerable without touching the paid tool. No link is returned once `status` is `"issued"`: the link is spent | (none) | `{ status: "pending" \| "issued" \| "no_session", verificationUrl?, expiresAt?, vcId?, vc?, cacheError? }` |
31
+ | `request_ai_birthcert_verification` | Start a **Verified** AI Birthcert issuance session with myid (MyDigital ID owner verification) — distinct from `subscribe_and_issue`'s self-declared Basic AI Birthcert. Optional `gasPayer` (`"sponsored"` | `"self"`) overrides the gas payer for this call only — precedence is per-call `gasPayer` > `GAS_PREFERENCE` config > `sponsored` hardcoded default. If a sponsored quote is refused before any money moved, the wallet automatically falls back to paying gas itself; it never falls back once payment is merely pending/indeterminate. `dryRun: true` quotes instead of paying: it stops after the 402 challenge, pays nothing, creates no session, and returns `{ quote }` — the asset, the amount and which side pays gas. Quote and paid request send byte-identical signed bodies | `{ agentName, agentPurpose?, evidenceAssuranceLevel?, ownerType?, ownerVerified?, gasPayer?, dryRun?, discardStuckReceiptAndPayFresh? }` | `{ sessionId, verificationUrl, expiresAt }`, `{ quote: { asset, maxAmountRequired, payTo?, gasModel } }`, or — instead of a session — `{ settlementPending: true, paymentReceipt, message }` after ~90s, whose meaning depends on which flag (if any) is set below: never assume the payment succeeded from this shape alone. While a merely-stuck receipt (outcome still unresolved) is held this tool can only REPLAY it — it never buys a new credential, which is what stops a second charge. A credential-service refusal is different again: `issuerRejected: true` means the service was reached and refused the request (the settlement is NOT what failed); `paymentInvalid: true` means the service specifically ruled the payment or receipt invalid (this one IS about the payment). Neither tells you whether the fee was taken, in either direction. A **void** receipt (SSIVC status_code 67/68) is different again: the wallet discards it itself, with no confirmation and no flag, so the very next call is an ordinary fresh purchase — see `{ error }` below. `discardStuckReceiptAndPayFresh: "<exact receipt id>"` is the way to start over on a merely-stuck receipt instead: it throws that payment away and pays the fee again, refuses a mismatched id (discarding and paying nothing), refuses a receipt that belongs to a live session, and returns `discardedPaymentReceipt` so the lost payment stays traceable. `{ error }` (`RequestVerificationFailure`) also carries `discardedPaymentReceipt` when this call itself discarded a dead receipt — that confirmed stuck-receipt discard, or an unprompted void one, or **both on one call**; it also rides on `settlementPending` results, not only successes. When both happen (the confirmed discard, then the fresh payment ruled void) the field holds the **void** receipt and the id the user confirmed is named at the end of the `error`/`message` text ("ALSO DISCARDED earlier on this same call") — quote both ids to support, not just the field. Retrying is fine only for an insufficient-funds or payment-cap `{ error }` (nothing was paid); any other `{ error }` (already-settled, receipt not saved, receipt void, outcome unknown) may mean money moved, so get the user's explicit agreement before calling again |
32
+ | `check_ai_birthcert_verification` | **Free** (never pays). Poll the most recently requested Verified AI Birthcert session; on `status: "issued"`, also fetches, verifies, and caches the credential. While the session is still open it replays the stored `verificationUrl` alongside SSIVC's live `expiresAt` — SSIVC issues the link only once, at creation, so this store is the only place it survives. That makes *"where is my link?"* answerable without touching the paid tool. No link is returned once `status` is `"issued"`: the link is spent. If a prior payment is still clearing this tool **advances** it — it replays the saved receipt (never a new payment) and returns the live session once it settles, so "check back in a few minutes" genuinely progresses the flow | (none) | `{ status: "pending" | "issued" | "no_session" | "settlement_pending" | "receipt_void", verificationUrl?, expiresAt?, vcId?, vc?, cacheError?, paymentReceipt?, outcomeUnknown?, issuerRejected?, paymentInvalid?, message? }` — `settlement_pending` means a payment was made and is being followed: without `outcomeUnknown`, `issuerRejected` or `paymentInvalid` the message leads `PAYMENT SENT` and covers **two** states that the following clause tells apart — *"still being processed"* is a confirmed queued settlement (check again shortly), while *"has not been confirmed yet"* means the outcome could not be determined at all yet, so it must not be reported as progressing or as succeeded; either way the receipt is saved, nothing is paid again, and checking again later is the next step; with `outcomeUnknown: true` the outcome could not be determined at all and has been unresolved longer than `SETTLEMENT_STUCK_AFTER_MS` (24h default), so it is not coming back on its own — `stuckFor` says how long, the fee was most likely already taken, and quoting `paymentReceipt` to support is the way forward. Starting over via `clear_stuck_payment_receipt` costs a second fee. `issuerRejected: true` is a distinct case: the credential service was reached and refused the request (a `4xx` that is not a settlement verdict — measured live as `status_code 99` "Error retrieving ZVG access token"), so the settlement is NOT what failed; `message` quotes what it said, the receipt is kept, no new payment was made, and it can clear once the service recovers. `paymentInvalid: true` is different again: the service has specifically ruled the payment or receipt invalid — this one IS about the payment. Both say nothing about whether the fee was taken, in either direction. `receipt_void` is terminal and different: SSIVC has ruled the receipt finished (status_code 67 expired / 68 failed), so checking again cannot help. The wallet **discards the dead receipt itself** here — no confirmation needed, because a receipt the server will never honour protects nothing and only blocks the next purchase — and returns it as `discardedPaymentReceipt`. It does NOT mean the fee was refunded, and buying the credential means calling `request_ai_birthcert_verification` again, which pays the fee again |
33
+ | `clear_stuck_payment_receipt` | **Free, last resort, destructive.** Discard a stuck Verified AI Birthcert payment receipt the wallet is holding and refusing to pay past — the supported replacement for deleting `<stateDir>/ssivc-session.json` by hand, which a hosted subscriber cannot do. Two steps by construction: with no argument it clears nothing and returns the receipt id plus a warning; clearing requires echoing that exact id back as `confirmReceiptId`, and a mismatch clears nothing. If the settlement completed in between — which is what happens when you follow the advice to call `check_ai_birthcert_verification` first — the receipt now belongs to a **live, paid-for session**, and the id alone no longer clears it: the tool hands back `sessionId` and `verificationUrl` instead, and only `confirmDiscardLiveSession: true` will discard them. The discarded payment is unrecoverable | `{ confirmReceiptId?, confirmDiscardLiveSession? }` | `{ cleared, paymentReceipt?, requiresConfirmation?, sessionId?, verificationUrl?, message?, error? }` |
33
34
 
34
35
  > **VCs are cached locally**, keyed by `templateId`, under `~/.agentic-wallet-mcp/vc-cache/`
35
36
  > (scoped per network + holder — different identities or networks never share a cache).
@@ -183,6 +184,8 @@ Node ≥ 18 required (built-in `fetch`).
183
184
  | `AI_BIRTHCERT_VERIFIED_TEMPLATE_ID` | no | The Verified AI Birthcert's on-chain `did:zid:...` template id. Auto-derived per network by default — **the mainnet default is unverified**, so override this explicitly once the mainnet template id is confirmed |
184
185
  | `GAS_PREFERENCE` | no | `sponsored` or `self` — the default gas payer for the Verified AI Birthcert flow (`request_ai_birthcert_verification`). **Defaults to `sponsored`; any unrecognised value also falls back to `sponsored`** rather than throwing. Sponsorship only actually engages when the resource server's x402 quote offers a sponsored option (`extra.gasModel: "facilitator"`) — a server that only quotes self-pay behaves unchanged. **Sponsorship is testnet-only today**; override per-call with the tool's `gasPayer` parameter |
185
186
  | `MAX_SETTLEMENT_ATTEMPTS` | no | Cap on retry attempts while polling a sponsored payment for settlement after SSIVC returns `202 Accepted` (queued). **Defaults to `20`; a non-positive or unparseable value also falls back to `20`** |
187
+ | `SETTLEMENT_WAIT_BUDGET_MS` | no | Total time `request_ai_birthcert_verification` **and** `check_ai_birthcert_verification` will each wait for a queued settlement before returning `{ settlementPending: true }` / `{ status: "settlement_pending" }` — `check_` advances the settlement too, so it can block for this long. The attempt cap alone cannot bound this, because the delay between attempts is server-supplied. At least one poll always happens, even if the budget is shorter than the first retry delay. Not capped at the top end — raising it *and* `MAX_SETTLEMENT_ATTEMPTS` together can reinstate the long blocking call this budget exists to prevent, so raise it deliberately and temporarily. **Defaults to `90000` (90s); a non-positive or unparseable value also falls back to `90000`** |
188
+ | `SETTLEMENT_STUCK_AFTER_MS` | no | How long a settlement SSIVC cannot confirm may stay unresolved before `check_ai_birthcert_verification` stops calling it "still settling" and calls it permanently stuck. SSIVC returns the same `status_code 69` for a settlement two minutes old and one three weeks old, so the receipt's own age is what separates *"payment sent, check back shortly"* from *"this is not coming back, the fee was most likely already taken, and starting over costs a second fee"*. This changes **only the wording**: either side of the line the wallet never pays again and never discards the receipt by itself. **Defaults to `86400000` (24h); a non-positive or unparseable value also falls back to `86400000`** |
186
189
 
187
190
  \* sensitive — never logged, never returned in a tool result, and never a tool parameter.
188
191