shll-skills 3.0.0 → 4.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/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.1.0
4
+ version: 5.0.0
5
5
  author: SHLL Team
6
6
  website: https://shll.run
7
7
  twitter: https://twitter.com/shllrun
@@ -144,6 +144,9 @@ Examples:
144
144
  - "Swap 0.1 BNB for USDC"
145
145
  - "What's my portfolio?"
146
146
  - "What's the price of CAKE?"
147
+ - "Lend 10 USDT on Venus"
148
+ - "How much am I earning on Venus?"
149
+ - "Redeem my USDT from Venus"
147
150
 
148
151
  ---
149
152
 
@@ -161,12 +164,22 @@ Examples:
161
164
  ### Trading
162
165
  | Command | What it does |
163
166
  |---------|-------------|
164
- | `shll-run swap -f <FROM> -t <TO> -a <AMT> -k <ID>` | Token swap on PancakeSwap |
167
+ | `shll-run swap -f <FROM> -t <TO> -a <AMT> -k <ID>` | Token swap (auto-routes PancakeSwap V2/V3) |
168
+ | `shll-run swap ... --dex v3 --fee 500` | Force V3 with 0.05% fee tier |
165
169
  | `shll-run wrap -a <BNB> -k <ID>` | BNB -> WBNB |
166
170
  | `shll-run unwrap -a <BNB> -k <ID>` | WBNB -> BNB |
167
171
  | `shll-run transfer --token <SYM> -a <AMT> --to <ADDR> -k <ID>` | Send tokens from vault |
168
172
  | `shll-run raw --target <ADDR> --data <HEX> -k <ID>` | Raw calldata |
169
173
 
174
+ ### Lending (Venus Protocol)
175
+ | Command | What it does |
176
+ |---------|-------------|
177
+ | `shll-run lend -t <TOKEN> -a <AMT> -k <ID>` | Supply tokens to Venus to earn yield |
178
+ | `shll-run redeem -t <TOKEN> -a <AMT> -k <ID>` | Withdraw supplied tokens from Venus |
179
+ | `shll-run lending-info -k <ID>` | Show supply balances + APY across Venus markets |
180
+
181
+ Supported lending tokens: **BNB, USDT, USDC, BUSD**
182
+
170
183
  ### Market Data (read-only)
171
184
  | Command | What it does |
172
185
  |---------|-------------|
@@ -185,6 +198,9 @@ Examples:
185
198
 
186
199
  **Supported tokens:** BNB, USDC, USDT, WBNB, CAKE, ETH, BTCB, DAI, BUSD, or any 0x address.
187
200
 
201
+ **Swap routing modes:** `--dex auto` (default: compares V2/V3 quotes), `--dex v2`, `--dex v3`.
202
+ **V3 fee tiers:** `--fee 100` (0.01%), `--fee 500` (0.05%), `--fee 2500` (0.25%, default), `--fee 10000` (1%).
203
+
188
204
  ---
189
205
 
190
206
  ## HOW TO EXPLAIN THINGS TO USERS
@@ -201,7 +217,11 @@ Examples:
201
217
  ### "What are policies?"
202
218
  *"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."*
203
219
 
204
- ---
220
+ ### "What is Venus Protocol?"
221
+ *"Venus is a decentralized lending protocol on BSC. When you 'lend' tokens to Venus, you deposit them into a supply pool and earn interest (APY). Other users borrow from the same pool and pay interest. You can withdraw (redeem) your tokens plus earned interest at any time. Your tokens stay on-chain in Venus smart contracts — SHLL does not hold them."*
222
+
223
+ ### "Is lending safe?"
224
+ *"Venus is one of the most established protocols on BSC with over $1B TVL. However, DeFi lending always carries smart contract risk and market risk. Only lend amounts you're comfortable with. Your agent's DeFiGuard policy ensures only approved lending operations can be executed."*
205
225
 
206
226
  ## OUTPUT FORMAT
207
227
  All commands output JSON:
@@ -0,0 +1,502 @@
1
+ // node_modules/shll-policy-sdk/dist/index.js
2
+ import {
3
+ createPublicClient,
4
+ createWalletClient,
5
+ http
6
+ } from "viem";
7
+ import { privateKeyToAccount } from "viem/accounts";
8
+ import { bsc, bscTestnet } from "viem/chains";
9
+ var PolicyGuardV4Abi = [
10
+ {
11
+ name: "validate",
12
+ type: "function",
13
+ stateMutability: "view",
14
+ inputs: [
15
+ { name: "nfa", type: "address" },
16
+ { name: "tokenId", type: "uint256" },
17
+ { name: "agentAccount", type: "address" },
18
+ { name: "caller", type: "address" },
19
+ {
20
+ name: "action",
21
+ type: "tuple",
22
+ components: [
23
+ { name: "target", type: "address" },
24
+ { name: "value", type: "uint256" },
25
+ { name: "data", type: "bytes" }
26
+ ]
27
+ }
28
+ ],
29
+ outputs: [
30
+ { name: "ok", type: "bool" },
31
+ { name: "reason", type: "string" }
32
+ ]
33
+ },
34
+ {
35
+ name: "getActivePolicies",
36
+ type: "function",
37
+ stateMutability: "view",
38
+ inputs: [{ name: "instanceId", type: "uint256" }],
39
+ outputs: [{ name: "", type: "address[]" }]
40
+ },
41
+ {
42
+ name: "getTemplatePolicies",
43
+ type: "function",
44
+ stateMutability: "view",
45
+ inputs: [{ name: "templateId", type: "bytes32" }],
46
+ outputs: [{ name: "", type: "address[]" }]
47
+ },
48
+ {
49
+ name: "instanceTemplateId",
50
+ type: "function",
51
+ stateMutability: "view",
52
+ inputs: [{ name: "instanceId", type: "uint256" }],
53
+ outputs: [{ name: "", type: "bytes32" }]
54
+ }
55
+ ];
56
+ var AgentNFAAbi = [
57
+ {
58
+ name: "execute",
59
+ type: "function",
60
+ stateMutability: "payable",
61
+ inputs: [
62
+ { name: "tokenId", type: "uint256" },
63
+ {
64
+ name: "action",
65
+ type: "tuple",
66
+ components: [
67
+ { name: "target", type: "address" },
68
+ { name: "value", type: "uint256" },
69
+ { name: "data", type: "bytes" }
70
+ ]
71
+ }
72
+ ],
73
+ outputs: [{ name: "result", type: "bytes" }]
74
+ },
75
+ {
76
+ name: "executeBatch",
77
+ type: "function",
78
+ stateMutability: "payable",
79
+ inputs: [
80
+ { name: "tokenId", type: "uint256" },
81
+ {
82
+ name: "actions",
83
+ type: "tuple[]",
84
+ components: [
85
+ { name: "target", type: "address" },
86
+ { name: "value", type: "uint256" },
87
+ { name: "data", type: "bytes" }
88
+ ]
89
+ }
90
+ ],
91
+ outputs: [{ name: "results", type: "bytes[]" }]
92
+ },
93
+ {
94
+ name: "accountOf",
95
+ type: "function",
96
+ stateMutability: "view",
97
+ inputs: [{ name: "tokenId", type: "uint256" }],
98
+ outputs: [{ name: "", type: "address" }]
99
+ },
100
+ {
101
+ name: "userOf",
102
+ type: "function",
103
+ stateMutability: "view",
104
+ inputs: [{ name: "tokenId", type: "uint256" }],
105
+ outputs: [{ name: "", type: "address" }]
106
+ },
107
+ {
108
+ name: "userExpires",
109
+ type: "function",
110
+ stateMutability: "view",
111
+ inputs: [{ name: "tokenId", type: "uint256" }],
112
+ outputs: [{ name: "", type: "uint256" }]
113
+ },
114
+ {
115
+ name: "operatorOf",
116
+ type: "function",
117
+ stateMutability: "view",
118
+ inputs: [{ name: "tokenId", type: "uint256" }],
119
+ outputs: [{ name: "", type: "address" }]
120
+ },
121
+ {
122
+ name: "operatorExpiresOf",
123
+ type: "function",
124
+ stateMutability: "view",
125
+ inputs: [{ name: "tokenId", type: "uint256" }],
126
+ outputs: [{ name: "", type: "uint256" }]
127
+ },
128
+ {
129
+ name: "operatorNonceOf",
130
+ type: "function",
131
+ stateMutability: "view",
132
+ inputs: [{ name: "tokenId", type: "uint256" }],
133
+ outputs: [{ name: "", type: "uint256" }]
134
+ },
135
+ {
136
+ name: "setOperatorWithSig",
137
+ type: "function",
138
+ stateMutability: "nonpayable",
139
+ inputs: [
140
+ {
141
+ name: "req",
142
+ type: "tuple",
143
+ components: [
144
+ { name: "tokenId", type: "uint256" },
145
+ { name: "renter", type: "address" },
146
+ { name: "operator", type: "address" },
147
+ { name: "expires", type: "uint64" },
148
+ { name: "nonce", type: "uint256" },
149
+ { name: "deadline", type: "uint256" }
150
+ ]
151
+ },
152
+ { name: "signature", type: "bytes" }
153
+ ],
154
+ outputs: []
155
+ },
156
+ {
157
+ name: "clearOperator",
158
+ type: "function",
159
+ stateMutability: "nonpayable",
160
+ inputs: [{ name: "tokenId", type: "uint256" }],
161
+ outputs: []
162
+ }
163
+ ];
164
+ var IPolicyAbi = [
165
+ {
166
+ name: "policyType",
167
+ type: "function",
168
+ stateMutability: "pure",
169
+ inputs: [],
170
+ outputs: [{ name: "", type: "bytes32" }]
171
+ },
172
+ {
173
+ name: "renterConfigurable",
174
+ type: "function",
175
+ stateMutability: "pure",
176
+ inputs: [],
177
+ outputs: [{ name: "", type: "bool" }]
178
+ },
179
+ {
180
+ name: "check",
181
+ type: "function",
182
+ stateMutability: "view",
183
+ inputs: [
184
+ { name: "instanceId", type: "uint256" },
185
+ { name: "caller", type: "address" },
186
+ { name: "target", type: "address" },
187
+ { name: "selector", type: "bytes4" },
188
+ { name: "callData", type: "bytes" },
189
+ { name: "value", type: "uint256" }
190
+ ],
191
+ outputs: [
192
+ { name: "ok", type: "bool" },
193
+ { name: "reason", type: "string" }
194
+ ]
195
+ }
196
+ ];
197
+ var POLICY_TYPE_NAMES = {
198
+ // keccak256(abi.encodePacked("spending_limit"))
199
+ "0xfc881f20d88f7ec7e942f0b796f40ce121974015a83f091cd5ef2cf146443d4e": "spending_limit",
200
+ // keccak256(abi.encodePacked("cooldown"))
201
+ "0x7230b2ae91f8efdf9ea182e9a9d1fef92adfda3e61d5512778c2f81580b13d10": "cooldown",
202
+ // keccak256(abi.encodePacked("receiver_guard"))
203
+ "0xccead04e892f4bd05d3e2491948809479bf4641d233bf37f3fac8411960a97de": "receiver_guard",
204
+ // keccak256(abi.encodePacked("dex_whitelist"))
205
+ "0xb5ba0ef255153fe2c4c7201fbd5f2658f86e25a723f1844d5bf64a5c48e0c9ed": "dex_whitelist",
206
+ // keccak256(abi.encodePacked("defi_guard"))
207
+ "0xe109b0d2bf06e570d9aecb2cd18dafe849a5c1d8b9b9f6ecd8b9ed727f816bb9": "defi_guard",
208
+ // keccak256(abi.encodePacked("token_whitelist"))
209
+ "0xf4717a1c69e76a67584dd6d514cc591ec961b52113ec085dfe157bd9e238e1f3": "token_whitelist"
210
+ };
211
+ var PolicyClient = class {
212
+ publicClient;
213
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
214
+ _walletClient;
215
+ _operatorAddress;
216
+ agentNfaAddress;
217
+ policyGuardAddress;
218
+ constructor(config) {
219
+ const chain = config.chainId === 97 ? bscTestnet : bsc;
220
+ this.publicClient = createPublicClient({
221
+ chain,
222
+ transport: http(config.rpcUrl)
223
+ });
224
+ this.agentNfaAddress = config.agentNfaAddress;
225
+ this.policyGuardAddress = config.policyGuardAddress;
226
+ if (config.operatorPrivateKey) {
227
+ const account = privateKeyToAccount(config.operatorPrivateKey);
228
+ this._operatorAddress = account.address;
229
+ this._walletClient = createWalletClient({
230
+ account,
231
+ chain,
232
+ transport: http(config.rpcUrl)
233
+ });
234
+ } else {
235
+ this._operatorAddress = null;
236
+ this._walletClient = null;
237
+ }
238
+ }
239
+ // ═══════════════════════════════════════════════════════
240
+ // VALIDATION
241
+ // ═══════════════════════════════════════════════════════
242
+ /**
243
+ * Validate an action against all active on-chain policies.
244
+ * This is an off-chain simulation — saves gas by catching rejections before submission.
245
+ *
246
+ * @param tokenId - Agent NFA token ID
247
+ * @param action - The action to validate
248
+ * @param callerOverride - Optional caller address override (defaults to operator)
249
+ */
250
+ async validate(tokenId, action, callerOverride) {
251
+ const caller = callerOverride ?? this._operatorAddress;
252
+ if (!caller) {
253
+ return { ok: false, reason: "No caller address \u2014 provide operatorPrivateKey or callerOverride" };
254
+ }
255
+ const vault = await this.getVault(tokenId);
256
+ try {
257
+ const [ok, reason] = await this.publicClient.readContract({
258
+ address: this.policyGuardAddress,
259
+ abi: PolicyGuardV4Abi,
260
+ functionName: "validate",
261
+ args: [
262
+ this.agentNfaAddress,
263
+ tokenId,
264
+ vault,
265
+ caller,
266
+ { target: action.target, value: action.value, data: action.data }
267
+ ]
268
+ });
269
+ return { ok, reason: reason || void 0 };
270
+ } catch (error) {
271
+ const message = error instanceof Error ? error.message : "Simulation reverted";
272
+ return { ok: false, reason: `Simulation reverted: ${message.slice(0, 300)}` };
273
+ }
274
+ }
275
+ // ═══════════════════════════════════════════════════════
276
+ // EXECUTION
277
+ // ═══════════════════════════════════════════════════════
278
+ /**
279
+ * Execute a single action through AgentNFA.
280
+ * Simulates first, then submits if validation passes.
281
+ *
282
+ * @param tokenId - Agent NFA token ID
283
+ * @param action - The action to execute
284
+ * @param skipValidation - Skip pre-flight validation (default: false)
285
+ */
286
+ async execute(tokenId, action, skipValidation = false) {
287
+ this.requireWallet();
288
+ if (!skipValidation) {
289
+ const sim = await this.validate(tokenId, action);
290
+ if (!sim.ok) {
291
+ throw new Error(`Policy rejected: ${sim.reason}`);
292
+ }
293
+ }
294
+ const hash = await this._walletClient.writeContract({
295
+ address: this.agentNfaAddress,
296
+ abi: AgentNFAAbi,
297
+ functionName: "execute",
298
+ args: [tokenId, { target: action.target, value: action.value, data: action.data }]
299
+ });
300
+ return { hash };
301
+ }
302
+ /**
303
+ * Execute multiple actions atomically via AgentNFA.executeBatch().
304
+ * Useful for approve+swap in a single transaction.
305
+ *
306
+ * @param tokenId - Agent NFA token ID
307
+ * @param actions - Array of actions to execute
308
+ * @param skipValidation - Skip per-action pre-flight validation (default: false)
309
+ */
310
+ async executeBatch(tokenId, actions, skipValidation = false) {
311
+ this.requireWallet();
312
+ if (!skipValidation) {
313
+ for (const action of actions) {
314
+ const sim = await this.validate(tokenId, action);
315
+ if (!sim.ok) {
316
+ throw new Error(`Policy rejected action (target=${action.target}): ${sim.reason}`);
317
+ }
318
+ }
319
+ }
320
+ const hash = await this._walletClient.writeContract({
321
+ address: this.agentNfaAddress,
322
+ abi: AgentNFAAbi,
323
+ functionName: "executeBatch",
324
+ args: [
325
+ tokenId,
326
+ actions.map((a) => ({ target: a.target, value: a.value, data: a.data }))
327
+ ]
328
+ });
329
+ return { hash };
330
+ }
331
+ // ═══════════════════════════════════════════════════════
332
+ // POLICY QUERIES
333
+ // ═══════════════════════════════════════════════════════
334
+ /**
335
+ * Get all active policies for an agent instance, with type info.
336
+ *
337
+ * @param tokenId - Agent NFA token ID
338
+ * @returns Array of PolicyInfo objects
339
+ */
340
+ async getPolicies(tokenId) {
341
+ const addresses = await this.publicClient.readContract({
342
+ address: this.policyGuardAddress,
343
+ abi: PolicyGuardV4Abi,
344
+ functionName: "getActivePolicies",
345
+ args: [tokenId]
346
+ });
347
+ const results = [];
348
+ for (const addr of addresses) {
349
+ try {
350
+ const [policyType, renterConfigurable] = await Promise.all([
351
+ this.publicClient.readContract({
352
+ address: addr,
353
+ abi: IPolicyAbi,
354
+ functionName: "policyType"
355
+ }),
356
+ this.publicClient.readContract({
357
+ address: addr,
358
+ abi: IPolicyAbi,
359
+ functionName: "renterConfigurable"
360
+ })
361
+ ]);
362
+ const typeName = POLICY_TYPE_NAMES[policyType] ?? "unknown";
363
+ results.push({
364
+ address: addr,
365
+ policyType,
366
+ policyTypeName: typeName,
367
+ renterConfigurable
368
+ });
369
+ } catch {
370
+ results.push({
371
+ address: addr,
372
+ policyType: "0x",
373
+ policyTypeName: "unknown",
374
+ renterConfigurable: false
375
+ });
376
+ }
377
+ }
378
+ return results;
379
+ }
380
+ // ═══════════════════════════════════════════════════════
381
+ // OPERATOR PERMIT
382
+ // ═══════════════════════════════════════════════════════
383
+ /**
384
+ * Read the current operator state for a token — useful for constructing permits.
385
+ *
386
+ * @param tokenId - Agent NFA token ID
387
+ * @returns Current operator, renter, vault, and nonce info
388
+ */
389
+ async getOperatorState(tokenId) {
390
+ const [operator, operatorExpires, renter, renterExpires, vault, operatorNonce] = await Promise.all([
391
+ this.publicClient.readContract({
392
+ address: this.agentNfaAddress,
393
+ abi: AgentNFAAbi,
394
+ functionName: "operatorOf",
395
+ args: [tokenId]
396
+ }),
397
+ this.publicClient.readContract({
398
+ address: this.agentNfaAddress,
399
+ abi: AgentNFAAbi,
400
+ functionName: "operatorExpiresOf",
401
+ args: [tokenId]
402
+ }),
403
+ this.publicClient.readContract({
404
+ address: this.agentNfaAddress,
405
+ abi: AgentNFAAbi,
406
+ functionName: "userOf",
407
+ args: [tokenId]
408
+ }),
409
+ this.publicClient.readContract({
410
+ address: this.agentNfaAddress,
411
+ abi: AgentNFAAbi,
412
+ functionName: "userExpires",
413
+ args: [tokenId]
414
+ }),
415
+ this.publicClient.readContract({
416
+ address: this.agentNfaAddress,
417
+ abi: AgentNFAAbi,
418
+ functionName: "accountOf",
419
+ args: [tokenId]
420
+ }),
421
+ this.publicClient.readContract({
422
+ address: this.agentNfaAddress,
423
+ abi: AgentNFAAbi,
424
+ functionName: "operatorNonceOf",
425
+ args: [tokenId]
426
+ })
427
+ ]);
428
+ return { operator, operatorExpires, renter, renterExpires, vault, operatorNonce };
429
+ }
430
+ /**
431
+ * Submit a signed OperatorPermit on-chain to authorize this runner as operator.
432
+ *
433
+ * @param permit - The OperatorPermit struct
434
+ * @param signature - EIP-712 signature from the renter
435
+ */
436
+ async setOperatorWithPermit(permit, signature) {
437
+ this.requireWallet();
438
+ const hash = await this._walletClient.writeContract({
439
+ address: this.agentNfaAddress,
440
+ abi: AgentNFAAbi,
441
+ functionName: "setOperatorWithSig",
442
+ args: [
443
+ {
444
+ tokenId: permit.tokenId,
445
+ renter: permit.renter,
446
+ operator: permit.operator,
447
+ expires: permit.expires,
448
+ nonce: permit.nonce,
449
+ deadline: permit.deadline
450
+ },
451
+ signature
452
+ ]
453
+ });
454
+ return { hash };
455
+ }
456
+ /**
457
+ * Clear the operator permit for a token.
458
+ *
459
+ * @param tokenId - Agent NFA token ID
460
+ */
461
+ async clearOperator(tokenId) {
462
+ this.requireWallet();
463
+ const hash = await this._walletClient.writeContract({
464
+ address: this.agentNfaAddress,
465
+ abi: AgentNFAAbi,
466
+ functionName: "clearOperator",
467
+ args: [tokenId]
468
+ });
469
+ return { hash };
470
+ }
471
+ // ═══════════════════════════════════════════════════════
472
+ // HELPERS
473
+ // ═══════════════════════════════════════════════════════
474
+ /**
475
+ * Get the vault (AgentAccount) address for a token.
476
+ */
477
+ async getVault(tokenId) {
478
+ return await this.publicClient.readContract({
479
+ address: this.agentNfaAddress,
480
+ abi: AgentNFAAbi,
481
+ functionName: "accountOf",
482
+ args: [tokenId]
483
+ });
484
+ }
485
+ /**
486
+ * Get the operator address configured for this client.
487
+ */
488
+ getOperatorAddress() {
489
+ return this._operatorAddress;
490
+ }
491
+ requireWallet() {
492
+ if (!this._walletClient) {
493
+ throw new Error(
494
+ "PolicyClient: operatorPrivateKey is required for write operations (execute, setOperator)"
495
+ );
496
+ }
497
+ }
498
+ };
499
+
500
+ export {
501
+ PolicyClient
502
+ };