@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 +102 -31
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +20 -9
- package/dist/index.js.map +1 -1
- package/package.json +4 -1
- package/scripts/setup-security.sh +504 -0
- package/skills/xpr-agent-operator/SKILL.md +8 -7
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
|
-
#
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
#
|
|
35
|
-
#
|
|
36
|
-
#
|
|
37
|
-
|
|
38
|
-
#
|
|
39
|
-
#
|
|
40
|
-
#
|
|
41
|
-
#
|
|
42
|
-
|
|
43
|
-
#
|
|
44
|
-
#
|
|
45
|
-
#
|
|
46
|
-
#
|
|
47
|
-
#
|
|
48
|
-
#
|
|
49
|
-
#
|
|
50
|
-
#
|
|
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
|
|
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.
|
|
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
|
|
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
|
package/dist/index.d.ts.map
CHANGED
|
@@ -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,
|
|
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
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
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":";AAAA;;;;;;;;;;;GAWG;;;AA0EH,
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xpr-agents/openclaw",
|
|
3
|
-
"version": "0.4.
|
|
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. **
|
|
212
|
-
3. **Always
|
|
213
|
-
4. **
|
|
214
|
-
5. **
|
|
215
|
-
6. **
|
|
216
|
-
7. **
|
|
217
|
-
8. **
|
|
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
|
|