Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
57 commits
Select commit Hold shift + click to select a range
91ebb3c
feat: flywheel instant rewards
jourdanDunkley Jan 10, 2025
a864807
feat: permissioned minter for restricting morpho collateral mints
jourdanDunkley Jan 12, 2025
6c29094
fix: left placeholder for correct mint logic
jourdanDunkley Jan 12, 2025
824639e
update morpho market contract
antisaa Jan 21, 2025
8618e53
Merge branch 'development' into feat/morpho-collateral
rhlsthrm Jan 24, 2025
6ab2f49
feat: import
rhlsthrm Jan 27, 2025
2d2183b
feat: deploy
rhlsthrm Jan 27, 2025
2a3946c
fix: removed rewardsdelegate from inheritcance hierarcyh to fix sizin…
jourdanDunkley Jan 27, 2025
77e68f8
feat: deploy
rhlsthrm Jan 27, 2025
d17c905
fix: morpho collat deploy script
jourdanDunkley Jan 27, 2025
c7c8bc3
feat: deploy
rhlsthrm Jan 28, 2025
ef74508
pass morpho URD as param
jourdanDunkley Jan 29, 2025
2cbf875
feat: deploy
rhlsthrm Jan 29, 2025
bab70e9
Merge branch 'development' into feat/morpho-collateral
rhlsthrm Jan 29, 2025
e5ba697
delete
rhlsthrm Jan 29, 2025
509fe0c
feat: Morpho rewards distributor
jourdanDunkley Feb 3, 2025
760c2dd
Merge branch 'feat/morpho-collateral' of https://github.com/ionicprot…
jourdanDunkley Feb 3, 2025
a081def
made distributor dynamic
jourdanDunkley Feb 3, 2025
5a244f0
Merge branch 'development' of https://github.com/ionicprotocol/monore…
jourdanDunkley Feb 3, 2025
a1b95b1
new morpho deploy
jourdanDunkley Feb 3, 2025
9b0270d
checks for what markets are already added and what markets dont have …
jourdanDunkley Feb 3, 2025
b7470ea
deployment of mode staking strategies
jourdanDunkley Feb 3, 2025
663cb1e
finalized bribeRewards and rewardAccumulator deploys
jourdanDunkley Feb 4, 2025
83be7f9
bribe distributor deployments
jourdanDunkley Feb 4, 2025
88fa199
remove submodule
rhlsthrm Feb 4, 2025
f549997
fix: build
rhlsthrm Feb 4, 2025
d4c00de
fix: abi
rhlsthrm Feb 4, 2025
8d745bd
Merge branch 'development' into feat/morpho-collateral
rhlsthrm Apr 15, 2025
1002c24
Refactor and update ABI definitions in generated.ts
rhlsthrm Apr 15, 2025
ee2acd7
Add smUSDC asset and update market deployment for Seamless
rhlsthrm Apr 16, 2025
2476a8b
Merge branch 'development' into feat/morpho-collateral
rhlsthrm Apr 16, 2025
31d75d3
Add market setting task for Morpho Ionic and enhance market data hook
rhlsthrm Apr 17, 2025
759e163
Add smUSDC icon asset for UI
rhlsthrm Apr 17, 2025
2595289
Remove deprecated task for setting collateral factors in Morpho Ionic…
rhlsthrm Apr 17, 2025
a1ac887
Remove Morpho Ionic market definition from constants
rhlsthrm Apr 17, 2025
4268fcf
Update Node.js setup action in GitHub workflow
rhlsthrm Apr 17, 2025
3d3ae46
Update Yarn version from 4.7.0 to 4.9.1
rhlsthrm Apr 22, 2025
5fd2ebe
Refactor health factor hook usage in WithdrawTab component
rhlsthrm Apr 22, 2025
af68d66
Add new task for deploying Morpho market with enhanced parameters
jourdanDunkley Apr 27, 2025
db18973
Implement MorphoBribeDistributor deployment task and enhance market d…
jourdanDunkley Apr 27, 2025
42cdb14
Add Lisk network support in configuration and testing
jourdanDunkley May 1, 2025
9bef0b1
Enhance DevTesting and bribe management tasks
jourdanDunkley Jun 3, 2025
c8f1d63
Implement historical price management in Voter contract
jourdanDunkley Jun 3, 2025
66d915f
Refactor historical price functions in Voter contract
rhlsthrm Jun 3, 2025
d168c14
Implement new BribeRewards and RewardAccumulator contracts with enhan…
jourdanDunkley Jun 3, 2025
7f750d3
Update DevTesting and add checkRewards task for historical price mana…
jourdanDunkley Jun 3, 2025
cec4aae
Enhance voter deployment script to check and set market reward accumu…
jourdanDunkley Jun 4, 2025
9d3ab07
Implement new RewardAccumulator contracts and update deployment scripts
jourdanDunkley Jun 27, 2025
f3fc6fe
Enhance Voter contract and DevTesting for bribe management
jourdanDunkley Jul 1, 2025
e557404
Update Voter contract and deployment configurations
jourdanDunkley Jul 1, 2025
c5b8f28
Enhance Voter contract and DevTesting with new features
jourdanDunkley Jul 2, 2025
26c4370
Update Voter contract and deployment configurations
jourdanDunkley Jul 2, 2025
f018c95
Update DevTesting with new testRewardCycles function and adjust testD…
jourdanDunkley Jul 7, 2025
c8604f3
fixed merge conflicts
jourdanDunkley Jul 19, 2025
118be90
Enhance DevTesting and setBribeHistoricalPrices task
jourdanDunkley Aug 7, 2025
4a175d5
Update dependencies and refactor prune script
jourdanDunkley Sep 2, 2025
3723364
fixes
jourdanDunkley Dec 15, 2025
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
The table of contents is too big for display.
Diff view
Diff view
  •  
  •  
  •  
6 changes: 0 additions & 6 deletions .gitmodules
Original file line number Diff line number Diff line change
Expand Up @@ -22,9 +22,3 @@ url = https://github.com/gelatodigital/ops
[submodule "packages/contracts/lib/adrastia-periphery"]
path = packages/contracts/lib/adrastia-periphery
url = https://github.com/adrastia-oracle/adrastia-periphery
[submodule "packages/contracts/lib/devtools"]
path = packages/contracts/lib/devtools
url = https://github.com/LayerZero-Labs/devtools
[submodule "packages/contracts/lib/layerzero-v2"]
path = packages/contracts/lib/layerzero-v2
url = https://github.com/LayerZero-Labs/layerzero-v2
935 changes: 0 additions & 935 deletions .yarn/releases/yarn-4.7.0.cjs

This file was deleted.

948 changes: 948 additions & 0 deletions .yarn/releases/yarn-4.9.1.cjs

Large diffs are not rendered by default.

2 changes: 1 addition & 1 deletion .yarnrc.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@ enableGlobalCache: false

nodeLinker: node-modules

yarnPath: .yarn/releases/yarn-4.7.0.cjs
yarnPath: .yarn/releases/yarn-4.9.1.cjs
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
{
"name": "monorepo",
"packageManager": "yarn@4.7.0",
"packageManager": "yarn@4.9.1",
"private": true,
"workspaces": [
"packages/bots/liquidator",
Expand Down
3 changes: 2 additions & 1 deletion packages/bots/liquidator/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@
"dev": "HTTPS=true NODE_ENV=development ts-node src/run.ts",
"test": "NODE_ENV=test nyc ./node_modules/.bin/mocha --require ts-node/register ./src/test/**/**/**/**/*.test.ts",
"build": "rimraf build && tsc -p tsconfig.json",
"dev-pyth": "HTTPS=true NODE_ENV=development ts-node src/runPythLiquidator.ts"
"dev-pyth": "HTTPS=true NODE_ENV=development ts-node src/runPythLiquidator.ts",
"dev-all-borrowers": "HTTPS=true NODE_ENV=development ts-node src/runAllBorrowers.ts"
},
"repository": {
"type": "git",
Expand Down
8 changes: 4 additions & 4 deletions packages/bots/liquidator/src/justSubmit.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { Client, OpportunityParams } from "@pythnetwork/express-relay-evm-js";
import { ionicLiquidatorAbi } from "@ionicprotocol/sdk";
import { createWalletClient, encodeAbiParameters, encodeFunctionData, fallback, Hex, http } from "viem";
import { mode } from "viem/chains";
import { chainIdtoChain } from "@ionicprotocol/chains";
import { privateKeyToAccount } from "viem/accounts";

import config from "./config";
Expand All @@ -9,20 +9,20 @@
import { setUpSdk } from "./utils";
import { createIonicPublicClient } from "./utils/client";

(BigInt.prototype as any).toJSON = function () {

Check warning on line 12 in packages/bots/liquidator/src/justSubmit.ts

View workflow job for this annotation

GitHub Actions / lint-bots

Unexpected any. Specify a different type
return this.toString();
};
const account = privateKeyToAccount(config.adminPrivateKey as Hex);
const publicClient = createIonicPublicClient(mode, config.rpcUrls);
const publicClient = createIonicPublicClient(chainIdtoChain[config.chainId], config.rpcUrls);
const walletClient = createWalletClient({
account,
chain: mode,
chain: chainIdtoChain[config.chainId],
transport: fallback(config.rpcUrls.map((url) => http(url))),
});

(async function () {
const chainName: string = config.chainName;
const ionicSdk = setUpSdk(mode.id, publicClient, walletClient);
const ionicSdk = setUpSdk(config.chainId, publicClient, walletClient);
const ionicLiquidator = ionicSdk.contracts.IonicLiquidator.address as `0x${string}`;
const client: Client = new Client({ baseUrl: config.expressRelayEndpoint });
const liquidation = {
Expand Down Expand Up @@ -87,7 +87,7 @@
logger.info("Opportunity:", JSON.stringify(opportunity, null, 2));
try {
await client.submitOpportunity(opportunity);
console.info("Opportunity submitted successfully:", opportunity);

Check warning on line 90 in packages/bots/liquidator/src/justSubmit.ts

View workflow job for this annotation

GitHub Actions / lint-bots

Unexpected console statement
} catch (error) {
const errorMessage = error instanceof Error ? error.message : error;
console.error("Failed to submit opportunity:", {
Expand Down
174 changes: 174 additions & 0 deletions packages/bots/liquidator/src/runAllBorrowers.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,174 @@
import { createPublicClient, createWalletClient, fallback, Hex, http, parseEther, formatEther } from "viem";

Check failure on line 1 in packages/bots/liquidator/src/runAllBorrowers.ts

View workflow job for this annotation

GitHub Actions / lint-bots

Member 'formatEther' of the import declaration should be sorted alphabetically
import { privateKeyToAccount } from "viem/accounts";
import { chainIdtoChain } from "@ionicprotocol/chains";

import config from "./config";
import { logger } from "./logger";
import { setUpSdk } from "./utils";

// Define the start time as a Unix timestamp
const startTime = Math.floor(new Date().getTime() / 1000);

(BigInt.prototype as any).toJSON = function () {
return this.toString();
};

const account = privateKeyToAccount(config.adminPrivateKey as Hex);
const clientConfig = {
batch: { multicall: { wait: 16 } },
chain: chainIdtoChain[config.chainId],
transport: fallback(config.rpcUrls.map((url) => http(url))),
cacheTime: 4_000,
pollingInterval: 4_000,
};
const publicClient = createPublicClient(clientConfig);
const walletClient = createWalletClient({
account,
chain: chainIdtoChain[config.chainId],
transport: fallback(config.rpcUrls.map((url) => http(url))),
});

const HEALTH_CHECK_CONCURRENCY = parseInt(process.env.HEALTH_CHECK_CONCURRENCY || "12", 10);
const HEALTH_CHECK_RETRIES = parseInt(process.env.HEALTH_CHECK_RETRIES || "3", 10);
const HEALTH_CHECK_BASE_DELAY_MS = parseInt(process.env.HEALTH_CHECK_BASE_DELAY_MS || "300", 10);

async function sleep(ms: number) {
return new Promise((resolve) => setTimeout(resolve, ms));
}

async function withRetry<T>(fn: () => Promise<T>, label: string): Promise<T> {
let attempt = 0;
// Exponential backoff with jitter
while (true) {
try {
return await fn();
} catch (e: any) {
attempt++;
if (attempt > HEALTH_CHECK_RETRIES) {
logger.error(`Max retries reached for ${label}: ${e?.message || e}`);
throw e;
}
const delay = HEALTH_CHECK_BASE_DELAY_MS * Math.pow(2, attempt - 1) + Math.floor(Math.random() * 100);
logger.warn(
`Retrying ${label} (attempt ${attempt}/${HEALTH_CHECK_RETRIES}) in ${delay}ms due to: ${e?.message || e}`
);
await sleep(delay);
}
}
}

// Main function to fetch all borrowers without pagination
(async function runAllBorrowers() {
const chainName: string = config.chainName;
const ionicSdk = setUpSdk(config.chainId, publicClient, walletClient);

logger.info(`Target Chain: ${chainName} (${config.chainId})`);
logger.info(`Config for bot: ${JSON.stringify({ ...ionicSdk.chainLiquidationConfig, ...config })}`);

try {
const message = `
**runAllBorrowers Started**
- **Start Time**: ${new Date(startTime * 1000).toISOString()}
**----------------------------------------------------------------------------------------**
`;
logger.info(`${message}`);

// Get all active pools
const [, allPools] = await ionicSdk.contracts.PoolDirectory.read.getActivePools();
logger.info(`Found ${allPools.length} active pools`);

for (const pool of allPools) {
const { comptroller, name } = pool;
logger.info(`\n=== Processing Pool: ${name} (${comptroller}) ===`);

try {
// Get all borrowers for this comptroller using a different approach
const comptrollerInstance = ionicSdk.createComptroller(comptroller);

// Try to get all borrowers at once with a very large page size
const [, allBorrowers] = await comptrollerInstance.read.getPaginatedBorrowers([
BigInt(0),
BigInt(10000), // Very large page size to get all at once
]);

logger.info(`Total borrowers in ${name}: ${allBorrowers.length}`);

if (allBorrowers.length > 0) {
// Log first 50 borrowers
const sample = allBorrowers.slice(0, 50);
logger.info(`First ${sample.length} borrowers: ${sample.join(", ")}`);

if (allBorrowers.length > 50) {
logger.info(`... and ${allBorrowers.length - 50} more borrowers`);
}

// Check health factors for all borrowers (throttled + retries)
logger.info(`Checking health factors for all ${allBorrowers.length} borrowers...`);

let healthyCount = 0;
let unhealthyCount = 0;
let errorCount = 0;
const unhealthyList: Array<{ address: `0x${string}`; health: bigint }> = [];

let processed = 0;
for (let i = 0; i < allBorrowers.length; i += HEALTH_CHECK_CONCURRENCY) {
const chunk = allBorrowers.slice(i, i + HEALTH_CHECK_CONCURRENCY);
const results = await Promise.allSettled(
chunk.map((borrower, idx) =>
withRetry(async () => {
const health = await ionicSdk.contracts.PoolLens.read.getHealthFactor([borrower, comptroller]);
const globalIndex = i + idx + 1;
if (globalIndex <= 20 || health < parseEther("1.1")) {
// Log first 20 or any unhealthy-ish
logger.info(`[${globalIndex}/${allBorrowers.length}] ${borrower}: health=${health.toString()}`);
}
if (health < parseEther("1.0")) {
unhealthyCount++;
unhealthyList.push({ address: borrower as `0x${string}`, health });
} else {
healthyCount++;
}
}, `health(${name}:${borrower})`)
)
);

// Count errors without throwing the whole batch
for (const r of results) {
if (r.status === "rejected") errorCount++;
}
processed += chunk.length;
if (processed % 50 === 0 || processed === allBorrowers.length) {
logger.info(`Progress: ${processed}/${allBorrowers.length} borrowers processed in ${name}`);
}
}

logger.info(`\nHealth Summary for ${name}:`);
logger.info(`- Total borrowers: ${allBorrowers.length}`);
logger.info(`- Healthy (health >= 1.0): ${healthyCount}`);
logger.info(`- Unhealthy (health < 1.0): ${unhealthyCount}`);
logger.info(`- Errors: ${errorCount}`);
if (unhealthyList.length > 0) {
logger.info(`\nUnhealthy borrowers for ${name} (health < 1.0):`);
for (const u of unhealthyList) {
logger.info(`${u.address}, health=${formatEther(u.health)} ETH`);
}
}
} else {
logger.info(`No borrowers found in ${name}`);
}
} catch (error) {
logger.error(`Error processing pool ${name} (${comptroller}): ${error}`);
}
}
} catch (error) {
logger.error(`Error during borrower fetch process: ${error}`);
} finally {
const endMessage = `
**runAllBorrowers Ended**
- **Start Time**: ${new Date(startTime * 1000).toISOString()}
- **End Time**: ${new Date().toISOString()}
**----------------------------------------------------------------------------------------**
`;
logger.info(`${endMessage}`);
}
})();
8 changes: 4 additions & 4 deletions packages/bots/liquidator/src/runPythLiquidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
http,
type PublicClientConfig,
} from "viem";
import { mode } from "viem/chains";
import { chainIdtoChain } from "@ionicprotocol/chains";
import { privateKeyToAccount } from "viem/accounts";

import { sendDiscordNotification } from "./services/PERdiscord";
Expand All @@ -29,22 +29,22 @@ const startTime = Math.floor(new Date().getTime() / 1000);
const account = privateKeyToAccount(config.adminPrivateKey as Hex);
const clientConfig: PublicClientConfig = {
batch: { multicall: { wait: 16 } },
chain: mode,
chain: chainIdtoChain[config.chainId],
transport: fallback(config.rpcUrls.map((url) => http(url))),
cacheTime: 4_000,
pollingInterval: 4_000,
};
const publicClient = createPublicClient(clientConfig);
const walletClient = createWalletClient({
account,
chain: mode,
chain: chainIdtoChain[config.chainId],
transport: fallback(config.rpcUrls.map((url) => http(url))),
});

// Main function to handle liquidations
(async function runPythLiquidator() {
const chainName: string = config.chainName;
const ionicSdk = setUpSdk(mode.id, publicClient, walletClient);
const ionicSdk = setUpSdk(config.chainId, publicClient, walletClient);
const ionicLiquidator = ionicSdk.contracts.IonicLiquidator.address as `0x${string}`;
logger.info(`Target Liquidator Contract: ${ionicLiquidator}`);
logger.info(`Config for bot: ${JSON.stringify({ ...ionicSdk.chainLiquidationConfig, ...config })}`);
Expand Down
36 changes: 36 additions & 0 deletions packages/bots/liquidator/src/services/liquidator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,11 +26,47 @@ export class Liquidator {
options?: { blockNumber?: bigint }
): Promise<T[]> {
try {
// Preflight visibility: what does the PoolDirectory report?
try {
const [, allPools] = await this.sdk.contracts.PoolDirectory.read.getActivePools();
logger.info(`PoolDirectory returned ${allPools.length} active pools on chain ${this.sdk.chainId}`);
if (allPools.length > 0) {
const sample = allPools.slice(0, 10).map((p) => `${p.name}(${p.comptroller})`);
logger.info(
`First ${sample.length} pools: ${sample.join(", ")}${
allPools.length > sample.length ? ` ... (+${allPools.length - sample.length} more)` : ""
}`
);
}
} catch (e) {
logger.warn(`Unable to read active pools from PoolDirectory for visibility: ${e}`);
}

if (config.excludedComptrollers.length > 0) {
logger.info(
`Excluded comptrollers (${config.excludedComptrollers.length}): ${config.excludedComptrollers.join(",")}`
);
}

const [liquidatablePools, erroredPools] = await this.sdk.getPotentialLiquidations<T>(
config.excludedComptrollers as Address[],
botType,
options?.blockNumber
);

logger.info(
`SDK getPotentialLiquidations returned ${liquidatablePools.length} pools with liquidations; errored pools: ${erroredPools.length}`
);
if (liquidatablePools.length > 0) {
const perPool = liquidatablePools
.slice(0, 10)
.map((p) => `${p.comptroller} liquidations=${p.liquidations.length}`);
logger.info(
`First ${perPool.length} liquidatable pools: ${perPool.join(", ")}${
liquidatablePools.length > perPool.length ? ` ... (+${liquidatablePools.length - perPool.length} more)` : ""
}`
);
}
const filteredErroredPools = erroredPools.filter(
(pool) => !Object.values(EXCLUDED_ERROR_CODES).includes(pool.error.code)
);
Expand Down
22 changes: 20 additions & 2 deletions packages/chains/src/base/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@ export const msUSD = "0x526728DBc96689597F85ae4cd716d4f7fCcBAE9d";
export const ionicUSDC = "0x23479229e52Ab6aaD312D0B03DF9F33B46753B5e";
export const ionicWETH = "0x5A32099837D89E3a794a44fb131CBbAD41f87a8C";
export const mBASIS = "0x1C2757c1FeF1038428b5bEF062495ce94BBe92b2";
export const smUSDC = "0x616a4E1db48e22028f6bbf20444Cd3b8e3273738";

export const assets: SupportedAsset[] = [
{
Expand All @@ -54,7 +55,10 @@ export const assets: SupportedAsset[] = [
name: "Wrapped Ether",
decimals: 18,
oracle: OracleTypes.FixedNativePriceOracle,
extraDocs: wrappedAssetDocs(SupportedChains.base)
extraDocs: wrappedAssetDocs(SupportedChains.base),
initialSupplyCap: parseEther(String(1_500)).toString(),
initialBorrowCap: parseEther(String(1_200)).toString(),
initialCf: "0.85"
},
{
symbol: assetSymbols.USDC,
Expand All @@ -66,7 +70,10 @@ export const assets: SupportedAsset[] = [
aggregator: "0x4ba73879B0C073Db595aBE9Ba27104D83f024286",
feedBaseCurrency: ChainlinkFeedBaseCurrency.USD
},
extraDocs: defaultDocs("https://basescan.org", USDC)
extraDocs: defaultDocs("https://basescan.org", USDC),
initialSupplyCap: parseUnits(String(10_000_000_000), 6).toString(),
initialBorrowCap: parseUnits(String(8_000_000_000), 6).toString(),
initialCf: "0.85"
},
{
symbol: assetSymbols.wstETH,
Expand Down Expand Up @@ -480,6 +487,17 @@ export const assets: SupportedAsset[] = [
initialCf: "0.10",
initialSupplyCap: parseEther(String(99_000)).toString(),
initialBorrowCap: parseEther(String(99_000)).toString()
},
{
symbol: assetSymbols.smUSDC,
underlying: smUSDC,
name: "Seamless USDC Vault",
decimals: 18,
oracle: OracleTypes.ERC4626Oracle,
extraDocs: defaultDocs("https://basescan.org", smUSDC),
initialSupplyCap: parseEther(String(2_000)).toString(),
initialBorrowCap: "1",
initialCf: "0.80"
}
// DO NOT ADD TO MARKET UNLESS PROPER ORACLE IS DEPLOYED
// {
Expand Down
Loading
Loading