Help us improve
Share bugs, ideas, or general feedback.
From agentic-defi
Use when interacting with SparkLend protocol (supply, borrow, repay, withdraw, check positions, fetch investment opportunities) via the agentic-wallet MCP. Covers reserve APY fetching, calldata encoding, permission setup, approve+action batching, and health factor monitoring.
npx claudepluginhub bootnodedev/agentic-defi --plugin agentic-defiHow this skill is triggered — by the user, by Claude, or both
Slash command
/agentic-defi:spark-agentic-walletThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
SparkLend is a permissionless, non-custodial lending protocol (an Aave V3 fork) focused on DAI/USDS and stablecoin lending. It borrows from Sky's stablecoin reserves to deploy capital across DeFi. Deployed on **Ethereum** and **Gnosis**.
Creates p5.js generative art with seeded randomness, noise fields, and interactive parameter exploration. Use for algorithmic art, flow fields, or particle systems.
Share bugs, ideas, or general feedback.
SparkLend is a permissionless, non-custodial lending protocol (an Aave V3 fork) focused on DAI/USDS and stablecoin lending. It borrows from Sky's stablecoin reserves to deploy capital across DeFi. Deployed on Ethereum and Gnosis.
Interactions require ABI-encoded calldata passed to the agentic-wallet MCP tools. The wallet handles signing and submission — you encode the call and specify the Pool address as to.
Transaction tools:
execute_tx / execute_batch_tx — Autonomous execution using session permissions (requires grant_permissions first)send_tx — One-time user-approved transaction (user signs via URL, no session needed)Key constraint: ERC-20 operations (supply, repay) require a prior approve to the Pool. Always batch these atomically with execute_batch_tx.
All scripts run via:
node ${CLAUDE_PLUGIN_DATA}/dist/skills/spark-agentic-wallet/spark.js <subcommand> [options]
Standalone usage (not as a plugin): run from the repository root with
node dist/skills/spark-agentic-wallet/spark.js <subcommand> [options]
Fetch and rank supply/borrow APYs across one or all SparkLend networks.
# All networks
node ${CLAUDE_PLUGIN_DATA}/dist/skills/spark-agentic-wallet/spark.js opportunities --network all
# Single network
node ${CLAUDE_PLUGIN_DATA}/dist/skills/spark-agentic-wallet/spark.js opportunities --network ethereum
Show health factor, collateral, and debt for a wallet on a given network.
node ${CLAUDE_PLUGIN_DATA}/dist/skills/spark-agentic-wallet/spark.js position \
--wallet 0xYourWalletAddress \
--network ethereum
Print the parameters to pass to the grant_permissions MCP tool. Covers approve, supply, borrow, repay, and withdraw for a given token.
node ${CLAUDE_PLUGIN_DATA}/dist/skills/spark-agentic-wallet/spark.js grant-permissions \
--wallet 0xYourWalletAddress \
--network ethereum \
--token 0x6B175474E89094C44Da98b954EedeAC495271d0F \
--limit 1000000000000000000000 \
--expiry-hours 24
Print the parameters to pass to execute_batch_tx (approve + supply).
node ${CLAUDE_PLUGIN_DATA}/dist/skills/spark-agentic-wallet/spark.js supply \
--wallet 0xYourWalletAddress \
--network ethereum \
--asset 0x6B175474E89094C44Da98b954EedeAC495271d0F \
--amount 1000
Print the parameters to pass to execute_tx (borrow).
node ${CLAUDE_PLUGIN_DATA}/dist/skills/spark-agentic-wallet/spark.js borrow \
--wallet 0xYourWalletAddress \
--network ethereum \
--asset 0x6B175474E89094C44Da98b954EedeAC495271d0F \
--amount 500
Print the parameters to pass to execute_batch_tx (approve + repay). Use --amount max to repay full debt.
node ${CLAUDE_PLUGIN_DATA}/dist/skills/spark-agentic-wallet/spark.js repay \
--wallet 0xYourWalletAddress \
--network ethereum \
--asset 0x6B175474E89094C44Da98b954EedeAC495271d0F \
--amount max
Print the parameters to pass to execute_tx (withdraw). Use --amount max to withdraw full balance.
node ${CLAUDE_PLUGIN_DATA}/dist/skills/spark-agentic-wallet/spark.js withdraw \
--wallet 0xYourWalletAddress \
--network ethereum \
--asset 0x6B175474E89094C44Da98b954EedeAC495271d0F \
--amount max
| Network | Pool Address |
|---|---|
| Ethereum | 0xC13e21B648A5Ee794902342038FF3aDAB66BE987 |
| Gnosis | 0x2Dae5307c5E3FD1CF5A72Cb6F698f915860607e0 |
Canonical source: Spark Address Registry and Spark Docs
Interest rate mode: Always 2 (variable). Stable rate (1) is deprecated.
SparkLend is an Aave V3 fork — all function signatures are identical.
All calldata uses encodeFunctionData from viem:
import { encodeFunctionData, parseUnits, maxUint256 } from "viem";
const POOL = "0xC13e21B648A5Ee794902342038FF3aDAB66BE987"; // Ethereum
const DAI = "0x6B175474E89094C44Da98b954EedeAC495271d0F";
const userAddress = "0xYourWalletAddress";
const amount = parseUnits("1000", 18); // 1000 DAI (18 decimals)
Requires: approve(Pool, amount) + supply(asset, amount, onBehalfOf, 0)
// Step 1: approve
const approveTx = {
to: DAI,
data: encodeFunctionData({
abi: [{ name: "approve", type: "function", inputs: [
{ name: "spender", type: "address" },
{ name: "amount", type: "uint256" }
], outputs: [{ type: "bool" }] }],
functionName: "approve",
args: [POOL, amount],
}),
value: "0x0",
};
// Step 2: supply
const supplyTx = {
to: POOL,
data: encodeFunctionData({
abi: [{ name: "supply", type: "function", inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "onBehalfOf", type: "address" },
{ name: "referralCode", type: "uint16" }
], outputs: [] }],
functionName: "supply",
args: [DAI, amount, userAddress, 0],
}),
value: "0x0",
};
// Send atomically — get sessionId from list_wallets
await execute_batch_tx({ sessionId: "<from list_wallets>", calls: [approveTx, supplyTx], feeToken: DAI });
No prior approval needed. Pool pulls nothing — it sends tokens to you.
const borrowTx = {
to: POOL,
data: encodeFunctionData({
abi: [{ name: "borrow", type: "function", inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "interestRateMode", type: "uint256" },
{ name: "referralCode", type: "uint16" },
{ name: "onBehalfOf", type: "address" }
], outputs: [] }],
functionName: "borrow",
args: [DAI, amount, 2n, 0, userAddress],
}),
value: "0x0",
};
// Get sessionId from list_wallets
await execute_tx({ sessionId: "<from list_wallets>", to: POOL, data: borrowTx.data, value: "0x0", feeToken: DAI });
Requires: approve(Pool, amount) + repay(asset, amount, 2, onBehalfOf)
Use maxUint256 as amount to repay the full debt:
const repayAmount = maxUint256; // or exact amount in wei
const approveTx = {
to: DAI,
data: encodeFunctionData({
abi: [{ name: "approve", type: "function", inputs: [
{ name: "spender", type: "address" },
{ name: "amount", type: "uint256" }
], outputs: [{ type: "bool" }] }],
functionName: "approve",
args: [POOL, repayAmount],
}),
value: "0x0",
};
const repayTx = {
to: POOL,
data: encodeFunctionData({
abi: [{ name: "repay", type: "function", inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "interestRateMode", type: "uint256" },
{ name: "onBehalfOf", type: "address" }
], outputs: [{ name: "", type: "uint256" }] }],
functionName: "repay",
args: [DAI, repayAmount, 2n, userAddress],
}),
value: "0x0",
};
// Get sessionId from list_wallets
await execute_batch_tx({ sessionId: "<from list_wallets>", calls: [approveTx, repayTx], feeToken: DAI });
No approval needed. Use maxUint256 to withdraw entire balance.
const withdrawTx = {
to: POOL,
data: encodeFunctionData({
abi: [{ name: "withdraw", type: "function", inputs: [
{ name: "asset", type: "address" },
{ name: "amount", type: "uint256" },
{ name: "to", type: "address" }
], outputs: [{ name: "", type: "uint256" }] }],
functionName: "withdraw",
args: [DAI, maxUint256, userAddress],
}),
value: "0x0",
};
// Get sessionId from list_wallets
await execute_tx({ sessionId: "<from list_wallets>", to: POOL, data: withdrawTx.data, value: "0x0", feeToken: DAI });
Use UiPoolDataProvider.getReservesData() — a single read call returning all reserves with live APYs, liquidity, and collateral parameters. No wallet or transaction needed.
| Network | UiPoolDataProvider | PoolAddressesProvider |
|---|---|---|
| Ethereum | 0xF028c2F4b19898718fD0F77b9b881CbfdAa5e8Bb | 0x02C3eA4e34C0cBd694D2adFa2c690EECbC1793eE |
| Gnosis | 0xF028c2F4b19898718fD0F77b9b881CbfdAa5e8Bb | 0xA98DaCB3fC964A6A0d2ce3B77294241585EAbA6d |
Canonical source: Spark Address Registry
liquidityRate is in ray units (1e27). A simple linear approximation (rate / 1e27 * 100) is accurate enough for display.
Use getUserAccountData to read position before/after operations:
import { createPublicClient, http } from "viem";
import { mainnet } from "viem/chains";
const client = createPublicClient({ chain: mainnet, transport: http() });
const [
totalCollateralBase,
totalDebtBase,
availableBorrowsBase,
currentLiquidationThreshold,
ltv,
healthFactor,
] = await client.readContract({
address: POOL,
abi: [{ name: "getUserAccountData", type: "function", stateMutability: "view",
inputs: [{ name: "user", type: "address" }],
outputs: [
{ name: "totalCollateralBase", type: "uint256" },
{ name: "totalDebtBase", type: "uint256" },
{ name: "availableBorrowsBase", type: "uint256" },
{ name: "currentLiquidationThreshold", type: "uint256" },
{ name: "ltv", type: "uint256" },
{ name: "healthFactor", type: "uint256" },
]
}],
functionName: "getUserAccountData",
args: [userAddress],
});
// healthFactor is in 1e18 units — safe when > 1.5e18, liquidation risk when < 1.1e18
const hfFormatted = Number(healthFactor) / 1e18;
Request permissions that cover the operations you need. Always include the fee token in spend permissions.
// Example: grant permissions for supply + borrow on Ethereum
await grant_permissions({
chainId: 1,
description: "SparkLend operations on Ethereum",
calls: [
{ to: DAI, signature: "approve(address,uint256)", label: "Approve DAI" },
{ to: POOL, signature: "supply(address,uint256,address,uint16)", label: "Supply to SparkLend" },
{ to: POOL, signature: "borrow(address,uint256,uint256,uint16,address)", label: "Borrow from SparkLend" },
{ to: POOL, signature: "repay(address,uint256,uint256,address)", label: "Repay SparkLend debt" },
{ to: POOL, signature: "withdraw(address,uint256,address)", label: "Withdraw from SparkLend" },
],
spend: [
{ token: DAI, limit: "1000000000000000000000", period: "day", label: "DAI spend" }, // 1000 DAI/day
],
expiry: 86400, // 24 hours (duration in seconds from now)
});
list_wallets (no params) — returns wallet address and all sessions with sessionId, description, chainId, permissionscreate_wallet() — chainId and description are required when granting permissions at creationsupported_gas_tokens({ chainId }) — chainId is required (get it from supported_chains)grant_permissions({ chainId, description, calls, spend, expiry }) — creates a new session. Include Pool calls + token spend limits (include fee token in spend). Suggest a description and confirm with the user before calling.list_wallets to find the right sessionId based on description/permissions for the operationcheck_permissions({ sessionId, to, feeToken }) before sending — data and value are optionalencodeFunctionDataexecute_batch_tx({ sessionId, calls, feeToken }) for approve+action, execute_tx({ sessionId, to, feeToken }) for borrow/withdrawgetUserAccountData on-chainrevoke_session({ sessionId }) — revokes a specific session, others remain active| Mistake | Fix |
|---|---|
Forgetting approve before supply/repay | Always batch approve+action with execute_batch_tx |
Using interest rate mode 1 (stable) | Always use 2 (variable); stable is deprecated |
healthFactor < 1e18 after borrow | Check availableBorrowsBase before borrowing |
| Wrong token decimals | DAI = 18, USDC/USDT = 6, WETH/WBTC = 18/8 — use parseUnits |
| Insufficient spend permission | Include both the asset AND the fee token in spend permissions |
Not scoping Pool address in calls | Specify to: POOL to keep permissions minimal |
| Using Aave Pool address instead of SparkLend | SparkLend has its own Pool contracts — do not mix with Aave addresses |