@xpr-agents/openclaw 0.4.1 → 0.4.3

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
@@ -27,44 +27,92 @@ cd my-agent
27
27
 
28
28
  ### Inside an OpenClaw harness
29
29
 
30
+ The supported install path on OpenClaw runtimes (OpenClaw 2026.3.x verified, Pinata Agents verified):
31
+
32
+ ```bash
33
+ # 1. Install via OpenClaw's plugin CLI (NOT `npm install` — see "Why not
34
+ # plain npm install" below)
35
+ openclaw plugins install @xpr-agents/openclaw
36
+ ```
37
+
38
+ This downloads the package from npm, copies it to `~/.openclaw/extensions/openclaw/`, and **auto-writes** the following to your `~/.openclaw/openclaw.json`:
39
+
40
+ ```jsonc
41
+ {
42
+ "plugins": {
43
+ "entries": {
44
+ "openclaw": { "enabled": true }
45
+ },
46
+ "installs": {
47
+ "openclaw": {
48
+ "source": "npm",
49
+ "spec": "@xpr-agents/openclaw",
50
+ "version": "0.4.2",
51
+ "installPath": "/home/<user>/.openclaw/extensions/openclaw",
52
+ "integrity": "sha512-<...>",
53
+ "shasum": "<...>",
54
+ "installedAt": "<iso-timestamp>"
55
+ }
56
+ }
57
+ }
58
+ }
59
+ ```
60
+
30
61
  ```bash
31
- # 1. Install
32
- npm install @xpr-agents/openclaw
33
-
34
- # 2. Register with the harness. The mechanism depends on the runtime:
35
- # - Pinata Agents: see docs/PINATA.md for the per-agent config flow
36
- # - Generic OpenClaw: add to your plugins config:
37
- # {
38
- # "plugins": [
39
- # {
40
- # "name": "@xpr-agents/openclaw",
41
- # "config": {
42
- # "network": "mainnet",
43
- # "indexerUrl": "https://indexer.xpragents.com"
44
- # }
45
- # }
46
- # ]
47
- # }
48
- #
49
- # 3. Restart the agent. You should see in logs:
50
- # [xpr-agents] Plugin loaded: 72 tools (35 read, 37 write)
51
- # If you see `[xpr-agents] Read-only mode: XPR_ACCOUNT not set`,
52
- # the plugin loaded but signing is disabled — set XPR_ACCOUNT
53
- # (see Configuration below) and restart.
54
- #
55
- # 4. Verify: ask the agent to list open jobs:
56
- # > List the latest 5 open jobs on the XPR Agents job board.
57
- # Expect a real list. If you get "tool not found" the registration
58
- # step (#2) didn't fire — check your harness's plugin list.
62
+ # 2. Set XPR_ACCOUNT at the gateway env layer (see "Configuration" below
63
+ # for which surface — it is NOT inside plugins.entries.openclaw.config).
64
+
65
+ # 3. Restart the gateway. Most OpenClaw harnesses restart automatically
66
+ # when openclaw.json is patched (a SIGUSR1 fires). If yours doesn't,
67
+ # use whichever restart command your harness supports.
68
+
69
+ # 4. Verify the load by tailing the gateway log. Look for:
70
+ # [xpr-agents] Plugin loaded: 72 tools, mainnet (https://proton.eosusa.io)
71
+ # If you also see `[xpr-agents] Read-only mode: XPR_ACCOUNT not set.`,
72
+ # the plugin loaded but signing is disabled — re-check step 2.
73
+
74
+ # 5. Run your first signed write (the plugin auto-registration in the
75
+ # standalone scaffold does NOT fire on the harness path):
76
+ # > Register <your-agent> as an agent with name '...', description
77
+ # '...', endpoint '...', protocol 'https', capabilities ['general',
78
+ # 'jobs', 'bidding'].
79
+ # Expect `xpr_register_agent` → real `transaction_id` in the response →
80
+ # `[proton-cli] action agentcore::register auth=<your-agent>@active`
81
+ # in the gateway log.
59
82
  ```
60
83
 
61
- The harness provides the LLM — **do not** set `ANTHROPIC_API_KEY` and **do not** run `start.sh` on this path. Plugin path is install + register only. Full walkthrough for Pinata Agents specifically: [`docs/PINATA.md`](https://github.com/XPRNetwork/xpr-agents/blob/main/docs/PINATA.md).
84
+ The harness provides the LLM — **do not** set `ANTHROPIC_API_KEY` and **do not** run `start.sh` on this path. Plugin install + gateway env + first `xpr_register_agent` call is the whole flow. Full walkthrough for Pinata Agents specifically: [`docs/PINATA.md`](https://github.com/XPRNetwork/xpr-agents/blob/main/docs/PINATA.md).
85
+
86
+ #### Why not plain `npm install`?
87
+
88
+ OpenClaw harnesses do **not** auto-scan workspace `node_modules` for plugins. They scan:
89
+ - `plugins.load.paths` (explicit)
90
+ - `~/.openclaw/extensions/<plugin>/`
91
+ - bundled `/app/extensions/<plugin>/`
92
+
93
+ So `npm install @xpr-agents/openclaw` puts the package on disk but doesn't make it discoverable. Use `openclaw plugins install` (above) or, for monorepo development, add the workspace path to `plugins.load.paths` manually.
94
+
95
+ #### Why the harness path doesn't auto-register
96
+
97
+ The standalone `create-xpr-agent` scaffold auto-registers the agent in `agentcore::agents` on first boot via the runner's `ensureRegistered()`. The harness path loads only the plugin — not the runner — so the account is **not** in the registry until you explicitly call `xpr_register_agent`. Until then, every `xpr_update_*` / `xpr_set_agent_status` call returns `Agent not found`. Tracked for v0.5 to optionally self-register on first signed call when `XPR_ACCOUNT` is set.
98
+
99
+ ## Security notes (read this if your OpenClaw install scanner flags us)
100
+
101
+ OpenClaw's plugin installer statically scans every package before install and reports patterns it considers risky. As of 0.4.2 it flags **19 patterns** in `@xpr-agents/openclaw`. Every one of them is intentional and named below — do not bail on the install when you see them.
102
+
103
+ | Count | Pattern | Where | Why it's intentional |
104
+ |---|---|---|---|
105
+ | 16× | "Environment variable access combined with network send — possible credential harvesting" | `skills/{creative,defi,governance,lending,nft,smart-contracts,tax,xmd}/{src,dist}/index.ts:<line>`, `dist/tools/a2a.js`, `dist/tools/shellbook.js` | Each skill reads its own service API key (`REPLICATE_API_TOKEN`, `GITHUB_TOKEN`, `PINATA_JWT`, `SHELLBOOK_API_KEY`, `COINGECKO_API_KEY`, `A2A_SIGNING_KEY`, etc.) and calls that service's HTTPS API. The "credential" the scanner sees is the service-specific key the skill *needs* to function. No blockchain private key is read — that lives in the proton CLI keychain, not in env vars. |
106
+ | 1× | "Shell command execution (child_process)" | `dist/proton-cli.js:<line>` | **This is the post-charliebot security feature itself.** All on-chain signing shells out to `proton transaction:push`. The whole reason for v0.4.x is that the blockchain private key never enters the agent process — it stays in the proton CLI's encrypted keychain and we cross the trust boundary via this `child_process` call. If you'd rather hold the key in process, use a different package. |
107
+ | 2× | "Dynamic code execution detected" | `skills/code-sandbox/{src,dist}/index.ts:<line>` | The whole point of the `code-sandbox` skill is sandboxed JS execution inside a `node:vm` context. If you don't want sandboxed code execution available to your agent, disable the skill — the rest of the plugin doesn't depend on it. |
108
+
109
+ If your harness lets you set `plugins.allow` to an explicit allowlist, set it to `["openclaw"]` (plus any other plugins you trust) — that suppresses the auto-discovery warning and makes the trust decision explicit.
62
110
 
63
111
  ## Bundled Skills (13 total — since v0.4.0)
64
112
 
65
- The plugin ships pre-built skills in its tarball; the `openclaw.plugin.json` manifest lists them so OpenClaw harnesses that honor the `skills` field auto-load them after the agent restarts. Verify by asking the agent `list your skills` — you should see 13.
113
+ The plugin ships pre-built skills in its tarball; the `openclaw.plugin.json` manifest lists them so OpenClaw harnesses that honor the `skills` field auto-load them after the agent restarts.
66
114
 
67
- If your harness doesn't honor the manifest's `skills` field, the skill folders are still on disk at `node_modules/@xpr-agents/openclaw/skills/<name>/` and can be registered manually.
115
+ When you install via `openclaw plugins install @xpr-agents/openclaw`, the skill folders land at `~/.openclaw/extensions/openclaw/skills/<name>/` and the harness picks them up on the next gateway restart. If your harness ignores the manifest's `skills` array, the skill folders are still on disk and can be registered through whatever per-skill mechanism your runtime exposes.
68
116
 
69
117
  | Skill | Purpose |
70
118
  |-------|---------|
@@ -155,6 +203,29 @@ XPR_ACCOUNT=myagent # REQUIRED — without this, the plugi
155
203
  XPR_NETWORK=mainnet # mainnet | testnet (default: mainnet)
156
204
  ```
157
205
 
206
+ > **Where to put `XPR_ACCOUNT` matters.** On OpenClaw harnesses it goes in the gateway environment layer, NOT in the plugin's config:
207
+ >
208
+ > ```jsonc
209
+ > // ~/.openclaw/openclaw.json — CORRECT
210
+ > {
211
+ > "env": {
212
+ > "vars": {
213
+ > "XPR_ACCOUNT": "myagent"
214
+ > }
215
+ > },
216
+ > "plugins": {
217
+ > "entries": {
218
+ > "openclaw": {
219
+ > "enabled": true,
220
+ > "config": { "network": "mainnet" }
221
+ > }
222
+ > }
223
+ > }
224
+ > }
225
+ > ```
226
+ >
227
+ > Putting `XPR_ACCOUNT` inside `plugins.entries.openclaw.config` does NOT work — the plugin reads it from `process.env`, which is populated from `env.vars`, not from per-plugin config. We verified this empirically on OpenClaw 2026.3.x.
228
+
158
229
  ### Optional environment variables
159
230
 
160
231
  ```env
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,OAAO,KAAK,EAAE,SAAS,EAAgC,MAAM,SAAS,CAAC;AAGvE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1E,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAIzD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EACL,UAAU,EACV,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAElG;;;GAGG;AACH,UAAU,iBAAiB;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,YAAY,CAAC,IAAI,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,OAAO,CAAC;QACpB,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;YAChE,OAAO,EAAE,KAAK,CAAC;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,CAAC,CAAC;SACjD,CAAC,CAAC;KACJ,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AA0BD,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI,CA8DpF"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAUH,OAAO,KAAK,EAAE,SAAS,EAAgC,MAAM,SAAS,CAAC;AAGvE,YAAY,EAAE,aAAa,EAAE,QAAQ,EAAE,WAAW,EAAE,MAAM,eAAe,CAAC;AAC1E,YAAY,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,SAAS,CAAC;AAIzD,OAAO,EAAE,gBAAgB,EAAE,YAAY,EAAE,MAAM,eAAe,CAAC;AAC/D,YAAY,EAAE,iBAAiB,EAAE,MAAM,EAAE,MAAM,eAAe,CAAC;AAC/D,OAAO,EACL,UAAU,EACV,mBAAmB,EACnB,YAAY,EACZ,cAAc,EACd,sBAAsB,EACtB,cAAc,GACf,MAAM,cAAc,CAAC;AACtB,YAAY,EAAE,YAAY,EAAE,SAAS,EAAE,oBAAoB,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAElG;;;GAGG;AACH,UAAU,iBAAiB;IACzB,EAAE,EAAE,MAAM,CAAC;IACX,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACjC,YAAY,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;IACvC,YAAY,CAAC,IAAI,EAAE;QACjB,IAAI,EAAE,MAAM,CAAC;QACb,WAAW,EAAE,MAAM,CAAC;QACpB,UAAU,EAAE,OAAO,CAAC;QACpB,OAAO,EAAE,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,KAAK,OAAO,CAAC;YAChE,OAAO,EAAE,KAAK,CAAC;gBAAE,IAAI,EAAE,MAAM,CAAC;gBAAC,IAAI,CAAC,EAAE,MAAM,CAAA;aAAE,CAAC,CAAC;SACjD,CAAC,CAAC;KACJ,EAAE,IAAI,CAAC,EAAE,OAAO,GAAG,IAAI,CAAC;IACzB,CAAC,GAAG,EAAE,MAAM,GAAG,OAAO,CAAC;CACxB;AA0BD,MAAM,CAAC,OAAO,UAAU,eAAe,CAAC,OAAO,EAAE,iBAAiB,GAAG,SAAS,GAAG,IAAI,CAyEpF"}
package/dist/index.js CHANGED
@@ -98,17 +98,28 @@ function xprAgentsPlugin(realApi) {
98
98
  confirmHighRisk: rawConfig.confirmHighRisk !== false,
99
99
  maxTransferAmount: rawConfig.maxTransferAmount || 10000000,
100
100
  };
101
- // Register all tool groups
102
- (0, agent_1.registerAgentTools)(api, config);
103
- (0, feedback_1.registerFeedbackTools)(api, config);
104
- (0, validation_1.registerValidationTools)(api, config);
105
- (0, escrow_1.registerEscrowTools)(api, config);
106
- (0, indexer_1.registerIndexerTools)(api, config);
107
- (0, a2a_1.registerA2ATools)(api, config);
108
- (0, shellbook_1.registerShellbookTools)(api);
101
+ // Register all tool groups. Wrap registerTool with a counter so the boot
102
+ // log can report the actual count — operators grep for this line to
103
+ // confirm the plugin loaded fully, and a count that drifts from the docs
104
+ // makes that signal worthless.
105
+ let toolCount = 0;
106
+ const countingApi = {
107
+ ...api,
108
+ registerTool: (tool) => {
109
+ toolCount++;
110
+ return api.registerTool(tool);
111
+ },
112
+ };
113
+ (0, agent_1.registerAgentTools)(countingApi, config);
114
+ (0, feedback_1.registerFeedbackTools)(countingApi, config);
115
+ (0, validation_1.registerValidationTools)(countingApi, config);
116
+ (0, escrow_1.registerEscrowTools)(countingApi, config);
117
+ (0, indexer_1.registerIndexerTools)(countingApi, config);
118
+ (0, a2a_1.registerA2ATools)(countingApi, config);
119
+ (0, shellbook_1.registerShellbookTools)(countingApi);
109
120
  if (!hasCredentials) {
110
121
  console.log('[xpr-agents] Read-only mode: XPR_ACCOUNT not set. Write tools will fail.');
111
122
  }
112
- console.log(`[xpr-agents] Plugin loaded: ${config.network} (${rpcEndpoint})`);
123
+ console.log(`[xpr-agents] Plugin loaded: ${toolCount} tools, ${config.network} (${rpcEndpoint})`);
113
124
  }
114
125
  //# sourceMappingURL=index.js.map
package/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAAA;;;;;;;;;;;GAWG;;;AA0EH,kCAyEC;AAjJD,uCAA6D;AAC7D,yCAAmD;AACnD,+CAAyD;AACzD,mDAA6D;AAC7D,2CAAqD;AACrD,6CAAuD;AACvD,qCAA+C;AAC/C,iDAA2D;AAO3D,sEAAsE;AACtE,wEAAwE;AACxE,6CAA+D;AAAtD,+GAAA,gBAAgB,OAAA;AAAE,2GAAA,YAAY,OAAA;AAEvC,2CAOsB;AANpB,wGAAA,UAAU,OAAA;AACV,iHAAA,mBAAmB,OAAA;AACnB,0GAAA,YAAY,OAAA;AACZ,4GAAA,cAAc,OAAA;AACd,oHAAA,sBAAsB,OAAA;AACtB,4GAAA,cAAc,OAAA;AAwBhB;;;GAGG;AACH,SAAS,aAAa,CAAC,OAA0B;IAC/C,OAAO;QACL,YAAY,CAAC,IAAoB;YAC/B,OAAO,CAAC,YAAY,CAAC;gBACnB,IAAI,EAAE,IAAI,CAAC,IAAI;gBACf,WAAW,EAAE,IAAI,CAAC,WAAW;gBAC7B,UAAU,EAAE,IAAI,CAAC,UAAU;gBAC3B,KAAK,CAAC,OAAO,CAAC,GAAW,EAAE,MAA+B;oBACxD,MAAM,MAAM,GAAG,MAAM,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,CAAC;oBAC1C,MAAM,IAAI,GAAG,OAAO,MAAM,KAAK,QAAQ,CAAC,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;oBACnF,OAAO,EAAE,OAAO,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;gBAC/C,CAAC;aACF,CAAC,CAAC;QACL,CAAC;QACD,SAAS;YACP,OAAO,OAAO,CAAC,YAAY,IAAI,EAAE,CAAC;QACpC,CAAC;KACF,CAAC;AACJ,CAAC;AAED,SAAwB,eAAe,CAAC,OAAsC;IAC5E,6EAA6E;IAC7E,mFAAmF;IACnF,MAAM,GAAG,GAAc,OAAQ,OAAe,CAAC,SAAS,KAAK,UAAU;QACrE,CAAC,CAAC,OAAoB;QACtB,CAAC,CAAC,aAAa,CAAC,OAA4B,CAAC,CAAC;IAEhD,MAAM,SAAS,GAAG,GAAG,CAAC,SAAS,EAAE,CAAC;IAElC,MAAM,OAAO,GAAI,SAAS,CAAC,OAAkB,IAAI,SAAS,CAAC;IAC3D,MAAM,UAAU,GAAG,OAAO,KAAK,SAAS,CAAC,CAAC,CAAC,0BAA0B,CAAC,CAAC,CAAC,0BAA0B,CAAC;IACnG,MAAM,WAAW,GAAI,SAAS,CAAC,WAAsB,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,IAAI,UAAU,CAAC;IAEpG,yEAAyE;IACzE,wEAAwE;IACxE,2EAA2E;IAC3E,MAAM,cAAc,GAAG,CAAC,CAAC,OAAO,CAAC,GAAG,CAAC,WAAW,CAAC;IAEjD,6CAA6C;IAC7C,IAAI,GAAG,CAAC;IACR,IAAI,OAAO,CAAC;IAEZ,IAAI,cAAc,EAAE,CAAC;QACnB,MAAM,MAAM,GAAG,IAAA,uBAAa,EAAC,EAAE,WAAW,EAAE,CAAC,CAAC;QAC9C,GAAG,GAAG,MAAM,CAAC,GAAG,CAAC;QACjB,OAAO,GAAG,MAAM,CAAC,OAAO,CAAC;IAC3B,CAAC;SAAM,CAAC;QACN,GAAG,GAAG,IAAA,2BAAiB,EAAC,WAAW,CAAC,CAAC;IACvC,CAAC;IAED,MAAM,YAAY,GAAG,CAAC,SAAS,CAAC,SAAS,IAAI,EAAE,CAA2B,CAAC;IAE3E,MAAM,MAAM,GAAiB;QAC3B,GAAG,EAAE,GAAU;QACf,OAAO;QACP,OAAO,EAAG,SAAS,CAAC,OAAiC,IAAI,SAAS;QAClE,WAAW;QACX,UAAU,EAAG,SAAS,CAAC,UAAqB,IAAI,OAAO,CAAC,GAAG,CAAC,WAAW,IAAI,+BAA+B;QAC1G,SAAS,EAAE;YACT,SAAS,EAAE,YAAY,CAAC,SAAS,IAAI,WAAW;YAChD,SAAS,EAAE,YAAY,CAAC,SAAS,IAAI,WAAW;YAChD,UAAU,EAAE,YAAY,CAAC,UAAU,IAAI,YAAY;YACnD,WAAW,EAAE,YAAY,CAAC,WAAW,IAAI,aAAa;SACvD;QACD,eAAe,EAAE,SAAS,CAAC,eAAe,KAAK,KAAK;QACpD,iBAAiB,EAAG,SAAS,CAAC,iBAA4B,IAAI,QAAQ;KACvE,CAAC;IAEF,yEAAyE;IACzE,oEAAoE;IACpE,yEAAyE;IACzE,+BAA+B;IAC/B,IAAI,SAAS,GAAG,CAAC,CAAC;IAClB,MAAM,WAAW,GAAc;QAC7B,GAAG,GAAG;QACN,YAAY,EAAE,CAAC,IAAS,EAAE,EAAE;YAC1B,SAAS,EAAE,CAAC;YACZ,OAAO,GAAG,CAAC,YAAY,CAAC,IAAI,CAAC,CAAC;QAChC,CAAC;KACF,CAAC;IACF,IAAA,0BAAkB,EAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACxC,IAAA,gCAAqB,EAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC3C,IAAA,oCAAuB,EAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC7C,IAAA,4BAAmB,EAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACzC,IAAA,8BAAoB,EAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IAC1C,IAAA,sBAAgB,EAAC,WAAW,EAAE,MAAM,CAAC,CAAC;IACtC,IAAA,kCAAsB,EAAC,WAAW,CAAC,CAAC;IAEpC,IAAI,CAAC,cAAc,EAAE,CAAC;QACpB,OAAO,CAAC,GAAG,CAAC,0EAA0E,CAAC,CAAC;IAC1F,CAAC;IAED,OAAO,CAAC,GAAG,CAAC,+BAA+B,SAAS,WAAW,MAAM,CAAC,OAAO,KAAK,WAAW,GAAG,CAAC,CAAC;AACpG,CAAC"}
package/package.json CHANGED
@@ -1,8 +1,11 @@
1
1
  {
2
2
  "name": "@xpr-agents/openclaw",
3
- "version": "0.4.1",
3
+ "version": "0.4.3",
4
4
  "description": "OpenClaw plugin for XPR Network Trustless Agent Registry - autonomous agent operation, escrow jobs, feedback, and validation",
5
5
  "author": "XPR Network",
6
+ "bin": {
7
+ "xpr-agents-setup-security": "scripts/setup-security.sh"
8
+ },
6
9
  "repository": {
7
10
  "type": "git",
8
11
  "url": "git+https://github.com/XPRNetwork/xpr-agents.git",
@@ -0,0 +1,504 @@
1
+ #!/usr/bin/env bash
2
+ # setup-security.sh — Pillar 2 lockdown for an XPR Agents account.
3
+ #
4
+ # Delegates the agent's `owner` permission to a separate human-controlled
5
+ # account, so even if the agent's active key is compromised the attacker
6
+ # cannot rotate the account away from you.
7
+ #
8
+ # Idempotent: if owner is already controlled by a non-raw-key account
9
+ # permission, exits cleanly with no changes.
10
+ #
11
+ # Refuses to run unattended — TTY required, explicit yes/no on every
12
+ # prompt, type-to-confirm account names, hard-fails on any precondition
13
+ # (account doesn't exist, key not in keychain, etc).
14
+ #
15
+ # See docs/SECURITY.md for the full security model.
16
+
17
+ set -eu
18
+
19
+ # ── Colors ─────────────────────────────────────
20
+ if [ -t 1 ]; then
21
+ RED=$'\033[31m'
22
+ GREEN=$'\033[32m'
23
+ YELLOW=$'\033[33m'
24
+ BOLD=$'\033[1m'
25
+ NC=$'\033[0m'
26
+ else
27
+ RED=''; GREEN=''; YELLOW=''; BOLD=''; NC=''
28
+ fi
29
+
30
+ err() { printf "${RED}${BOLD}ERROR:${NC} %s\n" "$*" >&2; }
31
+ warn() { printf "${YELLOW}!${NC} %s\n" "$*"; }
32
+ ok() { printf "${GREEN}✓${NC} %s\n" "$*"; }
33
+ info() { printf " %s\n" "$*"; }
34
+ step() { printf "\n${BOLD}[%s]${NC} %s\n" "$1" "$2"; }
35
+ abort() { err "$*"; exit 1; }
36
+
37
+ # ── Hard preconditions ─────────────────────────
38
+
39
+ # Require a TTY. We will NOT run this from a pipe, a heredoc, or under
40
+ # automation. The whole point is human-in-the-loop.
41
+ if [ ! -t 0 ] || [ ! -t 1 ]; then
42
+ err "setup-security.sh requires an interactive terminal."
43
+ err "Do not pipe input or run this from automation."
44
+ exit 1
45
+ fi
46
+
47
+ # Require proton CLI on PATH.
48
+ if ! command -v proton >/dev/null 2>&1; then
49
+ err "proton CLI not found on PATH."
50
+ info "Install it: npm i -g @proton/cli"
51
+ info "Then add the npm global bin to PATH:"
52
+ info " export PATH=\"\$(npm config get prefix)/bin:\$PATH\""
53
+ exit 1
54
+ fi
55
+
56
+ # Require XPR_ACCOUNT or --account arg.
57
+ AGENT_ACCOUNT="${XPR_ACCOUNT:-}"
58
+ while [ $# -gt 0 ]; do
59
+ case "$1" in
60
+ --account) AGENT_ACCOUNT="$2"; shift 2 ;;
61
+ --help|-h)
62
+ cat <<'EOF'
63
+ Usage: ./setup-security.sh [--account <agent-account>]
64
+
65
+ Locks down the agent's `owner` permission so that only a human-controlled
66
+ XPR account can change permissions. Run once per agent. Idempotent.
67
+
68
+ The agent's `active` key stays in the proton CLI keychain — daily signing
69
+ is unchanged. Only the `owner` permission moves to your human account.
70
+
71
+ See docs/SECURITY.md for the full rationale.
72
+ EOF
73
+ exit 0 ;;
74
+ *) shift ;;
75
+ esac
76
+ done
77
+
78
+ if [ -z "$AGENT_ACCOUNT" ]; then
79
+ err "Agent account not specified."
80
+ info "Pass --account <name> or set XPR_ACCOUNT in env."
81
+ exit 1
82
+ fi
83
+
84
+ # Validate name shape (EOSIO: 1-12 chars, .12345abcdefghijklmnopqrstuvwxyz)
85
+ if ! printf '%s' "$AGENT_ACCOUNT" | grep -qE '^[.1-5a-z]{1,12}$'; then
86
+ abort "'$AGENT_ACCOUNT' is not a valid XPR Network account name (1-12 chars from .12345a-z)."
87
+ fi
88
+
89
+ # ── Banner ─────────────────────────────────────
90
+ cat <<EOF
91
+
92
+ ========================================================================
93
+ ${BOLD}XPR AGENTS — SECURITY SETUP${NC} (Pillar 2: lock down owner)
94
+ ========================================================================
95
+
96
+ Target agent account: ${BOLD}${AGENT_ACCOUNT}${NC}
97
+
98
+ This script will delegate '$AGENT_ACCOUNT's owner permission to a
99
+ separate human-controlled XPR account. After this:
100
+
101
+ • Your human account controls recovery if the agent's key leaks.
102
+ • The agent's active key stays in the proton CLI keychain — daily
103
+ signing is unchanged.
104
+ • The agent's owner permission will have NO raw keys — only your
105
+ human account can change permissions.
106
+
107
+ This is recommended but not automatic. See docs/SECURITY.md for the
108
+ full security model.
109
+
110
+ EOF
111
+
112
+ # ── Step 1: Read current account state ─────────
113
+ step "1/6" "Reading current state for '$AGENT_ACCOUNT'..."
114
+
115
+ if ! ACCOUNT_JSON=$(proton account "$AGENT_ACCOUNT" --json 2>/dev/null); then
116
+ # Fallback: --json may not be supported on older proton CLI versions
117
+ if ! ACCOUNT_OUT=$(proton account "$AGENT_ACCOUNT" 2>&1); then
118
+ err "Failed to look up account '$AGENT_ACCOUNT'."
119
+ info "Does the account exist? Check: proton account $AGENT_ACCOUNT"
120
+ info "Are you on the right chain? Check: proton chain"
121
+ exit 1
122
+ fi
123
+ # No JSON support — fall back to text parsing. Less robust.
124
+ ACCOUNT_JSON=""
125
+ fi
126
+
127
+ # Extract permissions. Prefer JSON if available.
128
+ if [ -n "$ACCOUNT_JSON" ]; then
129
+ OWNER_KEYS=$(printf '%s' "$ACCOUNT_JSON" | node -e "
130
+ let s=''; process.stdin.on('data',d=>s+=d); process.stdin.on('end',()=>{
131
+ try {
132
+ const a = JSON.parse(s);
133
+ const owner = (a.permissions||[]).find(p=>p.perm_name==='owner');
134
+ if (!owner) { console.log(''); return; }
135
+ const keys = (owner.required_auth?.keys||[]).map(k=>k.key);
136
+ console.log(keys.join(','));
137
+ } catch(e) { console.log(''); }
138
+ });
139
+ " <<< "$ACCOUNT_JSON")
140
+ OWNER_ACCOUNTS=$(printf '%s' "$ACCOUNT_JSON" | node -e "
141
+ let s=''; process.stdin.on('data',d=>s+=d); process.stdin.on('end',()=>{
142
+ try {
143
+ const a = JSON.parse(s);
144
+ const owner = (a.permissions||[]).find(p=>p.perm_name==='owner');
145
+ if (!owner) { console.log(''); return; }
146
+ const accts = (owner.required_auth?.accounts||[]).map(x=>x.permission.actor+'@'+x.permission.permission);
147
+ console.log(accts.join(','));
148
+ } catch(e) { console.log(''); }
149
+ });
150
+ " <<< "$ACCOUNT_JSON")
151
+ ACTIVE_KEYS=$(printf '%s' "$ACCOUNT_JSON" | node -e "
152
+ let s=''; process.stdin.on('data',d=>s+=d); process.stdin.on('end',()=>{
153
+ try {
154
+ const a = JSON.parse(s);
155
+ const active = (a.permissions||[]).find(p=>p.perm_name==='active');
156
+ if (!active) { console.log(''); return; }
157
+ const keys = (active.required_auth?.keys||[]).map(k=>k.key);
158
+ console.log(keys.join(','));
159
+ } catch(e) { console.log(''); }
160
+ });
161
+ " <<< "$ACCOUNT_JSON")
162
+ else
163
+ abort "proton account output format not recognized. Update proton CLI: npm i -g @proton/cli"
164
+ fi
165
+
166
+ info "owner perm keys: ${OWNER_KEYS:-(none)}"
167
+ info "owner perm accounts: ${OWNER_ACCOUNTS:-(none)}"
168
+ info "active perm keys: ${ACTIVE_KEYS:-(none)}"
169
+
170
+ # Idempotency check: if owner has no raw keys, we're already done.
171
+ if [ -z "$OWNER_KEYS" ] && [ -n "$OWNER_ACCOUNTS" ]; then
172
+ ok "Already secured. owner is controlled by: $OWNER_ACCOUNTS"
173
+ info "No changes needed. If you want to change the owner-controlling account,"
174
+ info "run a manual updateauth — see docs/SECURITY.md."
175
+ exit 0
176
+ fi
177
+
178
+ if [ -z "$OWNER_KEYS" ]; then
179
+ abort "owner permission has no keys AND no accounts? This is unusual. Check: proton account $AGENT_ACCOUNT"
180
+ fi
181
+
182
+ # We have raw keys on owner. Proceed.
183
+ warn "owner permission currently has raw keys. This is the default after account creation."
184
+ warn "If that key leaks, an attacker can rotate you out of your own account."
185
+
186
+ # ── Step 2: Verify we can sign as <agent>@owner ─
187
+ step "2/6" "Verifying we can sign as ${AGENT_ACCOUNT}@owner..."
188
+
189
+ KEYLIST=$(proton key:list 2>/dev/null || true)
190
+ MATCHED_OWNER_KEY=""
191
+ IFS=',' read -ra OWNER_KEY_ARR <<< "$OWNER_KEYS"
192
+ for k in "${OWNER_KEY_ARR[@]}"; do
193
+ if printf '%s' "$KEYLIST" | grep -qF "\"publicKey\": \"$k\""; then
194
+ MATCHED_OWNER_KEY="$k"
195
+ break
196
+ fi
197
+ done
198
+
199
+ if [ -z "$MATCHED_OWNER_KEY" ]; then
200
+ err "None of the owner-permission keys are loaded in the proton CLI keychain."
201
+ info "Owner keys on chain: $OWNER_KEYS"
202
+ info ""
203
+ info "Load the owner private key (PVT_K1_...) into the keychain:"
204
+ info " proton key:add"
205
+ info ""
206
+ info "For WebAuth-created accounts: the K1 backup key is in your wallet"
207
+ info "(WebAuth → Settings → Backup → reveal key)."
208
+ exit 1
209
+ fi
210
+ ok "Found matching key for ${AGENT_ACCOUNT}@owner: $MATCHED_OWNER_KEY"
211
+
212
+ # Determine the K1 we'll put on active. If active is currently a WA key
213
+ # (WebAuth biometric, unusable for autonomous signing), we replace it with
214
+ # the K1 currently on owner. If active is already a K1, we leave it alone.
215
+ NEW_ACTIVE_KEY=""
216
+ ACTIVE_NEEDS_REWRITE="no"
217
+
218
+ ACTIVE_HAS_WA="no"
219
+ IFS=',' read -ra ACTIVE_KEY_ARR <<< "$ACTIVE_KEYS"
220
+ for k in "${ACTIVE_KEY_ARR[@]}"; do
221
+ case "$k" in
222
+ PUB_WA_*) ACTIVE_HAS_WA="yes" ;;
223
+ esac
224
+ done
225
+
226
+ if [ "$ACTIVE_HAS_WA" = "yes" ]; then
227
+ # WebAuth case — must rewrite active to the K1.
228
+ NEW_ACTIVE_KEY="$MATCHED_OWNER_KEY"
229
+ ACTIVE_NEEDS_REWRITE="yes"
230
+ info "active permission is WebAuth-only (PUB_WA_...) — agent can't sign autonomously."
231
+ info "Will replace active with the K1 currently on owner: $MATCHED_OWNER_KEY"
232
+ elif [ -z "$ACTIVE_KEYS" ]; then
233
+ err "active permission has no keys. This is unusual — check manually:"
234
+ info " proton account $AGENT_ACCOUNT"
235
+ exit 1
236
+ else
237
+ # Active already has K1(s). Leave alone.
238
+ NEW_ACTIVE_KEY="$(printf '%s' "$ACTIVE_KEYS" | cut -d, -f1)"
239
+ ACTIVE_NEEDS_REWRITE="no"
240
+ info "active permission already has K1 key: $NEW_ACTIVE_KEY"
241
+ info "No change to active needed."
242
+ fi
243
+
244
+ # Verify the K1 we want on active is in the keychain (whether we're
245
+ # rewriting or leaving alone — either way, the agent needs to sign with
246
+ # it daily).
247
+ if ! printf '%s' "$KEYLIST" | grep -qF "\"publicKey\": \"$NEW_ACTIVE_KEY\""; then
248
+ err "Final active key ($NEW_ACTIVE_KEY) is not in the proton CLI keychain."
249
+ err "The agent would not be able to sign after this change. Aborting."
250
+ info "Load the matching PVT_K1_ key first: proton key:add"
251
+ exit 1
252
+ fi
253
+ ok "Final active key ($NEW_ACTIVE_KEY) is in the keychain."
254
+
255
+ # ── Step 3: Ask for human account ──────────────
256
+ step "3/6" "Your personal XPR account"
257
+
258
+ cat <<EOF
259
+
260
+ ${YELLOW}This account will control recovery of '$AGENT_ACCOUNT' forever.${NC}
261
+
262
+ It MUST be an account you fully control TODAY. Strongly recommended:
263
+ • KYC-verified (gives the agent +30 trust score via the claim system)
264
+ • WebAuth-secured (Face ID / fingerprint signing, key never on a server)
265
+ • Not an account you share with anyone
266
+ • Not the same account as the agent itself
267
+
268
+ Be CAREFUL: a typo here delegates owner to a nonexistent or someone
269
+ else's account, and you will lose control of '$AGENT_ACCOUNT' forever.
270
+
271
+ EOF
272
+
273
+ read -rp " Your personal XPR account name: " HUMAN_ACCOUNT
274
+ HUMAN_ACCOUNT="$(printf '%s' "$HUMAN_ACCOUNT" | tr -d '[:space:]')"
275
+
276
+ if [ -z "$HUMAN_ACCOUNT" ]; then
277
+ abort "No account name given. Aborting."
278
+ fi
279
+ if ! printf '%s' "$HUMAN_ACCOUNT" | grep -qE '^[.1-5a-z]{1,12}$'; then
280
+ abort "'$HUMAN_ACCOUNT' is not a valid XPR Network account name (1-12 chars from .12345a-z)."
281
+ fi
282
+ if [ "$HUMAN_ACCOUNT" = "$AGENT_ACCOUNT" ]; then
283
+ abort "The human account cannot be the same as the agent account. That defeats the entire purpose."
284
+ fi
285
+
286
+ info ""
287
+ info "Looking up '$HUMAN_ACCOUNT' on chain..."
288
+ if ! HUMAN_JSON=$(proton account "$HUMAN_ACCOUNT" --json 2>/dev/null); then
289
+ err "Account '$HUMAN_ACCOUNT' not found on chain."
290
+ info "Did you typo it? Verify at:"
291
+ info " https://explorer.xprnetwork.org/account/$HUMAN_ACCOUNT"
292
+ info ""
293
+ abort "Refusing to delegate owner to a nonexistent account."
294
+ fi
295
+
296
+ # Extract human account info for display
297
+ HUMAN_BALANCE=$(printf '%s' "$HUMAN_JSON" | node -e "
298
+ let s=''; process.stdin.on('data',d=>s+=d); process.stdin.on('end',()=>{
299
+ try {
300
+ const a = JSON.parse(s);
301
+ console.log(a.core_liquid_balance || '0 XPR');
302
+ } catch(e) { console.log('?'); }
303
+ });
304
+ " <<< "$HUMAN_JSON")
305
+ HUMAN_CREATED=$(printf '%s' "$HUMAN_JSON" | node -e "
306
+ let s=''; process.stdin.on('data',d=>s+=d); process.stdin.on('end',()=>{
307
+ try {
308
+ const a = JSON.parse(s);
309
+ console.log((a.created || '').slice(0,10) || '?');
310
+ } catch(e) { console.log('?'); }
311
+ });
312
+ " <<< "$HUMAN_JSON")
313
+ HUMAN_ACTIVE_KEYS=$(printf '%s' "$HUMAN_JSON" | node -e "
314
+ let s=''; process.stdin.on('data',d=>s+=d); process.stdin.on('end',()=>{
315
+ try {
316
+ const a = JSON.parse(s);
317
+ const active = (a.permissions||[]).find(p=>p.perm_name==='active');
318
+ const keys = (active?.required_auth?.keys||[]).map(k=>k.key);
319
+ console.log(keys.join(',') || '(no raw keys)');
320
+ } catch(e) { console.log('?'); }
321
+ });
322
+ " <<< "$HUMAN_JSON")
323
+
324
+ ok "Account exists"
325
+ info " Created: $HUMAN_CREATED"
326
+ info " XPR balance: $HUMAN_BALANCE"
327
+ info " active perm keys: $HUMAN_ACTIVE_KEYS"
328
+
329
+ # Warn if human account isn't WebAuth-secured (recommendation, not a block)
330
+ case "$HUMAN_ACTIVE_KEYS" in
331
+ *PUB_WA_*) info " ✓ active key is WebAuth (biometric) — good." ;;
332
+ *) warn " active key is NOT WebAuth-biometric. This is allowed but weaker." ;;
333
+ esac
334
+
335
+ # ── Step 4: Critical confirmation ──────────────
336
+ step "4/6" "${BOLD}${RED}CRITICAL CONFIRMATION${NC}"
337
+
338
+ cat <<EOF
339
+
340
+ Open this URL in your browser ${BOLD}right now${NC}:
341
+ ${BOLD}https://explorer.xprnetwork.org/account/$HUMAN_ACCOUNT${NC}
342
+
343
+ Visually verify on the explorer:
344
+ ☐ The account name is exactly '$HUMAN_ACCOUNT' (no typos)
345
+ ☐ The balance / activity look like YOUR account
346
+ ☐ You can sign transactions from this account today
347
+
348
+ If ANY of these are wrong, hit ${BOLD}Ctrl+C${NC} now and start over.
349
+
350
+ After this script completes, '$AGENT_ACCOUNT' is controlled by '$HUMAN_ACCOUNT'
351
+ forever — only '$HUMAN_ACCOUNT' can change it back.
352
+
353
+ EOF
354
+
355
+ read -rp " Type the agent account name '${AGENT_ACCOUNT}' to confirm target: " CONFIRM_AGENT
356
+ if [ "$CONFIRM_AGENT" != "$AGENT_ACCOUNT" ]; then
357
+ abort "Agent account didn't match. Aborting (no changes made)."
358
+ fi
359
+
360
+ read -rp " Type the human account name '${HUMAN_ACCOUNT}' to confirm controller: " CONFIRM_HUMAN
361
+ if [ "$CONFIRM_HUMAN" != "$HUMAN_ACCOUNT" ]; then
362
+ abort "Human account didn't match. Aborting (no changes made)."
363
+ fi
364
+
365
+ # ── Step 5: Show transaction plan ──────────────
366
+ step "5/6" "Transaction plan"
367
+
368
+ cat <<EOF
369
+
370
+ Will push ONE atomic transaction to '$AGENT_ACCOUNT' with the
371
+ following updateauth actions:
372
+
373
+ EOF
374
+
375
+ if [ "$ACTIVE_NEEDS_REWRITE" = "yes" ]; then
376
+ cat <<EOF
377
+ ${BOLD}action 1: active → K1${NC}
378
+ permission: active
379
+ parent: owner
380
+ threshold: 1
381
+ keys: [$NEW_ACTIVE_KEY]
382
+ accounts: []
383
+ (was: $ACTIVE_KEYS)
384
+ EOF
385
+ fi
386
+
387
+ cat <<EOF
388
+
389
+ ${BOLD}action $([ "$ACTIVE_NEEDS_REWRITE" = "yes" ] && echo 2 || echo 1): owner → human account${NC}
390
+ permission: owner
391
+ parent: (root)
392
+ threshold: 1
393
+ keys: []
394
+ accounts: [${HUMAN_ACCOUNT}@active]
395
+ (was raw key: $MATCHED_OWNER_KEY)
396
+
397
+ Signed by: ${AGENT_ACCOUNT}@owner (from your proton CLI keychain)
398
+
399
+ ${RED}${BOLD}This is irreversible from the agent side.${NC} After this lands,
400
+ only ${HUMAN_ACCOUNT}@active can change ${AGENT_ACCOUNT}'s permissions.
401
+
402
+ EOF
403
+
404
+ read -rp " Type ${BOLD}'yes I understand'${NC} to proceed (anything else aborts): " FINAL
405
+ if [ "$FINAL" != "yes I understand" ]; then
406
+ abort "Aborted at final confirmation. No changes made."
407
+ fi
408
+
409
+ # ── Step 6: Build and push transaction ─────────
410
+ step "6/6" "Pushing transaction..."
411
+
412
+ # Build the actions array
413
+ ACTIONS_JSON=""
414
+ if [ "$ACTIVE_NEEDS_REWRITE" = "yes" ]; then
415
+ ACTIONS_JSON="{\"account\":\"eosio\",\"name\":\"updateauth\",\"authorization\":[{\"actor\":\"${AGENT_ACCOUNT}\",\"permission\":\"owner\"}],\"data\":{\"account\":\"${AGENT_ACCOUNT}\",\"permission\":\"active\",\"parent\":\"owner\",\"auth\":{\"threshold\":1,\"keys\":[{\"key\":\"${NEW_ACTIVE_KEY}\",\"weight\":1}],\"accounts\":[],\"waits\":[]}}},"
416
+ fi
417
+ ACTIONS_JSON="${ACTIONS_JSON}{\"account\":\"eosio\",\"name\":\"updateauth\",\"authorization\":[{\"actor\":\"${AGENT_ACCOUNT}\",\"permission\":\"owner\"}],\"data\":{\"account\":\"${AGENT_ACCOUNT}\",\"permission\":\"owner\",\"parent\":\"\",\"auth\":{\"threshold\":1,\"keys\":[],\"accounts\":[{\"permission\":{\"actor\":\"${HUMAN_ACCOUNT}\",\"permission\":\"active\"},\"weight\":1}],\"waits\":[]}}}"
418
+
419
+ TX_JSON="{\"actions\":[${ACTIONS_JSON}]}"
420
+
421
+ # Push. Capture both stdout and stderr.
422
+ if ! TX_RESULT=$(proton transaction:push "$TX_JSON" 2>&1); then
423
+ err "Transaction failed:"
424
+ printf '%s\n' "$TX_RESULT"
425
+ err ""
426
+ err "No permission changes were made (EOSIO transactions are atomic)."
427
+ err "If this is recoverable, re-run the script. Otherwise check:"
428
+ info " proton account $AGENT_ACCOUNT"
429
+ exit 1
430
+ fi
431
+
432
+ # Extract tx id from the result (proton CLI prints it)
433
+ TX_ID=$(printf '%s' "$TX_RESULT" | grep -oE '[a-f0-9]{64}' | head -1)
434
+ if [ -n "$TX_ID" ]; then
435
+ ok "tx $TX_ID submitted"
436
+ else
437
+ warn "Transaction submitted but tx id not parsed from output."
438
+ info "Output: $TX_RESULT"
439
+ fi
440
+
441
+ # Wait a beat for the tx to land
442
+ sleep 3
443
+
444
+ # Re-fetch and verify end-state
445
+ info ""
446
+ info "Verifying final state..."
447
+ if ! POST_JSON=$(proton account "$AGENT_ACCOUNT" --json 2>/dev/null); then
448
+ err "Failed to re-fetch account state. Verify manually:"
449
+ info " proton account $AGENT_ACCOUNT"
450
+ info " https://explorer.xprnetwork.org/account/$AGENT_ACCOUNT"
451
+ exit 1
452
+ fi
453
+
454
+ POST_OWNER_KEYS=$(printf '%s' "$POST_JSON" | node -e "
455
+ let s=''; process.stdin.on('data',d=>s+=d); process.stdin.on('end',()=>{
456
+ try {
457
+ const a = JSON.parse(s);
458
+ const owner = (a.permissions||[]).find(p=>p.perm_name==='owner');
459
+ const keys = (owner?.required_auth?.keys||[]).map(k=>k.key);
460
+ console.log(keys.join(','));
461
+ } catch(e) { console.log(''); }
462
+ });
463
+ " <<< "$POST_JSON")
464
+ POST_OWNER_ACCOUNTS=$(printf '%s' "$POST_JSON" | node -e "
465
+ let s=''; process.stdin.on('data',d=>s+=d); process.stdin.on('end',()=>{
466
+ try {
467
+ const a = JSON.parse(s);
468
+ const owner = (a.permissions||[]).find(p=>p.perm_name==='owner');
469
+ const accts = (owner?.required_auth?.accounts||[]).map(x=>x.permission.actor+'@'+x.permission.permission);
470
+ console.log(accts.join(','));
471
+ } catch(e) { console.log(''); }
472
+ });
473
+ " <<< "$POST_JSON")
474
+
475
+ if [ -n "$POST_OWNER_KEYS" ]; then
476
+ err "owner still has raw keys after the change: $POST_OWNER_KEYS"
477
+ err "Pillar 2 is NOT in place. Investigate via the explorer."
478
+ exit 1
479
+ fi
480
+
481
+ if [ "$POST_OWNER_ACCOUNTS" != "${HUMAN_ACCOUNT}@active" ]; then
482
+ err "owner is not what we expected. Got: $POST_OWNER_ACCOUNTS"
483
+ err "Investigate via the explorer."
484
+ exit 1
485
+ fi
486
+
487
+ ok "owner now controlled by: ${HUMAN_ACCOUNT}@active (no raw keys)"
488
+ ok "Pillar 2 is in place."
489
+
490
+ cat <<EOF
491
+
492
+ ${GREEN}${BOLD}Done.${NC} '$AGENT_ACCOUNT' is secured.
493
+
494
+ • Daily signing: still works (active key is in the proton CLI keychain).
495
+ • Recovery: signed from ${HUMAN_ACCOUNT}@active if you ever need to rotate active.
496
+
497
+ Verify visually:
498
+ https://explorer.xprnetwork.org/account/$AGENT_ACCOUNT
499
+
500
+ Next steps (optional):
501
+ • Dry-run the recovery: see docs/SECURITY.md → "Optional: test recovery".
502
+ • Claim the agent via your KYC'd human account for +30 trust score.
503
+
504
+ EOF
@@ -208,13 +208,14 @@ When a job involves token swaps, OTC deals, or any financial trade:
208
208
  ## Safety Rules
209
209
 
210
210
  1. **Never reveal private keys** — Your blockchain key lives in the proton CLI's encrypted keychain (loaded once via `proton key:add`) and never enters this process's memory. Do not attempt to read it, dump it, or print it. Recommend operators use a dedicated agent account, not their personal account.
211
- 2. **Always verify before accepting** - Read job details thoroughly before committing
212
- 3. **Always provide evidence** - When delivering or disputing, include evidence URIs
213
- 4. **Respect confirmation gates** - High-risk actions (registration, funding, disputes) require confirmation
214
- 5. **Monitor your reputation** - A declining trust score needs investigation
215
- 6. **Don't over-commit** - Only accept jobs you can realistically complete
216
- 7. **Always check market price before trading** - Never execute a trade without verifying current rates
217
- 8. **Never sell tokens below market rate** - Protect your holdings from bad deals
211
+ 2. **Two-pillar security model** — Your account should be secured at two layers: (a) **active key** lives in the proton CLI keychain — you sign every transaction via shell-out (Pillar 1). (b) **owner permission** should point at the operator's separate human XPR account, not a raw key (Pillar 2). If the operator hasn't done Pillar 2, recommend they run `./setup-security.sh` (standalone scaffold) or `npx @xpr-agents/openclaw xpr-agents-setup-security --account <name>` (harness). The boot-time security warning from start.sh will flag this. See `docs/SECURITY.md`.
212
+ 3. **Always verify before accepting** - Read job details thoroughly before committing
213
+ 4. **Always provide evidence** - When delivering or disputing, include evidence URIs
214
+ 5. **Respect confirmation gates** - High-risk actions (registration, funding, disputes) require confirmation
215
+ 6. **Monitor your reputation** - A declining trust score needs investigation
216
+ 7. **Don't over-commit** - Only accept jobs you can realistically complete
217
+ 8. **Always check market price before trading** - Never execute a trade without verifying current rates
218
+ 9. **Never sell tokens below market rate** - Protect your holdings from bad deals
218
219
 
219
220
  ## Tool Quick Reference
220
221