zetrix-agentic-wallet 0.3.4

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 ADDED
@@ -0,0 +1,177 @@
1
+ # Zetrix Agentic Wallet — OpenClaw plugin
2
+
3
+ **Using the wallet?** See the
4
+ **[user guide](https://github.com/Zetrix-Chain/zetrix-agentic-wallet/blob/main/openclaw-plugin/USER_GUIDE.md)**
5
+ — install, first steps, spending limits and backup, written for subscribers. It also ships inside this
6
+ package as `USER_GUIDE.md`. This file is the engineering detail: why the plugin is built the way it is,
7
+ and what to know before changing it.
8
+
9
+ ---
10
+
11
+ Installs the Zetrix Agentic Wallet into an OpenClaw gateway, so a subscriber gets the wallet by
12
+ installing one package instead of hand-writing an `mcp.servers` entry — which they cannot do at all on
13
+ a hosted gateway they do not own.
14
+
15
+ ```bash
16
+ openclaw plugins install clawhub:zetrix-agentic-wallet@<version>
17
+ openclaw plugins enable zetrix-agentic-wallet
18
+ openclaw gateway restart
19
+ ```
20
+
21
+ Nothing else is required. No `npx`, no registry access at runtime, no MCP configuration, and **no
22
+ password**: the wallet generates and stores its own credentials.
23
+
24
+ ## Build
25
+
26
+ ```bash
27
+ npm run build # in the repo root — produces the wallet bundle
28
+ npm run build # in openclaw-plugin — bundles the plugin and vendors the wallet
29
+ npm test # 56 tests: registration logic, paths, and the package contract
30
+ ```
31
+
32
+ ## Configuration
33
+
34
+ Set by the subscriber, through the gateway's plugin config UI or `plugins.entries`:
35
+
36
+ | Field | Default | Meaning |
37
+ |---|---|---|
38
+ | `network` | `zetrix:testnet` | Mainnet spends real funds |
39
+ | `maxPaymentAmount` | `{"*":"0"}` | Per-asset ceiling in raw units. **Refuses every payment until set** |
40
+ | `zetrixAddress` | *(unset)* | Pin an existing holder account instead of creating one |
41
+
42
+ These defaults apply with no action from the subscriber, so a fresh install is safe but **cannot pay**
43
+ until a cap is set. Changing config takes effect without a gateway restart:
44
+
45
+ ```bash
46
+ openclaw config set plugins.entries.zetrix-agentic-wallet.config.maxPaymentAmount \
47
+ '{"ZTX":"1000000000","*":"0"}' --json
48
+ openclaw mcp reload
49
+ ```
50
+
51
+ ⚠️ **Configure through `plugins.entries`, never by editing `mcp.servers` directly.** The plugin owns
52
+ that entry and rewrites it from plugin config, so a hand-edit is the wrong layer in two ways. It is
53
+ overwritten the next time the plugin registers — and if it is *not* overwritten, that is worse: the
54
+ plugin fingerprints the entry it wrote and disowns anything that no longer matches, so a hand-edited
55
+ entry is left permanently unmanaged and stops tracking config changes. Neither failure announces itself.
56
+
57
+ Setting plugin config re-registers the entry immediately, because the CLI loads the plugin in-process
58
+ (see [If the MCP entry goes missing](#if-the-mcp-entry-goes-missing)). Editing `openclaw.json` by hand
59
+ does not.
60
+
61
+ ## Why it works the way it does
62
+
63
+ Three things were tested against a live gateway (OpenClaw **2026.7.1-2**) and shaped this design. Each
64
+ is stated here with what was observed, because the design looks odd without it — and because if any of
65
+ them changes upstream, the corresponding workaround should be deleted rather than carried forever.
66
+
67
+ **The manifest declares no `mcpServers`, and that is deliberate.** It is the documented way for a plugin
68
+ to contribute an MCP server, but **it is not a field this OpenClaw release implements** — the ClawHub
69
+ Plugin Inspector lists every supported `PluginManifest` field for 2026.7.1-2 and `mcpServers` is not
70
+ among them. So a declared server is silently dropped: never spawned, no diagnostic. Native `api.registerTool` tools do register, but never
71
+ reach a `claude-cli` harness, so an agent cannot call them. Writing the entry into `mcp.servers` from
72
+ the registration hook is the only route that works end to end. *If the manifest route is ever fixed
73
+ upstream, `src/index.ts` is what should be deleted.*
74
+
75
+ **The wallet is vendored into the plugin, as a single CJS bundle.** `openclaw plugins install` is a
76
+ directory copy, not an npm install: a declared dependency produced no `node_modules` in the installed
77
+ copy, and `files` was ignored. So the wallet must ship inside the plugin.
78
+
79
+ Vendoring a real `npm install` tree was the first attempt, and it is the more faithful one — the wallet
80
+ publishes with `zetrix-sdk-nodejs` external, so that is the configuration it is tested in. It had to be
81
+ abandoned: ~8,250 files produced a tarball **OpenClaw cannot install**.
82
+
83
+ ```
84
+ failed to extract archive: Error: extract tar timed out after 120000ms
85
+ ```
86
+
87
+ A tarball is how ClawHub ships, so an unextractable package is fatal rather than merely slow. One
88
+ bundled file is 3.4 MB, packs to a 644 KB / 7-file tarball, and installs instantly.
89
+
90
+ ⚠️ **The bundle must be CJS.** The SDK and its dependencies use dynamic `require`, which esbuild cannot
91
+ express in ESM output — an ESM bundle loads and then dies at first use with *"Dynamic require of
92
+ `buffer` is not supported"*. In CJS `require` stays native, and both `account.getInfo` and the
93
+ protobufjs-heavy `contract.call` were verified working through the bundle against a live node. A test
94
+ asserts the format, because this fails at *call* time, not at load.
95
+
96
+ **The runtime is then copied *out* of the plugin, and so is the ownership marker.** OpenClaw exposes no
97
+ uninstall hook a plugin can use — `api.lifecycle` offers only `registerRuntimeLifecycle`, and a
98
+ disabled plugin is never loaded, so no cleanup code of ours can ever run. Two consequences:
99
+
100
+ - Pointing the config entry at the plugin's own directory would leave a **broken** server behind on
101
+ uninstall. Pointing it at `<gatewayStateDir>/zetrix-agentic-wallet/runtime` leaves a **working** one.
102
+ - The ownership marker lived inside the plugin at first, and `plugins install --force` wiped it — after
103
+ which the plugin treated its own entry as subscriber-owned and refused to manage it, orphaning the
104
+ entry on every update. It now lives beside the runtime.
105
+
106
+ ## If the MCP entry goes missing
107
+
108
+ Removing `mcp.servers["zetrix-agentic-wallet"]` by hand **stops the wallet working immediately** — the
109
+ tools disappear, because there is no server for the agent to call. The plugin still reports as enabled;
110
+ it just provides nothing.
111
+
112
+ The entry is self-healing, but not on the trigger you would expect. Tested on 2026.7.1-2:
113
+
114
+ | Action | Entry restored? |
115
+ |---|---|
116
+ | `openclaw gateway restart` | **No** — waited two minutes, no registration in the logs |
117
+ | `openclaw plugins install` / `enable` | Yes |
118
+ | `openclaw plugins inspect <id> --runtime` | Yes |
119
+ | `openclaw agent …` | Yes |
120
+
121
+ The registration hook runs when the **CLI** loads the plugin in-process, not on a gateway restart. So if
122
+ the entry is ever lost, the fix is any plugin-loading command:
123
+
124
+ ```bash
125
+ openclaw plugins inspect zetrix-agentic-wallet --runtime
126
+ ```
127
+
128
+ Worth knowing for two reasons: a restart is the natural thing to reach for and it will not help, and a
129
+ subscriber who deletes the entry to disable the wallet will find it back after the next plugin
130
+ operation. To disable the wallet properly, disable the plugin.
131
+
132
+ ## Uninstalling completely
133
+
134
+ Because there is no uninstall hook, removal is two steps. The wallet keeps working after step 1, which
135
+ is intentional — it is a wallet holding an account, and silently breaking it would be worse.
136
+
137
+ ```bash
138
+ openclaw plugins uninstall zetrix-agentic-wallet --force
139
+ openclaw mcp unset zetrix-agentic-wallet
140
+ ```
141
+
142
+ ⚠️ **Before deleting `<gatewayStateDir>/zetrix-agentic-wallet/`, back up the wallet.** It holds the
143
+ holder identity and the generated HSM password, and that password is the only thing that can authorize
144
+ signing for the account. Run `npx agentic-wallet-mcp export-credentials` in an interactive terminal
145
+ first. There is no recovery once it is gone.
146
+
147
+ Two things to know if `mcp unset` fails: OpenClaw rejects config writes that shrink the file sharply
148
+ (`Config write rejected … size-drop`), which a legitimate removal can trigger, and
149
+ `plugins uninstall --force` may leave the plugin directory behind — OpenClaw auto-loads anything under
150
+ `extensions/`, so the plugin keeps reporting as enabled until that directory is deleted.
151
+
152
+ ## Caveats for review
153
+
154
+ - **This plugin writes to `mcp.servers`, which is documented as the operator's authoritative override
155
+ surface.** It never overwrites an entry it did not create, but the inversion is real and was a
156
+ deliberate choice made only after the two supported routes were shown not to work.
157
+ - **Writing config directly bypasses OpenClaw's own write validation**, including the size-drop guard
158
+ above. Our writes only add keys, so they cannot shrink the file, but the bypass is worth knowing.
159
+ - **`plugins.allow` is a whitelist, not additive trust.** If you set it, enumerate every required
160
+ plugin — setting it to a single id excluded the agent harness during testing and left the agent
161
+ unable to run at all.
162
+
163
+ ## Validating before publish
164
+
165
+ ```bash
166
+ npm run build # from the repo root
167
+ node scripts/build.mjs # from openclaw-plugin
168
+ clawhub package validate . --openclaw-version <target>
169
+ ```
170
+
171
+ Run this before treating any plugin change as done. It extracts the real `PluginManifest` type from the
172
+ target OpenClaw release and checks the manifest against it, which catches a class of mistake nothing else
173
+ does: a field that is silently ignored rather than rejected. It found two in this plugin — `uiHints`
174
+ instead of `configUiHints`, and confirmed `mcpServers` is not implemented in 2026.7.1-2 at all.
175
+
176
+ Reports land in `reports/` (gitignored). `--runtime --allow-execute` additionally imports the plugin code
177
+ in an isolated workspace.
package/USER_GUIDE.md ADDED
@@ -0,0 +1,233 @@
1
+ # Zetrix Agentic Wallet — user guide
2
+
3
+ Gives your OpenClaw agent a Zetrix wallet. It can prove who you are, collect verifiable credentials,
4
+ and pay for pay-per-use resources — without you creating an account, choosing a password, or editing
5
+ any configuration.
6
+
7
+ **Two things to know before you start:**
8
+
9
+ - The wallet **creates its own account and password**. You never type either. That also means the
10
+ credentials exist only on this machine — [back them up](#backing-up-your-wallet).
11
+ - It **will not pay for anything** until you [set a spending limit](#setting-your-spending-limit).
12
+ That is deliberate, not a fault.
13
+
14
+ ---
15
+
16
+ ## 1. Install
17
+
18
+ ```bash
19
+ openclaw plugins install clawhub:zetrix-agentic-wallet
20
+ openclaw plugins enable zetrix-agentic-wallet
21
+ openclaw gateway restart
22
+ ```
23
+
24
+ Check it worked:
25
+
26
+ ```bash
27
+ openclaw plugins list # zetrix-agentic-wallet, enabled
28
+ openclaw mcp doctor # zetrix-agentic-wallet: ok
29
+ openclaw mcp probe zetrix-agentic-wallet # 10 tools
30
+ ```
31
+
32
+ Nothing else is needed. No `npx`, no account signup, no password, no MCP configuration.
33
+
34
+ The first time the wallet runs it creates a Zetrix account on **testnet** and stores it under
35
+ `~/.openclaw/zetrix-agentic-wallet/state/`.
36
+
37
+ ## 2. Try it
38
+
39
+ Open the dashboard and ask in plain language — you do not need to name tools:
40
+
41
+ ```bash
42
+ openclaw dashboard
43
+ ```
44
+
45
+ > **What is my Zetrix wallet address?**
46
+
47
+ You should get an address starting `ZTX3…`, a holder DID, and the network (`zetrix:testnet`).
48
+
49
+ Other things that work straight away, all free:
50
+
51
+ > Do I hold any verifiable credentials?
52
+ > What is my ZTX balance?
53
+ > What attributes does credential template X require?
54
+
55
+ ## 3. What it can do
56
+
57
+ | Ask for | Costs money? |
58
+ |---|---|
59
+ | Wallet address, DID, network, balance | no |
60
+ | Which credentials you hold | no |
61
+ | What a credential template requires | no |
62
+ | Reading on-chain contract data | no |
63
+ | Proving your identity to a service | no |
64
+ | **Fetching a resource that charges per use** | **yes** |
65
+ | **Obtaining a verifiable credential** | **yes** |
66
+ | **Starting a Verified AI Birthcert check (owner identity verification via MyDigital ID)** | **yes** |
67
+
68
+ The three paid actions are refused until you set a limit.
69
+
70
+ ---
71
+
72
+ ## Setting your spending limit
73
+
74
+ The wallet starts at **zero** — every payment is declined. This is on purpose: a wallet that could
75
+ spend whatever a website asked for, straight out of the box, is not a safe default.
76
+
77
+ ### Through the dashboard
78
+
79
+ Open the plugin's settings and set **Maximum payment per call**. That is the whole change.
80
+
81
+ ### From the command line
82
+
83
+ ```bash
84
+ openclaw config set plugins.entries.zetrix-agentic-wallet.config.maxPaymentAmount \
85
+ '{"ZTX":"1000000000","*":"0"}' --json
86
+ openclaw mcp reload
87
+ ```
88
+
89
+ It takes effect immediately — no gateway restart.
90
+
91
+ ### Reading the limit
92
+
93
+ The value is a map of **token → maximum per payment**, and `"*"` is the fallback for any token you have
94
+ not listed.
95
+
96
+ ```json
97
+ { "ZTX": "1000000000", "*": "0" }
98
+ ```
99
+
100
+ - **`"ZTX": "1000000000"`** — up to 1,000,000,000 raw units of ZTX per payment
101
+ - **`"*": "0"`** — everything else refused
102
+
103
+ Amounts are in **raw units**, not whole tokens. ZTX has 6 decimals, so `1000000000` is **1,000 ZTX**.
104
+
105
+ ### One limit for everything, or a limit per token?
106
+
107
+ Both work. Pick based on how much you care about what you are paying *in*.
108
+
109
+ **One limit for everything** — simplest, and fine for testing:
110
+
111
+ ```json
112
+ { "*": "1000000000" }
113
+ ```
114
+
115
+ Any token, up to 1,000,000,000 raw units per payment.
116
+
117
+ **A limit per token** — safer, and what we recommend once real money is involved:
118
+
119
+ ```json
120
+ { "ZTX": "1000000000", "ZTX3WeinXtt28YMyr4vUZ14ddTgEMGeuc1e6b": "5000000", "*": "0" }
121
+ ```
122
+
123
+ Only those two are payable, each with its own ceiling; everything else is refused.
124
+
125
+ **A ticker like `"JMYR"` works too**, for tokens the wallet knows. A payment request identifies a
126
+ token by its contract address, so the wallet resolves the ticker to that address for you. If you
127
+ write **both**, the contract address wins — it is the more specific of the two.
128
+
129
+ A ticker the wallet does not recognise still matches nothing, and the payment falls through to `"*"`.
130
+ If a refusal mentions the `"*"` fallback, that is what happened: no limit was set for that asset, so
131
+ use its contract address instead.
132
+
133
+ To find a token's address, ask your agent:
134
+
135
+ > Look up the JMYR token contract address on this network
136
+
137
+ **Why per-token is safer**, despite being more work:
138
+
139
+ - **The same number is not the same value.** 1,000 ZTX and 1,000 JMYR are different amounts of money. A
140
+ single universal limit treats them as interchangeable, because it only counts units.
141
+ - **Decimals vary by token.** ZTX and JMYR both use 6, but nothing guarantees the next one does. On a
142
+ token with 2 decimals, `1000000000` raw units would be ten million tokens.
143
+ - **It auto-approves tokens you have never heard of.** With `{"*": "1000000000"}`, a service can quote
144
+ an obscure token and be paid, as long as the unit count fits. Listing tokens explicitly means you
145
+ decide what is acceptable currency, not the service asking for money.
146
+
147
+ **Once you list any token, the limit becomes an allowlist.** A token with no entry and no `"*"` fallback
148
+ is **denied**, not passed through. Keeping `"*": "0"` means "only the tokens I have listed".
149
+
150
+ ### It is a hard limit, not a prompt
151
+
152
+ The limit is enforced by the wallet itself, before any payment is signed. Your agent cannot talk its way
153
+ past it, and neither can a website. If a payment exceeds the limit it is refused, and the agent should
154
+ tell you so rather than retrying.
155
+
156
+ > ⚠️ **Set this through the plugin settings, not by editing `openclaw.json` directly.** The plugin
157
+ > manages its own entry in that file and will either overwrite your edit or stop managing the entry
158
+ > altogether. Neither is obvious when it happens.
159
+
160
+ ## Adding funds
161
+
162
+ A brand-new wallet has no funds, so even with a limit set it cannot pay yet.
163
+
164
+ 1. Ask your agent for your wallet address
165
+ 2. Send testnet ZTX to it from another Zetrix wallet
166
+ 3. Ask **"what is my ZTX balance?"** to confirm it arrived
167
+
168
+ Until the address has received anything, the wallet reports it is **not yet activated on chain** — that
169
+ means "send it some ZTX", not "something is broken".
170
+
171
+ ## Backing up your wallet
172
+
173
+ **Do this once, now.** The wallet generated its own password and keeps it on this machine. It is the
174
+ only thing that can authorise payments from your account. If you lose this machine without a backup, the
175
+ account and anything in it are **gone permanently** — nobody can recover it, including us.
176
+
177
+ ```bash
178
+ npx agentic-wallet-mcp export-credentials
179
+ ```
180
+
181
+ Run it in your own terminal window. It prints your address, DID and password. Store them somewhere
182
+ private, such as a password manager.
183
+
184
+ It only works in an interactive terminal, and it is deliberately **not** something your agent can do —
185
+ so your password never appears in a conversation.
186
+
187
+ ## Moving to mainnet
188
+
189
+ Testnet tokens are not real. To use real funds:
190
+
191
+ ```bash
192
+ openclaw config set plugins.entries.zetrix-agentic-wallet.config.network zetrix:mainnet
193
+ openclaw mcp reload
194
+ ```
195
+
196
+ Before you do:
197
+
198
+ - **Back up your wallet** (above) if you have not already
199
+ - **Review your spending limit** — the same number now means real money
200
+ - **The Verified AI Birthcert check is unavailable on mainnet through this plugin.** Its endpoint
201
+ was never confirmed reachable on mainnet, so the wallet refuses to use it there unless an
202
+ operator sets it explicitly — something this plugin does not currently expose a setting for.
203
+ Every other tool works normally.
204
+ - Note that mainnet uses a **different account** from your testnet one
205
+
206
+ ## If something is wrong
207
+
208
+ | What you see | What it means |
209
+ |---|---|
210
+ | Agent says the wallet tools are unavailable | Plugin not enabled, or gateway needs restarting. Check `openclaw plugins list` |
211
+ | *"payment blocked … exceeds configured MAX_PAYMENT_AMOUNT"* | Working as intended — raise the limit if the amount is right |
212
+ | *"not activated"* | The address has no funds yet. Send it ZTX |
213
+ | Balance lookup fails but the wallet otherwise works | Usually a network problem reaching the Zetrix node |
214
+ | Everything fails after connecting to a corporate VPN | Some VPNs block the Zetrix endpoints. Try disconnecting |
215
+
216
+ Useful commands:
217
+
218
+ ```bash
219
+ openclaw mcp doctor # is the wallet configured correctly?
220
+ openclaw mcp probe zetrix-agentic-wallet # can it start, and what tools does it offer?
221
+ openclaw logs --limit 200 # what did it actually say?
222
+ ```
223
+
224
+ ## Removing the wallet
225
+
226
+ ```bash
227
+ openclaw plugins uninstall zetrix-agentic-wallet --force
228
+ openclaw mcp unset zetrix-agentic-wallet
229
+ ```
230
+
231
+ ⚠️ **Back up first.** Deleting `~/.openclaw/zetrix-agentic-wallet/` destroys the account permanently.
232
+ The wallet keeps working between those two commands — that is intentional, so an uninstall does not
233
+ silently strand an account holding funds.
package/dist/index.js ADDED
@@ -0,0 +1,201 @@
1
+ // src/index.ts
2
+ import { cpSync, existsSync, mkdirSync, readFileSync, renameSync, rmSync, writeFileSync } from "node:fs";
3
+ import { createHash } from "node:crypto";
4
+ import { join as join2 } from "node:path";
5
+
6
+ // src/paths.ts
7
+ import { join } from "node:path";
8
+ function resolvePluginPaths(rootDir) {
9
+ const gatewayStateDir = join(rootDir, "..", "..");
10
+ const homeDir = join(gatewayStateDir, "zetrix-agentic-wallet");
11
+ const runtimeDir = join(homeDir, "runtime");
12
+ return {
13
+ gatewayStateDir,
14
+ configPath: join(gatewayStateDir, "openclaw.json"),
15
+ ownershipPath: join(homeDir, ".mcp-registration.json"),
16
+ homeDir,
17
+ runtimeDir,
18
+ shippedRuntimeDir: join(rootDir, "dist", "runtime"),
19
+ walletStateDir: join(homeDir, "state"),
20
+ walletBundlePath: join(runtimeDir, "server-bundle.cjs")
21
+ };
22
+ }
23
+
24
+ // src/mcp-registration.ts
25
+ var SERVER_NAME = "zetrix-agentic-wallet";
26
+ function buildServerEntry(walletBundlePath, config, stateDir) {
27
+ return {
28
+ command: "node",
29
+ args: [walletBundlePath],
30
+ env: {
31
+ ZETRIX_NETWORK: config.network ?? "zetrix:testnet",
32
+ // Deliberately absent when unconfigured, rather than defaulted here.
33
+ //
34
+ // The wallet's own default is network-aware — testnet allows exactly the credential fee,
35
+ // mainnet refuses everything, because the cap is per call and nothing limits how many calls
36
+ // are made. This layer cannot know the network at config time, so anything sent from here
37
+ // would shadow that and pin every subscriber to one behaviour.
38
+ //
39
+ // The manifest declares no `default` for the same reason: OpenClaw materialises configSchema
40
+ // defaults into plugin config with no operator action (SPIKE-0.4-FINDINGS.md §2.1), so a
41
+ // declared default would arrive here as a real value and never reach this branch.
42
+ ...config.maxPaymentAmount ? { MAX_PAYMENT_AMOUNT: JSON.stringify(config.maxPaymentAmount) } : {},
43
+ ZETRIX_WALLET_STATE_DIR: stateDir,
44
+ ...config.zetrixAddress ? { ZETRIX_ADDRESS: config.zetrixAddress } : {}
45
+ }
46
+ };
47
+ }
48
+ function readConfig(deps) {
49
+ let raw;
50
+ try {
51
+ raw = deps.readFile(deps.configPath);
52
+ } catch (e) {
53
+ deps.log(`could not read ${deps.configPath}: ${e.message} \u2014 skipping MCP registration`);
54
+ return null;
55
+ }
56
+ try {
57
+ const parsed = JSON.parse(raw);
58
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) {
59
+ deps.log(`could not use ${deps.configPath}: not a JSON object \u2014 skipping MCP registration`);
60
+ return null;
61
+ }
62
+ return parsed;
63
+ } catch (e) {
64
+ deps.log(`could not parse ${deps.configPath}: ${e.message} \u2014 skipping MCP registration`);
65
+ return null;
66
+ }
67
+ }
68
+ function readOwnership(deps) {
69
+ if (!deps.exists(deps.ownershipPath)) return null;
70
+ try {
71
+ return JSON.parse(deps.readFile(deps.ownershipPath));
72
+ } catch {
73
+ return null;
74
+ }
75
+ }
76
+ function weOwnIt(deps, existing) {
77
+ const record = readOwnership(deps);
78
+ if (!record) return false;
79
+ if (!existing) return true;
80
+ if (!record.entry) return true;
81
+ return JSON.stringify(record.entry) === JSON.stringify(existing);
82
+ }
83
+ function writeOwnership(deps, entry) {
84
+ const record = {
85
+ serverName: SERVER_NAME,
86
+ entry,
87
+ note: "Created by the Zetrix Agentic Wallet plugin. The entry above is a fingerprint: the plugin only manages mcp.servers while the live entry still matches it, so a hand-edited entry is never overwritten. Deleting this file makes the plugin treat the entry as subscriber-owned."
88
+ };
89
+ deps.writeFile(deps.ownershipPath, `${JSON.stringify(record, null, 2)}
90
+ `);
91
+ }
92
+ function writeConfigAtomically(deps, config) {
93
+ const tmp = `${deps.configPath}.zetrix-tmp`;
94
+ deps.writeFile(tmp, `${JSON.stringify(config, null, 2)}
95
+ `);
96
+ deps.renameFile(tmp, deps.configPath);
97
+ }
98
+ function registerServer(deps, entry) {
99
+ const config = readConfig(deps);
100
+ if (!config) return;
101
+ const existing = config.mcp?.servers?.[SERVER_NAME];
102
+ if (existing && !weOwnIt(deps, existing)) {
103
+ deps.log(
104
+ `mcp.servers["${SERVER_NAME}"] is already present and was not created by this plugin (or was changed since) \u2014 leaving it untouched. Remove it if you want the plugin to manage the server.`
105
+ );
106
+ return;
107
+ }
108
+ if (existing && JSON.stringify(existing) === JSON.stringify(entry)) {
109
+ writeOwnership(deps, entry);
110
+ return;
111
+ }
112
+ config.mcp = config.mcp ?? {};
113
+ config.mcp.servers = config.mcp.servers ?? {};
114
+ config.mcp.servers[SERVER_NAME] = entry;
115
+ writeConfigAtomically(deps, config);
116
+ writeOwnership(deps, entry);
117
+ deps.log(existing ? `refreshed mcp.servers["${SERVER_NAME}"]` : `registered mcp.servers["${SERVER_NAME}"]`);
118
+ }
119
+
120
+ // src/index.ts
121
+ function makeDeps(paths, log) {
122
+ return {
123
+ configPath: paths.configPath,
124
+ ownershipPath: paths.ownershipPath,
125
+ readFile: (p) => readFileSync(p, "utf8"),
126
+ writeFile: (p, c) => writeFileSync(p, c, { encoding: "utf8", mode: 384 }),
127
+ renameFile: (from, to) => renameSync(from, to),
128
+ exists: (p) => existsSync(p),
129
+ removeFile: (p) => rmSync(p, { force: true }),
130
+ log
131
+ };
132
+ }
133
+ function pathIsExpected(paths) {
134
+ const expectedSuffix = join2("zetrix-agentic-wallet", "runtime");
135
+ return paths.runtimeDir.endsWith(expectedSuffix) && paths.runtimeDir.startsWith(paths.homeDir) && paths.homeDir.length > expectedSuffix.length;
136
+ }
137
+ function shippedBundleMatchesDigest(paths, shippedVersion, log) {
138
+ const recorded = /sha256:([0-9a-f]{64})/.exec(shippedVersion)?.[1];
139
+ if (!recorded) return true;
140
+ const shippedBundle = join2(paths.shippedRuntimeDir, "server-bundle.cjs");
141
+ const actual = createHash("sha256").update(readFileSync(shippedBundle)).digest("hex");
142
+ if (actual === recorded) return true;
143
+ log(
144
+ `refusing to install the wallet runtime: the shipped bundle does not match the digest recorded in VERSION (expected ${recorded.slice(0, 12)}, got ${actual.slice(0, 12)}). The package may be corrupt or modified \u2014 reinstall it from a trusted source.`
145
+ );
146
+ return false;
147
+ }
148
+ function syncRuntime(paths, log) {
149
+ const shippedVersionFile = join2(paths.shippedRuntimeDir, "VERSION");
150
+ if (!existsSync(shippedVersionFile)) {
151
+ log(`the plugin is missing its wallet runtime at ${paths.shippedRuntimeDir} \u2014 reinstall the plugin`);
152
+ return false;
153
+ }
154
+ const shipped = readFileSync(shippedVersionFile, "utf8").trim();
155
+ const installedVersionFile = join2(paths.runtimeDir, "VERSION");
156
+ const installed = existsSync(installedVersionFile) ? readFileSync(installedVersionFile, "utf8").trim() : null;
157
+ if (installed === shipped && existsSync(paths.walletBundlePath)) return true;
158
+ if (!pathIsExpected(paths)) {
159
+ log(`refusing to install the wallet runtime: unexpected target path ${paths.runtimeDir}`);
160
+ return false;
161
+ }
162
+ if (!shippedBundleMatchesDigest(paths, shipped, log)) return false;
163
+ const describe = (v) => {
164
+ if (!v) return "none";
165
+ const [nameVersion, hashLine = ""] = v.split("\n");
166
+ const short = hashLine.replace("sha256:", "").slice(0, 12);
167
+ return short ? `${nameVersion} (${short})` : nameVersion;
168
+ };
169
+ try {
170
+ mkdirSync(paths.homeDir, { recursive: true });
171
+ rmSync(paths.runtimeDir, { recursive: true, force: true });
172
+ cpSync(paths.shippedRuntimeDir, paths.runtimeDir, { recursive: true });
173
+ log(
174
+ installed ? `updated the wallet runtime: ${describe(installed)} -> ${describe(shipped)}` : `installed the wallet runtime ${describe(shipped)}`
175
+ );
176
+ return true;
177
+ } catch (e) {
178
+ log(`could not install the wallet runtime into ${paths.runtimeDir}: ${e.message}`);
179
+ return false;
180
+ }
181
+ }
182
+ function register(api) {
183
+ const paths = resolvePluginPaths(api.rootDir);
184
+ const log = (m) => {
185
+ const line = `zetrix-agentic-wallet: ${m}`;
186
+ if (api.logger?.info) api.logger.info(line);
187
+ else process.stderr.write(`${line}
188
+ `);
189
+ };
190
+ const deps = makeDeps(paths, log);
191
+ if (!syncRuntime(paths, log)) {
192
+ log("the plugin is installed but cannot provide wallet tools");
193
+ return;
194
+ }
195
+ registerServer(deps, buildServerEntry(paths.walletBundlePath, api.pluginConfig ?? {}, paths.walletStateDir));
196
+ }
197
+ var index_default = { register };
198
+ export {
199
+ index_default as default,
200
+ register
201
+ };
@@ -0,0 +1,2 @@
1
+ agentic-wallet-mcp@0.11.0
2
+ sha256:38c12638ea7a325ce41aafe72486db87d67a25c5054f4b89e66bc0d1e4abd717