@xpr-agents/openclaw 0.4.2 → 0.4.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 +13 -2
- 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
|
@@ -173,8 +173,17 @@ npm i -g @proton/cli
|
|
|
173
173
|
# 2. Point at the chain
|
|
174
174
|
proton chain:set proton # mainnet (use `proton-test` for testnet)
|
|
175
175
|
|
|
176
|
-
# 3.
|
|
177
|
-
|
|
176
|
+
# 3. Create the agent account (if you don't have one yet).
|
|
177
|
+
# Most operators: sign up at https://webauth.com — it mints a fresh
|
|
178
|
+
# account, gives you a 12-word seed phrase, and supports KYC.
|
|
179
|
+
# Then extract the K1 private key from the seed via:
|
|
180
|
+
# - https://explorer.xprnetwork.org/wallet/utilities/format-keys
|
|
181
|
+
# ("Mnemonic to Private Key" section), or
|
|
182
|
+
# - WebAuth mobile app → Backup Wallet → reveal private key
|
|
183
|
+
#
|
|
184
|
+
# Alternatively, if you already control a funded XPR account, use:
|
|
185
|
+
# proton account:create myagent
|
|
186
|
+
# This creates the account + key pair in one step — no seed extraction.
|
|
178
187
|
|
|
179
188
|
# 4. Load the key (interactive — pastes are hidden)
|
|
180
189
|
proton key:add # paste your PVT_K1_ key; stored encrypted
|
|
@@ -192,6 +201,8 @@ proton key:list # shows your account + public key
|
|
|
192
201
|
# proton key:unlock <your-keychain-password>
|
|
193
202
|
```
|
|
194
203
|
|
|
204
|
+
After your first run, lock down the agent's `owner` permission so a leaked active key can't take over the account: `npx @xpr-agents/openclaw xpr-agents-setup-security --account myagent`. Full rationale: [`docs/SECURITY.md`](https://github.com/XPRNetwork/xpr-agents/blob/main/docs/SECURITY.md).
|
|
205
|
+
|
|
195
206
|
### Required environment variables
|
|
196
207
|
|
|
197
208
|
```env
|
package/package.json
CHANGED
|
@@ -1,8 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xpr-agents/openclaw",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.4",
|
|
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
|
|