A bot to automate liquidations on the Ajna platform.
- Each instance of the keeper connects to exactly one chain using a single RPC endpoint. The same keeper instance may interact with multiple pools on that chain.
- Pool addresses must be explicitly configured.
- Each instance of the keeper may unlock only a single wallet using a JSON keystore file. As such, if running multiple keepers on the same chain, different accounts should be used for each keeper to avoid nonce conflicts.
- Kick, ArbTake, Bond Collection, Reward LP Collection, Settlement, Take (connect outside liquidity to Ajna Liquidations) - can all be enabled/disabled per pool through the provided config.
You must setup one bot per-chain, per-signer.
You'll need node and related tools (npm, yarn). This was developed with node v22 but should work with later versions.
Download node here: https://nodejs.org/en Downloading node also installs npm.
Install yarn and dependencies:
npm install --global yarn
yarn --frozen-lockfileNote: If you encounter dependency conflicts or version mismatches, try:
rm yarn.lock
yarn installCompile to generate types using TypeChain:
yarn compileFor production deployments, see the Production Setup Guide.
The production guide covers the recommended approach using hosted services:
- Hosted RPC setup (Alchemy/QuickNode) vs local nodes
- Hosted subgraph deployment (BuiltByMom fork + Goldsky) vs local Graph Node
- Verified contract addresses for major chains (Avalanche, Hemi, Base, Arbitrum)
- Multiple DEX integration options (1inch, Uniswap V3, SushiSwap, Curve)
- API rate limits and service tier recommendations
- Real-world configuration examples
- Production monitoring and troubleshooting
- See
example-avalanche-config.tsandexample-hemi-config.tsfor chain-specific examples.
The production approach is more reliable and easier to maintain than running everything locally.
Create a new config.ts file in the ajna-keeper/ folder and copy the contents from example-config.ts.
(Any RPC endpoint can be used. But, these instructions are for Alchemy.)
Go to your acount on Alchemy.
Make sure you have an app with your L2 network enabled.
Navigate to the Apps > Networks tab and copy the url under your network. It will look something like: https://avax-mainnet.g.alchemy.com/v2/asf...
Replace the ethRpcUrl in your config.ts file with the url you got from alchemy.
Create an account on Coingecko and go to the URL https://www.coingecko.com/en/developers/dashboard
Here you will click "Add New Key" to add a new key.
In your config.ts file replace coinGeckoApiKey with the key you just created.
In config.ts for the section ajna, you will need to provide addresses for all the ajna specific contracts. These addresses can be found here: https://faqs.ajna.finance/info/deployment-addresses-and-bridges
In config.ts you may need to provide an address for multicallAddress for your specific chain. These addresses can be found here https://www.multicall3.com/deployments
If you add multicallAddress, then you will also need to add multicallBlock which is the block that multicall was added.
In a different folder clone the ajna-finance repo. It is recommended that you checkout the develop branch so that you have the latests networks settings for L2s.
git clone https://github.com/ajna-finance/subgraph.git
cd subgraph
git checkout developUpdate your ajna-subgraph/.env file to contain your alchemy key.
ajna-subgraph/.env
ETH_RPC_URL=https://avax-mainnet.g.alchemy.com/v2/asf.....
ETH_NETWORK=avalanche:https://avax-mainnet.g.alchemy.com/v2/asf.....Follow the setup instructions in ajna-subgraph/README.
Keeper uses ethers Encrypted JSON Wallet, which is encrypted using a password.
The easiest way to create an encrypted JSON wallet is to use the create-keystore script provided in keeper:
Run the script with yarn create-keystore. Then follow the onscreen prompts.
Ensure that the generated wallet is saved in the directory specified by the keeperKeystore property in config.ts.
yarn start --config config.tsFor each desired chain:
- A JSON-RPC endpoint is needed to query pool data and submit transactions.
- A subgraph connection is needed to iterate through buckets/borrowers/liquidations within a configured pool.
- Funds used to pay gas on that chain.
- Funds (quote token) to post liquidation bonds in each pool configured to kick.
Starts a liquidation when a loan's threshold price exceeds the lowest utilized price in the pool by a configurable percentage.
When auction price drops a configurable percentage below a DEX price, swaps collateral for quote token using a DEX or DEX aggregator, repaying debt and earning profit for the taker. This usually requires contract deployment for either 1inch or individual DEX's like Uniswap V3 or SushiSwap. Please see contract deployment section below.
When auction price drops a configurable percentage below the highest price bucket, exchanges quote token in that bucket for collateral, earning a share of that bucket.
Note if keeper is configured to both take and arbTake, and prices are appropriate for both, the keeper will attempt to execute both strategies. Whichever transaction is included in a block first will "win", with the other strategy potentially reverting onchain. To conserve gas when using both, ensure one is configured at a more aggressive price than the other.
Collects liquidation bonds (which were used to kick loans) once they are fully claimable. Note: This does not settle auctions.
Redeems rewarded LP for either Quote or Collateral based on config. Note: This will only collect LP rewarded while the bot is running and will not collect deposits.
Automatically settles completed auctions to unlock kicker bonds and handle bad debt scenarios. Settlement is triggered when:
- Auctions have ended with remaining bad debt (collateral = 0, debt > 0)
- Kicker bonds are locked and preventing normal operations
- Auctions meet the configured minimum age requirement
Settlement processes auctions in multiple iterations if needed, settling debt against available buckets in the pool. The keeper can be configured to only settle auctions where the bot has bond rewards to claim, ensuring profitability.
Key Benefits:
- Automated bond recovery: Unlocks kicker bonds automatically when auctions complete
- Bad debt handling: Processes auctions with remaining debt that need settlement
- Reactive operation: Triggers settlement when bond collection or LP collection fails due to locked bonds
- Configurable timing: Respects minimum auction age before attempting settlement
Settlement Configuration:
enabled- Enable/disable settlement for this poolminAuctionAge- Minimum time (seconds) to wait before settling an auctionmaxBucketDepth- Number of buckets to process per settlement transactionmaxIterations- Maximum settlement iterations per auctioncheckBotIncentive- 'true' means only settle if bot is the kicker with bond rewards, 'false' means you are altruistically protecting the pool.
Settlement integrates seamlessly with other keeper operations - when bond collection or LP reward collection fails due to locked bonds, the keeper automatically attempts settlement before retrying the operation.
While *.json config files are supported, it is recommended to use *.ts config files so that you get the benefits of type checking.
See example-config.ts for reference.
- coingecko - using their simple price API
- fixed - hardcoded number, useful for stable pools
- pool - can use lup or htp
If the price source only has quote token priced in collateral, you may add "invert": true to price config to invert the configured price.
The keeper supports four DEX integration approaches for external takes and LP reward swapping:
| DEX Integration | External Takes | LP Rewards | Contract Required | Best For |
|---|---|---|---|---|
| 1inch | ✅ | ✅ | Yes (Single) | Major chains (Ethereum, Avalanche, Base, Arbitrum) |
| Uniswap V3 | ✅ | ✅ | Yes (Factory) | All chains with Uniswap V3 |
| SushiSwap | ✅ | ✅ | Yes (Factory) | Chains with SushiSwap V3 |
| Curve | ✅ | ✅ | Yes (Factory) | Chains with Curve pools (stablecoin/crypto pairs) |
To enable 1inch swaps, you need to set up environment variables and add specific fields to config.ts. Also be sure to set delayBetweenActions to 1 second or greater to avoid 1inch API rate limiting.
Create a .env file in your project root with:
ONEINCH_API=https://api.1inch.dev/swap/v6.0
ONEINCH_API_KEY=[your-1inch-api-key-here]
A 1inch API key may be obtained from their developer portal.
External takes connect Ajna liquidation auctions to external DEX liquidity with Atomic Swaps. This requires deploying smart contracts to atomically take collateral and swap it.
The defaultFeeTier values in your router overrides get compiled into deployed smart contracts and cannot be changed without redeploying. This directly impacts external take profitability.
Fee Tier Value → Percentage → Common Use:
500= 0.05% = 5 basis points (stablecoins)3000= 0.3% = 30 basis points (most common)10000= 1.0% = 100 basis points (exotic pairs)
External Takes vs Post-Auction Swaps:
For External Takes (Time-Sensitive):
- Fee tier is permanently set at contract deployment time
- Uses
universalRouterOverrides.defaultFeeTierorsushiswapRouterOverrides.defaultFeeTier - Changing requires full contract redeployment
- Choose carefully based on your most valuable/frequent liquidation pairs
For Post-Auction LP Rewards (Flexible):
- Can override per pool using
fee: FeeAmount.MEDIUMinrewardAction - Falls back to contract's default if no override specified
- Flexible and changeable without redeployment
Before running deployment scripts, research liquidity for ALL your configured pools:
Step 1: List all token pairs from your pools
Step 2: Check Uniswap Info or SushiSwap Analytics for EACH pair's fee tier liquidity
Step 3: Choose the fee tier that optimizes for your highest-value pairs
Step 4: Set defaultFeeTier in config
Step 5: Deploy contracts (fee tier is now permanent)
Example research process:
Pools: USDC/WETH (high value), DAI/USDC (medium), RARE/WETH (low)
Research Results:
- USDC/WETH: 500 tier has $50M TVL, 3000 tier has $200M TVL
- DAI/USDC: 500 tier has $100M TVL, 3000 tier has $30M TVL
- RARE/WETH: Only exists in 10000 tier
Decision: Use defaultFeeTier: 3000
Rationale: Optimizes highest-value pair (USDC/WETH)
Plan: Override DAI/USDC in LP rewards, RARE/WETH gets suboptimal external takes
Option A: 1inch Integration (Major Chains)
- For chains with 1inch support (Ethereum, Avalanche, Base, Arbitrum)
- Single contract deployment
- Uses 1inch aggregator for best pricing
# Compile contracts first
yarn compile
# Deploy 1inch connector contract
yarn ts-node scripts/query-1inch.ts --config your-config.ts --action deploy
# Update your config with the deployed address
# keeperTaker: '0x[deployed-address]'Option B: Factory System (Multi-DEX Chains)
- For chains with Uniswap V3 and/or SushiSwap V3 and/or Curve
- Multi-DEX factory pattern supporting multiple DEXs
- Direct DEX integration via router contracts
# Compile contracts first
yarn compile
# Deploy factory system
yarn ts-node scripts/deploy-factory-system.ts your-config.ts
# Update your config with deployed addresses:
# keeperTakerFactory: '0x[factory-address]'
# takerContracts: { 'UniswapV3': '0x[taker-address]', 'SushiSwap': '0x[taker-address]' }Option C: No External Takes
- Skip contract deployment
- Use arbTake and settlement only
- Still supports LP reward swapping (no contracts needed for LP rewards)
Note: LP reward swapping works with all approaches and doesn't require contracts for Uniswap V3 or SushiSwap (only 1inch requires contracts for LP rewards).
External takes require contract deployment and specific configuration:
IMPORTANT: 1inch contract deployment is now required even for LP reward swaps.
Contract Deployment:
yarn ts-node scripts/query-1inch.ts --config your-config.ts --action deployConfig.ts Setup:
const config: KeeperConfig = {
// Required for 1inch external takes AND LP rewards
keeperTaker: '0x[deployed-address]',
oneInchRouters: {
1: '0x1111111254EEB25477B68fb85Ed929f73A960582', // Ethereum
43114: '0x111111125421ca6dc452d289314280a0f8842a65', // Avalanche
8453: '0x1111111254EEB25477B68fb85Ed929f73A960582', // Base
},
pools: [{
take: {
liquiditySource: LiquiditySource.ONEINCH,
marketPriceFactor: 0.98, // Take when auction < market * 0.98
}
}]
}Important: 1inch Fee Tier Considerations 1inch automatically routes through optimal fee tiers, so you don't need to worry about fee tier selection for 1inch integration. The API handles routing optimization.
Contract Deployment:
yarn ts-node scripts/deploy-factory-system.ts your-config.tsConfig.ts Setup:
const config: KeeperConfig = {
// Required for Uniswap V3 external takes
keeperTakerFactory: '0x[factory-address]',
takerContracts: {
'UniswapV3': '0x[taker-address]'
},
universalRouterOverrides: {
universalRouterAddress: '0x533c7A53389e0538AB6aE1D7798D6C1213eAc28B',
wethAddress: '0x4200000000000000000000000000000000000006',
permit2Address: '0xB952578f3520EE8Ea45b7914994dcf4702cEe578',
poolFactoryAddress: '0x346239972d1fa486FC4a521031BC81bFB7D6e8a4',
quoterV2Address: '0xcBa55304013187D49d4012F4d7e4B63a04405cd5',
defaultFeeTier: 3000, // ← COMPILED INTO CONTRACT: All external takes use 0.3%
defaultSlippage: 0.5,
},
pools: [{
take: {
liquiditySource: LiquiditySource.UNISWAPV3,
marketPriceFactor: 0.99, // Take when auction < market * 0.99
}
}]
}Before running in production, manually verify which fee tier has the highest liquidity for your token pairs. The Universal Router will ONLY use the specific fee tier you configure (500 = 0.05%, 3000 = 0.3%, 10000 = 1%) - it does not automatically select the optimal route.
To check liquidity:
- Visit Uniswap Info for your network
- Search for your token pair (e.g., USDC/WETH)
- Compare TVL across different fee tiers
- Set
defaultFeeTierto the most liquid option - Monitor and update as liquidity shifts over time
Low-liquidity pools can cause swap failures or poor pricing that impacts liquidation profitability.
Contract Deployment:
yarn ts-node scripts/deploy-factory-system.ts your-config.tsConfig.ts Setup:
const config: KeeperConfig = {
// Required for SushiSwap external takes
keeperTakerFactory: '0x[factory-address]',
takerContracts: {
'SushiSwap': '0x[taker-address]'
},
sushiswapRouterOverrides: {
swapRouterAddress: '0x33d91116e0370970444B0281AB117e161fEbFcdD', //addresses for Hemi Chain
quoterV2Address: '0x1400feFD6F9b897970f00Df6237Ff2B8b27Dc82C',
factoryAddress: '0xCdBCd51a5E8728E0AF4895ce5771b7d17fF71959',
wethAddress: '0x4200000000000000000000000000000000000006',
defaultFeeTier: 500, // ← COMPILED INTO CONTRACT: All external takes use 0.05%
defaultSlippage: 10.0,
},
pools: [{
take: {
liquiditySource: LiquiditySource.SUSHISWAP,
marketPriceFactor: 0.99, // Take when auction < market * 0.99
}
}]
}SushiSwap routing requires manual fee tier selection. The router will only use the defaultFeeTier you specify (typically 500 = 0.05%, 3000 = 0.3%) - it does not find alternative routes automatically.
To verify optimal pools:
- Check SushiSwap Analytics for your network
- Compare liquidity across fee tiers for your token pairs
- Set
defaultFeeTierto the highest liquidity option - Test with small amounts before production deployment
- Adjust configuration as market conditions change
Using low-liquidity pools may result in failed swaps or unfavorable pricing.
Contract Deployment:
yarn ts-node scripts/deploy-factory-system.ts your-config.tsConfig.ts Setup:
const config: KeeperConfig = {
// Required for Curve external takes
keeperTakerFactory: '0x[factory-address]',
takerContracts: {
'Curve': '0x[curve-taker-address]'
},
curveRouterOverrides: {
poolConfigs: {
// Stablecoin pools (use STABLE pool type)
'usdc-usdt': {
address: '0x[CURVE_STABLE_POOL_ADDRESS]',
poolType: CurvePoolType.STABLE
},
// Crypto pools (use CRYPTO pool type)
'weth-wbtc': {
address: '0x[CURVE_CRYPTO_POOL_ADDRESS]',
poolType: CurvePoolType.CRYPTO
}
},
defaultSlippage: 1.0,
wethAddress: '0x4200000000000000000000000000000000000006',
},
// Required: Token symbol to address mapping
tokenAddresses: {
weth: '0x4200000000000000000000000000000000000006',
usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913',
usdt: '0x[USDT_ADDRESS]',
wbtc: '0x[WBTC_ADDRESS]',
},
pools: [{
take: {
liquiditySource: LiquiditySource.CURVE,
marketPriceFactor: 0.99, // Take when auction < market * 0.99
}
}]
}Step 1: Find Curve Pool Addresses
- Visit Curve.fi or use block explorers to find pool addresses for your network
- Look for pools containing your desired token pairs (e.g., 3Pool for USDC/USDT/DAI)
- Note: One pool address can serve multiple token pairs
Step 2: Determine Pool Type
- STABLE pools: Stablecoin pools (USDC/DAI/USDT) - use
CurvePoolType.STABLE - CRYPTO pools: Volatile asset pools (ETH/BTC/tricrypto) - use
CurvePoolType.CRYPTO - Check pool contract on block explorer: STABLE pools use
int128indices, CRYPTO pools useuint256
Step 3: Configure Token Address Mapping
tokenAddresses: {
usdc: '0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913', // Must match exact addresses
dai: '0x[DAI_ADDRESS]',
usdt: '0x[USDT_ADDRESS]',
weth: '0x4200000000000000000000000000000000000006',
}Step 4: Set Up Pool Configurations
curveRouterOverrides: {
poolConfigs: {
// Use token symbols from tokenAddresses above
'usdc-dai': {
address: '0x[3POOL_ADDRESS]', // Same pool can serve multiple pairs
poolType: CurvePoolType.STABLE
},
'usdc-usdt': {
address: '0x[3POOL_ADDRESS]', // Same address if tokens are in same pool
poolType: CurvePoolType.STABLE
}
},
defaultSlippage: 1.0, // 1% for stable, 2-4% for crypto pairs
wethAddress: '0x4200000000000000000000000000000000000006',
}The keeper automatically detects your configuration:
- Single: Uses existing 1inch integration (
src/take.ts) - Factory: Uses multi-DEX system (
src/take-factory.ts) - None: ArbTake and settlement only
No manual selection needed - the bot chooses based on your config.
Major Chain Example (1inch):
// example-avalanche-config.ts shows 1inch external takes
const config: KeeperConfig = {
keeperTaker: '0x[deployed-1inch-contract]',
oneInchRouters: { 43114: '0x111111125421ca6dc452d289314280a0f8842a65' },
pools: [{
take: {
liquiditySource: LiquiditySource.ONEINCH,
marketPriceFactor: 0.98
}
}]
}Multi-DEX Chain Example (Factory):
// hemi-config.ts shows factory external takes
const config: KeeperConfig = {
keeperTakerFactory: '0x[factory-address]',
takerContracts: {
'UniswapV3': '0x[taker-address]',
'SushiSwap': '0x[taker-address]'
},
universalRouterOverrides: {
defaultFeeTier: 3000, // 0.3% compiled into Uniswap contract
/* other addresses */
},
sushiswapRouterOverrides: {
defaultFeeTier: 3000, // 0.3% compiled into SushiSwap contract
/* other addresses */
},
pools: [{
take: {
liquiditySource: LiquiditySource.SUSHISWAP, // or UNISWAPV3
marketPriceFactor: 0.99
}
}]
}See example-avalanche-config.ts, example-hemi-config.ts, for complete examples.
The following sections provide comprehensive examples for configuring LP reward swapping:
IMPORTANT: 1inch LP reward swaps now require smart contract deployment.
# Deploy 1inch contract first (REQUIRED)
yarn ts-node scripts/query-1inch.ts --config your-config.ts --action deployEdit config.ts to include these fields:
oneInchRouters:
A dictionary of 1inch router addresses for each chain ID you want to support.
- Format:
{ [chainId]: "router-address" } - Example:
oneInchRouters: {
1: "0x1111111254EEB25477B68fb85Ed929f73A960582", // Ethereum Mainnet
8453: "0x1111111254EEB25477B68fb85Ed929f73A960582", // Base
43114: "0x1111111254EEB25477B68fb85Ed929f73A960582" // Avalanche
},
tokenAddresses:
A dictionary of token addresses for swaps (required for Avalanche, optional otherwise).
- Format:
{ [tokenName]: "token-address" } - Example:
tokenAddresses: {
weth: "0x4200000000000000000000000000000000000006", // WETH on Base
usdc: "0x833589fCD6eDb6E08f4c7C32D4f71b54bdA02913", // USDC on Base
avax: "0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE" // Native AVAX
},
connectorTokens (Optional):
An array of token addresses used as intermediate connectors in 1inch swap routes. These tokens can facilitate multi-hop trades to optimize the swap path between the input and output tokens.
- Format:
Array<string> - Example:
connectorTokens: [
"0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48", // USDC on Ethereum
"0x6B175474E89094C44Da98b954EedeAC495271d0F" // DAI on Ethereum
],
pools.collectLpReward.rewardAction:
LP in buckets can be reedemed for quote token and/or collateral, depending on what the bucket holds at time of redemption. redeemFirst controls the redemption strategy, favoring either quote token (most situations) or collateral (useful in shorting pools). To defer redeeming the second token, it's minAmount can be set to a sufficiently high value that manually swapping tokens on an exchange becomes practical.
Separate reward actions may be assigned to quote token and collateral, allowing tokens to be swapped out as desired. For pools where you want to swap rewards with 1inch, set dexProvider: PostAuctionDex.ONEINCH in the rewardAction.
- Example: Volatile-to-volatile pool, swap both tokens for stables
pools: [
{
name: "wstETH / WETH",
address: "0x63a366fc5976ff72999c89f69366f388b7d233e8",
...
collectLpReward: {
redeemFirst: TokenToCollect.QUOTE, // favor redeeming LP for WETH before redeeming for wstETH
minAmountQuote: 0.001, // don't redeem LP for dust amount of WETH
minAmountCollateral: 0.005, // ensure we're redeeming enough to cover swapping fees
rewardActionQuote: {
action: RewardActionLabel.EXCHANGE,
address: "0x4200000000000000000000000000000000000006", // Token to swap (WETH)
targetToken: "DAI", // Desired token
slippage: 1, // Slippage percentage (0-100)
dexProvider: PostAuctionDex.ONEINCH // Use enum
}
rewardActionCollateral: {
action: RewardActionLabel.EXCHANGE,
address: "0xc1CBa3fCea344f92D9239c08C0568f6F2F0ee452", // Token to swap (wstETH)
targetToken: "DAI", // Desired token
slippage: 1, // Slippage percentage (0-100)
dexProvider: PostAuctionDex.ONEINCH // Use enum
},
},
}
],
- Example: Stablecoin pool, swap collateral for quote token
pools: [
{
name: 'savUSD / USDC',
address: '0x936e0fdec18d4dc5055b3e091fa063bc75d6215c',
...
collectLpReward: {
redeemFirst: TokenToCollect.QUOTE,
minAmountQuote: 0.01, // don't redeem LP for less than a penny
minAmountCollateral: 0.05, // don't redeem LP for less than what it may cost to swap collateral for USDC
rewardActionCollateral: {
action: RewardActionLabel.EXCHANGE,
address: "0x06d47F3fb376649c3A9Dafe069B3D6E35572219E", // Token to swap (savUSD)
targetToken: "usdc", // Target token (USDC)
slippage: 1, // Slippage percentage (0-100)
dexProvider: PostAuctionDex.ONEINCH // Use enum
},
},
}
],
- Example: Shorting pool, no automated swapping
pools: [
{
name: "DAI / wSOL",
address: "0x63a366fc5976ff72999c89f69366f388b7d233e8",
...
collectLpReward: {
redeemFirst: TokenToCollect.COLLATERAL, // favor redeeming LP for DAI
minAmountQuote: 200, // don't exchange LP for an amount of wSOL not worth manually swapping
minAmountCollateral: 0.15, // don't redeem LP for less than transaction fees
},
}
],
- Contract deployment is required for 1inch LP reward swaps
- If
dexProvider: PostAuctionDex.ONEINCHbutkeeperTakeris missing, the script will fail. - Ensure the
.envfile is loaded (viadotenv/config) in your project.
Edit config.ts to include these optional fields:
universalRouterOverrides:
Required for Uniswap V3 swaps. Provides addresses for Universal Router integration.
- Format:
universalRouterOverrides: {
universalRouterAddress: "0x533c7A53389e0538AB6aE1D7798D6C1213eAc28B",
wethAddress: "0x4200000000000000000000000000000000000006",
permit2Address: "0xB952578f3520EE8Ea45b7914994dcf4702cEe578",
poolFactoryAddress: "0x346239972d1fa486FC4a521031BC81bFB7D6e8a4",
defaultFeeTier: 3000,
defaultSlippage: 0.5,
}
tokenAddresses (Optional):
Useful for specifying target tokens (e.g., WETH) if not using universalRouterOverrides.
- Example:
tokenAddresses: {
weth: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2", // WETH on Ethereum
usdc: "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48" // USDC on Ethereum
},
pools.collectLpReward.rewardAction:
For pools where you want to swap rewards with Uniswap V3, set dexProvider: PostAuctionDex.UNISWAP_V3 and optionally add a fee.
- Format:
{
name: "Your Pool Name",
address: "0xpoolAddress",
// Other pool settings...
collectLpReward: {
redeemFirst: "QUOTE", // or "COLLATERAL"
minAmount: 0.001,
rewardAction: {
action: "EXCHANGE",
address: "0xtokenAddress", // Token to swap
targetToken: "weth", // Target token (e.g., "weth", "usdc")
slippage: 1, // Slippage (ignored for Uniswap)
dexProvider: PostAuctionDex.UNISWAP_V3, // Use enum
fee: 3000 // Fee tier (500, 3000, 10000)
}
}
}
- Example:
pools: [
{
name: "WETH / USDC",
address: "0x0b17159f2486f669a1f930926638008e2ccb4287",
collectLpReward: {
redeemFirst: "COLLATERAL",
minAmount: 0.001,
rewardActionCollateral: {
action: RewardActionLabel.EXCHANGE,
address: "0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2",
targetToken: "usdc",
slippage: 1,
dexProvider: PostAuctionDex.UNISWAP_V3, // Use enum
fee: FeeAmount.MEDIUM // Can use different fee tier than external takes!
}
}
}
],
Note: LP reward swaps can use different fee tiers than external takes by specifying fee: FeeAmount.LOW (500), fee: FeeAmount.MEDIUM (3000), or fee: FeeAmount.HIGH (10000).
For SushiSwap integration, add the sushiswapRouterOverrides configuration:
sushiswapRouterOverrides:
Required for SushiSwap swaps. Provides addresses for SushiSwap router integration.
- Format:
sushiswapRouterOverrides: {
swapRouterAddress: "0x33d91116e0370970444B0281AB117e161fEbFcdD",
quoterV2Address: "0x1400feFD6F9b897970f00Df6237Ff2B8b27Dc82C",
factoryAddress: "0xCdBCd51a5E8728E0AF4895ce5771b7d17fF71959",
wethAddress: "0x4200000000000000000000000000000000000006",
defaultFeeTier: 500,
defaultSlippage: 10.0,
}
- Example:
pools: [
{
name: "USD_T1 / USD_T2",
address: "0x600ca6e0b5cf41e3e4b4242a5b170f3b02ce3da7",
collectLpReward: {
redeemFirst: "COLLATERAL",
minAmount: 0.001,
rewardActionCollateral: {
action: RewardActionLabel.EXCHANGE,
address: "0x1f0d51a052aa79527fffaf3108fb4440d3f53ce6",
targetToken: "usd_t2",
slippage: 10,
dexProvider: PostAuctionDex.SUSHISWAP, // SushiSwap option
fee: FeeAmount.LOW // Can use different fee tier than external takes!
}
}
}
],
Note: Like Uniswap V3, LP reward swaps can use different fee tiers than external takes for optimal routing.
feeis the fee tier (e.g.,500for 0.05%,3000for 0.3%,10000for 1%).slippageis respected for all DEX providers.- If
targetTokenisn't WETH, ensure it matches the configured WETH address.
Follow instructions for Installation and Prequisites. Then get your alchemy API token and your Goingecko API token.
Add your alchemy API key and coingecko API key to .env
ALCHEMY_API_KEY="<api_key>"
COINGECKO_API_KEY="<api_key>"Note: You will need to enable mainnet in Alchemy since hardhat queries from mainnet.
yarn unit-testsIn one terminal run:
npx hardhat nodeIn a second terminal run:
yarn integration-testsUser assumes all risk of data presented and transactions placed by this keeper; see license for more details.