Executes Starknet DeFi operations including AVNU token swaps, DCA recurring buys, STRK staking, and lending/borrowing. Supports gasless transactions for agent workflows.
npx claudepluginhub keep-starknet-strange/starknet-agentic --plugin starknet-agentic-skillsThis skill is limited to using the following tools:
Execute DeFi operations on Starknet using avnu aggregator and native protocols.
Creates and manages Starknet wallets for AI agents: transfer tokens, check balances, manage session keys, deploy accounts, invoke contracts with native Account Abstraction.
Builds unsigned DeFi transactions from natural language intents across EVM chains like Ethereum, Arbitrum, Base, Optimism, Polygon. Supports send, swap, stake, lend on Aave, Uniswap, Lido, etc.
Aggregates 500+ DEXs for optimal token swaps across 20+ chains like Ethereum, Solana, Base, BSC. Quotes prices, executes trades, builds calldata with slippage and impact controls.
Share bugs, ideas, or general feedback.
Execute DeFi operations on Starknet using avnu aggregator and native protocols.
npm install starknet@^8.9.1 @avnu/avnu-sdk@^4.0.1
import { 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);
interface 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;
};
}
import { 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])
import { 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
},
},
},
});
import { 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,
});
import { 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,
});
import { 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
});
import { getUserStakingInfo } from "@avnu/avnu-sdk";
const userInfo = await getUserStakingInfo(TOKENS.STRK, account.address);
console.log("Staked:", userInfo.amount);
console.log("Unclaimed rewards:", userInfo.unclaimedRewards);
import { executeClaimRewards } from "@avnu/avnu-sdk";
// Claim and restake (compound)
await executeClaimRewards({
provider: account,
poolAddress: poolAddress,
restake: true,
});
import { 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,
});
import { 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 |
async 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;
}
}