From 4266fc4c887c1d63d30055d81c1ece2e5cee21cf Mon Sep 17 00:00:00 2001 From: saurabh-7797 Date: Fri, 10 Oct 2025 15:13:48 +0530 Subject: [PATCH] Add SURF Morpho Rebalancer adapter - Track initial deposit amounts from all vaults - Support Base chain with 112 vaults discovered - Total volume: 1.04M USDC (613 rebalances + 43 withdrawals + 4.59K initial deposits) --- dexs/surf-morpho-rebalancer/index.ts | 414 ++++++++++++++++++ .../user-vault-tracker.ts | 328 ++++++++++++++ 2 files changed, 742 insertions(+) create mode 100644 dexs/surf-morpho-rebalancer/index.ts create mode 100644 dexs/surf-morpho-rebalancer/user-vault-tracker.ts diff --git a/dexs/surf-morpho-rebalancer/index.ts b/dexs/surf-morpho-rebalancer/index.ts new file mode 100644 index 0000000000..5dbbe1f81f --- /dev/null +++ b/dexs/surf-morpho-rebalancer/index.ts @@ -0,0 +1,414 @@ +import type { FetchOptions, FetchResultV2, FetchV2, SimpleAdapter } from "../../adapters/types"; +import { CHAIN } from "../../helpers/chains"; +import ADDRESSES from '../../helpers/coreAssets.json' +import { UserVaultTracker, ExtendedVaultInfo } from './user-vault-tracker'; + +// Admin address that performs rebalancing +const ADMIN_ADDRESS = "0xEeEE7d713aDf6f408dd3637987191B35E3A872b0"; + +// USDC token address for different chains +const USDC_ADDRESSES = { + [CHAIN.ETHEREUM]: ADDRESSES.ethereum.USDC, + [CHAIN.ARBITRUM]: ADDRESSES.arbitrum.USDC, + [CHAIN.BASE]: ADDRESSES.base.USDC, + [CHAIN.POLYGON]: ADDRESSES.polygon.USDC, + [CHAIN.OPTIMISM]: ADDRESSES.optimism.USDC, + [CHAIN.BSC]: ADDRESSES.bsc.USDC, +}; + +// Core Event ABIs - only the events actually used in your project +const REBALANCED_EVENT = 'event Rebalanced(address indexed fromVault, address indexed toVault, uint256 amount)'; +const WITHDRAWAL_EVENT = 'event Withdrawal(address indexed vault, address indexed recipient, uint256 amount)'; +const DEPOSIT_EVENT = 'event Deposit(address indexed vault, address indexed depositor, uint256 amount)'; + +// Dynamic vault discovery using UserVaultTracker + +const fetch: FetchV2 = async (options: FetchOptions): Promise => { + const { chain, getLogs, createBalances, api } = options; + const dailyVolume = createBalances(); + const totalVolume = createBalances(); + + // Get USDC address for the current chain + const usdcAddress = USDC_ADDRESSES[chain]; + if (!usdcAddress) { + console.warn(`USDC address not found for chain: ${chain}`); + return { dailyVolume, totalVolume }; + } + + try { + // Only process Base chain, skip others + if (chain !== CHAIN.BASE) { + console.log(`šŸ“Š Skipping chain: ${chain} (only Base chain supported)`); + return { dailyVolume, totalVolume }; + } + + console.log(`šŸ” Starting core volume tracking for chain: ${chain}`); + + // Step 1: Get vault addresses dynamically using UserVaultTracker + let vaultAddresses: string[] = []; + + console.log(`šŸ­ Discovering vaults using UserVaultTracker...`); + const vaultTracker = new UserVaultTracker(chain); + const allVaults = await vaultTracker.getAllUserVaults(); + vaultAddresses = allVaults.map(vault => vault.vaultAddress); + console.log(`šŸ“Š Discovered ${vaultAddresses.length} vault addresses dynamically`); + + // Step 2: Get blocks for both requested period and total history + console.log(`šŸ“… Requested time range: ${new Date(options.fromTimestamp * 1000).toISOString()} to ${new Date(options.toTimestamp * 1000).toISOString()}`); + + // Get block numbers for requested daily time range + const dailyFromBlock = await options.getFromBlock(); + const dailyToBlock = await options.getToBlock(); + console.log(`šŸ”¢ Daily blocks: ${dailyFromBlock} to ${dailyToBlock}`); + + // Use extended time range from deployment date (Aug-30-2025) to current date for total volume + const deploymentDate = new Date('2025-08-30T12:52:57Z'); + const currentDate = new Date(); + const extendedFromTimestamp = Math.floor(deploymentDate.getTime() / 1000); + const extendedToTimestamp = Math.floor(currentDate.getTime() / 1000); + + console.log(`šŸ“… TOTAL HISTORY time range: ${deploymentDate.toISOString()} to ${currentDate.toISOString()}`); + + // Get block numbers for extended time range (total history) + const extendedFromBlock = await options.getBlock(extendedFromTimestamp, chain, {} as any); + const extendedToBlock = await options.getBlock(extendedToTimestamp, chain, {} as any); + console.log(`šŸ”¢ Total history blocks: ${extendedFromBlock} to ${extendedToBlock}`); + + // Step 3: Track REBALANCED events for DAILY period + console.log(`šŸ”„ Fetching daily rebalance events...`); + let dailyRebalancedLogs: any[] = []; + + try { + dailyRebalancedLogs = await getLogs({ + noTarget: true, + eventAbi: REBALANCED_EVENT, + fromBlock: dailyFromBlock, + toBlock: dailyToBlock, + }); + } catch (error: any) { + console.warn(`āš ļø Error fetching DAILY REBALANCED events:`, error.message); + } + + // Step 4: Track REBALANCED events for TOTAL HISTORY + console.log(`šŸ”„ Fetching total history rebalance events...`); + let totalRebalancedLogs: any[] = []; + + try { + totalRebalancedLogs = await getLogs({ + noTarget: true, + eventAbi: REBALANCED_EVENT, + fromBlock: extendedFromBlock, + toBlock: extendedToBlock, + }); + } catch (error: any) { + console.warn(`āš ļø Error fetching TOTAL REBALANCED events globally:`, error.message); + + // Fallback: try from vault addresses if global search fails + if (vaultAddresses.length > 0) { + try { + totalRebalancedLogs = await getLogs({ + targets: vaultAddresses, + eventAbi: REBALANCED_EVENT, + fromBlock: extendedFromBlock, + toBlock: extendedToBlock, + }); + } catch (secondError: any) { + console.warn(`āš ļø Error fetching TOTAL REBALANCED events from vaults:`, secondError.message); + } + } + } + + // Step 5: Track WITHDRAWAL events for DAILY period + console.log(`šŸ’ø Fetching daily withdrawal events...`); + let dailyWithdrawalLogs: any[] = []; + if (vaultAddresses.length > 0) { + try { + dailyWithdrawalLogs = await getLogs({ + targets: vaultAddresses, + eventAbi: WITHDRAWAL_EVENT, + fromBlock: dailyFromBlock, + toBlock: dailyToBlock, + }); + } catch (error: any) { + console.warn(`āš ļø Error fetching DAILY WITHDRAWAL events:`, error.message); + } + } + + // Step 6: Track WITHDRAWAL events for TOTAL HISTORY + console.log(`šŸ’ø Fetching total history withdrawal events...`); + let totalWithdrawalLogs: any[] = []; + if (vaultAddresses.length > 0) { + try { + totalWithdrawalLogs = await getLogs({ + targets: vaultAddresses, + eventAbi: WITHDRAWAL_EVENT, + fromBlock: extendedFromBlock, + toBlock: extendedToBlock, + }); + } catch (error: any) { + console.warn(`āš ļø Error fetching TOTAL WITHDRAWAL events:`, error.message); + } + } + + // Step 7: Track DEPOSIT events for DAILY period + console.log(`šŸ’° Fetching daily deposit events...`); + let dailyDepositLogs: any[] = []; + if (vaultAddresses.length > 0) { + try { + dailyDepositLogs = await getLogs({ + targets: vaultAddresses, + eventAbi: DEPOSIT_EVENT, + fromBlock: dailyFromBlock, + toBlock: dailyToBlock, + }); + } catch (error: any) { + console.warn(`āš ļø Error fetching DAILY DEPOSIT events:`, error.message); + } + } + + // Step 8: Track DEPOSIT events for TOTAL HISTORY + console.log(`šŸ’° Fetching total history deposit events...`); + let totalDepositLogs: any[] = []; + if (vaultAddresses.length > 0) { + try { + totalDepositLogs = await getLogs({ + targets: vaultAddresses, + eventAbi: DEPOSIT_EVENT, + fromBlock: extendedFromBlock, + toBlock: extendedToBlock, + }); + } catch (error: any) { + console.warn(`āš ļø Error fetching TOTAL DEPOSIT events:`, error.message); + } + } + + // Step 9: Get initialDepositAmount from each vault + console.log(`šŸ”¢ Fetching initial deposit amounts from ${vaultAddresses.length} vaults...`); + let totalInitialDepositAmounts = BigInt(0); + let dailyInitialDepositAmounts = BigInt(0); + let successfulCalls = 0; + let vaultInitialDeposits: Array<{vault: string, amount: bigint, createdInDaily: boolean}> = []; + + if (vaultAddresses.length > 0) { + for (const vaultInfo of allVaults) { + const vaultAddress = vaultInfo.vaultAddress; + const deployedAt = vaultInfo.deployedAt; + const isCreatedInDailyPeriod = deployedAt >= dailyFromBlock && deployedAt <= dailyToBlock; + + try { + const initialDepositAmount = await api.call({ + target: vaultAddress, + abi: 'function initialDepositAmount() view returns (uint256)', + }); + if (initialDepositAmount && initialDepositAmount !== '0') { + const amount = BigInt(initialDepositAmount); + totalInitialDepositAmounts += amount; + if (isCreatedInDailyPeriod) { + dailyInitialDepositAmounts += amount; + } + vaultInitialDeposits.push({ vault: vaultAddress, amount, createdInDaily: isCreatedInDailyPeriod }); + } + successfulCalls++; + } catch (error: any) { + // Try alternative ABI format + try { + const initialDepositAmount = await api.call({ + target: vaultAddress, + abi: 'initialDepositAmount', + }); + if (initialDepositAmount && initialDepositAmount !== '0') { + const amount = BigInt(initialDepositAmount); + totalInitialDepositAmounts += amount; + if (isCreatedInDailyPeriod) { + dailyInitialDepositAmounts += amount; + } + vaultInitialDeposits.push({ vault: vaultAddress, amount, createdInDaily: isCreatedInDailyPeriod }); + } + successfulCalls++; + } catch (secondError: any) { + // This vault might be a different contract version or type + continue; + } + } + } + } + + // Step 10: Calculate DAILY volume + console.log(`šŸ“Š Calculating volumes...`); + let dailyRebalanced = BigInt(0); + let dailyWithdrawals = BigInt(0); + let dailyDeposits = BigInt(0); + + // Process DAILY REBALANCED events + for (const log of dailyRebalancedLogs) { + const amount = BigInt(log.amount); + dailyRebalanced += amount; + } + + // Process DAILY WITHDRAWAL events + for (const log of dailyWithdrawalLogs) { + const amount = BigInt((log as any)[2] || (log as any).amount || 0); + dailyWithdrawals += amount; + } + + // Process DAILY DEPOSIT events (tracked but NOT added to volume) + for (const log of dailyDepositLogs) { + const amount = BigInt((log as any)[2] || (log as any).amount || 0); + dailyDeposits += amount; + } + + // Calculate daily volume: Rebalanced + Withdrawals (NO initial deposits for daily) + const dailyVolumeAmount = dailyRebalanced + dailyWithdrawals; + + // Add the daily volume + if (dailyVolumeAmount > 0) { + dailyVolume.add(usdcAddress, dailyVolumeAmount); + } + + // Step 11: Calculate TOTAL HISTORY volume + let historyRebalanced = BigInt(0); + let historyWithdrawals = BigInt(0); + let historyDeposits = BigInt(0); + + // Process TOTAL REBALANCED events + for (const log of totalRebalancedLogs) { + const amount = BigInt(log.amount); + historyRebalanced += amount; + } + + // Process TOTAL WITHDRAWAL events + for (const log of totalWithdrawalLogs) { + const amount = BigInt((log as any)[2] || (log as any).amount || 0); + historyWithdrawals += amount; + } + + // Process TOTAL DEPOSIT events (tracked but NOT added to volume) + for (const log of totalDepositLogs) { + const amount = BigInt((log as any)[2] || (log as any).amount || 0); + historyDeposits += amount; + } + + // Calculate total volume: Rebalanced + Withdrawals + Initial Deposit Amounts + const totalVolumeAmount = historyRebalanced + historyWithdrawals + totalInitialDepositAmounts; + + // Add the total volume + if (totalVolumeAmount > 0) { + totalVolume.add(usdcAddress, totalVolumeAmount); + } + + // Step 12: Log clean summary with all information + // Helper function to format numbers + const formatAmount = (amount: bigint): string => { + const usdc = Number(amount) / 1e6; // Convert from smallest unit to USDC + if (usdc === 0) return '0 USDC'; + if (usdc < 1000) return `${usdc.toFixed(2)} USDC`; + if (usdc < 1_000_000) return `${(usdc / 1000).toFixed(2)}K USDC`; + if (usdc < 1_000_000_000) return `${(usdc / 1_000_000).toFixed(2)}M USDC`; + return `${(usdc / 1_000_000_000).toFixed(2)}B USDC`; + }; + + console.log(`\n${"═".repeat(100)}`); + console.log(`šŸ“Š SURF MORPHO REBALANCER - VOLUME SUMMARY`); + console.log(`${"═".repeat(100)}`); + + // Daily Summary + const dailyVaultsCreated = vaultInitialDeposits.filter(v => v.createdInDaily).length; + console.log(`\nšŸ“… DAILY VOLUME (${new Date(options.fromTimestamp * 1000).toISOString().split('T')[0]} to ${new Date(options.toTimestamp * 1000).toISOString().split('T')[0]}):`); + console.log(` Rebalanced Events: ${dailyRebalancedLogs.length} → ${formatAmount(dailyRebalanced)}`); + console.log(` Withdrawal Events: ${dailyWithdrawalLogs.length} → ${formatAmount(dailyWithdrawals)}`); + console.log(` Initial Deposits: ${dailyVaultsCreated} vaults → ${formatAmount(dailyInitialDepositAmounts)}`); + console.log(` ────────────────────────────────────────────────────────────`); + console.log(` DAILY TOTAL: ${formatAmount(dailyVolumeAmount)}`); + + // Total History Summary + console.log(`\nšŸ“ˆ TOTAL HISTORY (Aug 30, 2025 to now):`); + console.log(` Rebalanced Events: ${totalRebalancedLogs.length} → ${formatAmount(historyRebalanced)}`); + console.log(` Withdrawal Events: ${totalWithdrawalLogs.length} → ${formatAmount(historyWithdrawals)}`); + console.log(` Initial Deposits: ${vaultAddresses.length} vaults → ${formatAmount(totalInitialDepositAmounts)}`); + console.log(` ────────────────────────────────────────────────────────────`); + console.log(` TOTAL VOLUME: ${formatAmount(totalVolumeAmount)}`); + + // Event Breakdown + console.log(`\nšŸ“Š EVENT BREAKDOWN:`); + console.log(` Rebalance Events (globally): ${totalRebalancedLogs.length}`); + console.log(` Withdrawal Events: ${totalWithdrawalLogs.length}`); + console.log(` Vaults Tracked: ${vaultAddresses.length}`); + console.log(` Function Calls: ${successfulCalls}/${vaultAddresses.length}`); + + // Initial Deposit Breakdown by Vault + console.log(`\nšŸ’° INITIAL DEPOSIT AMOUNTS BY VAULT:`); + if (vaultInitialDeposits.length > 0) { + // Sort by amount (descending) + const sortedDeposits = vaultInitialDeposits.sort((a, b) => + Number(b.amount - a.amount) + ); + + // Show top 10 vaults + const displayCount = Math.min(10, sortedDeposits.length); + console.log(` Showing top ${displayCount} of ${sortedDeposits.length} vaults with initial deposits:\n`); + + sortedDeposits.slice(0, displayCount).forEach((item, index) => { + const usdc = Number(item.amount) / 1e6; + const shortAddress = `${item.vault.slice(0, 6)}...${item.vault.slice(-4)}`; + const dailyTag = item.createdInDaily ? ' [NEW TODAY]' : ''; + console.log(` ${index + 1}. ${shortAddress}: ${formatAmount(item.amount)}${dailyTag}`); + }); + + if (sortedDeposits.length > displayCount) { + console.log(` ... and ${sortedDeposits.length - displayCount} more vaults`); + } + + console.log(`\n Total from ${vaultInitialDeposits.length} vaults: ${formatAmount(totalInitialDepositAmounts)}`); + if (dailyVaultsCreated > 0) { + console.log(` Created today: ${dailyVaultsCreated} vaults → ${formatAmount(dailyInitialDepositAmounts)}`); + } + } else { + console.log(` No vaults with initial deposits found`); + } + + console.log(`${"═".repeat(100)}\n`); + + } catch (error: any) { + console.error(`āŒ Error fetching core volume data for chain ${chain}:`, error); + } + + return { + dailyVolume, + totalVolume, + }; +}; + +const adapter: SimpleAdapter = { + version: 2, + adapter: { + [CHAIN.ETHEREUM]: { + fetch, + start: "2024-01-01", // Adjust start date as needed + }, + [CHAIN.ARBITRUM]: { + fetch, + start: "2024-01-01", + }, + [CHAIN.BASE]: { + fetch, + start: "2025-08-30", // Start from August 30, 2025 (deployment date) + }, + [CHAIN.POLYGON]: { + fetch, + start: "2024-01-01", + }, + [CHAIN.OPTIMISM]: { + fetch, + start: "2024-01-01", + }, + [CHAIN.BSC]: { + fetch, + start: "2024-01-01", + }, + }, + methodology: { + Volume: "Daily Volume = Rebalanced events + Withdrawal events for the requested time period. Total Volume = All Rebalanced events (globally, all history from Aug 30, 2025) + All Withdrawal events (from vaults, all history) + Initial Deposit Amounts (from all vaults). DEPOSIT events are tracked but filtered out and NOT included in volume to avoid double-counting. Uses dynamic vault discovery via UserVaultTracker from factory contract 0x1D283b668F947E03E8ac8ce8DA5505020434ea0E on Base chain.", + }, +}; + +export default adapter; \ No newline at end of file diff --git a/dexs/surf-morpho-rebalancer/user-vault-tracker.ts b/dexs/surf-morpho-rebalancer/user-vault-tracker.ts new file mode 100644 index 0000000000..aea192591c --- /dev/null +++ b/dexs/surf-morpho-rebalancer/user-vault-tracker.ts @@ -0,0 +1,328 @@ +import { ChainApi } from '@defillama/sdk'; +import { CHAIN } from "../../helpers/chains"; +import ADDRESSES from '../../helpers/coreAssets.json' + +// Factory contract address +const FACTORY_ADDRESS = "0x1D283b668F947E03E8ac8ce8DA5505020434ea0E"; + +// Admin address that performs rebalancing +const ADMIN_ADDRESS = "0xEeEE7d713aDf6f408dd3637987191B35E3A872b0"; + +// Factory contract ABI +const FACTORY_ABI = { + getTotalVaults: "function getTotalVaults() external view returns (uint256)", + getVaultInfo: "function getVaultInfo(uint256 index) external view returns (tuple(address vaultAddress, address owner, address admin, uint256 chainId, bytes32 salt, uint256 deployedAt))" +}; + +// UserVault contract ABI +const USER_VAULT_ABI = { + currentVault: "function currentVault() external view returns (address)" +}; + +// VaultInfo struct type +interface VaultInfo { + vaultAddress: string; + owner: string; + admin: string; + chainId: number; + salt: string; + deployedAt: number; +} + +// Extended vault info with current Morpho vault +interface ExtendedVaultInfo extends VaultInfo { + currentMorphoVault: string; +} + +// USDC token address for different chains +const USDC_ADDRESSES = { + [CHAIN.ETHEREUM]: ADDRESSES.ethereum.USDC, + [CHAIN.ARBITRUM]: ADDRESSES.arbitrum.USDC, + [CHAIN.BASE]: ADDRESSES.base.USDC, + [CHAIN.POLYGON]: ADDRESSES.polygon.USDC, + [CHAIN.OPTIMISM]: ADDRESSES.optimism.USDC, + [CHAIN.BSC]: ADDRESSES.bsc.USDC, +}; + +export class UserVaultTracker { + private api: ChainApi; + private chain: string; + + constructor(chain: string) { + this.chain = chain; + this.api = new ChainApi({ chain }); + } + + /** + * Get total number of vaults deployed by the factory + */ + async getTotalVaults(): Promise { + try { + console.log(`šŸ” Getting total vaults from factory ${FACTORY_ADDRESS}...`); + const totalVaults = await this.api.call({ + target: FACTORY_ADDRESS, + abi: FACTORY_ABI.getTotalVaults, + }); + const count = Number(totalVaults); + console.log(`šŸ“Š Total vaults found: ${count}`); + return count; + } catch (error) { + console.error(`āŒ Error getting total vaults on ${this.chain}:`, error); + return 0; + } + } + + /** + * Get vault information by index + */ + async getVaultInfo(index: number): Promise { + try { + const vaultInfo = await this.api.call({ + target: FACTORY_ADDRESS, + abi: FACTORY_ABI.getVaultInfo, + params: [index], + }) as VaultInfo; + + return vaultInfo; + } catch (error) { + console.error(`āŒ Error getting vault info for index ${index} on ${this.chain}:`, error); + return null; + } + } + + /** + * Get current Morpho vault address for a UserVault + */ + async getCurrentMorphoVault(vaultAddress: string): Promise { + try { + const currentVault = await this.api.call({ + target: vaultAddress, + abi: USER_VAULT_ABI.currentVault, + }); + + return currentVault as string; + } catch (error) { + console.error(`āŒ Error getting current vault for ${vaultAddress} on ${this.chain}:`, error); + return null; + } + } + + /** + * Get all vaults with their current Morpho vault addresses + */ + async getAllUserVaults(): Promise { + console.log(`\nšŸ­ SURF MORPHO USER VAULT TRACKER`); + console.log(`=================================`); + console.log(`Factory Address: ${FACTORY_ADDRESS}`); + console.log(`Chain: ${this.chain}`); + console.log(`Admin Address: ${ADMIN_ADDRESS}\n`); + + const totalVaults = await this.getTotalVaults(); + + if (totalVaults === 0) { + console.log(`āŒ No vaults found in factory`); + return []; + } + + const allVaults: ExtendedVaultInfo[] = []; + console.log(`\nšŸ” Fetching vault details...`); + + for (let i = 0; i < totalVaults; i++) { + process.stdout.write(`\ršŸ“‹ Processing vault ${i + 1}/${totalVaults}...`); + + const vaultInfo = await this.getVaultInfo(i); + if (!vaultInfo) { + console.warn(`\nāš ļø Failed to get vault info for index ${i}`); + continue; + } + + const currentMorphoVault = await this.getCurrentMorphoVault(vaultInfo.vaultAddress); + + const extendedVaultInfo: ExtendedVaultInfo = { + ...vaultInfo, + currentMorphoVault: currentMorphoVault || 'N/A' + }; + + allVaults.push(extendedVaultInfo); + } + + console.log(`\nāœ… Successfully processed ${allVaults.length} vaults!`); + return allVaults; + } + + /** + * Get vaults by owner address + */ + async getVaultsByOwner(ownerAddress: string): Promise { + const allVaults = await this.getAllUserVaults(); + return allVaults.filter(vault => + vault.owner.toLowerCase() === ownerAddress.toLowerCase() + ); + } + + /** + * Get vaults by admin address + */ + async getVaultsByAdmin(adminAddress: string): Promise { + const allVaults = await this.getAllUserVaults(); + return allVaults.filter(vault => + vault.admin.toLowerCase() === adminAddress.toLowerCase() + ); + } + + /** + * Get vaults by current Morpho vault + */ + async getVaultsByMorphoVault(morphoVaultAddress: string): Promise { + const allVaults = await this.getAllUserVaults(); + return allVaults.filter(vault => + vault.currentMorphoVault.toLowerCase() === morphoVaultAddress.toLowerCase() + ); + } + + /** + * Get comprehensive vault summary + */ + async getVaultSummary(): Promise<{ + totalVaults: number; + uniqueOwners: number; + uniqueAdmins: number; + uniqueMorphoVaults: number; + vaultsByChain: Record; + adminVaults: number; + recentVaults: ExtendedVaultInfo[]; + }> { + const allVaults = await this.getAllUserVaults(); + + const uniqueOwners = new Set(allVaults.map(v => v.owner)).size; + const uniqueAdmins = new Set(allVaults.map(v => v.admin)).size; + const uniqueMorphoVaults = new Set( + allVaults + .map(v => v.currentMorphoVault) + .filter(v => v !== 'N/A') + ).size; + + const vaultsByChain = allVaults.reduce((acc, vault) => { + acc[vault.chainId] = (acc[vault.chainId] || 0) + 1; + return acc; + }, {} as Record); + + const adminVaults = allVaults.filter(v => + v.admin.toLowerCase() === ADMIN_ADDRESS.toLowerCase() + ).length; + + // Get recent vaults (last 10) + const recentVaults = allVaults + .sort((a, b) => b.deployedAt - a.deployedAt) + .slice(0, 10); + + return { + totalVaults: allVaults.length, + uniqueOwners, + uniqueAdmins, + uniqueMorphoVaults, + vaultsByChain, + adminVaults, + recentVaults + }; + } + + /** + * Display detailed vault information + */ + async displayVaultDetails(): Promise { + const allVaults = await this.getAllUserVaults(); + + if (allVaults.length === 0) { + console.log(`āŒ No vaults found`); + return; + } + + const summary = await this.getVaultSummary(); + + console.log(`\nšŸ“Š VAULT SUMMARY:`); + console.log(`================`); + console.log(`Total Vaults: ${summary.totalVaults}`); + console.log(`Unique Owners: ${summary.uniqueOwners}`); + console.log(`Unique Admins: ${summary.uniqueAdmins}`); + console.log(`Unique Morpho Vaults: ${summary.uniqueMorphoVaults}`); + console.log(`Admin Vaults: ${summary.adminVaults}`); + console.log(`Vaults by Chain:`, summary.vaultsByChain); + + // Display all vaults + console.log(`\nšŸ“‹ ALL USER VAULTS:`); + console.log(`==================`); + allVaults.forEach((vault, index) => { + console.log(`\n${index + 1}. Vault Address: ${vault.vaultAddress}`); + console.log(` Owner: ${vault.owner}`); + console.log(` Admin: ${vault.admin}`); + console.log(` Chain ID: ${vault.chainId}`); + console.log(` Current Morpho Vault: ${vault.currentMorphoVault}`); + console.log(` Deployed At: ${new Date(vault.deployedAt * 1000).toISOString()}`); + console.log(` Salt: ${vault.salt}`); + console.log(` Is Admin Vault: ${vault.admin.toLowerCase() === ADMIN_ADDRESS.toLowerCase() ? 'āœ… YES' : 'āŒ NO'}`); + }); + + // Display admin vaults specifically + const adminVaults = allVaults.filter(v => + v.admin.toLowerCase() === ADMIN_ADDRESS.toLowerCase() + ); + + if (adminVaults.length > 0) { + console.log(`\nšŸŽÆ VAULTS MANAGED BY ADMIN (${ADMIN_ADDRESS}):`); + console.log(`=============================================`); + adminVaults.forEach((vault, index) => { + console.log(`\n${index + 1}. Vault: ${vault.vaultAddress}`); + console.log(` Owner: ${vault.owner}`); + console.log(` Current Morpho Vault: ${vault.currentMorphoVault}`); + console.log(` Deployed: ${new Date(vault.deployedAt * 1000).toISOString()}`); + }); + } else { + console.log(`\nāš ļø No vaults found managed by admin address ${ADMIN_ADDRESS}`); + } + } +} + +// Main execution function +export async function trackAllUserVaults(chain: string = CHAIN.BASE): Promise { + console.log(`šŸš€ Starting User Vault Tracking on ${chain}`); + console.log(`==========================================\n`); + + const tracker = new UserVaultTracker(chain); + + try { + await tracker.displayVaultDetails(); + const allVaults = await tracker.getAllUserVaults(); + + console.log(`\nāœ… Tracking completed successfully!`); + console.log(`šŸ“Š Found ${allVaults.length} user vaults total`); + + return allVaults; + } catch (error) { + console.error(`āŒ Error tracking vaults:`, error); + return []; + } +} + +// Export for use in other files +export { + FACTORY_ADDRESS, + ADMIN_ADDRESS, + FACTORY_ABI, + USER_VAULT_ABI, + USDC_ADDRESSES +}; +export type { VaultInfo, ExtendedVaultInfo }; + +// Run if this file is executed directly +if (require.main === module) { + trackAllUserVaults(CHAIN.BASE) + .then((vaults) => { + console.log(`\nšŸŽ‰ Process completed! Found ${vaults.length} vaults.`); + process.exit(0); + }) + .catch((error) => { + console.error(`āŒ Process failed:`, error); + process.exit(1); + }); +}