Skip to content

Remove hard chain dependencies and follow SOLID principles #346

@RitzyDevUK

Description

@RitzyDevUK

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');

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions