shll-skills 1.3.0 → 2.0.0
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 +26 -11
- package/SKILL.md +60 -34
- package/dist/index.js +122 -3
- package/dist/index.mjs +122 -3
- package/package.json +2 -2
- package/src/index.ts +151 -4
package/README.md
CHANGED
|
@@ -13,13 +13,17 @@ npm install -g shll-skills
|
|
|
13
13
|
## Quick Start
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
|
-
|
|
16
|
+
# 1. Generate an operator wallet (hot wallet for AI)
|
|
17
|
+
shll-run generate-wallet
|
|
18
|
+
# -> Outputs address + private key
|
|
17
19
|
|
|
18
|
-
|
|
19
|
-
shll-run init --listing-id 0xABC...DEF --days 30 --fund 0.5
|
|
20
|
-
# -> Agent #5 is ready!
|
|
20
|
+
export RUNNER_PRIVATE_KEY="0x...(operator key)..."
|
|
21
21
|
|
|
22
|
-
# 2.
|
|
22
|
+
# 2. Get setup instructions (user completes on shll.run with their OWN wallet)
|
|
23
|
+
shll-run setup-guide --listing-id 0xABC...DEF --days 30
|
|
24
|
+
# -> Outputs shll.run link for rent + authorize + fund
|
|
25
|
+
|
|
26
|
+
# 3. After setup, trade with your token-id
|
|
23
27
|
shll-run swap --from BNB --to USDC --amount 0.1 --token-id 5
|
|
24
28
|
```
|
|
25
29
|
|
|
@@ -77,18 +81,29 @@ AI Agent -> CLI command -> PolicyClient.validate() -> PolicyGuard (on-chain) ->
|
|
|
77
81
|
3. If approved, `AgentNFA.execute()` routes through PolicyGuard -> vault
|
|
78
82
|
4. PolicyGuard enforces: spending limits, cooldowns, DEX whitelist, receiver guard
|
|
79
83
|
|
|
80
|
-
## Security
|
|
84
|
+
## Security: Dual-Wallet Architecture
|
|
85
|
+
|
|
86
|
+
SHLL enforces **separation of owner and operator wallets**:
|
|
87
|
+
|
|
88
|
+
| | Owner Wallet | Operator Wallet (RUNNER_PRIVATE_KEY) |
|
|
89
|
+
|---|---|---|
|
|
90
|
+
| **Who holds it** | User (MetaMask/hardware) | AI agent |
|
|
91
|
+
| **Can trade** | — | ✅ Within PolicyGuard limits |
|
|
92
|
+
| **Can withdraw vault** | ✅ | ❌ |
|
|
93
|
+
| **Can transfer NFT** | ✅ | ❌ |
|
|
94
|
+
| **Risk if leaked** | 🚨 Full vault access | ⚠️ Limited to policy-allowed trades |
|
|
81
95
|
|
|
82
|
-
|
|
83
|
-
-
|
|
84
|
-
-
|
|
85
|
-
-
|
|
96
|
+
**Additional on-chain enforcement:**
|
|
97
|
+
- PolicyGuard validates every transaction, not the AI
|
|
98
|
+
- Vault isolation — operator key cannot directly access vault funds
|
|
99
|
+
- Risk limits can only be tightened, never loosened
|
|
100
|
+
- Unknown selectors, targets, or recipients are rejected
|
|
86
101
|
|
|
87
102
|
## Environment Variables
|
|
88
103
|
|
|
89
104
|
| Variable | Required | Description |
|
|
90
105
|
|----------|----------|-------------|
|
|
91
|
-
| `RUNNER_PRIVATE_KEY` | Yes |
|
|
106
|
+
| `RUNNER_PRIVATE_KEY` | Yes | Operator wallet key (hot wallet, ~$1 BNB for gas) |
|
|
92
107
|
| `RPC_URL` | No | BSC RPC (default: public endpoint) |
|
|
93
108
|
| `NFA_ADDRESS` | No | AgentNFA contract override |
|
|
94
109
|
| `GUARD_ADDRESS` | No | PolicyGuard contract override |
|
package/SKILL.md
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: shll-run
|
|
3
3
|
description: Execute DeFi transactions on BSC via SHLL AgentNFA. The AI handles all commands — users only need to chat.
|
|
4
|
-
version:
|
|
4
|
+
version: 4.0.0
|
|
5
5
|
author: SHLL Team
|
|
6
6
|
website: https://shll.run
|
|
7
7
|
twitter: https://twitter.com/shllrun
|
|
@@ -16,32 +16,56 @@ You are an AI agent with access to SHLL DeFi tools on BSC. **The user should nev
|
|
|
16
16
|
|
|
17
17
|
---
|
|
18
18
|
|
|
19
|
+
## ⛔ SAFETY RULES (MANDATORY)
|
|
20
|
+
|
|
21
|
+
These rules are **non-negotiable**. Violating any of them is a critical failure.
|
|
22
|
+
|
|
23
|
+
1. **Token-ID must come from the user.** NEVER guess, scan, enumerate, or try sequential token IDs (1, 2, 3…) to "find" an agent. Only use a token-id the user explicitly provides in this conversation.
|
|
24
|
+
2. **Do NOT operate on token IDs the user has not mentioned.** Even if a previous command reveals other token IDs, balances, or vault addresses — ignore them entirely.
|
|
25
|
+
3. **Confirm before every write operation.** Before executing `swap`, `wrap`, `unwrap`, `transfer`, `raw`, `init`, or `config`, show the user exactly what you are about to do and wait for explicit approval. Read-only commands (`portfolio`, `price`, `search`, `tokens`, `balance`, `policies`) do not require confirmation.
|
|
26
|
+
4. **One agent per conversation.** Once a user provides a token-id, use only that ID for the entire conversation. If they want to switch, they must explicitly say so.
|
|
27
|
+
5. **Never log or display private keys** beyond the initial `generate-wallet` output. If the user asks you to repeat it, remind them to check their saved copy.
|
|
28
|
+
6. **Do not infer trading intent.** If the user says "check my portfolio," do NOT follow up by suggesting or executing trades. Only trade when the user explicitly asks.
|
|
29
|
+
|
|
30
|
+
---
|
|
31
|
+
|
|
32
|
+
## 🔐 SECURITY MODEL: Dual-Wallet Architecture
|
|
33
|
+
|
|
34
|
+
SHLL uses **two separate wallets** for maximum security:
|
|
35
|
+
|
|
36
|
+
| | Owner Wallet (Cold) | Operator Wallet (Hot) |
|
|
37
|
+
|---|---|---|
|
|
38
|
+
| **Who holds it** | User (MetaMask, hardware wallet) | AI agent (RUNNER_PRIVATE_KEY) |
|
|
39
|
+
| **Can trade** | — | ✅ Within PolicyGuard limits |
|
|
40
|
+
| **Can withdraw vault** | ✅ | ❌ |
|
|
41
|
+
| **Can transfer NFT** | ✅ | ❌ |
|
|
42
|
+
| **Can change operator** | ✅ | ❌ |
|
|
43
|
+
| **Risk if leaked** | 🚨 Full vault access | ⚠️ Limited to policy-allowed trades |
|
|
44
|
+
|
|
45
|
+
**Critical rule: You (the AI) ONLY have access to the operator wallet. You must NEVER ask for, store, or handle the user's owner wallet key. All owner-level operations (rent, fund, withdraw) are done by the user on https://shll.run.**
|
|
46
|
+
|
|
47
|
+
---
|
|
48
|
+
|
|
19
49
|
## CONVERSATION FLOW: New User Onboarding
|
|
20
50
|
|
|
21
51
|
When a user wants to use SHLL for the first time, follow this decision tree:
|
|
22
52
|
|
|
23
|
-
### Step 1:
|
|
53
|
+
### Step 1: Generate operator wallet
|
|
24
54
|
|
|
25
|
-
Ask: *"Do you already have
|
|
55
|
+
Ask: *"Do you already have an operator wallet set up for SHLL? (a RUNNER_PRIVATE_KEY)"*
|
|
26
56
|
|
|
27
|
-
**If NO**
|
|
57
|
+
**If NO** → Generate one:
|
|
28
58
|
```bash
|
|
29
59
|
shll-run generate-wallet
|
|
30
60
|
```
|
|
31
|
-
This outputs:
|
|
32
|
-
```json
|
|
33
|
-
{"status":"success","address":"0xABC...","privateKey":"0x123...","note":"..."}
|
|
34
|
-
```
|
|
35
61
|
|
|
36
62
|
Tell the user:
|
|
37
|
-
- *"I've generated
|
|
63
|
+
- *"I've generated an operator wallet for you."*
|
|
38
64
|
- *"Address: `0xABC...` — send about $1 of BNB here for gas fees."*
|
|
39
|
-
- *"Private key: `0x123...` — save this
|
|
40
|
-
- *"This
|
|
41
|
-
|
|
42
|
-
Then wait for them to fund it.
|
|
65
|
+
- *"Private key: `0x123...` — save this securely, then set it as RUNNER_PRIVATE_KEY."*
|
|
66
|
+
- *"This is an OPERATOR wallet — it can only trade within safety limits. It CANNOT withdraw your vault funds or touch your agent ownership."*
|
|
43
67
|
|
|
44
|
-
**If YES**
|
|
68
|
+
**If YES** → Set it:
|
|
45
69
|
```bash
|
|
46
70
|
export RUNNER_PRIVATE_KEY="0x..."
|
|
47
71
|
```
|
|
@@ -53,26 +77,28 @@ shll-run balance
|
|
|
53
77
|
```
|
|
54
78
|
|
|
55
79
|
If `sufficient: false`, tell the user:
|
|
56
|
-
*"Your
|
|
80
|
+
*"Your operator wallet needs more BNB for gas fees. Send at least $1 of BNB to `<address>`. You can buy BNB on Binance, OKX, or any exchange and withdraw to BSC (BEP-20)."*
|
|
57
81
|
|
|
58
|
-
Wait until balance is sufficient
|
|
82
|
+
Wait until balance is sufficient.
|
|
59
83
|
|
|
60
|
-
### Step 3:
|
|
84
|
+
### Step 3: Set up agent (user does this themselves)
|
|
61
85
|
|
|
62
86
|
Ask: *"Do you already have a SHLL Agent? (a token-id number)"*
|
|
63
87
|
|
|
64
|
-
**If NO**
|
|
88
|
+
**If NO** → Guide them to set up via shll.run:
|
|
65
89
|
```bash
|
|
66
|
-
shll-run
|
|
90
|
+
shll-run setup-guide --listing-id <LISTING_ID> --days 30
|
|
67
91
|
```
|
|
68
92
|
|
|
69
|
-
|
|
70
|
-
*"
|
|
93
|
+
This outputs a link to https://shll.run/setup with pre-filled parameters. Tell the user:
|
|
94
|
+
1. *"Open the setup link in your browser."*
|
|
95
|
+
2. *"Connect YOUR personal wallet (MetaMask, WalletConnect) — this becomes the owner wallet."*
|
|
96
|
+
3. *"Follow the steps to: rent an agent → authorize the operator wallet → fund the vault."*
|
|
97
|
+
4. *"When done, tell me your token-id number."*
|
|
71
98
|
|
|
72
|
-
|
|
73
|
-
*"Your Agent #<tokenId> is ready! Your trading vault is at `<vault>`. I've funded it with 0.1 BNB. You can start trading now."*
|
|
99
|
+
**⚠️ NEVER use `init` for new users.** The `init` command is deprecated because it uses the same key for owner and operator, which is a security risk.
|
|
74
100
|
|
|
75
|
-
**If YES**
|
|
101
|
+
**If YES** → Verify:
|
|
76
102
|
```bash
|
|
77
103
|
shll-run portfolio -k <ID>
|
|
78
104
|
```
|
|
@@ -89,16 +115,13 @@ The user is now set up. They can ask you things like:
|
|
|
89
115
|
|
|
90
116
|
## COMMAND REFERENCE
|
|
91
117
|
|
|
92
|
-
### Wallet Setup
|
|
118
|
+
### Wallet & Setup
|
|
93
119
|
| Command | What it does |
|
|
94
120
|
|---------|-------------|
|
|
95
|
-
| `shll-run generate-wallet` | Create a new
|
|
96
|
-
| `shll-run balance` | Check
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
| Command | What it does |
|
|
100
|
-
|---------|-------------|
|
|
101
|
-
| `shll-run init --listing-id <ID> --days <N> [--fund <BNB>]` | Rent agent + authorize + fund vault |
|
|
121
|
+
| `shll-run generate-wallet` | Create a new operator wallet (address + private key) |
|
|
122
|
+
| `shll-run balance` | Check operator wallet BNB balance |
|
|
123
|
+
| `shll-run setup-guide -l <LISTING> -d <DAYS>` | Output setup instructions + shll.run link for secure onboarding |
|
|
124
|
+
| ~~`shll-run init`~~ | **DEPRECATED** — insecure single-wallet mode |
|
|
102
125
|
|
|
103
126
|
### Trading
|
|
104
127
|
| Command | What it does |
|
|
@@ -130,10 +153,13 @@ The user is now set up. They can ask you things like:
|
|
|
130
153
|
## HOW TO EXPLAIN THINGS TO USERS
|
|
131
154
|
|
|
132
155
|
### "What is RUNNER_PRIVATE_KEY?"
|
|
133
|
-
*"It's
|
|
156
|
+
*"It's your operator wallet — a hot wallet that the AI uses to execute trades within safety limits. Think of it like a company credit card with a spending cap. It can NOT withdraw funds from your vault or transfer your agent ownership. You only need ~$1 of BNB in it for gas fees."*
|
|
157
|
+
|
|
158
|
+
### "Why do I need two wallets?"
|
|
159
|
+
*"Security. Your personal wallet (owner) controls high-risk operations like withdrawing vault funds. The operator wallet can only trade within PolicyGuard limits. Even if the operator key is compromised, an attacker can NOT drain your vault — they can only make trades within the safety rules you've set. Your owner wallet stays offline and safe."*
|
|
134
160
|
|
|
135
161
|
### "Is my money safe?"
|
|
136
|
-
*"Yes.
|
|
162
|
+
*"Yes, on multiple levels. First, the operator wallet (which the AI uses) cannot withdraw vault funds — only your owner wallet can. Second, all trades go through PolicyGuard, which enforces spending limits, cooldowns, and DEX whitelists. Even if someone got the operator key, your money is protected by on-chain smart contract rules."*
|
|
137
163
|
|
|
138
164
|
### "What are policies?"
|
|
139
165
|
*"Policies are on-chain safety rules: how much you can spend per transaction, how often you can trade, which DEXs are allowed, etc. You can tighten these rules but never loosen them beyond the template ceiling."*
|
package/dist/index.js
CHANGED
|
@@ -858,9 +858,16 @@ tokensCmd.action(() => {
|
|
|
858
858
|
}));
|
|
859
859
|
output({ status: "success", tokens });
|
|
860
860
|
});
|
|
861
|
-
var initCmd = new import_commander.Command("init").description("One-click setup
|
|
861
|
+
var initCmd = new import_commander.Command("init").description("[DEPRECATED] One-click setup \u2014 uses same key as owner AND operator. Use setup-guide instead.").requiredOption("-l, --listing-id <bytes32>", "Template listing ID (bytes32 hex)").requiredOption("-d, --days <number>", "Number of days to rent").option("-f, --fund <bnb>", "BNB to deposit into vault (human-readable, e.g. 0.1)", "0").option("--i-understand-the-risk", "Acknowledge the security risk of using same key for owner and operator").option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC).option("--listing-manager <address>", "ListingManagerV2 address", DEFAULT_LISTING_MANAGER).option("--nfa-address <address>", "AgentNFA contract address", DEFAULT_NFA);
|
|
862
862
|
initCmd.action(async (opts) => {
|
|
863
863
|
try {
|
|
864
|
+
if (!opts.iUnderstandTheRisk) {
|
|
865
|
+
output({
|
|
866
|
+
status: "error",
|
|
867
|
+
message: "\u26A0\uFE0F DEPRECATED: 'init' uses the same private key as both owner AND operator. If this key is leaked (e.g. via AI prompt injection), an attacker can withdraw ALL vault funds. Use 'setup-guide' instead for a secure dual-wallet setup. If you understand the risk and still want to proceed, add --i-understand-the-risk."
|
|
868
|
+
});
|
|
869
|
+
process.exit(1);
|
|
870
|
+
}
|
|
864
871
|
if (!process.env.RUNNER_PRIVATE_KEY) {
|
|
865
872
|
output({ status: "error", message: "RUNNER_PRIVATE_KEY environment variable is missing" });
|
|
866
873
|
process.exit(1);
|
|
@@ -1459,14 +1466,125 @@ configCmd.action(async (opts) => {
|
|
|
1459
1466
|
process.exit(1);
|
|
1460
1467
|
}
|
|
1461
1468
|
});
|
|
1462
|
-
var
|
|
1469
|
+
var setupGuideCmd = new import_commander.Command("setup-guide").description("Output step-by-step instructions for secure dual-wallet agent setup").requiredOption("-l, --listing-id <bytes32>", "Template listing ID (bytes32 hex)").requiredOption("-d, --days <number>", "Number of days to rent").option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC).option("--listing-manager <address>", "ListingManagerV2 address", DEFAULT_LISTING_MANAGER).option("--nfa-address <address>", "AgentNFA contract address", DEFAULT_NFA);
|
|
1470
|
+
setupGuideCmd.action(async (opts) => {
|
|
1471
|
+
try {
|
|
1472
|
+
const pk = process.env.RUNNER_PRIVATE_KEY;
|
|
1473
|
+
let operatorAddress;
|
|
1474
|
+
if (pk) {
|
|
1475
|
+
const account = (0, import_accounts2.privateKeyToAccount)(toHex(pk));
|
|
1476
|
+
operatorAddress = account.address;
|
|
1477
|
+
} else {
|
|
1478
|
+
output({
|
|
1479
|
+
status: "error",
|
|
1480
|
+
message: "RUNNER_PRIVATE_KEY not set. Run 'generate-wallet' first to create an operator wallet, then set RUNNER_PRIVATE_KEY."
|
|
1481
|
+
});
|
|
1482
|
+
process.exit(1);
|
|
1483
|
+
return;
|
|
1484
|
+
}
|
|
1485
|
+
const rpcUrl = opts.rpc || DEFAULT_RPC;
|
|
1486
|
+
const listingManagerAddr = toHex(opts.listingManager || DEFAULT_LISTING_MANAGER);
|
|
1487
|
+
const nfaAddr = toHex(opts.nfaAddress || DEFAULT_NFA);
|
|
1488
|
+
const listingId = opts.listingId;
|
|
1489
|
+
const daysToRent = Number(opts.days);
|
|
1490
|
+
const publicClient = (0, import_viem2.createPublicClient)({ chain: import_chains2.bsc, transport: (0, import_viem2.http)(rpcUrl) });
|
|
1491
|
+
let rentCost = "unknown";
|
|
1492
|
+
let priceInfo = "";
|
|
1493
|
+
try {
|
|
1494
|
+
const listing = await publicClient.readContract({
|
|
1495
|
+
address: listingManagerAddr,
|
|
1496
|
+
abi: LISTING_MANAGER_ABI,
|
|
1497
|
+
functionName: "listings",
|
|
1498
|
+
args: [listingId]
|
|
1499
|
+
});
|
|
1500
|
+
const [, , , pricePerDay, minDays, active] = listing;
|
|
1501
|
+
if (!active) {
|
|
1502
|
+
output({ status: "error", message: "Listing is not active" });
|
|
1503
|
+
process.exit(1);
|
|
1504
|
+
}
|
|
1505
|
+
if (daysToRent < minDays) {
|
|
1506
|
+
output({ status: "error", message: `Minimum rental is ${minDays} days, you requested ${daysToRent}` });
|
|
1507
|
+
process.exit(1);
|
|
1508
|
+
}
|
|
1509
|
+
const totalRent = BigInt(pricePerDay) * BigInt(daysToRent);
|
|
1510
|
+
rentCost = `${(Number(totalRent) / 1e18).toFixed(6)} BNB`;
|
|
1511
|
+
priceInfo = ` (${totalRent.toString()} wei)`;
|
|
1512
|
+
} catch {
|
|
1513
|
+
rentCost = "unable to query \u2014 check listing-id";
|
|
1514
|
+
}
|
|
1515
|
+
const expiryTimestamp = Math.floor(Date.now() / 1e3) + daysToRent * 86400;
|
|
1516
|
+
const setupUrl = `https://shll.run/setup?operator=${operatorAddress}&listing=${encodeURIComponent(listingId)}&days=${daysToRent}`;
|
|
1517
|
+
output({
|
|
1518
|
+
status: "guide",
|
|
1519
|
+
securityModel: "DUAL-WALLET: Your wallet (owner) stays offline. AI only uses the operator wallet, which CANNOT withdraw vault funds.",
|
|
1520
|
+
operatorAddress,
|
|
1521
|
+
setupUrl,
|
|
1522
|
+
rentCost: `${rentCost}${priceInfo}`,
|
|
1523
|
+
steps: [
|
|
1524
|
+
{
|
|
1525
|
+
step: 1,
|
|
1526
|
+
title: "Open SHLL Setup Page",
|
|
1527
|
+
action: `Open ${setupUrl} in your browser`,
|
|
1528
|
+
note: "Connect YOUR wallet (MetaMask/WalletConnect). This is your owner wallet \u2014 keep it safe and offline after setup."
|
|
1529
|
+
},
|
|
1530
|
+
{
|
|
1531
|
+
step: 2,
|
|
1532
|
+
title: "Rent Agent",
|
|
1533
|
+
action: "Click 'Rent Agent' and confirm the transaction",
|
|
1534
|
+
fallback: {
|
|
1535
|
+
method: "BscScan (manual)",
|
|
1536
|
+
contract: listingManagerAddr,
|
|
1537
|
+
function: "rentToMintWithParams(bytes32,uint32,uint32,uint16,bytes)",
|
|
1538
|
+
args: [listingId, daysToRent, 1, 1, "0x01"],
|
|
1539
|
+
value: rentCost
|
|
1540
|
+
}
|
|
1541
|
+
},
|
|
1542
|
+
{
|
|
1543
|
+
step: 3,
|
|
1544
|
+
title: "Authorize Operator",
|
|
1545
|
+
action: "Click 'Authorize Operator' \u2014 this gives the AI wallet permission to trade within PolicyGuard safety limits",
|
|
1546
|
+
note: `Operator address: ${operatorAddress}`,
|
|
1547
|
+
fallback: {
|
|
1548
|
+
method: "BscScan (manual)",
|
|
1549
|
+
contract: nfaAddr,
|
|
1550
|
+
function: "setOperator(uint256,address,uint64)",
|
|
1551
|
+
args: ["<tokenId from step 2>", operatorAddress, expiryTimestamp]
|
|
1552
|
+
}
|
|
1553
|
+
},
|
|
1554
|
+
{
|
|
1555
|
+
step: 4,
|
|
1556
|
+
title: "Fund Vault (optional)",
|
|
1557
|
+
action: "Deposit BNB into the vault for trading",
|
|
1558
|
+
fallback: {
|
|
1559
|
+
method: "BscScan (manual)",
|
|
1560
|
+
contract: nfaAddr,
|
|
1561
|
+
function: "fundAgent(uint256)",
|
|
1562
|
+
args: ["<tokenId>"],
|
|
1563
|
+
value: "amount of BNB to deposit"
|
|
1564
|
+
}
|
|
1565
|
+
},
|
|
1566
|
+
{
|
|
1567
|
+
step: 5,
|
|
1568
|
+
title: "Tell AI your token-id",
|
|
1569
|
+
action: "Come back and tell the AI your token-id number. The AI will verify your portfolio and you're ready to trade."
|
|
1570
|
+
}
|
|
1571
|
+
]
|
|
1572
|
+
});
|
|
1573
|
+
} catch (error) {
|
|
1574
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1575
|
+
output({ status: "error", message });
|
|
1576
|
+
process.exit(1);
|
|
1577
|
+
}
|
|
1578
|
+
});
|
|
1579
|
+
var genWalletCmd = new import_commander.Command("generate-wallet").description("Generate a new operator wallet (address + private key) for AI to use").action(() => {
|
|
1463
1580
|
const pk = (0, import_accounts2.generatePrivateKey)();
|
|
1464
1581
|
const account = (0, import_accounts2.privateKeyToAccount)(pk);
|
|
1465
1582
|
output({
|
|
1466
1583
|
status: "success",
|
|
1467
1584
|
address: account.address,
|
|
1468
1585
|
privateKey: pk,
|
|
1469
|
-
note: "SAVE THIS PRIVATE KEY SECURELY. This wallet
|
|
1586
|
+
note: "SAVE THIS PRIVATE KEY SECURELY. This is the OPERATOR wallet \u2014 it can only trade within PolicyGuard limits. It CANNOT withdraw vault funds or transfer your Agent NFT. Send ~$1 of BNB here for gas fees, then set RUNNER_PRIVATE_KEY to this privateKey value.",
|
|
1587
|
+
securityReminder: "Use a SEPARATE wallet (MetaMask, hardware wallet) as the owner to rent the agent and fund the vault. Run 'setup-guide' for step-by-step instructions."
|
|
1470
1588
|
});
|
|
1471
1589
|
});
|
|
1472
1590
|
var balanceCmd = new import_commander.Command("balance").description("Check BNB balance of the gas-paying wallet (RUNNER_PRIVATE_KEY)").option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC).action(async (opts) => {
|
|
@@ -1507,6 +1625,7 @@ program.addCommand(unwrapCmd);
|
|
|
1507
1625
|
program.addCommand(transferCmd);
|
|
1508
1626
|
program.addCommand(policiesCmd);
|
|
1509
1627
|
program.addCommand(configCmd);
|
|
1628
|
+
program.addCommand(setupGuideCmd);
|
|
1510
1629
|
program.addCommand(genWalletCmd);
|
|
1511
1630
|
program.addCommand(balanceCmd);
|
|
1512
1631
|
program.parse(process.argv);
|
package/dist/index.mjs
CHANGED
|
@@ -868,9 +868,16 @@ tokensCmd.action(() => {
|
|
|
868
868
|
}));
|
|
869
869
|
output({ status: "success", tokens });
|
|
870
870
|
});
|
|
871
|
-
var initCmd = new Command("init").description("One-click setup
|
|
871
|
+
var initCmd = new Command("init").description("[DEPRECATED] One-click setup \u2014 uses same key as owner AND operator. Use setup-guide instead.").requiredOption("-l, --listing-id <bytes32>", "Template listing ID (bytes32 hex)").requiredOption("-d, --days <number>", "Number of days to rent").option("-f, --fund <bnb>", "BNB to deposit into vault (human-readable, e.g. 0.1)", "0").option("--i-understand-the-risk", "Acknowledge the security risk of using same key for owner and operator").option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC).option("--listing-manager <address>", "ListingManagerV2 address", DEFAULT_LISTING_MANAGER).option("--nfa-address <address>", "AgentNFA contract address", DEFAULT_NFA);
|
|
872
872
|
initCmd.action(async (opts) => {
|
|
873
873
|
try {
|
|
874
|
+
if (!opts.iUnderstandTheRisk) {
|
|
875
|
+
output({
|
|
876
|
+
status: "error",
|
|
877
|
+
message: "\u26A0\uFE0F DEPRECATED: 'init' uses the same private key as both owner AND operator. If this key is leaked (e.g. via AI prompt injection), an attacker can withdraw ALL vault funds. Use 'setup-guide' instead for a secure dual-wallet setup. If you understand the risk and still want to proceed, add --i-understand-the-risk."
|
|
878
|
+
});
|
|
879
|
+
process.exit(1);
|
|
880
|
+
}
|
|
874
881
|
if (!process.env.RUNNER_PRIVATE_KEY) {
|
|
875
882
|
output({ status: "error", message: "RUNNER_PRIVATE_KEY environment variable is missing" });
|
|
876
883
|
process.exit(1);
|
|
@@ -1469,14 +1476,125 @@ configCmd.action(async (opts) => {
|
|
|
1469
1476
|
process.exit(1);
|
|
1470
1477
|
}
|
|
1471
1478
|
});
|
|
1472
|
-
var
|
|
1479
|
+
var setupGuideCmd = new Command("setup-guide").description("Output step-by-step instructions for secure dual-wallet agent setup").requiredOption("-l, --listing-id <bytes32>", "Template listing ID (bytes32 hex)").requiredOption("-d, --days <number>", "Number of days to rent").option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC).option("--listing-manager <address>", "ListingManagerV2 address", DEFAULT_LISTING_MANAGER).option("--nfa-address <address>", "AgentNFA contract address", DEFAULT_NFA);
|
|
1480
|
+
setupGuideCmd.action(async (opts) => {
|
|
1481
|
+
try {
|
|
1482
|
+
const pk = process.env.RUNNER_PRIVATE_KEY;
|
|
1483
|
+
let operatorAddress;
|
|
1484
|
+
if (pk) {
|
|
1485
|
+
const account = privateKeyToAccount2(toHex(pk));
|
|
1486
|
+
operatorAddress = account.address;
|
|
1487
|
+
} else {
|
|
1488
|
+
output({
|
|
1489
|
+
status: "error",
|
|
1490
|
+
message: "RUNNER_PRIVATE_KEY not set. Run 'generate-wallet' first to create an operator wallet, then set RUNNER_PRIVATE_KEY."
|
|
1491
|
+
});
|
|
1492
|
+
process.exit(1);
|
|
1493
|
+
return;
|
|
1494
|
+
}
|
|
1495
|
+
const rpcUrl = opts.rpc || DEFAULT_RPC;
|
|
1496
|
+
const listingManagerAddr = toHex(opts.listingManager || DEFAULT_LISTING_MANAGER);
|
|
1497
|
+
const nfaAddr = toHex(opts.nfaAddress || DEFAULT_NFA);
|
|
1498
|
+
const listingId = opts.listingId;
|
|
1499
|
+
const daysToRent = Number(opts.days);
|
|
1500
|
+
const publicClient = createPublicClient2({ chain: bsc2, transport: http2(rpcUrl) });
|
|
1501
|
+
let rentCost = "unknown";
|
|
1502
|
+
let priceInfo = "";
|
|
1503
|
+
try {
|
|
1504
|
+
const listing = await publicClient.readContract({
|
|
1505
|
+
address: listingManagerAddr,
|
|
1506
|
+
abi: LISTING_MANAGER_ABI,
|
|
1507
|
+
functionName: "listings",
|
|
1508
|
+
args: [listingId]
|
|
1509
|
+
});
|
|
1510
|
+
const [, , , pricePerDay, minDays, active] = listing;
|
|
1511
|
+
if (!active) {
|
|
1512
|
+
output({ status: "error", message: "Listing is not active" });
|
|
1513
|
+
process.exit(1);
|
|
1514
|
+
}
|
|
1515
|
+
if (daysToRent < minDays) {
|
|
1516
|
+
output({ status: "error", message: `Minimum rental is ${minDays} days, you requested ${daysToRent}` });
|
|
1517
|
+
process.exit(1);
|
|
1518
|
+
}
|
|
1519
|
+
const totalRent = BigInt(pricePerDay) * BigInt(daysToRent);
|
|
1520
|
+
rentCost = `${(Number(totalRent) / 1e18).toFixed(6)} BNB`;
|
|
1521
|
+
priceInfo = ` (${totalRent.toString()} wei)`;
|
|
1522
|
+
} catch {
|
|
1523
|
+
rentCost = "unable to query \u2014 check listing-id";
|
|
1524
|
+
}
|
|
1525
|
+
const expiryTimestamp = Math.floor(Date.now() / 1e3) + daysToRent * 86400;
|
|
1526
|
+
const setupUrl = `https://shll.run/setup?operator=${operatorAddress}&listing=${encodeURIComponent(listingId)}&days=${daysToRent}`;
|
|
1527
|
+
output({
|
|
1528
|
+
status: "guide",
|
|
1529
|
+
securityModel: "DUAL-WALLET: Your wallet (owner) stays offline. AI only uses the operator wallet, which CANNOT withdraw vault funds.",
|
|
1530
|
+
operatorAddress,
|
|
1531
|
+
setupUrl,
|
|
1532
|
+
rentCost: `${rentCost}${priceInfo}`,
|
|
1533
|
+
steps: [
|
|
1534
|
+
{
|
|
1535
|
+
step: 1,
|
|
1536
|
+
title: "Open SHLL Setup Page",
|
|
1537
|
+
action: `Open ${setupUrl} in your browser`,
|
|
1538
|
+
note: "Connect YOUR wallet (MetaMask/WalletConnect). This is your owner wallet \u2014 keep it safe and offline after setup."
|
|
1539
|
+
},
|
|
1540
|
+
{
|
|
1541
|
+
step: 2,
|
|
1542
|
+
title: "Rent Agent",
|
|
1543
|
+
action: "Click 'Rent Agent' and confirm the transaction",
|
|
1544
|
+
fallback: {
|
|
1545
|
+
method: "BscScan (manual)",
|
|
1546
|
+
contract: listingManagerAddr,
|
|
1547
|
+
function: "rentToMintWithParams(bytes32,uint32,uint32,uint16,bytes)",
|
|
1548
|
+
args: [listingId, daysToRent, 1, 1, "0x01"],
|
|
1549
|
+
value: rentCost
|
|
1550
|
+
}
|
|
1551
|
+
},
|
|
1552
|
+
{
|
|
1553
|
+
step: 3,
|
|
1554
|
+
title: "Authorize Operator",
|
|
1555
|
+
action: "Click 'Authorize Operator' \u2014 this gives the AI wallet permission to trade within PolicyGuard safety limits",
|
|
1556
|
+
note: `Operator address: ${operatorAddress}`,
|
|
1557
|
+
fallback: {
|
|
1558
|
+
method: "BscScan (manual)",
|
|
1559
|
+
contract: nfaAddr,
|
|
1560
|
+
function: "setOperator(uint256,address,uint64)",
|
|
1561
|
+
args: ["<tokenId from step 2>", operatorAddress, expiryTimestamp]
|
|
1562
|
+
}
|
|
1563
|
+
},
|
|
1564
|
+
{
|
|
1565
|
+
step: 4,
|
|
1566
|
+
title: "Fund Vault (optional)",
|
|
1567
|
+
action: "Deposit BNB into the vault for trading",
|
|
1568
|
+
fallback: {
|
|
1569
|
+
method: "BscScan (manual)",
|
|
1570
|
+
contract: nfaAddr,
|
|
1571
|
+
function: "fundAgent(uint256)",
|
|
1572
|
+
args: ["<tokenId>"],
|
|
1573
|
+
value: "amount of BNB to deposit"
|
|
1574
|
+
}
|
|
1575
|
+
},
|
|
1576
|
+
{
|
|
1577
|
+
step: 5,
|
|
1578
|
+
title: "Tell AI your token-id",
|
|
1579
|
+
action: "Come back and tell the AI your token-id number. The AI will verify your portfolio and you're ready to trade."
|
|
1580
|
+
}
|
|
1581
|
+
]
|
|
1582
|
+
});
|
|
1583
|
+
} catch (error) {
|
|
1584
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1585
|
+
output({ status: "error", message });
|
|
1586
|
+
process.exit(1);
|
|
1587
|
+
}
|
|
1588
|
+
});
|
|
1589
|
+
var genWalletCmd = new Command("generate-wallet").description("Generate a new operator wallet (address + private key) for AI to use").action(() => {
|
|
1473
1590
|
const pk = generatePrivateKey();
|
|
1474
1591
|
const account = privateKeyToAccount2(pk);
|
|
1475
1592
|
output({
|
|
1476
1593
|
status: "success",
|
|
1477
1594
|
address: account.address,
|
|
1478
1595
|
privateKey: pk,
|
|
1479
|
-
note: "SAVE THIS PRIVATE KEY SECURELY. This wallet
|
|
1596
|
+
note: "SAVE THIS PRIVATE KEY SECURELY. This is the OPERATOR wallet \u2014 it can only trade within PolicyGuard limits. It CANNOT withdraw vault funds or transfer your Agent NFT. Send ~$1 of BNB here for gas fees, then set RUNNER_PRIVATE_KEY to this privateKey value.",
|
|
1597
|
+
securityReminder: "Use a SEPARATE wallet (MetaMask, hardware wallet) as the owner to rent the agent and fund the vault. Run 'setup-guide' for step-by-step instructions."
|
|
1480
1598
|
});
|
|
1481
1599
|
});
|
|
1482
1600
|
var balanceCmd = new Command("balance").description("Check BNB balance of the gas-paying wallet (RUNNER_PRIVATE_KEY)").option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC).action(async (opts) => {
|
|
@@ -1517,6 +1635,7 @@ program.addCommand(unwrapCmd);
|
|
|
1517
1635
|
program.addCommand(transferCmd);
|
|
1518
1636
|
program.addCommand(policiesCmd);
|
|
1519
1637
|
program.addCommand(configCmd);
|
|
1638
|
+
program.addCommand(setupGuideCmd);
|
|
1520
1639
|
program.addCommand(genWalletCmd);
|
|
1521
1640
|
program.addCommand(balanceCmd);
|
|
1522
1641
|
program.parse(process.argv);
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "shll-skills",
|
|
3
|
-
"version": "
|
|
3
|
+
"version": "2.0.0",
|
|
4
4
|
"description": "SHLL Agent Runtime Skill for OpenClaw",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"bin": {
|
|
@@ -21,4 +21,4 @@
|
|
|
21
21
|
"typescript": "^5.4.5",
|
|
22
22
|
"@types/node": "^20.12.7"
|
|
23
23
|
}
|
|
24
|
-
}
|
|
24
|
+
}
|
package/src/index.ts
CHANGED
|
@@ -438,18 +438,31 @@ tokensCmd.action(() => {
|
|
|
438
438
|
output({ status: "success", tokens });
|
|
439
439
|
});
|
|
440
440
|
|
|
441
|
-
// ── Subcommand: init (
|
|
441
|
+
// ── Subcommand: init (DEPRECATED — uses same key for owner+operator) ──
|
|
442
442
|
const initCmd = new Command("init")
|
|
443
|
-
.description("One-click setup
|
|
443
|
+
.description("[DEPRECATED] One-click setup — uses same key as owner AND operator. Use setup-guide instead.")
|
|
444
444
|
.requiredOption("-l, --listing-id <bytes32>", "Template listing ID (bytes32 hex)")
|
|
445
445
|
.requiredOption("-d, --days <number>", "Number of days to rent")
|
|
446
446
|
.option("-f, --fund <bnb>", "BNB to deposit into vault (human-readable, e.g. 0.1)", "0")
|
|
447
|
+
.option("--i-understand-the-risk", "Acknowledge the security risk of using same key for owner and operator")
|
|
447
448
|
.option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC)
|
|
448
449
|
.option("--listing-manager <address>", "ListingManagerV2 address", DEFAULT_LISTING_MANAGER)
|
|
449
450
|
.option("--nfa-address <address>", "AgentNFA contract address", DEFAULT_NFA);
|
|
450
451
|
|
|
451
452
|
initCmd.action(async (opts) => {
|
|
452
453
|
try {
|
|
454
|
+
// Deprecation guard
|
|
455
|
+
if (!opts.iUnderstandTheRisk) {
|
|
456
|
+
output({
|
|
457
|
+
status: "error",
|
|
458
|
+
message: "⚠️ DEPRECATED: 'init' uses the same private key as both owner AND operator. " +
|
|
459
|
+
"If this key is leaked (e.g. via AI prompt injection), an attacker can withdraw ALL vault funds. " +
|
|
460
|
+
"Use 'setup-guide' instead for a secure dual-wallet setup. " +
|
|
461
|
+
"If you understand the risk and still want to proceed, add --i-understand-the-risk.",
|
|
462
|
+
});
|
|
463
|
+
process.exit(1);
|
|
464
|
+
}
|
|
465
|
+
|
|
453
466
|
// Validate private key
|
|
454
467
|
if (!process.env.RUNNER_PRIVATE_KEY) {
|
|
455
468
|
output({ status: "error", message: "RUNNER_PRIVATE_KEY environment variable is missing" });
|
|
@@ -1227,9 +1240,138 @@ configCmd.action(async (opts) => {
|
|
|
1227
1240
|
}
|
|
1228
1241
|
});
|
|
1229
1242
|
|
|
1243
|
+
// -- Subcommand: setup-guide (secure dual-wallet onboarding) ------
|
|
1244
|
+
const setupGuideCmd = new Command("setup-guide")
|
|
1245
|
+
.description("Output step-by-step instructions for secure dual-wallet agent setup")
|
|
1246
|
+
.requiredOption("-l, --listing-id <bytes32>", "Template listing ID (bytes32 hex)")
|
|
1247
|
+
.requiredOption("-d, --days <number>", "Number of days to rent")
|
|
1248
|
+
.option("-r, --rpc <url>", "BSC RPC URL", DEFAULT_RPC)
|
|
1249
|
+
.option("--listing-manager <address>", "ListingManagerV2 address", DEFAULT_LISTING_MANAGER)
|
|
1250
|
+
.option("--nfa-address <address>", "AgentNFA contract address", DEFAULT_NFA);
|
|
1251
|
+
|
|
1252
|
+
setupGuideCmd.action(async (opts) => {
|
|
1253
|
+
try {
|
|
1254
|
+
// Get operator address from RUNNER_PRIVATE_KEY
|
|
1255
|
+
const pk = process.env.RUNNER_PRIVATE_KEY;
|
|
1256
|
+
let operatorAddress: string;
|
|
1257
|
+
if (pk) {
|
|
1258
|
+
const account = privateKeyToAccount(toHex(pk) as Hex);
|
|
1259
|
+
operatorAddress = account.address;
|
|
1260
|
+
} else {
|
|
1261
|
+
output({
|
|
1262
|
+
status: "error",
|
|
1263
|
+
message: "RUNNER_PRIVATE_KEY not set. Run 'generate-wallet' first to create an operator wallet, then set RUNNER_PRIVATE_KEY.",
|
|
1264
|
+
});
|
|
1265
|
+
process.exit(1);
|
|
1266
|
+
return;
|
|
1267
|
+
}
|
|
1268
|
+
|
|
1269
|
+
const rpcUrl = opts.rpc || DEFAULT_RPC;
|
|
1270
|
+
const listingManagerAddr = toHex(opts.listingManager || DEFAULT_LISTING_MANAGER) as Address;
|
|
1271
|
+
const nfaAddr = toHex(opts.nfaAddress || DEFAULT_NFA) as Address;
|
|
1272
|
+
const listingId = opts.listingId as string;
|
|
1273
|
+
const daysToRent = Number(opts.days);
|
|
1274
|
+
|
|
1275
|
+
// Query listing to calculate rent cost
|
|
1276
|
+
const publicClient = createPublicClient({ chain: bsc, transport: http(rpcUrl) });
|
|
1277
|
+
let rentCost = "unknown";
|
|
1278
|
+
let priceInfo = "";
|
|
1279
|
+
try {
|
|
1280
|
+
const listing = await publicClient.readContract({
|
|
1281
|
+
address: listingManagerAddr,
|
|
1282
|
+
abi: LISTING_MANAGER_ABI,
|
|
1283
|
+
functionName: "listings",
|
|
1284
|
+
args: [listingId as Hex],
|
|
1285
|
+
});
|
|
1286
|
+
const [, , , pricePerDay, minDays, active] = listing;
|
|
1287
|
+
if (!active) {
|
|
1288
|
+
output({ status: "error", message: "Listing is not active" });
|
|
1289
|
+
process.exit(1);
|
|
1290
|
+
}
|
|
1291
|
+
if (daysToRent < minDays) {
|
|
1292
|
+
output({ status: "error", message: `Minimum rental is ${minDays} days, you requested ${daysToRent}` });
|
|
1293
|
+
process.exit(1);
|
|
1294
|
+
}
|
|
1295
|
+
const totalRent = BigInt(pricePerDay) * BigInt(daysToRent);
|
|
1296
|
+
rentCost = `${(Number(totalRent) / 1e18).toFixed(6)} BNB`;
|
|
1297
|
+
priceInfo = ` (${totalRent.toString()} wei)`;
|
|
1298
|
+
} catch {
|
|
1299
|
+
rentCost = "unable to query — check listing-id";
|
|
1300
|
+
}
|
|
1301
|
+
|
|
1302
|
+
// Calculate operator expiry timestamp
|
|
1303
|
+
const expiryTimestamp = Math.floor(Date.now() / 1000) + daysToRent * 86400;
|
|
1304
|
+
|
|
1305
|
+
// Build shll.run setup URL
|
|
1306
|
+
const setupUrl = `https://shll.run/setup?operator=${operatorAddress}&listing=${encodeURIComponent(listingId)}&days=${daysToRent}`;
|
|
1307
|
+
|
|
1308
|
+
output({
|
|
1309
|
+
status: "guide",
|
|
1310
|
+
securityModel: "DUAL-WALLET: Your wallet (owner) stays offline. AI only uses the operator wallet, which CANNOT withdraw vault funds.",
|
|
1311
|
+
operatorAddress,
|
|
1312
|
+
setupUrl,
|
|
1313
|
+
rentCost: `${rentCost}${priceInfo}`,
|
|
1314
|
+
steps: [
|
|
1315
|
+
{
|
|
1316
|
+
step: 1,
|
|
1317
|
+
title: "Open SHLL Setup Page",
|
|
1318
|
+
action: `Open ${setupUrl} in your browser`,
|
|
1319
|
+
note: "Connect YOUR wallet (MetaMask/WalletConnect). This is your owner wallet — keep it safe and offline after setup.",
|
|
1320
|
+
},
|
|
1321
|
+
{
|
|
1322
|
+
step: 2,
|
|
1323
|
+
title: "Rent Agent",
|
|
1324
|
+
action: "Click 'Rent Agent' and confirm the transaction",
|
|
1325
|
+
fallback: {
|
|
1326
|
+
method: "BscScan (manual)",
|
|
1327
|
+
contract: listingManagerAddr,
|
|
1328
|
+
function: "rentToMintWithParams(bytes32,uint32,uint32,uint16,bytes)",
|
|
1329
|
+
args: [listingId, daysToRent, 1, 1, "0x01"],
|
|
1330
|
+
value: rentCost,
|
|
1331
|
+
},
|
|
1332
|
+
},
|
|
1333
|
+
{
|
|
1334
|
+
step: 3,
|
|
1335
|
+
title: "Authorize Operator",
|
|
1336
|
+
action: "Click 'Authorize Operator' — this gives the AI wallet permission to trade within PolicyGuard safety limits",
|
|
1337
|
+
note: `Operator address: ${operatorAddress}`,
|
|
1338
|
+
fallback: {
|
|
1339
|
+
method: "BscScan (manual)",
|
|
1340
|
+
contract: nfaAddr,
|
|
1341
|
+
function: "setOperator(uint256,address,uint64)",
|
|
1342
|
+
args: ["<tokenId from step 2>", operatorAddress, expiryTimestamp],
|
|
1343
|
+
},
|
|
1344
|
+
},
|
|
1345
|
+
{
|
|
1346
|
+
step: 4,
|
|
1347
|
+
title: "Fund Vault (optional)",
|
|
1348
|
+
action: "Deposit BNB into the vault for trading",
|
|
1349
|
+
fallback: {
|
|
1350
|
+
method: "BscScan (manual)",
|
|
1351
|
+
contract: nfaAddr,
|
|
1352
|
+
function: "fundAgent(uint256)",
|
|
1353
|
+
args: ["<tokenId>"],
|
|
1354
|
+
value: "amount of BNB to deposit",
|
|
1355
|
+
},
|
|
1356
|
+
},
|
|
1357
|
+
{
|
|
1358
|
+
step: 5,
|
|
1359
|
+
title: "Tell AI your token-id",
|
|
1360
|
+
action: "Come back and tell the AI your token-id number. The AI will verify your portfolio and you're ready to trade.",
|
|
1361
|
+
},
|
|
1362
|
+
],
|
|
1363
|
+
});
|
|
1364
|
+
|
|
1365
|
+
} catch (error: unknown) {
|
|
1366
|
+
const message = error instanceof Error ? error.message : "Unknown error";
|
|
1367
|
+
output({ status: "error", message });
|
|
1368
|
+
process.exit(1);
|
|
1369
|
+
}
|
|
1370
|
+
});
|
|
1371
|
+
|
|
1230
1372
|
// -- Subcommand: generate-wallet ---------------------------------
|
|
1231
1373
|
const genWalletCmd = new Command("generate-wallet")
|
|
1232
|
-
.description("Generate a new
|
|
1374
|
+
.description("Generate a new operator wallet (address + private key) for AI to use")
|
|
1233
1375
|
.action(() => {
|
|
1234
1376
|
const pk = generatePrivateKey();
|
|
1235
1377
|
const account = privateKeyToAccount(pk);
|
|
@@ -1237,7 +1379,11 @@ const genWalletCmd = new Command("generate-wallet")
|
|
|
1237
1379
|
status: "success",
|
|
1238
1380
|
address: account.address,
|
|
1239
1381
|
privateKey: pk,
|
|
1240
|
-
note: "SAVE THIS PRIVATE KEY SECURELY. This
|
|
1382
|
+
note: "SAVE THIS PRIVATE KEY SECURELY. This is the OPERATOR wallet — it can only trade within PolicyGuard limits. " +
|
|
1383
|
+
"It CANNOT withdraw vault funds or transfer your Agent NFT. " +
|
|
1384
|
+
"Send ~$1 of BNB here for gas fees, then set RUNNER_PRIVATE_KEY to this privateKey value.",
|
|
1385
|
+
securityReminder: "Use a SEPARATE wallet (MetaMask, hardware wallet) as the owner to rent the agent and fund the vault. " +
|
|
1386
|
+
"Run 'setup-guide' for step-by-step instructions.",
|
|
1241
1387
|
});
|
|
1242
1388
|
});
|
|
1243
1389
|
|
|
@@ -1286,6 +1432,7 @@ program.addCommand(unwrapCmd);
|
|
|
1286
1432
|
program.addCommand(transferCmd);
|
|
1287
1433
|
program.addCommand(policiesCmd);
|
|
1288
1434
|
program.addCommand(configCmd);
|
|
1435
|
+
program.addCommand(setupGuideCmd);
|
|
1289
1436
|
program.addCommand(genWalletCmd);
|
|
1290
1437
|
program.addCommand(balanceCmd);
|
|
1291
1438
|
program.parse(process.argv);
|