Install any skill in seconds. Free to start, no credit card required.
Get Started Free →Execute DeFi operations on Starknet including token swaps via avnu aggregator, DCA recurring buys, STRK staking, and lending/borrowing. Supports gasless transactions.
.claude/skills/internet-court-starknet-defi/SKILL.md| Test case | Without → With | Effect | Δ tokens | Δ turns |
|---|---|---|---|---|
| case-03 | ✗→✓ | ▲ Improved | 91% | 0% |
| case-01 | ✗→✓ | ▲ Improved | 51% | 0% |
| case-02 | ✗→✓ | ▲ Improved | 34% | 0% |
| case-04 | ✗→✓ | ▲ Improved | 61% | 0% |
| case-05 | ✗→✓ | ▲ Improved | -5% | 0% |
Execute DeFi operations on Starknet using avnu aggregator and native protocols.
bashnpm install starknet@^8.9.1 @avnu/avnu-sdk@^4.0.1
typescriptimport { getQuotes, executeSwap, type QuoteRequest } from "@avnu/avnu-sdk"; import { Account, RpcProvider, ETransactionVersion } from "starknet"; const provider = new RpcProvider({ nodeUrl: process.env.STARKNET_RPC_URL }); // starknet.js v8: Account uses options object const account = new Account({ provider, address, signer: privateKey, transactionVersion: ETransactionVersion.V3, }); // Resolve token addresses via avnu SDK (or use MCP server's TokenService) import { fetchVerifiedTokenBySymbol } from '@avnu/avnu-sdk'; const eth = await fetchVerifiedTokenBySymbol('ETH'); const strk = await fetchVerifiedTokenBySymbol('STRK'); // SDK v4: getQuotes takes QuoteRequest object directly const quoteParams: QuoteRequest = { sellTokenAddress: eth.address, buyTokenAddress: strk.address, sellAmount: BigInt(10 ** 17), // 0.1 ETH takerAddress: account.address, }; const quotes = await getQuotes(quoteParams); const bestQuote = quotes[0]; // SDK v4: executeSwap takes single object param const result = await executeSwap({ provider: account, quote: bestQuote, slippage: 0.01, // 1% executeApprove: true, }); console.log("Tx:", result.transactionHash);
typescriptinterface Quote { quoteId: string; sellTokenAddress: string; buyTokenAddress: string; sellAmount: bigint; buyAmount: bigint; sellAmountInUsd: number; buyAmountInUsd: number; priceImpact: number; // In basis points (15 = 0.15%) gasFeesInUsd: number; routes: Array<{ name: string; // e.g., "Ekubo", "JediSwap" percent: number; // e.g., 0.8 = 80% }>; fee: { avnuFees: bigint; integratorFees: bigint; }; }
typescriptimport { quoteToCalls } from "@avnu/avnu-sdk"; const calls = await quoteToCalls({ quote: bestQuote, takerAddress: account.address, slippage: 0.01, includeApprove: true, }); // `calls` can be combined with other calls in account.execute([...calls, ...otherCalls])
typescriptimport { getQuotes, executeSwap } from "@avnu/avnu-sdk"; import { PaymasterRpc } from "starknet"; const quotes = await getQuotes(quoteParams); const bestQuote = quotes[0]; // SDK v4: Use PaymasterRpc from starknet.js // Mainnet: https://starknet.paymaster.avnu.fi // Sepolia: https://sepolia.paymaster.avnu.fi const paymaster = new PaymasterRpc({ nodeUrl: process.env.AVNU_PAYMASTER_URL || "https://starknet.paymaster.avnu.fi", }); const result = await executeSwap({ provider: account, quote: bestQuote, slippage: 0.01, executeApprove: true, paymaster: { active: true, provider: paymaster, params: { feeMode: { mode: "default", gasToken: "0x053c91253bc9682c04929ca02ed00b3e423f6710d2ee7e0d5ebb06f3ecf368a8", // USDC }, }, }, });
typescriptimport { executeCreateDca } from "@avnu/avnu-sdk"; import moment from "moment"; const dcaOrder = { sellTokenAddress: usdcAddress, buyTokenAddress: strkAddress, totalAmount: parseUnits("100", 6), // Total 100 USDC numberOfOrders: 10, // Split into 10 orders frequency: moment.duration(1, "day"), // moment.Duration object, not string startAt: Math.floor(Date.now() / 1000), }; const result = await executeCreateDca({ provider: account, order: dcaOrder, });
typescriptimport { getDcaOrders, executeCancelDca, DcaOrderStatus } from "@avnu/avnu-sdk"; const orders = await getDcaOrders({ traderAddress: account.address, status: DcaOrderStatus.OPEN, // Use enum, not string }); // Cancel an order await executeCancelDca({ provider: account, orderAddress: orders[0].orderAddress, });
typescriptimport { executeStake, getAvnuStakingInfo } from "@avnu/avnu-sdk"; // Get pool info const stakingInfo = await getAvnuStakingInfo(); // stakingInfo.pools[0] = { address, apy, tvl, token, minStake } const result = await executeStake({ provider: account, poolAddress: stakingInfo.pools[0].address, amount: parseUnits("100", 18), // 100 STRK });
typescriptimport { getUserStakingInfo } from "@avnu/avnu-sdk"; const userInfo = await getUserStakingInfo(TOKENS.STRK, account.address); console.log("Staked:", userInfo.amount); console.log("Unclaimed rewards:", userInfo.unclaimedRewards);
typescriptimport { executeClaimRewards } from "@avnu/avnu-sdk"; // Claim and restake (compound) await executeClaimRewards({ provider: account, poolAddress: poolAddress, restake: true, });
typescriptimport { executeInitiateUnstake, executeUnstake } from "@avnu/avnu-sdk"; // Step 1: Initiate (starts cooldown -- 21 days for STRK) await executeInitiateUnstake({ provider: account, poolAddress: poolAddress, amount: parseUnits("50", 18), }); // Step 2: Complete unstake (after cooldown period) await executeUnstake({ provider: account, poolAddress: poolAddress, });
typescriptimport { getPrices, fetchTokens, fetchVerifiedTokenBySymbol } from "@avnu/avnu-sdk"; // Get token by symbol const strk = await fetchVerifiedTokenBySymbol("STRK"); // Get prices for multiple tokens const prices = await getPrices([ethAddress, strkAddress, usdcAddress]); // prices = { "0x049d...": 3200.50, "0x047...": 1.23, ... } // Browse tokens with pagination const tokens = await fetchTokens({ page: 0, size: 20, tags: ["verified"] });
| Protocol | Operations | Notes | |----------|-----------|-------| | avnu | Swap aggregation, DCA, gasless | Best-price routing across all DEXs | | Ekubo | AMM, concentrated liquidity | Highest TVL on Starknet | | JediSwap | AMM, classic pools | V2 with concentrated liquidity | | zkLend | Lending, borrowing | Variable and stable rates | | Nostra | Lending, borrowing | Multi-asset pools |
| Variable | Purpose | Default | |----------|---------|---------| | STARKNET_RPC_URL | Starknet JSON-RPC endpoint | Required | | STARKNET_ACCOUNT_ADDRESS | Agent's account address | Required | | STARKNET_PRIVATE_KEY | Agent's signing key | Required | | AVNU_BASE_URL | avnu API base URL | https://starknet.api.avnu.fi | | AVNU_PAYMASTER_URL | avnu paymaster URL | https://starknet.paymaster.avnu.fi | | AVNU_API_KEY | Optional avnu integrator key | None |
| Network | API URL | Paymaster URL | |---------|---------|---------------| | Mainnet | https://starknet.api.avnu.fi | https://starknet.paymaster.avnu.fi | | Sepolia | https://sepolia.api.avnu.fi | https://sepolia.paymaster.avnu.fi |
typescriptasync function safeSwap(account, quote, slippage = 0.01) { try { return await executeSwap({ provider: account, quote, slippage, executeApprove: true, }); } catch (error) { if (error.message?.includes("INSUFFICIENT_BALANCE")) { throw new Error("Not enough tokens for swap"); } if (error.message?.includes("SLIPPAGE") || error.message?.includes("Insufficient tokens received")) { // Retry with higher slippage return await executeSwap({ provider: account, quote, slippage: slippage * 2, executeApprove: true, }); } if (error.message?.includes("QUOTE_EXPIRED")) { throw new Error("Quote expired. Please retry the operation."); } if (error.message?.includes("INSUFFICIENT_LIQUIDITY")) { throw new Error("Insufficient liquidity. Try a smaller amount."); } throw error; } }
| Case | Status | Duration (ms) | Turns | Tokens | Tool calls | ||||||||
|---|---|---|---|---|---|---|---|---|---|---|---|---|---|
| Without | With | Δ | Without | With | Δ | Without | With | Δ | Without | With | Δ | ||
case-03 | fail→pass | 12,350 | 8,925 | -28% | 1 | 1 | 0% | 2,509 | 4,791 | +91% | 0 | 0 | — |
case-01 | fail→pass | 13,393 | 7,959 | -41% | 1 | 1 | 0% | 3,024 | 4,562 | +51% | 0 | 0 | — |
case-02 | fail→pass | 15,229 | 8,814 | -42% | 1 | 1 | 0% | 3,519 | 4,705 | +34% | 0 | 0 | — |
case-04 | fail→pass | 13,819 | 8,491 | -39% | 1 | 1 | 0% | 2,933 | 4,721 | +61% | 0 | 0 | — |
case-05 | fail→pass | 19,238 | 6,857 | -64% | 1 | 1 | 0% | 4,283 | 4,074 | -5% | 0 | 0 | — |
case-06 | fail→pass | 17,100 | 6,016 | -65% | 1 | 1 | 0% | 3,542 | 4,086 | +15% | 0 | 0 | — |
case-07 | pass→pass | 15,006 | 7,008 | -53% | 1 | 1 | 0% | 2,849 | 4,338 | +52% | 0 | 0 | — |
case-08 | fail→pass | 10,324 | 4,607 | -55% | 1 | 1 | 0% | 1,896 | 3,644 | +92% | 0 | 0 | — |
case-09 | fail→pass | 15,452 | 5,506 | -64% | 1 | 1 | 0% | 3,126 | 4,033 | +29% | 0 | 0 | — |
case-10 | fail→pass | 12,602 | 4,258 | -66% | 1 | 1 | 0% | 2,990 | 3,687 | +23% | 0 | 0 | — |
case-11 | fail→pass | 14,073 | 6,015 | -57% | 1 | 1 | 0% | 3,121 | 3,982 | +28% | 0 | 0 | — |
case-12 | pass→pass | 14,256 | 10,138 | -29% | 1 | 1 | 0% | 3,003 | 5,234 | +74% | 0 | 0 | — |
case-13 | pass→pass | 15,995 | 14,263 | -11% | 1 | 1 | 0% | 3,262 | 5,776 | +77% | 0 | 0 | — |
case-14 | fail→pass | 8,371 | 2,469 | -71% | 1 | 1 | 0% | 1,757 | 3,205 | +82% | 0 | 0 | — |
case-15 | fail→pass | 14,754 | 2,995 | -80% | 1 | 1 | 0% | 2,962 | 3,295 | +11% | 0 | 0 | — |
case-16 | fail→pass | 5,607 | 2,734 | -51% | 1 | 1 | 0% | 1,061 | 3,366 | +217% | 0 | 0 | — |
case-17 | fail→pass | 13,117 | 3,775 | -71% | 1 | 1 | 0% | 2,662 | 3,560 | +34% | 0 | 0 | — |
case-18 | fail→pass | 9,457 | 3,321 | -65% | 1 | 1 | 0% | 1,780 | 3,487 | +96% | 0 | 0 | — |
case-19 | pass→pass | 6,489 | 1,841 | -72% | 1 | 1 | 0% | 1,161 | 3,101 | +167% | 0 | 0 | — |
case-20 | fail→pass | 15,541 | 4,365 | -72% | 1 | 1 | 0% | 2,860 | 3,635 | +27% | 0 | 0 | — |
case-21 | fail→pass | 9,356 | 2,054 | -78% | 1 | 1 | 0% | 1,721 | 3,053 | +77% | 0 | 0 | — |
case-22 | fail→pass | 13,210 | 7,437 | -44% | 1 | 1 | 0% | 2,631 | 4,419 | +68% | 0 | 0 | — |
case-23 | fail→fail | 14,653 | 12,938 | -12% | 1 | 1 | 0% | 3,342 | 5,960 | +78% | 0 | 0 | — |
case-24 | pass→pass | 7,687 | 7,419 | -3% | 1 | 1 | 0% | 939 | 3,764 | +301% | 0 | 0 | — |
case-25 | fail→fail | 14,365 | 8,752 | -39% | 1 | 1 | 0% | 3,282 | 4,582 | +40% | 0 | 0 | — |
DecimalAI ran this skill against gemini-3.6-flash twice over the same eval suite — once with the skill loaded and once without — and compared the two runs case by case. 25 cases were attempted. The headline lift of +72 percentage points is the difference between those two pass rates over the 25 comparable cases.
Without the skill loaded, the model failed this case. With it loaded, the same prompt on the same model passed. This is one improved case from the latest verified run; every case, including any that regressed, is in the table above.
Other measured skills in the registry, with their headline benchmark lift.