@xpr-agents/openclaw 0.3.1 → 0.4.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.
Files changed (57) hide show
  1. package/README.md +51 -10
  2. package/openclaw.plugin.json +15 -1
  3. package/package.json +7 -4
  4. package/skills/code-sandbox/SKILL.md +30 -0
  5. package/skills/code-sandbox/dist/index.js +188 -0
  6. package/skills/code-sandbox/skill.json +13 -0
  7. package/skills/code-sandbox/src/index.ts +212 -0
  8. package/skills/creative/SKILL.md +32 -0
  9. package/skills/creative/dist/index.js +667 -0
  10. package/skills/creative/skill.json +13 -0
  11. package/skills/creative/src/index.ts +679 -0
  12. package/skills/defi/SKILL.md +123 -0
  13. package/skills/defi/dist/index.js +1745 -0
  14. package/skills/defi/skill.json +44 -0
  15. package/skills/defi/src/index.ts +1788 -0
  16. package/skills/defi/test-read.mjs +281 -0
  17. package/skills/governance/SKILL.md +69 -0
  18. package/skills/governance/dist/index.js +632 -0
  19. package/skills/governance/skill.json +21 -0
  20. package/skills/governance/src/index.ts +656 -0
  21. package/skills/governance/test-read.mjs +176 -0
  22. package/skills/lending/SKILL.md +63 -0
  23. package/skills/lending/dist/index.js +1039 -0
  24. package/skills/lending/skill.json +29 -0
  25. package/skills/lending/src/index.ts +1105 -0
  26. package/skills/lending/test-read.mjs +156 -0
  27. package/skills/nft/SKILL.md +95 -0
  28. package/skills/nft/dist/index.js +1520 -0
  29. package/skills/nft/skill.json +37 -0
  30. package/skills/nft/src/index.ts +1539 -0
  31. package/skills/shellbook/SKILL.md +59 -0
  32. package/skills/shellbook/dist/index.js +381 -0
  33. package/skills/shellbook/skill.json +29 -0
  34. package/skills/shellbook/src/index.ts +391 -0
  35. package/skills/shellbook/tsconfig.json +14 -0
  36. package/skills/smart-contracts/SKILL.md +128 -0
  37. package/skills/smart-contracts/dist/index.js +1225 -0
  38. package/skills/smart-contracts/skill.json +25 -0
  39. package/skills/smart-contracts/src/index.ts +1327 -0
  40. package/skills/smart-contracts/tsconfig.json +14 -0
  41. package/skills/structured-data/SKILL.md +36 -0
  42. package/skills/structured-data/dist/index.js +501 -0
  43. package/skills/structured-data/skill.json +13 -0
  44. package/skills/structured-data/src/index.ts +597 -0
  45. package/skills/tax/SKILL.md +109 -0
  46. package/skills/tax/dist/index.js +1749 -0
  47. package/skills/tax/skill.json +20 -0
  48. package/skills/tax/src/index.ts +1985 -0
  49. package/skills/web-scraping/SKILL.md +29 -0
  50. package/skills/web-scraping/dist/index.js +311 -0
  51. package/skills/web-scraping/skill.json +13 -0
  52. package/skills/web-scraping/src/index.ts +371 -0
  53. package/skills/xmd/SKILL.md +52 -0
  54. package/skills/xmd/dist/index.js +596 -0
  55. package/skills/xmd/skill.json +22 -0
  56. package/skills/xmd/src/index.ts +635 -0
  57. package/skills/xmd/test-read.mjs +178 -0
@@ -0,0 +1,281 @@
1
+ /**
2
+ * DeFi Skill — Read-only integration tests
3
+ * Tests all 14 read-only tools against mainnet data.
4
+ *
5
+ * Usage: node openclaw/starter/agent/skills/defi/test-read.mjs
6
+ */
7
+
8
+ // ── Test runner ──
9
+ let passed = 0, failed = 0, total = 0;
10
+ function assert(cond, msg) {
11
+ total++;
12
+ if (cond) { passed++; }
13
+ else { failed++; console.error(` FAIL: ${msg}`); }
14
+ }
15
+
16
+ // ── Collect tools ──
17
+ const tools = [];
18
+ const mockApi = {
19
+ registerTool(t) { tools.push(t); },
20
+ getConfig() {
21
+ return {
22
+ network: 'mainnet',
23
+ rpcEndpoint: 'https://proton.eosusa.io',
24
+ };
25
+ },
26
+ };
27
+
28
+ // Load skill
29
+ const { default: defiSkill } = await import('./src/index.ts');
30
+ defiSkill(mockApi);
31
+
32
+ function findTool(name) {
33
+ return tools.find(t => t.name === name);
34
+ }
35
+
36
+ console.log(`Loaded ${tools.length} tools`);
37
+ assert(tools.length === 30, `Expected 30 tools, got ${tools.length}`);
38
+
39
+ // ── 1. defi_get_token_price ──
40
+ console.log('\n--- defi_get_token_price ---');
41
+ const price = await findTool('defi_get_token_price').handler({ symbol: 'XPR_XMD' });
42
+ assert(!price.error, `No error: ${price.error}`);
43
+ assert(price.symbol === 'XPR_XMD', `Symbol is XPR_XMD: ${price.symbol}`);
44
+ assert(typeof price.close === 'number', `Close is number: ${price.close}`);
45
+ assert(typeof price.volume_bid === 'number', `volume_bid: ${price.volume_bid}`);
46
+ assert(typeof price.change_24h_pct === 'number', `change_24h_pct: ${price.change_24h_pct}`);
47
+ console.log(` XPR/XMD: $${price.close}, 24h: ${price.change_24h_pct}%`);
48
+
49
+ // Bad symbol
50
+ const badPrice = await findTool('defi_get_token_price').handler({ symbol: 'FAKE_TOKEN' });
51
+ assert(badPrice.error, 'Returns error for unknown symbol');
52
+
53
+ // ── 2. defi_list_markets ──
54
+ console.log('\n--- defi_list_markets ---');
55
+ const markets = await findTool('defi_list_markets').handler({});
56
+ assert(!markets.error, `No error: ${markets.error}`);
57
+ assert(markets.total >= 15, `At least 15 markets: ${markets.total}`);
58
+ const xprMarket = markets.markets.find(m => m.symbol === 'XPR_XMD');
59
+ assert(xprMarket, 'XPR_XMD market exists');
60
+ assert(xprMarket.bid_token, 'Has bid_token info');
61
+ assert(xprMarket.ask_token, 'Has ask_token info');
62
+ console.log(` ${markets.total} markets`);
63
+
64
+ // ── 3. defi_get_swap_rate ──
65
+ console.log('\n--- defi_get_swap_rate ---');
66
+ const swap = await findTool('defi_get_swap_rate').handler({
67
+ from_token: '4,XPR,eosio.token',
68
+ to_token: '6,XUSDC,xtokens',
69
+ amount: 10000,
70
+ });
71
+ assert(!swap.error, `No error: ${swap.error}`);
72
+ assert(swap.output, `Has output: ${swap.output}`);
73
+ assert(parseFloat(swap.rate) > 0, `Rate > 0: ${swap.rate}`);
74
+ assert(swap.fee_pct, `Has fee: ${swap.fee_pct}`);
75
+ console.log(` 10000 XPR → ${swap.output}, rate=${swap.rate}, impact=${swap.price_impact_pct}%`);
76
+
77
+ // Bad pool
78
+ const badSwap = await findTool('defi_get_swap_rate').handler({
79
+ from_token: '4,XPR,eosio.token',
80
+ to_token: '6,FAKE,fake.token',
81
+ amount: 100,
82
+ });
83
+ assert(badSwap.error, 'Returns error for unknown pool');
84
+
85
+ // ── 4. defi_list_pools ──
86
+ console.log('\n--- defi_list_pools ---');
87
+ const pools = await findTool('defi_list_pools').handler({});
88
+ assert(!pools.error, `No error: ${pools.error}`);
89
+ assert(pools.total >= 5, `At least 5 pools: ${pools.total}`);
90
+ const firstPool = pools.pools[0];
91
+ assert(firstPool.lt_symbol, `Pool has lt_symbol: ${firstPool.lt_symbol}`);
92
+ assert(firstPool.token1, 'Pool has token1');
93
+ assert(firstPool.fee_pct, `Pool has fee: ${firstPool.fee_pct}`);
94
+ assert(firstPool.pool_type, `Pool type: ${firstPool.pool_type}`);
95
+ console.log(` ${pools.total} pools, first: ${firstPool.lt_symbol}`);
96
+
97
+ // ── 5. defi_get_ohlcv ──
98
+ console.log('\n--- defi_get_ohlcv ---');
99
+ const ohlcv = await findTool('defi_get_ohlcv').handler({
100
+ symbol: 'XPR_XMD',
101
+ interval: '1D',
102
+ limit: 5,
103
+ });
104
+ assert(!ohlcv.error, `No error: ${ohlcv.error}`);
105
+ assert(ohlcv.candles.length > 0, `Has candles: ${ohlcv.candles.length}`);
106
+ const candle = ohlcv.candles[0];
107
+ assert(candle.time, `Candle has time: ${candle.time}`);
108
+ assert(typeof candle.open === 'number', `Candle has open: ${candle.open}`);
109
+ assert(typeof candle.close === 'number', `Candle has close: ${candle.close}`);
110
+ console.log(` ${ohlcv.candles.length} candles, latest close: ${candle.close}`);
111
+
112
+ // ── 6. defi_get_orderbook ──
113
+ console.log('\n--- defi_get_orderbook ---');
114
+ const book = await findTool('defi_get_orderbook').handler({ symbol: 'XPR_XMD', step: 10000, limit: 5 });
115
+ assert(!book.error, `No error: ${book.error}`);
116
+ assert(Array.isArray(book.bids), 'Has bids array');
117
+ assert(Array.isArray(book.asks), 'Has asks array');
118
+ const totalDepth = (book.bids?.length || 0) + (book.asks?.length || 0);
119
+ assert(totalDepth > 0, `Has depth levels: ${totalDepth}`);
120
+ console.log(` ${book.bids?.length || 0} bids, ${book.asks?.length || 0} asks`);
121
+
122
+ // ── 7. defi_get_recent_trades ──
123
+ console.log('\n--- defi_get_recent_trades ---');
124
+ const recent = await findTool('defi_get_recent_trades').handler({ symbol: 'XPR_XMD', limit: 5 });
125
+ assert(!recent.error, `No error: ${recent.error}`);
126
+ assert(recent.trades.length > 0, `Has trades: ${recent.trades.length}`);
127
+ const trade = recent.trades[0];
128
+ assert(trade.price, `Trade has price: ${trade.price}`);
129
+ assert(trade.side, `Trade has side: ${trade.side}`);
130
+ assert(trade.trx_id, `Trade has trx_id`);
131
+ console.log(` ${recent.trades.length} trades, latest: ${trade.price} (${trade.side})`);
132
+
133
+ // ── 8. defi_get_open_orders ──
134
+ console.log('\n--- defi_get_open_orders ---');
135
+ // Use a known active market maker account
136
+ const openOrders = await findTool('defi_get_open_orders').handler({ account: 'communitymm3', limit: 5 });
137
+ assert(!openOrders.error, `No error: ${openOrders.error}`);
138
+ assert(Array.isArray(openOrders.orders), 'Has orders array');
139
+ if (openOrders.orders.length > 0) {
140
+ const order = openOrders.orders[0];
141
+ assert(order.order_id, 'Order has order_id');
142
+ assert(order.side, 'Order has side');
143
+ assert(order.price !== undefined, 'Order has price');
144
+ console.log(` ${openOrders.orders.length} open orders`);
145
+ } else {
146
+ console.log(' No open orders for communitymm3 (may be normal)');
147
+ }
148
+
149
+ // ── 9. defi_get_order_history ──
150
+ console.log('\n--- defi_get_order_history ---');
151
+ const orderHist = await findTool('defi_get_order_history').handler({ account: 'communitymm3', limit: 3 });
152
+ assert(!orderHist.error, `No error: ${orderHist.error}`);
153
+ assert(Array.isArray(orderHist.orders), 'Has orders array');
154
+ console.log(` ${orderHist.orders.length} historical orders`);
155
+
156
+ // ── 10. defi_get_trade_history ──
157
+ console.log('\n--- defi_get_trade_history ---');
158
+ const tradeHist = await findTool('defi_get_trade_history').handler({ account: 'communitymm3', limit: 3 });
159
+ assert(!tradeHist.error, `No error: ${tradeHist.error}`);
160
+ assert(Array.isArray(tradeHist.trades), 'Has trades array');
161
+ console.log(` ${tradeHist.trades.length} historical trades`);
162
+
163
+ // ── 11. defi_get_dex_balances ──
164
+ console.log('\n--- defi_get_dex_balances ---');
165
+ const balances = await findTool('defi_get_dex_balances').handler({ account: 'communitymm3' });
166
+ assert(!balances.error, `No error: ${balances.error}`);
167
+ assert(Array.isArray(balances.balances), 'Has balances array');
168
+ console.log(` ${balances.total} balance entries`);
169
+
170
+ // ── 12. defi_list_otc_offers ──
171
+ console.log('\n--- defi_list_otc_offers ---');
172
+ const otc = await findTool('defi_list_otc_offers').handler({ limit: 5 });
173
+ assert(!otc.error, `No error: ${otc.error}`);
174
+ assert(Array.isArray(otc.offers), 'Has offers array');
175
+ if (otc.offers.length > 0) {
176
+ const offer = otc.offers[0];
177
+ assert(offer.id !== undefined, 'Offer has id');
178
+ assert(offer.from, 'Offer has from');
179
+ console.log(` ${otc.offers.length} OTC offers, first: #${offer.id} from ${offer.from}`);
180
+ } else {
181
+ console.log(' No OTC offers (table may be empty)');
182
+ }
183
+
184
+ // ── 13. defi_list_farms ──
185
+ console.log('\n--- defi_list_farms ---');
186
+ const farms = await findTool('defi_list_farms').handler({});
187
+ assert(!farms.error, `No error: ${farms.error}`);
188
+ assert(farms.total >= 3, `At least 3 active farms: ${farms.total}`);
189
+ const firstFarm = farms.farms[0];
190
+ assert(firstFarm.stake_symbol, `Farm has stake_symbol: ${firstFarm.stake_symbol}`);
191
+ assert(firstFarm.stake_contract, `Farm has stake_contract: ${firstFarm.stake_contract}`);
192
+ assert(firstFarm.rewards?.length > 0, `Farm has rewards: ${firstFarm.rewards?.length}`);
193
+ assert(firstFarm.rewards[0].per_day > 0, `Farm has positive daily reward: ${firstFarm.rewards[0].per_day}`);
194
+ console.log(` ${farms.total} active farms, first: ${firstFarm.stake_symbol} (${firstFarm.rewards[0].per_day} ${firstFarm.rewards[0].token}/day)`);
195
+
196
+ // All farms (including inactive)
197
+ const allFarms = await findTool('defi_list_farms').handler({ active_only: false });
198
+ assert(!allFarms.error, `No error listing all farms: ${allFarms.error}`);
199
+ assert(allFarms.total >= farms.total, `All farms >= active farms: ${allFarms.total} >= ${farms.total}`);
200
+ console.log(` ${allFarms.total} total farms (including inactive)`);
201
+
202
+ // ── 14. defi_get_farm_stakes ──
203
+ console.log('\n--- defi_get_farm_stakes ---');
204
+ // Use "paul" account which has known farm positions
205
+ const farmStakes = await findTool('defi_get_farm_stakes').handler({ account: 'paul' });
206
+ assert(!farmStakes.error, `No error: ${farmStakes.error}`);
207
+ assert(Array.isArray(farmStakes.stakes), 'Has stakes array');
208
+ assert(farmStakes.total > 0, `Paul has farm stakes: ${farmStakes.total}`);
209
+ if (farmStakes.stakes.length > 0) {
210
+ const stake = farmStakes.stakes[0];
211
+ assert(stake.symbol, `Stake has symbol: ${stake.symbol}`);
212
+ assert(stake.contract, `Stake has contract: ${stake.contract}`);
213
+ assert(stake.balance > 0 || stake.accrued_rewards_raw.length > 0, 'Stake has balance or rewards');
214
+ console.log(` ${farmStakes.total} positions, first: ${stake.balance} ${stake.symbol}`);
215
+ }
216
+
217
+ // Unknown account
218
+ const noStakes = await findTool('defi_get_farm_stakes').handler({ account: 'zzzzzzzzzzzz' });
219
+ assert(!noStakes.error, 'No error for unknown account');
220
+ assert(noStakes.total === 0, 'Zero stakes for unknown account');
221
+
222
+ // ── Verify write tools exist and have confirmed parameter ──
223
+ console.log('\n--- Write tool validation ---');
224
+ const writeTools = [
225
+ 'defi_place_order', 'defi_cancel_order', 'defi_withdraw_dex',
226
+ 'defi_swap', 'defi_add_liquidity', 'defi_remove_liquidity',
227
+ 'defi_create_otc', 'defi_fill_otc', 'defi_cancel_otc',
228
+ 'defi_farm_stake', 'defi_farm_unstake', 'defi_farm_claim',
229
+ 'msig_propose', 'msig_approve',
230
+ ];
231
+ for (const name of writeTools) {
232
+ const tool = findTool(name);
233
+ assert(tool, `${name} exists`);
234
+ if (tool) {
235
+ const hasConfirmed = tool.parameters.properties?.confirmed || tool.parameters.required?.includes('confirmed');
236
+ assert(hasConfirmed, `${name} has confirmed parameter`);
237
+ }
238
+ }
239
+ console.log(` ${writeTools.length} write tools validated`);
240
+
241
+ // ── Verify confirmation gate on write tools ──
242
+ console.log('\n--- Confirmation gate tests ---');
243
+ const placeResult = await findTool('defi_place_order').handler({
244
+ symbol: 'XPR_XMD', side: 'buy', amount: 100, price: 0.003,
245
+ });
246
+ assert(placeResult.error && placeResult.error.includes('Confirmation'), 'defi_place_order blocked without confirmed');
247
+
248
+ const swapResult = await findTool('defi_swap').handler({
249
+ from_token: '4,XPR,eosio.token', to_token: '6,XUSDC,xtokens',
250
+ amount: 100, min_output: 0.1,
251
+ });
252
+ assert(swapResult.error && swapResult.error.includes('Confirmation'), 'defi_swap blocked without confirmed');
253
+
254
+ const otcResult = await findTool('defi_create_otc').handler({
255
+ from_tokens: [{ quantity: '100.0000 XPR', contract: 'eosio.token' }],
256
+ to_tokens: [{ quantity: '1.000000 XUSDC', contract: 'xtokens' }],
257
+ });
258
+ assert(otcResult.error && otcResult.error.includes('Confirmation'), 'defi_create_otc blocked without confirmed');
259
+
260
+ const stakeResult = await findTool('defi_farm_stake').handler({
261
+ lp_amount: '1.00000000 METAXMD', lp_contract: 'proton.swaps',
262
+ });
263
+ assert(stakeResult.error && stakeResult.error.includes('Confirmation'), 'defi_farm_stake blocked without confirmed');
264
+
265
+ const unstakeResult = await findTool('defi_farm_unstake').handler({
266
+ lp_amount: '1.00000000 METAXMD', lp_contract: 'proton.swaps',
267
+ });
268
+ assert(unstakeResult.error && unstakeResult.error.includes('Confirmation'), 'defi_farm_unstake blocked without confirmed');
269
+
270
+ const claimResult = await findTool('defi_farm_claim').handler({
271
+ stakes: ['METAXMD'],
272
+ });
273
+ assert(claimResult.error && claimResult.error.includes('Confirmation'), 'defi_farm_claim blocked without confirmed');
274
+
275
+ console.log(' All confirmation gates working');
276
+
277
+ // ── Summary ──
278
+ console.log(`\n${'='.repeat(50)}`);
279
+ console.log(`Results: ${passed}/${total} passed, ${failed} failed`);
280
+ if (failed > 0) process.exit(1);
281
+ else console.log('All tests passed!');
@@ -0,0 +1,69 @@
1
+ ---
2
+ name: governance
3
+ description: XPR Network governance — communities, proposals, voting on the gov contract
4
+ ---
5
+
6
+ ## XPR Network Governance
7
+
8
+ You have tools to interact with XPR Network's on-chain governance system via the `gov` contract. Communities create proposals, and token holders vote on them.
9
+
10
+ ### Key Concepts
11
+
12
+ - **Communities** — governance groups (XPR Network, Metal DAO, LOAN Protocol, XPR Grants, Metal X, D.O.G.E.). Each has its own voting strategy, proposal fee, and quorum.
13
+ - **Proposals** — on-chain records with candidates (voting options), start/end times, and an approval status. Proposal content (title, description) is stored off-chain in the Gov API.
14
+ - **Voting Strategies** — determine who can vote and how vote weight is calculated:
15
+ - `xpr-unstaked-and-staked-balances` — weight = XPR balance (staked + unstaked)
16
+ - `xmt-balances` — weight = XMT balance
17
+ - `loan-and-sloan-balances` — weight = LOAN + sLOAN balance
18
+ - `kyc-verification` — 1 vote per KYC-verified account
19
+ - **Voting Systems** — `"0"` = single choice, `"1"` = multiple choice, `"2"` = ranked choice, `"5"` = approval voting
20
+ - **Quorum** — minimum participation threshold (basis points, e.g. 300 = 3%)
21
+ - **Proposal Fee** — token payment required to create a proposal (varies by community, e.g. 20,000 XPR, 100 XMT, 50,000 LOAN)
22
+
23
+ ### Active Communities
24
+
25
+ | ID | Name | Strategy | Fee | Quorum |
26
+ |----|------|----------|-----|--------|
27
+ | 3 | XPR Network | XPR balances | 20,000 XPR | 3% |
28
+ | 4 | Metal DAO | XMT balances | 100 XMT | 3% |
29
+ | 5 | LOAN Protocol | LOAN+sLOAN | 50,000 LOAN | 25% |
30
+ | 6 | XPR Grants | XPR balances | 20,000 XPR | 3% |
31
+ | 7 | Metal X | XPR balances | 20,000 XPR | 3% |
32
+ | 8 | D.O.G.E. | KYC verification | 1 XDOGE | 0.01% |
33
+
34
+ ### Read-Only Tools (safe, no signing)
35
+
36
+ - `gov_list_communities` — list all governance communities with strategies, fees, quorum, and admins
37
+ - `gov_list_proposals` — list proposals with optional community and status filters
38
+ - `gov_get_proposal` — get full proposal details including title and description from Gov API, plus vote totals per candidate
39
+ - `gov_get_votes` — get individual votes cast on a proposal (scans from most recent)
40
+ - `gov_get_config` — get governance global config (paused state, total counts)
41
+
42
+ ### Write Tools (require `confirmed: true`)
43
+
44
+ - `gov_vote` — vote on an active proposal. Specify the candidate(s) and weight.
45
+ - `gov_post_proposal` — create a new governance proposal. Requires paying the community's proposal fee (token transfer + postprop action in one transaction).
46
+
47
+ ### Voting
48
+
49
+ To vote, you need the `communityId`, `proposalId`, and `winners` (array of candidate IDs with weights). For simple Yes/No proposals, use `[{id: 0, weight: 100}]` for Yes or `[{id: 1, weight: 100}]` for No.
50
+
51
+ ### Creating Proposals
52
+
53
+ Creating a proposal requires:
54
+ 1. A `content` ID — created via the Gov API (`https://gov.api.xprnetwork.org`)
55
+ 2. Paying the community's proposal fee (token transfer to `gov`)
56
+ 3. Calling `postprop` with all proposal parameters
57
+
58
+ The `gov_post_proposal` tool handles steps 2 and 3 (fee + postprop). You must provide the content ID from step 1.
59
+
60
+ ### Proposal URLs
61
+
62
+ Proposals can be viewed at: `https://gov.xprnetwork.org/communities/{communityId}/proposals/{proposalId}`
63
+
64
+ ### Safety Rules
65
+
66
+ - Proposals have start and end times — voting is only allowed during the active period
67
+ - Each community has different fee tokens — check the community's `proposalFee` before creating proposals
68
+ - Quorum is in basis points (300 = 3%) — proposals need sufficient participation to pass
69
+ - Admins can approve/decline proposals — the `approve` field shows the final status