import { Contract, ContractRunner } from 'ethers'; import { AbiManager } from './AbiManager'; import { ProtocolDetector } from './ProtocolDetector'; import { QuoterFactory } from './QuoterFactory'; import { NetworkConfig } from './NetworkConfig'; /** * ContractFactory creates ethers.Contract instances with protocol-specific ABIs * * Combines logic from PoolManager, QuoterV2Factory, and VaultContractManager * to provide a unified interface for contract creation. * * Supports dynamic quoter address resolution when network config is provided. */ export class ContractFactory { private signer: ContractRunner; private abiManager: AbiManager; private protocolDetector: ProtocolDetector; private quoterFactory?: QuoterFactory; /** * Create a ContractFactory instance * * @param signer - Contract runner for contract interactions * @param abiManager - Optional AbiManager instance * @param protocolDetector - Optional ProtocolDetector instance * @param networkConfig - Optional network configuration for dynamic address resolution */ constructor( signer: ContractRunner, abiManager?: AbiManager, protocolDetector?: ProtocolDetector, networkConfig?: NetworkConfig, ) { this.signer = signer; this.abiManager = abiManager || new AbiManager(); this.protocolDetector = protocolDetector || new ProtocolDetector(); // Create QuoterFactory if network config is provided if (networkConfig) { this.quoterFactory = new QuoterFactory( networkConfig, this.protocolDetector, this.abiManager, ); } } /** * Create a pool contract instance * * @param poolAddress - The pool contract address * @param beaconName - The beacon name to determine protocol and ABI * @param signer - Optional contract runner (defaults to constructor signer) * @returns ethers.Contract instance for the pool */ createPoolContract( poolAddress: string, beaconName: string, signer?: ContractRunner, ): Contract { const poolAbi = this.abiManager.getPoolAbi(beaconName); const contractSigner = signer || this.signer; return new Contract(poolAddress, poolAbi, contractSigner); } /** * Create a factory contract instance * * @param factoryAddress - The factory contract address * @param beaconName - The beacon name to determine protocol and ABI * @param signer - Optional contract runner (defaults to constructor signer) * @returns ethers.Contract instance for the factory */ createFactoryContract( factoryAddress: string, beaconName: string, signer?: ContractRunner, ): Contract { const factoryAbi = this.abiManager.getFactoryAbi(beaconName); const contractSigner = signer || this.signer; return new Contract(factoryAddress, factoryAbi, contractSigner); } /** * Create a quoter V2 contract instance * * Supports two modes: * 1. With explicit address: Uses the provided quoterAddress (backward compatible) * 2. Dynamic resolution: If quoterAddress not provided and networkConfig available, * resolves address dynamically via QuoterFactory * * @param quoterAddress - Optional quoter contract address * @param beaconName - The beacon name to determine protocol and ABI * @param signer - Optional contract runner (defaults to constructor signer) * @returns ethers.Contract instance for the quoter * @throws Error if quoterAddress not provided and networkConfig not available * * @example * ```typescript * // Explicit address (backward compatible) * const quoter = factory.createQuoterContract('0x123...', 'uniswapV3VaultBeacon'); * * // Dynamic resolution (requires networkConfig in constructor) * const quoter = factory.createQuoterContract(undefined, 'uniswapV3VaultBeacon'); * ``` */ createQuoterContract( beaconName?: string, signer?: ContractRunner, ): Contract { const contractSigner = signer || this.signer; console.log(`[ContractFactory] createQuoterContract called with beaconName: ${beaconName}`); console.log(`[ContractFactory] quoterFactory exists: ${!!this.quoterFactory}`); // If explicit address provided, use traditional method (backward compatible) // Dynamic resolution via QuoterFactory if (!beaconName) { throw new Error('beaconName is required for quoter contract creation'); } if (!this.quoterFactory) { throw new Error( 'QuoterFactory not initialized. Network config is required for dynamic quoter resolution. ' + 'Please provide a valid networkConfig in the ContractFactory constructor. ' + `Beacon name: ${beaconName}` ); } console.log(`[ContractFactory] Creating quoter contract via QuoterFactory`); return this.quoterFactory.createQuoterV2(beaconName, contractSigner); } /** * Create a vault contract instance with upgrade detection * * @param vaultAddress - The vault contract address * @param beaconName - The beacon name to determine protocol and ABI * @param isUpgraded - Whether to use upgraded vault ABI (defaults to true) * @param signer - Optional contract runner (defaults to constructor signer) * @returns ethers.Contract instance for the vault */ createVaultContract( vaultAddress: string, beaconName: string, isUpgraded = true, signer?: ContractRunner, ): Contract { const vaultAbi = this.abiManager.getVaultAbi(beaconName, isUpgraded); const contractSigner = signer || this.signer; return new Contract(vaultAddress, vaultAbi, contractSigner); } /** * Create a Steer Periphery contract instance * * @param peripheryAddress - The Steer Periphery contract address * @param signer - Optional contract runner (defaults to constructor signer) * @returns ethers.Contract instance for Steer Periphery */ createSteerPeripheryContract( peripheryAddress: string, signer?: ContractRunner, ): Contract { const peripheryAbi = this.abiManager.getSteerPeripheryAbi(); const contractSigner = signer || this.signer; return new Contract(peripheryAddress, peripheryAbi, contractSigner); } /** * Detect if a vault is upgraded by attempting to call totalFees0() * * @param vaultAddress - The vault contract address * @param beaconName - The beacon name to determine protocol * @returns Promise - true if vault is upgraded, false otherwise */ async detectVaultUpgrade( vaultAddress: string, beaconName: string, ): Promise { try { const vaultContract = this.createVaultContract( vaultAddress, beaconName, true, ); await vaultContract.totalFees0(); return true; } catch (e) { return false; } } /** * Detect if a vault is multi-position based on beacon name * * @param beaconName - The beacon name * @returns boolean - true if vault supports multiple positions */ isMultiPositionVault(beaconName: string): boolean { const lowerBeacon = beaconName.toLowerCase(); return lowerBeacon.includes('multi') || lowerBeacon.includes('aerodrome'); } }