@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.
- package/README.md +51 -10
- package/openclaw.plugin.json +15 -1
- package/package.json +7 -4
- package/skills/code-sandbox/SKILL.md +30 -0
- package/skills/code-sandbox/dist/index.js +188 -0
- package/skills/code-sandbox/skill.json +13 -0
- package/skills/code-sandbox/src/index.ts +212 -0
- package/skills/creative/SKILL.md +32 -0
- package/skills/creative/dist/index.js +667 -0
- package/skills/creative/skill.json +13 -0
- package/skills/creative/src/index.ts +679 -0
- package/skills/defi/SKILL.md +123 -0
- package/skills/defi/dist/index.js +1745 -0
- package/skills/defi/skill.json +44 -0
- package/skills/defi/src/index.ts +1788 -0
- package/skills/defi/test-read.mjs +281 -0
- package/skills/governance/SKILL.md +69 -0
- package/skills/governance/dist/index.js +632 -0
- package/skills/governance/skill.json +21 -0
- package/skills/governance/src/index.ts +656 -0
- package/skills/governance/test-read.mjs +176 -0
- package/skills/lending/SKILL.md +63 -0
- package/skills/lending/dist/index.js +1039 -0
- package/skills/lending/skill.json +29 -0
- package/skills/lending/src/index.ts +1105 -0
- package/skills/lending/test-read.mjs +156 -0
- package/skills/nft/SKILL.md +95 -0
- package/skills/nft/dist/index.js +1520 -0
- package/skills/nft/skill.json +37 -0
- package/skills/nft/src/index.ts +1539 -0
- package/skills/shellbook/SKILL.md +59 -0
- package/skills/shellbook/dist/index.js +381 -0
- package/skills/shellbook/skill.json +29 -0
- package/skills/shellbook/src/index.ts +391 -0
- package/skills/shellbook/tsconfig.json +14 -0
- package/skills/smart-contracts/SKILL.md +128 -0
- package/skills/smart-contracts/dist/index.js +1225 -0
- package/skills/smart-contracts/skill.json +25 -0
- package/skills/smart-contracts/src/index.ts +1327 -0
- package/skills/smart-contracts/tsconfig.json +14 -0
- package/skills/structured-data/SKILL.md +36 -0
- package/skills/structured-data/dist/index.js +501 -0
- package/skills/structured-data/skill.json +13 -0
- package/skills/structured-data/src/index.ts +597 -0
- package/skills/tax/SKILL.md +109 -0
- package/skills/tax/dist/index.js +1749 -0
- package/skills/tax/skill.json +20 -0
- package/skills/tax/src/index.ts +1985 -0
- package/skills/web-scraping/SKILL.md +29 -0
- package/skills/web-scraping/dist/index.js +311 -0
- package/skills/web-scraping/skill.json +13 -0
- package/skills/web-scraping/src/index.ts +371 -0
- package/skills/xmd/SKILL.md +52 -0
- package/skills/xmd/dist/index.js +596 -0
- package/skills/xmd/skill.json +22 -0
- package/skills/xmd/src/index.ts +635 -0
- package/skills/xmd/test-read.mjs +178 -0
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Quick integration test for governance skill read-only tools.
|
|
3
|
+
* Calls mainnet gov contract directly — no signing needed.
|
|
4
|
+
*
|
|
5
|
+
* Usage: node test-read.mjs
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
const RPC = 'https://proton.eosusa.io';
|
|
9
|
+
const GOV_API = 'https://gov.api.xprnetwork.org/api/v1/proposals';
|
|
10
|
+
const GOV = 'gov';
|
|
11
|
+
|
|
12
|
+
async function getTableRows(opts) {
|
|
13
|
+
const resp = await fetch(`${RPC}/v1/chain/get_table_rows`, {
|
|
14
|
+
method: 'POST',
|
|
15
|
+
headers: { 'Content-Type': 'application/json' },
|
|
16
|
+
body: JSON.stringify({
|
|
17
|
+
json: true,
|
|
18
|
+
code: opts.code,
|
|
19
|
+
scope: opts.scope,
|
|
20
|
+
table: opts.table,
|
|
21
|
+
lower_bound: opts.lower_bound,
|
|
22
|
+
upper_bound: opts.upper_bound,
|
|
23
|
+
limit: opts.limit || 100,
|
|
24
|
+
key_type: opts.key_type,
|
|
25
|
+
index_position: opts.index_position,
|
|
26
|
+
reverse: opts.reverse || false,
|
|
27
|
+
}),
|
|
28
|
+
});
|
|
29
|
+
const data = await resp.json();
|
|
30
|
+
return data;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
let passed = 0;
|
|
34
|
+
let failed = 0;
|
|
35
|
+
|
|
36
|
+
function assert(condition, msg) {
|
|
37
|
+
if (condition) { passed++; console.log(` PASS: ${msg}`); }
|
|
38
|
+
else { failed++; console.log(` FAIL: ${msg}`); }
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
// ── Test 1: gov_list_communities ──
|
|
42
|
+
console.log('\n--- gov_list_communities ---');
|
|
43
|
+
const { rows: communities } = await getTableRows({ code: GOV, scope: GOV, table: 'communities', limit: 50 });
|
|
44
|
+
assert(communities.length >= 6, `Found ${communities.length} communities (expected >= 6)`);
|
|
45
|
+
|
|
46
|
+
const xprCommunity = communities.find(c => c.name === 'XPR Network');
|
|
47
|
+
assert(!!xprCommunity, 'XPR Network community exists');
|
|
48
|
+
assert(xprCommunity.id === 3, `XPR Network id = 3 (got ${xprCommunity.id})`);
|
|
49
|
+
assert(xprCommunity.strategies.includes('xpr-unstaked-and-staked-balances'), 'Has XPR staking strategy');
|
|
50
|
+
assert(xprCommunity.proposalFee?.quantity === '20000.0000 XPR', `Fee = 20000 XPR (got ${xprCommunity.proposalFee?.quantity})`);
|
|
51
|
+
assert(xprCommunity.proposalFee?.contract === 'eosio.token', 'Fee contract = eosio.token');
|
|
52
|
+
assert(xprCommunity.quorum === 300, `Quorum = 300 basis points (got ${xprCommunity.quorum})`);
|
|
53
|
+
assert(Array.isArray(xprCommunity.admins), 'Has admins array');
|
|
54
|
+
|
|
55
|
+
const loanCommunity = communities.find(c => c.name === 'LOAN Protocol');
|
|
56
|
+
assert(!!loanCommunity, 'LOAN Protocol community exists');
|
|
57
|
+
assert(loanCommunity.proposalFee?.contract === 'loan.token', 'LOAN fee contract = loan.token');
|
|
58
|
+
|
|
59
|
+
const dogeCommunity = communities.find(c => c.name === 'D.O.G.E. Community');
|
|
60
|
+
assert(!!dogeCommunity, 'D.O.G.E. community exists');
|
|
61
|
+
|
|
62
|
+
// ── Test 2: gov_get_config ──
|
|
63
|
+
console.log('\n--- gov_get_config ---');
|
|
64
|
+
const { rows: globals } = await getTableRows({ code: GOV, scope: GOV, table: 'govglobal', limit: 1 });
|
|
65
|
+
assert(globals.length === 1, 'govglobal singleton exists');
|
|
66
|
+
assert(globals[0].isPaused === 0, 'Governance is not paused');
|
|
67
|
+
assert(globals[0].proposalId >= 300, `${globals[0].proposalId} proposals (>= 300)`);
|
|
68
|
+
assert(globals[0].voteId >= 17000, `${globals[0].voteId} votes (>= 17000)`);
|
|
69
|
+
assert(globals[0].communityId >= 9, `Next community ID >= 9 (got ${globals[0].communityId})`);
|
|
70
|
+
|
|
71
|
+
// ── Test 3: gov_list_proposals ──
|
|
72
|
+
console.log('\n--- gov_list_proposals (latest 10) ---');
|
|
73
|
+
const { rows: proposals } = await getTableRows({ code: GOV, scope: GOV, table: 'proposals', limit: 10, reverse: true });
|
|
74
|
+
assert(proposals.length === 10, `Got 10 proposals (got ${proposals.length})`);
|
|
75
|
+
assert(proposals[0].id > proposals[9].id, 'Reverse order (newest first)');
|
|
76
|
+
|
|
77
|
+
const latestProp = proposals[0];
|
|
78
|
+
assert(typeof latestProp.author === 'string' && latestProp.author.length > 0, `Author = ${latestProp.author}`);
|
|
79
|
+
assert(typeof latestProp.communityId === 'number', 'Has communityId');
|
|
80
|
+
assert(typeof latestProp.content === 'string' && latestProp.content.length > 0, `Content hash = ${latestProp.content}`);
|
|
81
|
+
assert(Array.isArray(latestProp.candidates), 'Has candidates array');
|
|
82
|
+
assert(latestProp.candidates.length >= 2, `${latestProp.candidates.length} candidates`);
|
|
83
|
+
assert(typeof latestProp.startTime === 'number', 'Has startTime');
|
|
84
|
+
assert(typeof latestProp.endTime === 'number', 'Has endTime');
|
|
85
|
+
assert(latestProp.endTime > latestProp.startTime, 'endTime > startTime');
|
|
86
|
+
|
|
87
|
+
// ── Test 4: gov_list_proposals filtered by community ──
|
|
88
|
+
console.log('\n--- gov_list_proposals (community 3 = XPR Network) ---');
|
|
89
|
+
const { rows: allProps } = await getTableRows({ code: GOV, scope: GOV, table: 'proposals', limit: 100, reverse: true });
|
|
90
|
+
const xprProps = allProps.filter(p => p.communityId === 3);
|
|
91
|
+
assert(xprProps.length > 0, `Found ${xprProps.length} XPR Network proposals`);
|
|
92
|
+
assert(xprProps.every(p => p.communityId === 3), 'All filtered to community 3');
|
|
93
|
+
|
|
94
|
+
// ── Test 5: gov_get_proposal (with Gov API) ──
|
|
95
|
+
console.log('\n--- gov_get_proposal (with Gov API title) ---');
|
|
96
|
+
const testProp = proposals[0];
|
|
97
|
+
const govResp = await fetch(`${GOV_API}/${testProp.content}`, {
|
|
98
|
+
headers: { 'Accept': 'application/json' },
|
|
99
|
+
});
|
|
100
|
+
const govData = await govResp.json();
|
|
101
|
+
assert(typeof govData.title === 'string' && govData.title.length > 0, `Title = "${govData.title.slice(0, 60)}..."`);
|
|
102
|
+
assert(typeof govData.description === 'string' && govData.description.length > 0, `Description length = ${govData.description.length} chars`);
|
|
103
|
+
assert(typeof govData.tokenAmount === 'number', `Total votes = ${govData.tokenAmount}`);
|
|
104
|
+
assert(Array.isArray(govData.candidates), 'Gov API returns candidates');
|
|
105
|
+
|
|
106
|
+
// Verify candidate vote amounts
|
|
107
|
+
for (const c of govData.candidates) {
|
|
108
|
+
assert(typeof c.tokenAmount === 'number', `Candidate "${c.name}" votes = ${c.tokenAmount}`);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
// ── Test 6: gov_get_proposal (known proposal) ──
|
|
112
|
+
console.log('\n--- gov_get_proposal (specific proposal #298) ---');
|
|
113
|
+
const { rows: prop298 } = await getTableRows({ code: GOV, scope: GOV, table: 'proposals', lower_bound: 298, upper_bound: 298, limit: 1 });
|
|
114
|
+
assert(prop298.length === 1, 'Found proposal #298');
|
|
115
|
+
assert(prop298[0].communityId === 3, 'Proposal #298 is in XPR Network community');
|
|
116
|
+
assert(prop298[0].candidates.length >= 2, `Has ${prop298[0].candidates.length} candidates`);
|
|
117
|
+
|
|
118
|
+
const gov298 = await fetch(`${GOV_API}/${prop298[0].content}`).then(r => r.json());
|
|
119
|
+
assert(typeof gov298.title === 'string' && gov298.title.length > 0, `Title = "${gov298.title.slice(0, 60)}"`);
|
|
120
|
+
|
|
121
|
+
// ── Test 7: gov_get_votes (scan for recent votes) ──
|
|
122
|
+
console.log('\n--- gov_get_votes (scan recent votes) ---');
|
|
123
|
+
const { rows: recentVotes } = await getTableRows({ code: GOV, scope: GOV, table: 'votes', limit: 20, reverse: true });
|
|
124
|
+
assert(recentVotes.length > 0, `Got ${recentVotes.length} recent votes`);
|
|
125
|
+
|
|
126
|
+
const firstVote = recentVotes[0];
|
|
127
|
+
assert(typeof firstVote.voter === 'string', `Voter = ${firstVote.voter}`);
|
|
128
|
+
assert(typeof firstVote.communityId === 'number', 'Has communityId');
|
|
129
|
+
assert(typeof firstVote.proposalId === 'number', `ProposalId = ${firstVote.proposalId}`);
|
|
130
|
+
assert(Array.isArray(firstVote.winners), 'Has winners array');
|
|
131
|
+
assert(firstVote.winners.length > 0, `${firstVote.winners.length} winner selections`);
|
|
132
|
+
assert(typeof firstVote.winners[0].id === 'number', `Winner id = ${firstVote.winners[0].id}`);
|
|
133
|
+
assert(typeof firstVote.winners[0].weight === 'number', `Winner weight = ${firstVote.winners[0].weight}`);
|
|
134
|
+
assert(typeof firstVote.timestamp === 'number', 'Has timestamp');
|
|
135
|
+
|
|
136
|
+
// Filter votes for a specific proposal
|
|
137
|
+
const targetProposal = firstVote.proposalId;
|
|
138
|
+
const matchedVotes = recentVotes.filter(v => v.proposalId === targetProposal);
|
|
139
|
+
assert(matchedVotes.length > 0, `Found ${matchedVotes.length} votes for proposal #${targetProposal}`);
|
|
140
|
+
const totalWeight = matchedVotes.reduce((sum, v) => sum + v.winners.reduce((ws, w) => ws + w.weight, 0), 0);
|
|
141
|
+
assert(totalWeight > 0, `Total weight for proposal #${targetProposal} = ${totalWeight}`);
|
|
142
|
+
|
|
143
|
+
// ── Test 8: non-existent proposal returns empty ──
|
|
144
|
+
console.log('\n--- non-existent proposal ---');
|
|
145
|
+
const { rows: noProps } = await getTableRows({ code: GOV, scope: GOV, table: 'proposals', lower_bound: 999999, upper_bound: 999999, limit: 1 });
|
|
146
|
+
assert(noProps.length === 0, 'Non-existent proposal returns empty');
|
|
147
|
+
|
|
148
|
+
// ── Test 9: proposal status logic ──
|
|
149
|
+
console.log('\n--- proposal status logic ---');
|
|
150
|
+
const now = Math.floor(Date.now() / 1000);
|
|
151
|
+
|
|
152
|
+
function proposalStatus(p) {
|
|
153
|
+
if (p.approve === 'Approved') return 'Approved';
|
|
154
|
+
if (p.approve === 'Declined') return 'Declined';
|
|
155
|
+
if (now < p.startTime) return 'Upcoming';
|
|
156
|
+
if (now <= p.endTime) return 'Active';
|
|
157
|
+
return 'Ended';
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
const approvedProps = allProps.filter(p => proposalStatus(p) === 'Approved');
|
|
161
|
+
assert(approvedProps.length > 0, `${approvedProps.length} approved proposals`);
|
|
162
|
+
|
|
163
|
+
const declinedProps = allProps.filter(p => proposalStatus(p) === 'Declined');
|
|
164
|
+
assert(declinedProps.length >= 0, `${declinedProps.length} declined proposals`);
|
|
165
|
+
|
|
166
|
+
// ── Test 10: community quorum math ──
|
|
167
|
+
console.log('\n--- community quorum math ---');
|
|
168
|
+
for (const c of communities) {
|
|
169
|
+
const quorumPct = (c.quorum / 100).toFixed(2);
|
|
170
|
+
assert(typeof c.quorum === 'number' && c.quorum >= 0, `${c.name}: quorum = ${quorumPct}%`);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
// ── Summary ──
|
|
174
|
+
console.log(`\n${'='.repeat(40)}`);
|
|
175
|
+
console.log(`Results: ${passed} passed, ${failed} failed`);
|
|
176
|
+
process.exit(failed > 0 ? 1 : 0);
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: lending
|
|
3
|
+
description: LOAN Protocol lending and borrowing on XPR Network (lending.loan contract)
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
## LOAN Protocol (part of the Metal X App)
|
|
7
|
+
|
|
8
|
+
You have tools to interact with the **LOAN Protocol** on XPR Network — a Compound-style pooled lending protocol at `lending.loan`, available within the **Metal X App** (metalx.com). Users supply assets to earn interest and borrow against their collateral. Always refer to it as "LOAN Protocol" (not "Metal Lending Protocol").
|
|
9
|
+
|
|
10
|
+
**IMPORTANT: LOAN Protocol is mainnet only.** There is no testnet deployment. All lending tools operate on mainnet.
|
|
11
|
+
|
|
12
|
+
### Key Concepts
|
|
13
|
+
|
|
14
|
+
- **L-Tokens** — share tokens (LBTC, LUSDC, LXPR, etc.) representing a user's share in the lending pool. When you supply XBTC, you receive LBTC. L-tokens auto-compound interest.
|
|
15
|
+
- **Collateral Factor** — max borrow percentage of collateral value (e.g. 70% = can borrow up to $70 per $100 deposited). Borrowing close to this limit risks immediate liquidation.
|
|
16
|
+
- **Utilization Rate** — ratio of borrowed to total assets. High utilization = higher borrow rates. Each market targets an optimal utilization (kink point).
|
|
17
|
+
- **Variable Rate** — borrow rate that fluctuates with utilization. All borrowers in a market pay the same variable rate.
|
|
18
|
+
- **Liquidation** — when a user's borrow exceeds their collateral factor threshold, liquidators can repay the debt and seize discounted collateral. Liquidation incentive is currently 10%.
|
|
19
|
+
- **LOAN Token** — governance/reward token. Suppliers and borrowers earn LOAN rewards proportional to their position.
|
|
20
|
+
|
|
21
|
+
### Available Markets
|
|
22
|
+
|
|
23
|
+
14 active markets including: XUSDC, XBTC, XETH, XPR, XMT, XDOGE, XLTC, XXRP, XSOL, XXLM, XADA, XHBAR, XUSDT, XMD.
|
|
24
|
+
|
|
25
|
+
### Read-Only Tools (safe, no signing)
|
|
26
|
+
|
|
27
|
+
- `loan_list_markets` — list all lending markets with utilization, reserves, interest models, and collateral factors
|
|
28
|
+
- `loan_get_market` — get detailed info for a specific market by L-token symbol (e.g. "LBTC")
|
|
29
|
+
- `loan_get_user_positions` — get a user's supply (L-token shares) and borrow positions across all markets
|
|
30
|
+
- `loan_get_user_rewards` — get a user's unclaimed LOAN rewards per market
|
|
31
|
+
- `loan_get_config` — get global lending config (oracle, close factor, liquidation incentive)
|
|
32
|
+
- `loan_get_market_apy` — get historical deposit/borrow APY including LOAN rewards (7d, 30d, 90d) from Metal X API
|
|
33
|
+
- `loan_get_market_tvl` — get historical TVL (total value locked) in USD with utilization ratio (7d, 30d, 90d)
|
|
34
|
+
|
|
35
|
+
### Write Tools (require `confirmed: true`)
|
|
36
|
+
|
|
37
|
+
All write operations require explicit confirmation. For supply and repay, the tool builds a token transfer to `lending.loan`. For borrow/redeem/withdraw, it calls the lending contract directly.
|
|
38
|
+
|
|
39
|
+
- `loan_enter_markets` — enter markets to enable lending/borrowing (must do this first)
|
|
40
|
+
- `loan_exit_markets` — exit markets (only if no outstanding positions)
|
|
41
|
+
- `loan_supply` — supply underlying tokens to earn interest (transfers to lending.loan with "mint" memo)
|
|
42
|
+
- `loan_borrow` — borrow underlying tokens against deposited collateral
|
|
43
|
+
- `loan_repay` — repay variable or stable borrows (transfers to lending.loan with "repay" memo)
|
|
44
|
+
- `loan_redeem` — redeem L-tokens for underlying tokens (burns deposited shares)
|
|
45
|
+
- `loan_withdraw_collateral` — withdraw L-tokens from collateral (reduces borrowing capacity)
|
|
46
|
+
- `loan_claim_rewards` — claim accrued LOAN token rewards
|
|
47
|
+
|
|
48
|
+
### Typical User Flow
|
|
49
|
+
|
|
50
|
+
1. **Enter markets** — `loan_enter_markets` with the market symbols you want to use
|
|
51
|
+
2. **Supply** — `loan_supply` underlying tokens (e.g. 1.0 XBTC) → auto-mints LBTC and deposits as collateral
|
|
52
|
+
3. **Borrow** — `loan_borrow` against collateral (e.g. borrow 500 XUSDC using LBTC collateral)
|
|
53
|
+
4. **Repay** — `loan_repay` when ready to pay back the loan
|
|
54
|
+
5. **Redeem** — `loan_redeem` to withdraw your original tokens + earned interest
|
|
55
|
+
6. **Claim** — `loan_claim_rewards` to claim any LOAN token rewards
|
|
56
|
+
|
|
57
|
+
### Safety Rules
|
|
58
|
+
|
|
59
|
+
- NEVER borrow close to the collateral factor limit — users will be liquidated
|
|
60
|
+
- Always recommend borrowing at most 50-60% of the collateral factor to leave a safety buffer
|
|
61
|
+
- Check utilization before redeeming — high utilization means less cash available for withdrawals
|
|
62
|
+
- Supply and repay use token transfers (to lending.loan with memo); borrow/redeem/withdraw use direct contract actions
|
|
63
|
+
- All write tools require `confirmed: true` — present the details first and ask for confirmation
|