logiqical 0.4.0 → 0.5.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/dist/vault.mjs ADDED
@@ -0,0 +1,271 @@
1
+ #!/usr/bin/env node
2
+ #!/usr/bin/env node
3
+
4
+ // src/cli/vault.ts
5
+ import { createServer } from "http";
6
+ import { Wallet, JsonRpcProvider, ethers } from "ethers";
7
+ import { readFileSync, writeFileSync, existsSync, mkdirSync } from "fs";
8
+ import { join } from "path";
9
+ import { homedir } from "os";
10
+ var CONFIG_DIR = join(homedir(), ".logiqical");
11
+ var KEYS_DIR = join(CONFIG_DIR, "keys");
12
+ var VAULT_STATE_FILE = join(CONFIG_DIR, "vault-state.json");
13
+ var DEFAULT_PORT = 7842;
14
+ function loadConfig() {
15
+ const configFile = join(CONFIG_DIR, "config.json");
16
+ if (existsSync(configFile)) return JSON.parse(readFileSync(configFile, "utf-8"));
17
+ return {};
18
+ }
19
+ function loadVaultState() {
20
+ if (existsSync(VAULT_STATE_FILE)) {
21
+ return JSON.parse(readFileSync(VAULT_STATE_FILE, "utf-8"));
22
+ }
23
+ const config = loadConfig();
24
+ return {
25
+ spentThisHour: "0",
26
+ spentToday: "0",
27
+ hourStart: Date.now(),
28
+ dayStart: Date.now(),
29
+ policy: config.policy || {}
30
+ };
31
+ }
32
+ function saveVaultState(state) {
33
+ if (!existsSync(CONFIG_DIR)) mkdirSync(CONFIG_DIR, { recursive: true });
34
+ writeFileSync(VAULT_STATE_FILE, JSON.stringify(state, null, 2));
35
+ }
36
+ function resetBudgetPeriods(state) {
37
+ const now = Date.now();
38
+ if (now - state.hourStart > 36e5) {
39
+ state.spentThisHour = "0";
40
+ state.hourStart = now;
41
+ }
42
+ if (now - state.dayStart > 864e5) {
43
+ state.spentToday = "0";
44
+ state.dayStart = now;
45
+ }
46
+ return state;
47
+ }
48
+ function checkPolicy(policy, tx, state) {
49
+ const valueWei = BigInt(tx.value || "0");
50
+ const valueEth = parseFloat(ethers.formatEther(valueWei));
51
+ if (policy.maxPerTx) {
52
+ const max = parseFloat(policy.maxPerTx);
53
+ if (valueEth > max) return `Transaction value ${valueEth} exceeds max per tx (${max})`;
54
+ }
55
+ if (policy.maxPerHour) {
56
+ const max = parseFloat(policy.maxPerHour);
57
+ const spent = parseFloat(state.spentThisHour);
58
+ if (spent + valueEth > max) return `Would exceed hourly budget (${spent + valueEth} > ${max})`;
59
+ }
60
+ if (policy.maxPerDay) {
61
+ const max = parseFloat(policy.maxPerDay);
62
+ const spent = parseFloat(state.spentToday);
63
+ if (spent + valueEth > max) return `Would exceed daily budget (${spent + valueEth} > ${max})`;
64
+ }
65
+ if (policy.allowedContracts && policy.allowedContracts.length > 0) {
66
+ const allowed = policy.allowedContracts.map((a) => a.toLowerCase());
67
+ if (!allowed.includes(tx.to.toLowerCase())) {
68
+ return `Contract ${tx.to} not in allowlist`;
69
+ }
70
+ }
71
+ if (policy.blockedContracts && policy.blockedContracts.length > 0) {
72
+ const blocked = policy.blockedContracts.map((b) => b.toLowerCase());
73
+ if (blocked.includes(tx.to.toLowerCase())) {
74
+ return `Contract ${tx.to} is blocked`;
75
+ }
76
+ }
77
+ return null;
78
+ }
79
+ function createVaultServer(wallet, provider, state) {
80
+ async function readBody(req) {
81
+ return new Promise((resolve, reject) => {
82
+ let data = "";
83
+ req.on("data", (chunk) => {
84
+ data += chunk.toString();
85
+ });
86
+ req.on("end", () => {
87
+ try {
88
+ resolve(JSON.parse(data));
89
+ } catch {
90
+ reject(new Error("Invalid JSON"));
91
+ }
92
+ });
93
+ });
94
+ }
95
+ function json(res, statusCode, body) {
96
+ res.writeHead(statusCode, { "Content-Type": "application/json" });
97
+ res.end(JSON.stringify(body));
98
+ }
99
+ return async (req, res) => {
100
+ const remoteAddr = req.socket.remoteAddress;
101
+ if (remoteAddr !== "127.0.0.1" && remoteAddr !== "::1" && remoteAddr !== "::ffff:127.0.0.1") {
102
+ return json(res, 403, { error: "Vault only accepts localhost connections" });
103
+ }
104
+ const url = req.url || "/";
105
+ const method = req.method || "GET";
106
+ try {
107
+ if (url === "/health" && method === "GET") {
108
+ return json(res, 200, { status: "ok", address: wallet.address });
109
+ }
110
+ if (url === "/address" && method === "GET") {
111
+ return json(res, 200, { address: wallet.address });
112
+ }
113
+ if (url === "/policy" && method === "GET") {
114
+ return json(res, 200, { policy: state.policy });
115
+ }
116
+ if (url === "/policy" && method === "POST") {
117
+ const body = await readBody(req);
118
+ state.policy = { ...state.policy, ...body };
119
+ saveVaultState(state);
120
+ return json(res, 200, { policy: state.policy });
121
+ }
122
+ if (url === "/budget" && method === "GET") {
123
+ state = resetBudgetPeriods(state);
124
+ return json(res, 200, {
125
+ spentThisHour: state.spentThisHour,
126
+ spentToday: state.spentToday,
127
+ hourlyLimit: state.policy.maxPerHour || null,
128
+ dailyLimit: state.policy.maxPerDay || null,
129
+ hourlyRemaining: state.policy.maxPerHour ? String(Math.max(0, parseFloat(state.policy.maxPerHour) - parseFloat(state.spentThisHour))) : null,
130
+ dailyRemaining: state.policy.maxPerDay ? String(Math.max(0, parseFloat(state.policy.maxPerDay) - parseFloat(state.spentToday))) : null
131
+ });
132
+ }
133
+ if (url === "/sign" && method === "POST") {
134
+ const body = await readBody(req);
135
+ const { to, data, value, chainId, gasLimit } = body;
136
+ if (!to) return json(res, 400, { error: "Missing 'to' field" });
137
+ state = resetBudgetPeriods(state);
138
+ const policyError = checkPolicy(state.policy, { to, value: value || "0" }, state);
139
+ if (policyError) {
140
+ return json(res, 403, { error: policyError, code: "POLICY_VIOLATION" });
141
+ }
142
+ if (state.policy.simulateBeforeSend) {
143
+ try {
144
+ await provider.call({
145
+ to,
146
+ data,
147
+ value: value ? BigInt(value) : void 0,
148
+ from: wallet.address
149
+ });
150
+ } catch (e) {
151
+ return json(res, 400, {
152
+ error: `Simulation failed: ${e.reason || e.message || "would revert"}`,
153
+ code: "SIMULATION_FAILED"
154
+ });
155
+ }
156
+ }
157
+ if (state.policy.dryRun) {
158
+ return json(res, 200, {
159
+ signedTx: null,
160
+ dryRun: true,
161
+ message: "Dry run \u2014 transaction not signed"
162
+ });
163
+ }
164
+ const tx = { to, data: data || "0x", value: value || "0" };
165
+ if (gasLimit) tx.gasLimit = gasLimit;
166
+ if (chainId) tx.chainId = chainId;
167
+ const signedTx = await wallet.signTransaction(tx);
168
+ const valueWei = BigInt(value || "0");
169
+ if (valueWei > 0n) {
170
+ const valueEth = parseFloat(ethers.formatEther(valueWei));
171
+ state.spentThisHour = String(parseFloat(state.spentThisHour) + valueEth);
172
+ state.spentToday = String(parseFloat(state.spentToday) + valueEth);
173
+ saveVaultState(state);
174
+ }
175
+ return json(res, 200, { signedTx });
176
+ }
177
+ if (url === "/sign-message" && method === "POST") {
178
+ const body = await readBody(req);
179
+ if (!body.message) return json(res, 400, { error: "Missing 'message' field" });
180
+ const signature = await wallet.signMessage(body.message);
181
+ return json(res, 200, { signature });
182
+ }
183
+ if (url === "/sign-typed-data" && method === "POST") {
184
+ const body = await readBody(req);
185
+ const { domain, types, value } = body;
186
+ if (!domain || !types || !value) {
187
+ return json(res, 400, { error: "Missing domain, types, or value" });
188
+ }
189
+ const signature = await wallet.signTypedData(domain, types, value);
190
+ return json(res, 200, { signature });
191
+ }
192
+ if (url === "/broadcast" && method === "POST") {
193
+ const body = await readBody(req);
194
+ if (!body.signedTx) return json(res, 400, { error: "Missing 'signedTx' field" });
195
+ const txResponse = await provider.broadcastTransaction(body.signedTx);
196
+ return json(res, 200, {
197
+ hash: txResponse.hash,
198
+ nonce: txResponse.nonce
199
+ });
200
+ }
201
+ return json(res, 404, { error: `Unknown endpoint: ${method} ${url}` });
202
+ } catch (e) {
203
+ return json(res, 500, { error: e.message });
204
+ }
205
+ };
206
+ }
207
+ async function main() {
208
+ const args = process.argv.slice(2).filter((a) => a !== "vault");
209
+ const portArg = args.indexOf("--port");
210
+ const port = portArg >= 0 ? parseInt(args[portArg + 1]) : parseInt(process.env.LOGIQICAL_VAULT_PORT || String(DEFAULT_PORT));
211
+ console.log("");
212
+ console.log(" \x1B[1mLogiqical Vault Daemon\x1B[0m");
213
+ console.log(" \x1B[2mSigns transactions, enforces policies. Keys never leave this process.\x1B[0m");
214
+ console.log("");
215
+ const keystorePath = join(KEYS_DIR, "agent.json");
216
+ if (!existsSync(keystorePath)) {
217
+ console.error(" No wallet found. Run: logiqical setup");
218
+ process.exit(1);
219
+ }
220
+ const config = loadConfig();
221
+ const password = config.password || "logiqical-agent";
222
+ console.log(" Loading keystore...");
223
+ const keystoreData = JSON.parse(readFileSync(keystorePath, "utf-8"));
224
+ const decrypted = await Wallet.fromEncryptedJson(keystoreData.encryptedJson, password);
225
+ const rpcUrl = config.rpcUrl || "https://api.avax.network/ext/bc/C/rpc";
226
+ const provider = new JsonRpcProvider(rpcUrl, 43114);
227
+ const wallet = new Wallet(decrypted.privateKey, provider);
228
+ let state = loadVaultState();
229
+ state = resetBudgetPeriods(state);
230
+ console.log(` Address: \x1B[32m${wallet.address}\x1B[0m`);
231
+ console.log(` Network: Avalanche C-Chain`);
232
+ console.log(` Port: ${port}`);
233
+ if (state.policy.maxPerTx || state.policy.maxPerDay) {
234
+ console.log(` Policy: max/tx: ${state.policy.maxPerTx || "none"}, max/day: ${state.policy.maxPerDay || "none"}, simulate: ${state.policy.simulateBeforeSend ?? false}`);
235
+ } else {
236
+ console.log(" Policy: \x1B[33mnone set \u2014 all transactions will be signed\x1B[0m");
237
+ }
238
+ const server = createServer(createVaultServer(wallet, provider, state));
239
+ server.listen(port, "127.0.0.1", () => {
240
+ console.log("");
241
+ console.log(` \x1B[32mVault running on http://127.0.0.1:${port}\x1B[0m`);
242
+ console.log("");
243
+ console.log(" Endpoints:");
244
+ console.log(" GET /health \u2014 health check");
245
+ console.log(" GET /address \u2014 wallet address");
246
+ console.log(" GET /policy \u2014 current policy");
247
+ console.log(" POST /policy \u2014 update policy");
248
+ console.log(" GET /budget \u2014 spending budget status");
249
+ console.log(" POST /sign \u2014 sign a transaction (policy-enforced)");
250
+ console.log(" POST /sign-message \u2014 sign an arbitrary message");
251
+ console.log(" POST /sign-typed-data \u2014 sign EIP-712 typed data");
252
+ console.log(" POST /broadcast \u2014 broadcast signed tx");
253
+ console.log("");
254
+ console.log(" \x1B[2mPress Ctrl+C to stop\x1B[0m");
255
+ });
256
+ process.on("SIGINT", () => {
257
+ console.log("\n Shutting down vault...");
258
+ saveVaultState(state);
259
+ server.close();
260
+ process.exit(0);
261
+ });
262
+ process.on("SIGTERM", () => {
263
+ saveVaultState(state);
264
+ server.close();
265
+ process.exit(0);
266
+ });
267
+ }
268
+ main().catch((e) => {
269
+ console.error("Error:", e.message);
270
+ process.exit(1);
271
+ });
package/package.json CHANGED
@@ -1,12 +1,14 @@
1
1
  {
2
2
  "name": "logiqical",
3
- "version": "0.4.0",
4
- "description": "Agent wallet SDK for Avalanche + Arena — swap, stake, launchpad, DEX, perps, bridge, social, signals, copy trading, 91 MCP tools. No backend needed.",
3
+ "version": "0.5.0",
4
+ "description": "Agent wallet SDK for Avalanche + Arena — 91 MCP tools, admin CLI, vault daemon, skill packs. No backend needed.",
5
5
  "main": "dist/index.js",
6
6
  "module": "dist/index.mjs",
7
7
  "types": "dist/index.d.ts",
8
8
  "bin": {
9
- "logiqical-mcp": "dist/cli.mjs"
9
+ "logiqical": "dist/admin.mjs",
10
+ "logiqical-mcp": "dist/cli.mjs",
11
+ "logiqical-vault": "dist/vault.mjs"
10
12
  },
11
13
  "exports": {
12
14
  ".": {
@@ -16,7 +18,8 @@
16
18
  }
17
19
  },
18
20
  "files": [
19
- "dist"
21
+ "dist",
22
+ "skills"
20
23
  ],
21
24
  "scripts": {
22
25
  "build": "tsup",
@@ -0,0 +1,180 @@
1
+ # Logiqical Agent SDK
2
+
3
+ You have access to the Logiqical SDK — a non-custodial agent wallet for Avalanche and Arena with 91 MCP tools across 15 modules.
4
+
5
+ ## Setup
6
+
7
+ The agent wallet is already booted. Use MCP tools directly. No imports needed.
8
+
9
+ ## Core Concepts
10
+
11
+ - **Every on-chain action goes through MCP tools** — you call tools, they sign and broadcast
12
+ - **Spending policies are enforced automatically** — per-tx limits, hourly/daily budgets, simulation
13
+ - **Arena API key is required** for social, perps, and tickets tools (set via `ARENA_API_KEY` env var)
14
+
15
+ ## Available Tools (91 total)
16
+
17
+ ### Wallet (4 tools)
18
+ - `get_address` — your agent wallet address
19
+ - `get_balance` — AVAX balance
20
+ - `send_avax` — send AVAX to any address
21
+ - `sign_message` — sign arbitrary messages
22
+
23
+ ### ARENA Token (6 tools)
24
+ - `get_balances` — AVAX + ARENA balances
25
+ - `swap_quote_buy` — quote AVAX to ARENA
26
+ - `swap_quote_sell` — quote ARENA to AVAX
27
+ - `swap_buy_arena` — buy ARENA with AVAX
28
+ - `swap_sell_arena` — sell ARENA for AVAX
29
+
30
+ ### Staking (4 tools)
31
+ - `stake_info` — staked amount + pending rewards
32
+ - `stake_arena` — stake ARENA tokens
33
+ - `unstake_arena` — unstake + claim rewards
34
+ - `buy_and_stake` — buy ARENA + stake in one tx
35
+
36
+ ### DEX — Any Token Swap (6 tools)
37
+ - `dex_tokens` — list known tokens
38
+ - `dex_token_info` — look up any ERC-20 by address
39
+ - `dex_balance` — check any token balance
40
+ - `dex_quote` — quote any token pair
41
+ - `dex_swap` — swap any tokens on Avalanche
42
+
43
+ ### Launchpad (6 tools)
44
+ - `launchpad_overview` — platform stats
45
+ - `launchpad_recent` — recently launched tokens
46
+ - `launchpad_token` — full token info by ID
47
+ - `launchpad_quote` — bonding curve price quote
48
+ - `launchpad_buy` — buy a launchpad token
49
+ - `launchpad_sell` — sell a launchpad token
50
+
51
+ ### Tickets (8 tools)
52
+ - `tickets_buy_price` / `tickets_sell_price` — price quotes
53
+ - `tickets_balance` — ticket balance
54
+ - `tickets_supply` — total supply
55
+ - `tickets_fees` — fee structure
56
+ - `tickets_buy` / `tickets_sell` — buy/sell tickets
57
+
58
+ ### Bridge — Cross-Chain (8 tools)
59
+ - `bridge_info` — supported chains + USDC addresses
60
+ - `bridge_chains` — all supported chains
61
+ - `bridge_tokens` — tokens on specified chains
62
+ - `bridge_quote` — bridge quote with transaction
63
+ - `bridge_routes` — multiple route options
64
+ - `bridge_status` — check transfer status
65
+
66
+ ### Perps — Perpetual Futures (12 tools)
67
+ - `perps_register` — register for Hyperliquid perps
68
+ - `perps_registration_status` — check registration
69
+ - `perps_wallet_address` — get Hyperliquid wallet
70
+ - `perps_trading_pairs` — all 250+ pairs
71
+ - `perps_update_leverage` — set leverage (1-50x)
72
+ - `perps_place_order` — place orders
73
+ - `perps_cancel_orders` — cancel orders
74
+ - `perps_close_position` — close a position
75
+ - `perps_orders` — view open orders
76
+ - `perps_positions` — positions + margin summary
77
+ - `perps_deposit_info` — Hyperliquid deposit addresses
78
+ - `perps_arbitrum_usdc_balance` — USDC balance on Arbitrum
79
+ - `perps_deposit_usdc` — build USDC deposit tx
80
+
81
+ ### Signals Intelligence (6 tools)
82
+ - `signals_market` — price, funding, OI, volume
83
+ - `signals_technical` — SMA, RSI, trend, support/resistance
84
+ - `signals_whales` — whale positions from orderbook
85
+ - `signals_funding` — funding rate extremes
86
+ - `signals_summary` — full signal digest + verdict
87
+ - `signals_scan` — scan all markets for opportunities
88
+
89
+ ### Social (14 tools)
90
+ - `social_search_users` — search Arena users
91
+ - `social_user_by_handle` — get user by handle
92
+ - `social_me` — your Arena profile
93
+ - `social_top_users` — top users
94
+ - `social_follow` / `social_unfollow` — follow/unfollow
95
+ - `social_update_profile` — update profile
96
+ - `social_conversations` — list chats
97
+ - `social_send_message` — send a message
98
+ - `social_messages` — read messages
99
+ - `social_create_thread` — create a post
100
+ - `social_like_thread` — like a thread
101
+ - `social_post_trade` — auto-post a trade update
102
+
103
+ ### Agent Registration (1 tool)
104
+ - `agent_register` — register a new AI agent on Arena (returns API key)
105
+
106
+ ### Copy Trading (3 tools)
107
+ - `copy_get_positions` — get target wallet positions
108
+ - `copy_calculate_orders` — calculate mirror orders
109
+ - `copy_execute` — one-shot copy trade
110
+
111
+ ### Market Data (6 tools)
112
+ - `market_price` — prices, 24h change, market cap
113
+ - `market_trending` — trending coins
114
+ - `market_top` — top by market cap
115
+ - `market_search` — search coins
116
+ - `market_avax_price` / `market_arena_price` — quick price checks
117
+
118
+ ### DeFi (8 tools)
119
+ - `defi_savax_info` — sAVAX exchange rate + balances
120
+ - `defi_savax_quote` — quote AVAX to sAVAX
121
+ - `defi_savax_stake` — stake AVAX to sAVAX
122
+ - `defi_savax_unstake` — unstake sAVAX
123
+ - `defi_vault_info` — ERC-4626 vault info
124
+ - `defi_vault_quote` — vault deposit quote
125
+ - `defi_vault_deposit` — deposit into vault
126
+ - `defi_vault_withdraw` — withdraw from vault
127
+
128
+ ### Policy (3 tools)
129
+ - `policy_get` — current spending policy
130
+ - `policy_set` — replace policy
131
+ - `policy_budget` — budget status (spent today, remaining)
132
+
133
+ ### Contract Call (1 tool)
134
+ - `call_contract` — call any smart contract method
135
+
136
+ ## Patterns
137
+
138
+ ### Before trading, always check balances first
139
+ ```
140
+ 1. get_balance (check AVAX)
141
+ 2. get_balances (check ARENA)
142
+ 3. Then swap/trade
143
+ ```
144
+
145
+ ### Before perps, check registration
146
+ ```
147
+ 1. perps_registration_status
148
+ 2. If not registered: perps_register
149
+ 3. Then trade
150
+ ```
151
+
152
+ ### For copy trading
153
+ ```
154
+ 1. copy_get_positions (see what the target holds)
155
+ 2. copy_calculate_orders (see what you'd need to do)
156
+ 3. copy_execute (or execute manually with perps_place_order)
157
+ ```
158
+
159
+ ### For cross-chain operations
160
+ ```
161
+ 1. bridge_info (get chain IDs and token addresses)
162
+ 2. bridge_quote (get quote + tx)
163
+ 3. Execute the bridge tx
164
+ 4. bridge_status (poll until complete)
165
+ ```
166
+
167
+ ### Always check policy before large trades
168
+ ```
169
+ 1. policy_budget (check remaining budget)
170
+ 2. If over limit, inform the user
171
+ 3. policy_set to adjust if user approves
172
+ ```
173
+
174
+ ## Important Notes
175
+
176
+ - All on-chain transactions are real and irreversible. Always confirm with the user before executing trades.
177
+ - The agent wallet's private key is stored in an encrypted keystore at `~/.logiqical/keys/agent.json`.
178
+ - Spending policies are your guardrails. Never disable simulation without the user's explicit consent.
179
+ - Arena API rate limits: 10 posts/hour, 120 orders/hour, 100 reads/minute.
180
+ - For perps, Arena holds the signing keys. Orders go through Arena's API, not direct on-chain.
@@ -0,0 +1,48 @@
1
+ # Logiqical Agent SDK — Codex Instructions
2
+
3
+ You are an AI agent with a non-custodial wallet on Avalanche. You have 91 tools across 15 modules for DeFi, trading, social, and more.
4
+
5
+ ## Your Capabilities
6
+
7
+ You can:
8
+ - **Trade**: Swap any token on Avalanche, buy/sell ARENA, trade launchpad tokens
9
+ - **Perps**: Trade 250+ perpetual futures on Hyperliquid (up to 50x leverage)
10
+ - **Copy Trade**: Mirror any Hyperliquid wallet's positions
11
+ - **Stake**: Stake ARENA for rewards, liquid stake AVAX to sAVAX
12
+ - **Bridge**: Move tokens across 20+ EVM chains
13
+ - **Social**: Post to Arena feed, chat, follow users, auto-post trade updates
14
+ - **Analyze**: Market signals, whale tracking, funding rates, technical analysis
15
+ - **DeFi**: Deposit into ERC-4626 vaults, liquid staking
16
+
17
+ ## How to Trade
18
+
19
+ Always follow this flow:
20
+ 1. Check your balance first (`get_balance`, `get_balances`, `dex_balance`)
21
+ 2. Get a quote (`dex_quote`, `swap_quote_buy`, `launchpad_quote`)
22
+ 3. Confirm with the user
23
+ 4. Execute (`dex_swap`, `swap_buy_arena`, `launchpad_buy`)
24
+
25
+ ## How to Analyze Markets
26
+
27
+ 1. Quick check: `market_avax_price`, `market_arena_price`, `market_price`
28
+ 2. Deep analysis: `signals_summary` for full market + technical + whale data
29
+ 3. Scan opportunities: `signals_scan` to find the best setups across all markets
30
+ 4. Funding plays: `signals_funding` to find extreme funding rates
31
+
32
+ ## How to Copy Trade
33
+
34
+ 1. `copy_get_positions` — see what the target wallet holds
35
+ 2. `copy_calculate_orders` — calculate what you need to match them
36
+ 3. `copy_execute` — execute all mirror orders in one shot
37
+
38
+ ## Spending Policies
39
+
40
+ Your wallet has spending limits. Check with `policy_budget` before large trades.
41
+ If a trade is rejected by policy, tell the user and suggest adjusting with `policy_set`.
42
+
43
+ ## Safety Rules
44
+
45
+ - Always confirm trades with the user before executing
46
+ - Check balances before every trade
47
+ - Never disable spending policies without explicit user consent
48
+ - All transactions are real and irreversible on the blockchain