Skip to content

Remove hard chain dependencies and follow SOLID principles #346

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
RitzyDevUK opened this issue Mar 23, 2025 · 0 comments
Open

Remove hard chain dependencies and follow SOLID principles #346

RitzyDevUK opened this issue Mar 23, 2025 · 0 comments

Comments

@RitzyDevUK
Copy link

The current architecture is tightly coupled to the ChainId dependency and its a poor architecture.

Fix Dependency Injection for the sdks

  1. SDK-Core Addresses and mappings are a mess ()
  2. There's code duplication in other sdks and no single source of truth (WETH addresses, and Quoter addresses are overridden and duplicated in smart-order-router, router-sdk, universal-router sdk)
  3. Adding a new chain requires redeployment and synchronization of multiple sdks.
  4. V2-SDK does not allow InitHash injection

At this point you're pretty tied into this poor architecture however you can do the following.

A: Inject all dependencies into all dependent classes, this a big headache since the pool and route dependencies will need to take in the factory and init hashes.

This would be an extreme refactor and cause a lot of existing third party code to break as a cause of the update

B: Static configuration Injection (Overall is a hack as the SDK should not be statically dependent however currently you don't support configurations per chain so it shouldn't be an issue.)

1. Add a class to statically initialize the chain configuration but fallback to the existing chains so people aren't forced to call the chain initializer 
// ChainAddressConfig.ts

import { ChainAddresses } from "./addresses";


export type ChainAddressesMap = Record<number, ChainAddresses>;

const GLOBAL_CHAIN_ADDRESSES_KEY = '__GLOBAL_CHAIN_ADDRESSES_CONFIG__';

export class ChainAddressConfig {
    private addressesMap: ChainAddressesMap = {};

    private constructor() { }

    private static get instance(): ChainAddressConfig {
        if (!(globalThis as any)[GLOBAL_CHAIN_ADDRESSES_KEY]) {
            (globalThis as any)[GLOBAL_CHAIN_ADDRESSES_KEY] = new ChainAddressConfig();
        }
        return (globalThis as any)[GLOBAL_CHAIN_ADDRESSES_KEY];
    }

    public static initialize(addressesMap: ChainAddressesMap, allowMerge = false): void {
        if (!allowMerge && Object.keys(this.instance.addressesMap).length > 0) {
            throw new Error('ChainAddressConfig already initialized. To merge, set allowMerge=true.');
        }
        this.instance.addressesMap = allowMerge
            ? { ...this.instance.addressesMap, ...addressesMap }
            : addressesMap;
    }

    public static getAddresses(chainId: number): ChainAddresses | undefined {
        return this.instance.addressesMap[chainId];
    }

    public static getAddress(
        chainId: number,
        key: Exclude<keyof ChainAddresses, 'weth'>
    ): string | undefined {
        const addresses = this.getAddresses(chainId);

        if (!addresses) {
            return undefined;
        }

        return addresses[key];
    }

    public static getChainIds(): number[] {
        return Object.keys(this.instance.addressesMap).map(Number);
    }

}

  1. Create a proxy to override the existing chain maps to dynamically support the static configuration: (Note: I created a fallback proxy provider which can be used in the smart-order router since you again decided to override some properties and dynamically create address maps e.g quoterV2Addresses)
export function createAddressProxy(propertyName: keyof ChainAddresses): AddressMap {
    return new Proxy({}, {
        get(_, chainId: string) {
            const numericChainId = Number(chainId);
            return CHAIN_TO_ADDRESSES_MAP[numericChainId]?.[propertyName];
        },

        ownKeys() {
            return Object.keys(CHAIN_TO_ADDRESSES_MAP).filter((chainId) => {
                return CHAIN_TO_ADDRESSES_MAP[Number(chainId)]?.[propertyName];
            });
        },

        getOwnPropertyDescriptor(_, chainId: string) {
            const numericChainId = Number(chainId);
            if (CHAIN_TO_ADDRESSES_MAP[numericChainId]?.[propertyName]) {
                return { enumerable: true, configurable: true };
            }
            return undefined;
        },

        has(_, chainId: string) {
            const numericChainId = Number(chainId);
            return CHAIN_TO_ADDRESSES_MAP[numericChainId]?.[propertyName] !== undefined;
        },
    });
}

export function createDynamicAddressMapProxyWithFallback(
    property: string,
    fallbackProperty: keyof ChainAddresses,
    propertyOverrides: Record<number, string>
): AddressMap {
    return new Proxy({}, {
        get(_, chainId: string) {
            const numericChainId = Number(chainId);
            const chainConfig = CHAIN_TO_ADDRESSES_MAP[numericChainId];

            return (
                (chainConfig as any)?.[property] ??
                propertyOverrides[numericChainId] ??
                chainConfig?.[fallbackProperty]
            );
        },

        has(_, chainId: string) {
            const numericChainId = Number(chainId);
            const chainConfig = CHAIN_TO_ADDRESSES_MAP[numericChainId];

            return (
                (chainConfig as any)?.[property] !== undefined ||
                propertyOverrides[numericChainId] !== undefined ||
                chainConfig?.[fallbackProperty] !== undefined
            );
        },

        ownKeys() {
            const chainIds = new Set<number>();

            // Include any chain with a matching primary or fallback property
            for (const [idStr, config] of Object.entries(CHAIN_TO_ADDRESSES_MAP)) {
                const id = Number(idStr);
                if ((config as any)?.[property] || config?.[fallbackProperty]) {
                    chainIds.add(id);
                }
            }

            // Include overrides if the property/fallback is not already present
            for (const idStr of Object.keys(propertyOverrides)) {
                const id = Number(idStr);
                const config = CHAIN_TO_ADDRESSES_MAP[id];
                if (!(config as any)?.[property] && !config?.[fallbackProperty]) {
                    chainIds.add(id);
                }
            }

            return Array.from(chainIds).map(String);
        },

        getOwnPropertyDescriptor(_, chainId: string) {
            const numericChainId = Number(chainId);
            const config = CHAIN_TO_ADDRESSES_MAP[numericChainId];

            const value = (
                (config as any)?.[property] ??
                propertyOverrides[numericChainId] ??
                config?.[fallbackProperty]
            );

            if (value !== undefined) {
                return { enumerable: true, configurable: true };
            }

            return undefined;
        }
    });
}
  1. Depricate old chain map and reference static chain configuration and build properties for the addtional address map
export const CHAIN_TO_ADDRESSES_MAP: Record<number, ChainAddresses> = new Proxy(
  {} as Record<number, ChainAddresses>,
  {
    get(_, chainId: string) {
      const numericChainId = Number(chainId);

      const configuredAddresses = ChainAddressConfig.getAddresses(numericChainId);
      if (configuredAddresses) return configuredAddresses;

      return DEPRICATED_CHAIN_TO_ADDRESSES_MAP[numericChainId];
    },

    ownKeys() {
      const dynamicKeys = ChainAddressConfig.getChainIds();
      const deprecatedKeys = Object.keys(DEPRICATED_CHAIN_TO_ADDRESSES_MAP).map(Number);
      return Array.from(new Set([...dynamicKeys, ...deprecatedKeys])).map(String);
    },

    getOwnPropertyDescriptor(_, chainId: string) {
      const numericChainId = Number(chainId);
      const exists =
        ChainAddressConfig.getAddresses(numericChainId) !== undefined ||
        DEPRICATED_CHAIN_TO_ADDRESSES_MAP[numericChainId] !== undefined;

      if (exists) {
        return { enumerable: true, configurable: true };
      }
      return undefined;
    },

    has(_, chainId: string) {
      const numericChainId = Number(chainId);
      return (
        ChainAddressConfig.getAddresses(numericChainId) !== undefined ||
        DEPRICATED_CHAIN_TO_ADDRESSES_MAP[numericChainId] !== undefined
      );
    },
  }
);


export const V3_CORE_FACTORY_ADDRESSES = createAddressProxy('v3CoreFactoryAddress');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

1 participant