agentic-wallet-mcp 0.12.0 → 0.12.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +61 -0
- package/README.md +8 -4
- package/dist/server-bundle.cjs +585 -321
- package/package.json +1 -1
package/CHANGELOG.md
CHANGED
|
@@ -8,6 +8,67 @@ 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.2] — 24 September 2026
|
|
12
|
+
|
|
13
|
+
### Fixed
|
|
14
|
+
|
|
15
|
+
- **A taken `agentName` is no longer reported as an already-settled payment.** The credential service
|
|
16
|
+
answers both "this name is already taken" and "this exact payment was already settled" with the same
|
|
17
|
+
HTTP 409, and the wallet used to treat every 409 as the settled case — telling the user their fee was
|
|
18
|
+
most likely taken and warning that retrying would charge it again, when picking a different name was
|
|
19
|
+
actually free to try. The two are now told apart, and a taken name says only that: choose a different
|
|
20
|
+
`agentName`; it does not claim the fee was, or was not, taken.
|
|
21
|
+
- **A payment can no longer be discarded and paid again until it is genuinely stuck.** In a live run the
|
|
22
|
+
first payment had already settled, the assistant offered to "discard the stuck receipt and start
|
|
23
|
+
fresh" for a second 1 JMYR, and a bare "retry" from the user was taken as agreement, so the user paid
|
|
24
|
+
twice. The rule that a receipt only counts as stuck after 24 hours (`SETTLEMENT_STUCK_AFTER_MS`) was
|
|
25
|
+
only advice; the wallet now enforces it on both ways of discarding a receipt and pays nothing when the
|
|
26
|
+
receipt is younger. Set `SETTLEMENT_STUCK_AFTER_MS` lower on purpose if you need to start over sooner.
|
|
27
|
+
- **The agent can now tell the user how long the verification link really lasts.** The wallet used to
|
|
28
|
+
return the session's expiry untouched and leave the arithmetic to the agent, and in one run the agent
|
|
29
|
+
mixed up timezones and said the link was good for about 8 hours when it had about 15 minutes. Results
|
|
30
|
+
now carry `expiresIn` ("about 14 minutes") and `expiresInSeconds`, worked out by the wallet, and the
|
|
31
|
+
tool descriptions tell the agent to quote them.
|
|
32
|
+
- **A payment the credential service has not confirmed yet is now reported with what the service actually
|
|
33
|
+
said**, including its status code and message, plus a short sentence the agent can pass on to the user
|
|
34
|
+
as-is. Previously the agent had nothing real to quote and sometimes made up an explanation.
|
|
35
|
+
|
|
36
|
+
### Added
|
|
37
|
+
|
|
38
|
+
- `SSIVC_TRACE=1` prints every request to and response from the credential service to stderr, for
|
|
39
|
+
diagnosing a settlement that will not resolve. Off by default; the trace contains the payment receipt,
|
|
40
|
+
so it is for reproductions only.
|
|
41
|
+
|
|
42
|
+
## [0.12.1] — 24 September 2026
|
|
43
|
+
|
|
44
|
+
### Fixed
|
|
45
|
+
|
|
46
|
+
- **A refused receipt replay is now reported as a refusal, not as a pending settlement.** When the
|
|
47
|
+
credential service was reached and answered with a 4xx (for example a token failure on its side),
|
|
48
|
+
the wallet used to say "PAYMENT SENT — nothing has gone wrong", even when something had. It now
|
|
49
|
+
returns `issuerRejected: true` with the service's own message and status code, and
|
|
50
|
+
`paymentInvalid: true` when the service specifically ruled the payment invalid. Neither says
|
|
51
|
+
whether the fee was taken, in either direction. The receipt is still kept and the call stays
|
|
52
|
+
retryable through `check_ai_birthcert_verification`. Indeterminate, void and already-settled
|
|
53
|
+
answers are deliberately not treated as refusals.
|
|
54
|
+
- **A receipt the payment service has declared void is discarded automatically**, so the next
|
|
55
|
+
request pays fresh instead of replaying a dead receipt forever. The discarded receipt id is
|
|
56
|
+
returned so the lost payment stays traceable.
|
|
57
|
+
- **No message denies a payment this call itself made.** On a fresh purchase that then fails to
|
|
58
|
+
settle, the wallet no longer says "no new payment was made" or "nothing is lost".
|
|
59
|
+
- **The already-settled answer no longer invites a second charge.** It now says the fee was most
|
|
60
|
+
likely taken and asks for the user's explicit agreement before retrying.
|
|
61
|
+
- **`discardedPaymentReceipt` is returned on every result after a discard**, including errors and
|
|
62
|
+
pending results. If the same call also discarded a void receipt, both ids are given.
|
|
63
|
+
- **Tool descriptions no longer tell an agent an undetermined settlement is "progressing".** Both
|
|
64
|
+
`request_ai_birthcert_verification` and `check_ai_birthcert_verification` now separate a
|
|
65
|
+
confirmed-queued settlement from one whose outcome could not be determined.
|
|
66
|
+
|
|
67
|
+
### Tests
|
|
68
|
+
|
|
69
|
+
- Every agent-facing tool and parameter description is now pinned by a reviewed snapshot plus
|
|
70
|
+
guards against false money claims and unsafe retry advice.
|
|
71
|
+
|
|
11
72
|
## [0.12.0] — 22 September 2026
|
|
12
73
|
|
|
13
74
|
### Added
|
package/README.md
CHANGED
|
@@ -28,9 +28,12 @@ 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
|
-
| `
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
31
|
+
| `get_policy_template_schema` | **Free** read of a POLICY template's declared attribute vocabulary. This is the only vocabulary that means anything on chain — the policy contract validates nothing, so an attribute outside this list deploys cleanly and then enforces nothing at all. Accepts `{ publisher, policyKey }` or `{ templateId }`. A template that cannot be read reports `{ error }` rather than `{ found: false }`, so "no such template" is never confused with "could not look it up" | `{ templateId?, publisher?, policyKey? }` | `{ found, attributes?, error? }` |
|
|
32
|
+
| `get_my_policy` | **Free** read of the spending policies this owner has deployed on chain; defaults to this wallet's own address. Costs 2 + N chain calls and warns above 50 keys. An owner who has never deployed one is a normal absence, **not** an error — the policy contract is created lazily on first write — and a failed lookup keeps its own error state, so "we could not list your policies" is never presented as "you have none" | `{ owner? }` | `{ owner, policies?, warning?, error? }` |
|
|
33
|
+
| `policy_preflight` | **Free** check of a draft policy *before* it is deployed. Two questions: is it well-formed (`blockers` lists every problem at once, so one round of fixes is enough), and — more important — does it MEAN what the user thinks (`interpretation` states in plain words what it actually does). Always show `interpretation`, **including when `ready` is true**: a valid policy can still mean something unintended — a spending cap with no window is a LIFETIME cap, not a monthly one. `notChecked` lists what could not be verified, including whether the policy would be enforced at all | `{ policyKey, attributes }` | `{ ready, interpretation, blockers, notChecked }` |
|
|
34
|
+
| `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, expiresIn, expiresInSeconds }` (`expiresIn` / `expiresInSeconds` are worked out by the wallet from its own clock, so an agent quotes `expiresIn` instead of doing timezone arithmetic itself), `{ 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, **refuses a receipt that is not stuck yet** (younger than `SETTLEMENT_STUCK_AFTER_MS`, 24h by default — enforced by the wallet, so an agent that mistakes a bare "retry" for consent still cannot cause a second payment; nothing is discarded and nothing is paid), 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 |
|
|
35
|
+
| `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` and the wallet's own `expiresIn` / `expiresInSeconds` (time left, worked out from the wallet clock, never from the agent's) — 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 |
|
|
36
|
+
| `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. A receipt younger than `SETTLEMENT_STUCK_AFTER_MS` (24h by default) is refused on both steps. 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? }` |
|
|
34
37
|
|
|
35
38
|
> **VCs are cached locally**, keyed by `templateId`, under `~/.agentic-wallet-mcp/vc-cache/`
|
|
36
39
|
> (scoped per network + holder — different identities or networks never share a cache).
|
|
@@ -185,7 +188,8 @@ Node ≥ 18 required (built-in `fetch`).
|
|
|
185
188
|
| `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 |
|
|
186
189
|
| `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
190
|
| `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"*.
|
|
191
|
+
| `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"*. It also decides when starting over is allowed: a receipt younger than this is **refused** by both discard routes (`discardStuckReceiptAndPayFresh` and `clear_stuck_payment_receipt`), with nothing discarded and nothing paid, so a very low value makes the second-fee path available after a shorter wait for a settlement that may still resolve. Lower it only deliberately, for an operator or a test. Either side of the line the wallet never pays again or discards the receipt by itself. **Defaults to `86400000` (24h); a non-positive or unparseable value also falls back to `86400000`** |
|
|
192
|
+
| `SSIVC_TRACE` | no | Set to exactly `1` to print every request to and response from SSIVC (headers, body, status) to **stderr**, for diagnosing a settlement that will not resolve. Off by default. **Reproduction only:** the trace contains the payment receipt, which is a bearer handle on a real payment, plus the request signature — never leave it on, never ship it to a log pipeline. Nothing is ever written to stdout. |
|
|
189
193
|
|
|
190
194
|
\* sensitive — never logged, never returned in a tool result, and never a tool parameter.
|
|
191
195
|
|