diff --git a/contracts/deployments/contractsEthers.ts b/contracts/deployments/contractsEthers.ts
new file mode 100644
index 000000000..46def6375
--- /dev/null
+++ b/contracts/deployments/contractsEthers.ts
@@ -0,0 +1,239 @@
+import { ethers } from "ethers";
+import {
+  klerosCoreConfig as devnetCoreConfig,
+  sortitionModuleConfig as devnetSortitionConfig,
+  disputeKitClassicConfig as devnetDkcConfig,
+  disputeResolverConfig as devnetDrConfig,
+  disputeTemplateRegistryConfig as devnetDtrConfig,
+  evidenceModuleConfig as devnetEvidenceConfig,
+  policyRegistryConfig as devnetPolicyRegistryConfig,
+  transactionBatcherConfig as devnetBatcherConfig,
+  chainlinkRngConfig as devnetChainlinkRngConfig,
+  blockHashRngConfig as devnetBlockHashRngConfig,
+  pnkConfig as devnetPnkConfig,
+  klerosCoreSnapshotProxyConfig as devnetSnapshotProxyConfig,
+  klerosCoreUniversityConfig as devnetCoreUniversityConfig,
+  sortitionModuleUniversityConfig as devnetSortitionUniversityConfig,
+  disputeKitClassicUniversityConfig as devnetDkcUniversityConfig,
+  disputeResolverUniversityConfig as devnetDrUniversityConfig,
+} from "./devnet.viem";
+import {
+  klerosCoreConfig as testnetCoreConfig,
+  sortitionModuleConfig as testnetSortitionConfig,
+  disputeKitClassicConfig as testnetDkcConfig,
+  disputeResolverConfig as testnetDrConfig,
+  disputeTemplateRegistryConfig as testnetDtrConfig,
+  evidenceModuleConfig as testnetEvidenceConfig,
+  policyRegistryConfig as testnetPolicyRegistryConfig,
+  transactionBatcherConfig as testnetBatcherConfig,
+  chainlinkRngConfig as testnetChainlinkRngConfig,
+  blockHashRngConfig as testnetBlockHashRngConfig,
+  pnkConfig as testnetPnkConfig,
+  klerosCoreSnapshotProxyConfig as testnetSnapshotProxyConfig,
+} from "./testnet.viem";
+import {
+  klerosCoreNeoConfig as mainnetCoreConfig,
+  sortitionModuleNeoConfig as mainnetSortitionConfig,
+  disputeKitClassicNeoConfig as mainnetDkcConfig,
+  disputeResolverNeoConfig as mainnetDrConfig,
+  disputeTemplateRegistryConfig as mainnetDtrConfig,
+  evidenceModuleConfig as mainnetEvidenceConfig,
+  policyRegistryConfig as mainnetPolicyRegistryConfig,
+  transactionBatcherConfig as mainnetBatcherConfig,
+  chainlinkRngConfig as mainnetChainlinkRngConfig,
+  randomizerRngConfig as mainnetRandomizerRngConfig,
+  blockHashRngConfig as mainnetBlockHashRngConfig,
+  pnkConfig as mainnetPnkConfig,
+  klerosCoreSnapshotProxyConfig as mainnetSnapshotProxyConfig,
+} from "./mainnet.viem";
+import {
+  KlerosCore,
+  KlerosCore__factory,
+  SortitionModule,
+  SortitionModule__factory,
+  DisputeKitClassic,
+  DisputeKitClassic__factory,
+  DisputeResolver,
+  DisputeResolver__factory,
+  DisputeTemplateRegistry,
+  DisputeTemplateRegistry__factory,
+  EvidenceModule,
+  EvidenceModule__factory,
+  PolicyRegistry,
+  PolicyRegistry__factory,
+  TransactionBatcher,
+  TransactionBatcher__factory,
+  ChainlinkRNG,
+  ChainlinkRNG__factory,
+  RandomizerRNG,
+  RandomizerRNG__factory,
+  BlockHashRNG,
+  BlockHashRNG__factory,
+  PNK,
+  PNK__factory,
+  KlerosCoreSnapshotProxy,
+  KlerosCoreSnapshotProxy__factory,
+  KlerosCoreUniversity,
+  KlerosCoreUniversity__factory,
+  SortitionModuleUniversity,
+  SortitionModuleUniversity__factory,
+  KlerosCoreNeo,
+  KlerosCoreNeo__factory,
+  SortitionModuleNeo,
+  SortitionModuleNeo__factory,
+} from "../typechain-types";
+import { type ContractConfig, type DeploymentName, deployments, getAddress } from "./utils";
+
+type CommonFactoriesConfigs = {
+  dkcConfig: ContractConfig;
+  drConfig: ContractConfig;
+  dtrConfig: ContractConfig;
+  evidenceConfig: ContractConfig;
+  policyRegistryConfig: ContractConfig;
+  batcherConfig: ContractConfig;
+  chainlinkRngConfig?: ContractConfig;
+  randomizerRngConfig?: ContractConfig;
+  blockHashRngConfig: ContractConfig;
+  pnkConfig: ContractConfig;
+  snapshotProxyConfig: ContractConfig;
+};
+
+type CommonFactories = {
+  disputeKitClassic: DisputeKitClassic;
+  disputeResolver: DisputeResolver;
+  disputeTemplateRegistry: DisputeTemplateRegistry;
+  evidence: EvidenceModule;
+  policyRegistry: PolicyRegistry;
+  transactionBatcher: TransactionBatcher;
+  chainlinkRng: ChainlinkRNG | null;
+  randomizerRng: RandomizerRNG | null;
+  blockHashRng: BlockHashRNG;
+  pnk: PNK;
+  klerosCoreSnapshotProxy: KlerosCoreSnapshotProxy;
+};
+
+function getCommonFactories(
+  configs: CommonFactoriesConfigs,
+  provider: ethers.Provider,
+  chainId: number
+): CommonFactories {
+  return {
+    disputeKitClassic: DisputeKitClassic__factory.connect(getAddress(configs.dkcConfig, chainId), provider),
+    disputeResolver: DisputeResolver__factory.connect(getAddress(configs.drConfig, chainId), provider),
+    disputeTemplateRegistry: DisputeTemplateRegistry__factory.connect(getAddress(configs.dtrConfig, chainId), provider),
+    evidence: EvidenceModule__factory.connect(getAddress(configs.evidenceConfig, chainId), provider),
+    policyRegistry: PolicyRegistry__factory.connect(getAddress(configs.policyRegistryConfig, chainId), provider),
+    transactionBatcher: TransactionBatcher__factory.connect(getAddress(configs.batcherConfig, chainId), provider),
+    chainlinkRng: configs.chainlinkRngConfig
+      ? ChainlinkRNG__factory.connect(getAddress(configs.chainlinkRngConfig, chainId), provider)
+      : null,
+    randomizerRng: configs.randomizerRngConfig
+      ? RandomizerRNG__factory.connect(getAddress(configs.randomizerRngConfig, chainId), provider)
+      : null,
+    blockHashRng: BlockHashRNG__factory.connect(getAddress(configs.blockHashRngConfig, chainId), provider),
+    pnk: PNK__factory.connect(getAddress(configs.pnkConfig, chainId), provider),
+    klerosCoreSnapshotProxy: KlerosCoreSnapshotProxy__factory.connect(
+      getAddress(configs.snapshotProxyConfig, chainId),
+      provider
+    ),
+  };
+}
+
+export const getContracts = async (provider: ethers.Provider, deployment: DeploymentName) => {
+  const { chainId } = deployments[deployment];
+  let klerosCore: KlerosCore | KlerosCoreNeo | KlerosCoreUniversity;
+  let sortition: SortitionModule | SortitionModuleNeo | SortitionModuleUniversity;
+  let commonFactories: CommonFactories;
+
+  switch (deployment) {
+    case "devnet": {
+      klerosCore = KlerosCore__factory.connect(getAddress(devnetCoreConfig, chainId), provider);
+      sortition = SortitionModule__factory.connect(getAddress(devnetSortitionConfig, chainId), provider);
+      commonFactories = getCommonFactories(
+        {
+          dkcConfig: devnetDkcConfig,
+          drConfig: devnetDrConfig,
+          dtrConfig: devnetDtrConfig,
+          evidenceConfig: devnetEvidenceConfig,
+          policyRegistryConfig: devnetPolicyRegistryConfig,
+          batcherConfig: devnetBatcherConfig,
+          chainlinkRngConfig: devnetChainlinkRngConfig,
+          blockHashRngConfig: devnetBlockHashRngConfig,
+          pnkConfig: devnetPnkConfig,
+          snapshotProxyConfig: devnetSnapshotProxyConfig,
+        },
+        provider,
+        chainId
+      );
+      break;
+    }
+    case "university": {
+      klerosCore = KlerosCoreUniversity__factory.connect(getAddress(devnetCoreUniversityConfig, chainId), provider);
+      sortition = SortitionModuleUniversity__factory.connect(
+        getAddress(devnetSortitionUniversityConfig, chainId),
+        provider
+      );
+      commonFactories = getCommonFactories(
+        {
+          dkcConfig: devnetDkcUniversityConfig,
+          drConfig: devnetDrUniversityConfig,
+          dtrConfig: devnetDtrConfig,
+          evidenceConfig: devnetEvidenceConfig,
+          policyRegistryConfig: devnetPolicyRegistryConfig,
+          batcherConfig: devnetBatcherConfig,
+          chainlinkRngConfig: devnetChainlinkRngConfig,
+          blockHashRngConfig: devnetBlockHashRngConfig,
+          pnkConfig: devnetPnkConfig,
+          snapshotProxyConfig: devnetSnapshotProxyConfig,
+        },
+        provider,
+        chainId
+      );
+      break;
+    }
+    case "testnet":
+      klerosCore = KlerosCore__factory.connect(getAddress(testnetCoreConfig, chainId), provider);
+      sortition = SortitionModule__factory.connect(getAddress(testnetSortitionConfig, chainId), provider);
+      commonFactories = getCommonFactories(
+        {
+          dkcConfig: testnetDkcConfig,
+          drConfig: testnetDrConfig,
+          dtrConfig: testnetDtrConfig,
+          evidenceConfig: testnetEvidenceConfig,
+          policyRegistryConfig: testnetPolicyRegistryConfig,
+          batcherConfig: testnetBatcherConfig,
+          chainlinkRngConfig: testnetChainlinkRngConfig,
+          blockHashRngConfig: testnetBlockHashRngConfig,
+          pnkConfig: testnetPnkConfig,
+          snapshotProxyConfig: testnetSnapshotProxyConfig,
+        },
+        provider,
+        chainId
+      );
+      break;
+    case "mainnetNeo":
+      klerosCore = KlerosCoreNeo__factory.connect(getAddress(mainnetCoreConfig, chainId), provider);
+      sortition = SortitionModuleNeo__factory.connect(getAddress(mainnetSortitionConfig, chainId), provider);
+      commonFactories = getCommonFactories(
+        {
+          dkcConfig: mainnetDkcConfig,
+          drConfig: mainnetDrConfig,
+          dtrConfig: mainnetDtrConfig,
+          evidenceConfig: mainnetEvidenceConfig,
+          policyRegistryConfig: mainnetPolicyRegistryConfig,
+          batcherConfig: mainnetBatcherConfig,
+          chainlinkRngConfig: mainnetChainlinkRngConfig,
+          randomizerRngConfig: mainnetRandomizerRngConfig,
+          blockHashRngConfig: mainnetBlockHashRngConfig,
+          pnkConfig: mainnetPnkConfig,
+          snapshotProxyConfig: mainnetSnapshotProxyConfig,
+        },
+        provider,
+        chainId
+      );
+      break;
+    default:
+      throw new Error(`Unsupported deployment: ${deployment}`);
+  }
+  return { klerosCore, sortition, ...commonFactories };
+};
diff --git a/contracts/deployments/contractsViem.ts b/contracts/deployments/contractsViem.ts
new file mode 100644
index 000000000..bbf3b9748
--- /dev/null
+++ b/contracts/deployments/contractsViem.ts
@@ -0,0 +1,289 @@
+import { type PublicClient, type WalletClient, getContract } from "viem";
+import { type ContractConfig, type DeploymentName, deployments, getAddress } from "./utils";
+import {
+  klerosCoreConfig as devnetCoreConfig,
+  sortitionModuleConfig as devnetSortitionConfig,
+  disputeKitClassicConfig as devnetDkcConfig,
+  disputeResolverConfig as devnetDrConfig,
+  disputeTemplateRegistryConfig as devnetDtrConfig,
+  evidenceModuleConfig as devnetEvidenceConfig,
+  policyRegistryConfig as devnetPolicyRegistryConfig,
+  transactionBatcherConfig as devnetBatcherConfig,
+  chainlinkRngConfig as devnetChainlinkRngConfig,
+  blockHashRngConfig as devnetBlockHashRngConfig,
+  pnkConfig as devnetPnkConfig,
+  klerosCoreSnapshotProxyConfig as devnetSnapshotProxyConfig,
+  klerosCoreUniversityConfig as devnetCoreUniversityConfig,
+  sortitionModuleUniversityConfig as devnetSortitionUniversityConfig,
+  disputeKitClassicUniversityConfig as devnetDkcUniversityConfig,
+  disputeResolverUniversityConfig as devnetDrUniversityConfig,
+} from "./devnet.viem";
+import {
+  klerosCoreConfig as testnetCoreConfig,
+  sortitionModuleConfig as testnetSortitionConfig,
+  disputeKitClassicConfig as testnetDkcConfig,
+  disputeResolverConfig as testnetDrConfig,
+  disputeTemplateRegistryConfig as testnetDtrConfig,
+  evidenceModuleConfig as testnetEvidenceConfig,
+  policyRegistryConfig as testnetPolicyRegistryConfig,
+  transactionBatcherConfig as testnetBatcherConfig,
+  chainlinkRngConfig as testnetChainlinkRngConfig,
+  blockHashRngConfig as testnetBlockHashRngConfig,
+  pnkConfig as testnetPnkConfig,
+  klerosCoreSnapshotProxyConfig as testnetSnapshotProxyConfig,
+} from "./testnet.viem";
+import {
+  klerosCoreNeoConfig as mainnetCoreConfig,
+  sortitionModuleNeoConfig as mainnetSortitionConfig,
+  disputeKitClassicNeoConfig as mainnetDkcConfig,
+  disputeResolverNeoConfig as mainnetDrConfig,
+  disputeTemplateRegistryConfig as mainnetDtrConfig,
+  evidenceModuleConfig as mainnetEvidenceConfig,
+  policyRegistryConfig as mainnetPolicyRegistryConfig,
+  transactionBatcherConfig as mainnetBatcherConfig,
+  chainlinkRngConfig as mainnetChainlinkRngConfig,
+  randomizerRngConfig as mainnetRandomizerRngConfig,
+  blockHashRngConfig as mainnetBlockHashRngConfig,
+  pnkConfig as mainnetPnkConfig,
+  klerosCoreSnapshotProxyConfig as mainnetSnapshotProxyConfig,
+} from "./mainnet.viem";
+
+type ContractInstance = {
+  address: `0x${string}`;
+  abi: readonly any[];
+};
+
+function getContractConfig({ config, chainId }: { config: ContractConfig; chainId: number }): ContractInstance {
+  return {
+    address: getAddress(config, chainId),
+    abi: config.abi,
+  };
+}
+
+type ContractInstances = {
+  klerosCore: ContractInstance;
+  sortition: ContractInstance;
+  disputeKitClassic: ContractInstance;
+  disputeResolver: ContractInstance;
+  disputeTemplateRegistry: ContractInstance;
+  evidence: ContractInstance;
+  policyRegistry: ContractInstance;
+  transactionBatcher: ContractInstance;
+  chainlinkRng?: ContractInstance;
+  randomizerRng?: ContractInstance;
+  blockHashRng: ContractInstance;
+  pnk: ContractInstance;
+  klerosCoreSnapshotProxy: ContractInstance;
+};
+
+function getCommonConfigs({
+  chainId,
+  configs,
+}: {
+  chainId: number;
+  configs: {
+    klerosCore: ContractConfig;
+    sortition: ContractConfig;
+    disputeKitClassic: ContractConfig;
+    disputeResolver: ContractConfig;
+    disputeTemplateRegistry: ContractConfig;
+    evidence: ContractConfig;
+    policyRegistry: ContractConfig;
+    transactionBatcher: ContractConfig;
+    blockHashRng: ContractConfig;
+    pnk: ContractConfig;
+    klerosCoreSnapshotProxy: ContractConfig;
+    chainlinkRng?: ContractConfig;
+    randomizerRng?: ContractConfig;
+  };
+}): ContractInstances {
+  const base: ContractInstances = {
+    klerosCore: getContractConfig({ config: configs.klerosCore, chainId }),
+    sortition: getContractConfig({ config: configs.sortition, chainId }),
+    disputeKitClassic: getContractConfig({ config: configs.disputeKitClassic, chainId }),
+    disputeResolver: getContractConfig({ config: configs.disputeResolver, chainId }),
+    disputeTemplateRegistry: getContractConfig({ config: configs.disputeTemplateRegistry, chainId }),
+    evidence: getContractConfig({ config: configs.evidence, chainId }),
+    policyRegistry: getContractConfig({ config: configs.policyRegistry, chainId }),
+    transactionBatcher: getContractConfig({ config: configs.transactionBatcher, chainId }),
+    blockHashRng: getContractConfig({ config: configs.blockHashRng, chainId }),
+    pnk: getContractConfig({ config: configs.pnk, chainId }),
+    klerosCoreSnapshotProxy: getContractConfig({ config: configs.klerosCoreSnapshotProxy, chainId }),
+  };
+
+  if (configs.chainlinkRng) base.chainlinkRng = getContractConfig({ config: configs.chainlinkRng, chainId });
+
+  if (configs.randomizerRng) base.randomizerRng = getContractConfig({ config: configs.randomizerRng, chainId });
+
+  return base;
+}
+
+export const getConfigs = ({ deployment }: { deployment: DeploymentName }): ContractInstances => {
+  const { chainId } = deployments[deployment];
+  switch (deployment) {
+    case "devnet":
+      return getCommonConfigs({
+        chainId,
+        configs: {
+          klerosCore: devnetCoreConfig,
+          sortition: devnetSortitionConfig,
+          disputeKitClassic: devnetDkcConfig,
+          disputeResolver: devnetDrConfig,
+          disputeTemplateRegistry: devnetDtrConfig,
+          evidence: devnetEvidenceConfig,
+          policyRegistry: devnetPolicyRegistryConfig,
+          transactionBatcher: devnetBatcherConfig,
+          blockHashRng: devnetBlockHashRngConfig,
+          pnk: devnetPnkConfig,
+          klerosCoreSnapshotProxy: devnetSnapshotProxyConfig,
+          chainlinkRng: devnetChainlinkRngConfig,
+        },
+      });
+
+    case "university":
+      return {
+        klerosCore: getContractConfig({ config: devnetCoreUniversityConfig, chainId }),
+        sortition: getContractConfig({ config: devnetSortitionUniversityConfig, chainId }),
+        disputeKitClassic: getContractConfig({ config: devnetDkcUniversityConfig, chainId }),
+        disputeResolver: getContractConfig({ config: devnetDrUniversityConfig, chainId }),
+        disputeTemplateRegistry: getContractConfig({ config: devnetDtrConfig, chainId }), // FIXME: should not be shared with devnet
+        evidence: getContractConfig({ config: devnetEvidenceConfig, chainId }), // Not arbitrator specific
+        policyRegistry: getContractConfig({ config: devnetPolicyRegistryConfig, chainId }), // Not arbitrator specific
+        transactionBatcher: getContractConfig({ config: devnetBatcherConfig, chainId }), // Not arbitrator specific
+        blockHashRng: getContractConfig({ config: devnetBlockHashRngConfig, chainId }), // Not used in university
+        pnk: getContractConfig({ config: devnetPnkConfig, chainId }), // Not arbitrator specific
+        klerosCoreSnapshotProxy: getContractConfig({ config: devnetSnapshotProxyConfig, chainId }), // Not used in university
+      };
+
+    case "testnet":
+      return getCommonConfigs({
+        chainId,
+        configs: {
+          klerosCore: testnetCoreConfig,
+          sortition: testnetSortitionConfig,
+          disputeKitClassic: testnetDkcConfig,
+          disputeResolver: testnetDrConfig,
+          disputeTemplateRegistry: testnetDtrConfig,
+          evidence: testnetEvidenceConfig,
+          policyRegistry: testnetPolicyRegistryConfig,
+          transactionBatcher: testnetBatcherConfig,
+          blockHashRng: testnetBlockHashRngConfig,
+          pnk: testnetPnkConfig,
+          klerosCoreSnapshotProxy: testnetSnapshotProxyConfig,
+          chainlinkRng: testnetChainlinkRngConfig,
+        },
+      });
+
+    case "mainnetNeo":
+      return getCommonConfigs({
+        chainId,
+        configs: {
+          klerosCore: mainnetCoreConfig,
+          sortition: mainnetSortitionConfig,
+          disputeKitClassic: mainnetDkcConfig,
+          disputeResolver: mainnetDrConfig,
+          disputeTemplateRegistry: mainnetDtrConfig,
+          evidence: mainnetEvidenceConfig,
+          policyRegistry: mainnetPolicyRegistryConfig,
+          transactionBatcher: mainnetBatcherConfig,
+          blockHashRng: mainnetBlockHashRngConfig,
+          pnk: mainnetPnkConfig,
+          klerosCoreSnapshotProxy: mainnetSnapshotProxyConfig,
+          chainlinkRng: mainnetChainlinkRngConfig,
+          randomizerRng: mainnetRandomizerRngConfig,
+        },
+      });
+
+    default:
+      throw new Error(`Unsupported deployment: ${deployment}`);
+  }
+};
+
+export const getContracts = ({
+  publicClient,
+  walletClient,
+  deployment,
+}: {
+  publicClient: PublicClient;
+  walletClient?: WalletClient;
+  deployment: DeploymentName;
+}) => {
+  const clientConfig = {
+    client: {
+      public: publicClient,
+      wallet: walletClient,
+    },
+  };
+  const contractConfigs = getConfigs({ deployment });
+  const klerosCore = getContract({
+    ...contractConfigs.klerosCore,
+    ...clientConfig,
+  });
+  const sortition = getContract({
+    ...contractConfigs.sortition,
+    ...clientConfig,
+  });
+  const disputeKitClassic = getContract({
+    ...contractConfigs.disputeKitClassic,
+    ...clientConfig,
+  });
+  const disputeResolver = getContract({
+    ...contractConfigs.disputeResolver,
+    ...clientConfig,
+  });
+  const disputeTemplateRegistry = getContract({
+    ...contractConfigs.disputeTemplateRegistry,
+    ...clientConfig,
+  });
+  const evidence = getContract({
+    ...contractConfigs.evidence,
+    ...clientConfig,
+  });
+  const policyRegistry = getContract({
+    ...contractConfigs.policyRegistry,
+    ...clientConfig,
+  });
+  const transactionBatcher = getContract({
+    ...contractConfigs.transactionBatcher,
+    ...clientConfig,
+  });
+  const chainlinkRng = contractConfigs.chainlinkRng
+    ? getContract({
+        ...contractConfigs.chainlinkRng,
+        ...clientConfig,
+      })
+    : undefined;
+  const randomizerRng = contractConfigs.randomizerRng
+    ? getContract({
+        ...contractConfigs.randomizerRng,
+        ...clientConfig,
+      })
+    : undefined;
+  const blockHashRng = getContract({
+    ...contractConfigs.blockHashRng,
+    ...clientConfig,
+  });
+  const pnk = getContract({
+    ...contractConfigs.pnk,
+    ...clientConfig,
+  });
+  const klerosCoreSnapshotProxy = getContract({
+    ...contractConfigs.klerosCoreSnapshotProxy,
+    ...clientConfig,
+  });
+  return {
+    klerosCore,
+    sortition,
+    disputeKitClassic,
+    disputeResolver,
+    disputeTemplateRegistry,
+    evidence,
+    policyRegistry,
+    transactionBatcher,
+    chainlinkRng,
+    randomizerRng,
+    blockHashRng,
+    pnk,
+    klerosCoreSnapshotProxy,
+  };
+};
diff --git a/contracts/deployments/index.ts b/contracts/deployments/index.ts
new file mode 100644
index 000000000..3479c5edf
--- /dev/null
+++ b/contracts/deployments/index.ts
@@ -0,0 +1,19 @@
+// Typechain Ethers v6 artifacts
+export * as arbitrum from "./arbitrum";
+export * as arbitrumSepolia from "./arbitrumSepolia";
+export * as arbitrumSepoliaDevnet from "./arbitrumSepoliaDevnet";
+
+// Viem artifacts
+export * as devnetViem from "./devnet.viem";
+export * as mainnetViem from "./mainnet.viem";
+export * as testnetViem from "./testnet.viem";
+
+// Typechain-types
+export * from "../typechain-types";
+
+// Common utils
+export * from "./utils";
+
+// Contracts getters
+export { getContracts as getContractsEthers } from "./contractsEthers";
+export { getContracts as getContractsViem } from "./contractsViem";
diff --git a/contracts/deployments/utils.ts b/contracts/deployments/utils.ts
new file mode 100644
index 000000000..e2711a377
--- /dev/null
+++ b/contracts/deployments/utils.ts
@@ -0,0 +1,29 @@
+import { arbitrum, arbitrumSepolia } from "viem/chains";
+
+export const deployments = {
+  devnet: {
+    chainId: arbitrumSepolia.id,
+  },
+  university: {
+    chainId: arbitrumSepolia.id,
+  },
+  testnet: {
+    chainId: arbitrumSepolia.id,
+  },
+  mainnetNeo: {
+    chainId: arbitrum.id,
+  },
+} as const;
+
+export type DeploymentName = keyof typeof deployments;
+
+export type ContractConfig = {
+  address: Record<number, `0x${string}`>;
+  abi: readonly any[];
+};
+
+export function getAddress(config: ContractConfig, chainId: number): `0x${string}` {
+  const address = config.address[chainId];
+  if (!address) throw new Error(`No address found for chainId ${chainId}`);
+  return address;
+}
diff --git a/contracts/package.json b/contracts/package.json
index 641cac636..0b4d3dd75 100644
--- a/contracts/package.json
+++ b/contracts/package.json
@@ -1,8 +1,44 @@
 {
   "name": "@kleros/kleros-v2-contracts",
-  "version": "0.8.1",
+  "version": "0.9.3",
   "description": "Smart contracts for Kleros version 2",
-  "main": "typechain-types/index.ts",
+  "main": "./cjs/deployments/index.js",
+  "module": "./esm/deployments/index.js",
+  "types": "./types/deployments/index.d.ts",
+  "exports": {
+    ".": {
+      "types": "./types/deployments/index.d.ts",
+      "import": "./esm/deployments/index.js",
+      "require": "./cjs/deployments/index.js",
+      "default": "./esm/deployments/index.js"
+    },
+    "./cjs/deployments": {
+      "types": "./types/deployments/index.d.ts",
+      "require": "./cjs/deployments/index.js",
+      "default": "./cjs/deployments/index.js"
+    },
+    "./esm/deployments": {
+      "types": "./types/deployments/index.d.ts",
+      "import": "./esm/deployments/index.js",
+      "default": "./esm/deployments/index.js"
+    }
+  },
+  "files": [
+    "types",
+    "esm",
+    "cjs",
+    "arbitration",
+    "gateway",
+    "kleros-v1",
+    "libraries",
+    "proxy",
+    "rng",
+    "token",
+    "utils",
+    "typechain-types",
+    "deployments",
+    "index.ts"
+  ],
   "repository": "git@github.com:kleros/kleros-v2.git",
   "homepage": "https://github.com/kleros/kleros-v2/tree/master/contracts#readme",
   "author": "Kleros",
@@ -64,10 +100,10 @@
     "release:minor": "scripts/publish.sh minor",
     "release:major": "scripts/publish.sh major",
     "tenderly-verify": "hardhat tenderly:verify",
-    "build:all": "yarn rimraf ./dist && yarn build:cjs && yarn build:esm && yarn build:types",
-    "build:cjs": "tsc --project tsconfig.json --module commonjs --outDir ./dist/cjs && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json",
-    "build:esm": "tsc --project tsconfig.json --module es2020 --outDir ./dist/esm && echo '{\"type\": \"module\"}' > ./dist/esm/package.json",
-    "build:types": "tsc --project tsconfig.json --module esnext --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap"
+    "build:all": "rm -rf ./dist && yarn build:cjs && yarn build:esm && yarn build:types",
+    "build:cjs": "tsc --project tsconfig-release.json --module CommonJS --moduleResolution Node --outDir ./dist/cjs && echo '{\"type\": \"commonjs\"}' > ./dist/cjs/package.json",
+    "build:esm": "tsc --project tsconfig-release.json --module NodeNext --moduleResolution NodeNext --outDir ./dist/esm && echo '{\"type\": \"module\"}' > ./dist/esm/package.json",
+    "build:types": "tsc --project tsconfig-release.json --declarationDir ./dist/types --emitDeclarationOnly --declaration --declarationMap"
   },
   "devDependencies": {
     "@defi-wonderland/natspec-smells": "^1.1.5",
@@ -84,6 +120,7 @@
     "@types/chai": "^4.3.20",
     "@types/mocha": "^10.0.10",
     "@types/node": "^20.17.6",
+    "@types/sinon": "^17.0.4",
     "@wagmi/cli": "^2.2.0",
     "abitype": "^0.10.3",
     "chai": "^4.5.0",
@@ -109,6 +146,7 @@
     "prettier": "^3.3.3",
     "prettier-plugin-solidity": "^1.4.2",
     "shelljs": "^0.8.5",
+    "sinon": "^20.0.0",
     "solhint-plugin-prettier": "^0.1.0",
     "solidity-coverage": "^0.8.13",
     "ts-node": "^10.9.2",
diff --git a/contracts/scripts/publish.sh b/contracts/scripts/publish.sh
index cef61141d..cfc441064 100755
--- a/contracts/scripts/publish.sh
+++ b/contracts/scripts/publish.sh
@@ -1,6 +1,8 @@
-#!/bin/bash
+#!/usr/bin/env bash
 
-SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
+shopt -s extglob
+
+SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"
 
 #--------------------------------------
 # Error handling
@@ -9,7 +11,7 @@ SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
 set -Ee
 function _catch {
     # Don't propagate to outer shell
-    exit 0 
+    exit 0
 }
 function _finally {
     # TODO: rollback version bump
@@ -20,31 +22,57 @@ trap _finally EXIT
 
 #--------------------------------------
 
+if [[ "$PWD" != */contracts ]]; then
+    echo "Error: This script must be run from the contracts directory"
+    exit 1
+fi
+
+# Bump the version
 yarn version $1
 
+# Recompile the contracts
+yarn clean
+yarn build
+
+# Rebuild the typechain without mocks
+rm -rf artifacts/src/**/*[mM]ock*
+find artifacts/src -name "*.dbg.json" -type f -delete
+rm -rf typechain-types
+yarn typechain --out-dir typechain-types --glob 'artifacts/src/**/*.json' --target ethers-v6 --node16-modules
+
+# Generate the viem artifacts
 yarn viem:generate-devnet
 yarn viem:generate-testnet
 yarn viem:generate-mainnet
+
+# Generate the Hardhat artifacts
 yarn export:devnet
 yarn export:testnet
 yarn export:mainnet
 
+# Build the dist
 rm -rf dist
 mkdir dist
+yarn build:all
+
+# Copy the README and contracts
+cp -pr README.md src/ dist/
 
-yarn tsc --project tsconfig-release.json --outDir ./dist
-cp -pr README.md src/ dist/ 
+# Remove unwanted files
 rm -rf dist/config
 rm -rf dist/deploy
 rm -rf dist/scripts
 rm -rf dist/test
+rm -rf dist/**/mock
+rm -rf dist/**/*Mock*
 rm -rf dist/hardhat.config*
 rm -rf dist/deployments/**/solcInputs
 rm -rf dist/deployments/localhost
 rm -rf dist/deployments/hardhat
 rm -rf dist/deployments/hardhat.viem.ts
-jq 'del(.scripts.prepare)' package.json > dist/package.json
+jq 'del(.scripts.prepare)' package.json >dist/package.json
 
+# Publish the package
 cd dist
 npm publish
 cd -
diff --git a/contracts/scripts/utils/contracts.ts b/contracts/scripts/utils/contracts.ts
index 933f889c8..31321fbfd 100644
--- a/contracts/scripts/utils/contracts.ts
+++ b/contracts/scripts/utils/contracts.ts
@@ -27,6 +27,11 @@ export const Cores = {
 
 export type Core = (typeof Cores)[keyof typeof Cores];
 
+/**
+ * Get contract names by specifying the coreType (BASE, NEO, UNIVERSITY).
+ * @param coreType - Core type
+ * @returns Contract names
+ */
 export const getContractNames = (coreType: Core) => {
   const coreSpecificNames = {
     [Cores.NEO]: {
@@ -65,34 +70,34 @@ export const getContractNames = (coreType: Core) => {
   };
 };
 
+/**
+ * Get contracts by specifying the coreType (BASE, NEO, UNIVERSITY).
+ * @param hre - Hardhat runtime environment
+ * @param coreType - Core type
+ * @returns Contracts
+ */
 export const getContracts = async (hre: HardhatRuntimeEnvironment, coreType: Core) => {
   const { ethers } = hre;
   let core: KlerosCore | KlerosCoreNeo | KlerosCoreUniversity;
   let sortition: SortitionModule | SortitionModuleNeo | SortitionModuleUniversity;
-  let disputeKitClassic: DisputeKitClassic;
-  let disputeResolver: DisputeResolver;
   switch (coreType) {
     case Cores.NEO:
       core = await ethers.getContract<KlerosCoreNeo>(getContractNames(coreType).core);
       sortition = await ethers.getContract<SortitionModuleNeo>(getContractNames(coreType).sortition);
-      disputeKitClassic = await ethers.getContract<DisputeKitClassic>(getContractNames(coreType).disputeKitClassic);
-      disputeResolver = await ethers.getContract<DisputeResolver>(getContractNames(coreType).disputeResolver);
       break;
     case Cores.BASE:
       core = await ethers.getContract<KlerosCore>(getContractNames(coreType).core);
       sortition = await ethers.getContract<SortitionModule>(getContractNames(coreType).sortition);
-      disputeKitClassic = await ethers.getContract<DisputeKitClassic>(getContractNames(coreType).disputeKitClassic);
-      disputeResolver = await ethers.getContract<DisputeResolver>(getContractNames(coreType).disputeResolver);
       break;
     case Cores.UNIVERSITY:
       core = await ethers.getContract<KlerosCoreUniversity>(getContractNames(coreType).core);
       sortition = await ethers.getContract<SortitionModuleUniversity>(getContractNames(coreType).sortition);
-      disputeKitClassic = await ethers.getContract<DisputeKitClassic>(getContractNames(coreType).disputeKitClassic);
-      disputeResolver = await ethers.getContract<DisputeResolver>(getContractNames(coreType).disputeResolver);
       break;
     default:
       throw new Error("Invalid core type, must be one of BASE, NEO, or UNIVERSITY");
   }
+  const disputeKitClassic = await ethers.getContract<DisputeKitClassic>(getContractNames(coreType).disputeKitClassic);
+  const disputeResolver = await ethers.getContract<DisputeResolver>(getContractNames(coreType).disputeResolver);
   const disputeTemplateRegistry = await ethers.getContract<DisputeTemplateRegistry>(
     getContractNames(coreType).disputeTemplateRegistry
   );
@@ -123,6 +128,11 @@ export const getContracts = async (hre: HardhatRuntimeEnvironment, coreType: Cor
   };
 };
 
+/**
+ * Get contracts by inferring the coreType (BASE, NEO, UNIVERSITY) from the network, most convenient for most cases.
+ * @param hre - Hardhat runtime environment
+ * @returns Contracts
+ */
 export const getContractsFromNetwork = async (hre: HardhatRuntimeEnvironment) => {
   const { network } = hre;
   if (network.name === "arbitrumSepoliaDevnet" || network.name === "arbitrumSepolia") {
@@ -134,6 +144,11 @@ export const getContractsFromNetwork = async (hre: HardhatRuntimeEnvironment) =>
   }
 };
 
+/**
+ * Get contract names by inferring the coreType (BASE, NEO, UNIVERSITY) from the network, most convenient for most cases.
+ * @param hre - Hardhat runtime environment
+ * @returns Contract names
+ */
 export const getContractNamesFromNetwork = async (hre: HardhatRuntimeEnvironment) => {
   const { network } = hre;
   if (network.name === "arbitrumSepoliaDevnet" || network.name === "arbitrumSepolia") {
diff --git a/contracts/test/integration/getContractsEthers.test.ts b/contracts/test/integration/getContractsEthers.test.ts
new file mode 100644
index 000000000..7c46f33c7
--- /dev/null
+++ b/contracts/test/integration/getContractsEthers.test.ts
@@ -0,0 +1,259 @@
+import { expect } from "chai";
+import { ethers } from "ethers";
+import { arbitrum, arbitrumSepolia } from "viem/chains";
+import { getContracts } from "../../deployments/contractsEthers";
+import {
+  KlerosCore__factory,
+  KlerosCoreNeo__factory,
+  KlerosCoreUniversity__factory,
+  SortitionModule__factory,
+  SortitionModuleNeo__factory,
+  SortitionModuleUniversity__factory,
+  DisputeKitClassic__factory,
+  DisputeResolver__factory,
+  DisputeTemplateRegistry__factory,
+  EvidenceModule__factory,
+  PolicyRegistry__factory,
+  TransactionBatcher__factory,
+  ChainlinkRNG__factory,
+  RandomizerRNG__factory,
+  BlockHashRNG__factory,
+  PNK__factory,
+  KlerosCoreSnapshotProxy__factory,
+} from "../../typechain-types";
+import { getActualAddress } from "../utils/getActualAddress";
+
+// Network names for deployments
+const NETWORKS = {
+  DEVNET: "arbitrumSepoliaDevnet",
+  TESTNET: "arbitrumSepolia",
+  MAINNET: "arbitrum",
+} as const;
+
+type NetworkType = (typeof NETWORKS)[keyof typeof NETWORKS];
+
+type ContractMapping = {
+  [K in keyof Awaited<ReturnType<typeof getContracts>>]: {
+    name: string;
+    optional?: boolean;
+  };
+};
+
+const baseContractMapping: ContractMapping = {
+  klerosCore: { name: "KlerosCore" },
+  sortition: { name: "SortitionModule" },
+  disputeKitClassic: { name: "DisputeKitClassic" },
+  disputeResolver: { name: "DisputeResolver" },
+  disputeTemplateRegistry: { name: "DisputeTemplateRegistry" },
+  evidence: { name: "EvidenceModule" },
+  policyRegistry: { name: "PolicyRegistry" },
+  transactionBatcher: { name: "TransactionBatcher" },
+  chainlinkRng: { name: "ChainlinkRNG", optional: true },
+  randomizerRng: { name: "RandomizerRNG", optional: true },
+  blockHashRng: { name: "BlockHashRNG" },
+  pnk: { name: "PNK" },
+  klerosCoreSnapshotProxy: { name: "KlerosCoreSnapshotProxy" },
+};
+
+const universityContractMapping: ContractMapping = {
+  klerosCore: { name: "KlerosCoreUniversity" },
+  sortition: { name: "SortitionModuleUniversity" },
+  disputeKitClassic: { name: "DisputeKitClassicUniversity" },
+  disputeResolver: { name: "DisputeResolverUniversity" },
+  disputeTemplateRegistry: { name: "DisputeTemplateRegistry" },
+  evidence: { name: "EvidenceModule" },
+  policyRegistry: { name: "PolicyRegistry" },
+  transactionBatcher: { name: "TransactionBatcher" },
+  chainlinkRng: { name: "ChainlinkRNG", optional: true },
+  randomizerRng: { name: "RandomizerRNG", optional: true },
+  blockHashRng: { name: "BlockHashRNG" },
+  pnk: { name: "PNK" },
+  klerosCoreSnapshotProxy: { name: "KlerosCoreSnapshotProxy" },
+};
+
+const neoContractMapping: ContractMapping = {
+  klerosCore: { name: "KlerosCoreNeo" },
+  sortition: { name: "SortitionModuleNeo" },
+  disputeKitClassic: { name: "DisputeKitClassicNeo" },
+  disputeResolver: { name: "DisputeResolverNeo" },
+  disputeTemplateRegistry: { name: "DisputeTemplateRegistry" },
+  evidence: { name: "EvidenceModule" },
+  policyRegistry: { name: "PolicyRegistry" },
+  transactionBatcher: { name: "TransactionBatcher" },
+  chainlinkRng: { name: "ChainlinkRNG", optional: false },
+  randomizerRng: { name: "RandomizerRNG", optional: false },
+  blockHashRng: { name: "BlockHashRNG" },
+  pnk: { name: "PNK" },
+  klerosCoreSnapshotProxy: { name: "KlerosCoreSnapshotProxy" },
+};
+
+describe("getContractsEthers", () => {
+  // Use real providers for each network
+  const arbitrumSepoliaProvider = new ethers.JsonRpcProvider("https://sepolia-rollup.arbitrum.io/rpc");
+  const arbitrumProvider = new ethers.JsonRpcProvider("https://arb1.arbitrum.io/rpc");
+
+  function getConstructor<T extends { constructor: any }>(
+    factory: { connect: (address: string, provider: ethers.Provider) => T },
+    provider: ethers.Provider
+  ) {
+    return factory.connect("0x0", provider).constructor;
+  }
+
+  function verifyCommonContractInstances(
+    contracts: Awaited<ReturnType<typeof getContracts>>,
+    provider: ethers.Provider
+  ) {
+    expect(contracts.disputeKitClassic).to.be.instanceOf(getConstructor(DisputeKitClassic__factory, provider));
+    expect(contracts.disputeResolver).to.be.instanceOf(getConstructor(DisputeResolver__factory, provider));
+    expect(contracts.disputeTemplateRegistry).to.be.instanceOf(
+      getConstructor(DisputeTemplateRegistry__factory, provider)
+    );
+    expect(contracts.evidence).to.be.instanceOf(getConstructor(EvidenceModule__factory, provider));
+    expect(contracts.policyRegistry).to.be.instanceOf(getConstructor(PolicyRegistry__factory, provider));
+    expect(contracts.transactionBatcher).to.be.instanceOf(getConstructor(TransactionBatcher__factory, provider));
+    expect(contracts.blockHashRng).to.be.instanceOf(getConstructor(BlockHashRNG__factory, provider));
+    expect(contracts.pnk).to.be.instanceOf(getConstructor(PNK__factory, provider));
+    expect(contracts.klerosCoreSnapshotProxy).to.be.instanceOf(
+      getConstructor(KlerosCoreSnapshotProxy__factory, provider)
+    );
+    if (contracts.chainlinkRng) {
+      expect(contracts.chainlinkRng).to.be.instanceOf(getConstructor(ChainlinkRNG__factory, provider));
+    }
+    if (contracts.randomizerRng) {
+      expect(contracts.randomizerRng).to.be.instanceOf(getConstructor(RandomizerRNG__factory, provider));
+    }
+  }
+
+  // Helper to verify contract addresses
+  async function verifyContractAddress(address: Promise<string>) {
+    const resolvedAddress = await address;
+    expect(resolvedAddress).to.match(/^0x[a-fA-F0-9]{40}$/);
+    expect(resolvedAddress).to.not.equal("0x0000000000000000000000000000000000000000");
+  }
+
+  // Helper to verify all contract addresses
+  async function verifyAllContractAddresses(contracts: Awaited<ReturnType<typeof getContracts>>) {
+    await verifyContractAddress(contracts.klerosCore.getAddress());
+    await verifyContractAddress(contracts.sortition.getAddress());
+    await verifyContractAddress(contracts.disputeKitClassic.getAddress());
+    await verifyContractAddress(contracts.disputeResolver.getAddress());
+    await verifyContractAddress(contracts.disputeTemplateRegistry.getAddress());
+    await verifyContractAddress(contracts.evidence.getAddress());
+    await verifyContractAddress(contracts.policyRegistry.getAddress());
+    await verifyContractAddress(contracts.transactionBatcher.getAddress());
+    if (contracts.chainlinkRng) {
+      await verifyContractAddress(contracts.chainlinkRng.getAddress());
+    }
+    if (contracts.randomizerRng) {
+      await verifyContractAddress(contracts.randomizerRng.getAddress());
+    }
+    await verifyContractAddress(contracts.blockHashRng.getAddress());
+    await verifyContractAddress(contracts.pnk.getAddress());
+    await verifyContractAddress(contracts.klerosCoreSnapshotProxy.getAddress());
+  }
+
+  // Helper to verify contract addresses against deployment files
+  async function verifyDeployedAddresses(
+    contracts: Awaited<ReturnType<typeof getContracts>>,
+    network: NetworkType,
+    contractMapping: ContractMapping
+  ) {
+    for (const [key, { name, optional }] of Object.entries(contractMapping)) {
+      const contract = contracts[key as keyof typeof contracts];
+      if (contract === null) {
+        if (!optional) {
+          throw new Error(`Required contract ${name} is null`);
+        }
+        continue;
+      }
+      expect(await contract.getAddress()).to.equal(await getActualAddress(network, name));
+    }
+  }
+
+  it("should return correct contract instances for devnet", async () => {
+    const contracts = await getContracts(arbitrumSepoliaProvider, "devnet");
+
+    // Verify chain ID
+    const network = await arbitrumSepoliaProvider.getNetwork();
+    expect(network.chainId).to.equal(arbitrumSepolia.id);
+
+    // Verify contract instances
+    expect(contracts.klerosCore).to.be.instanceOf(getConstructor(KlerosCore__factory, arbitrumSepoliaProvider));
+    expect(contracts.sortition).to.be.instanceOf(getConstructor(SortitionModule__factory, arbitrumSepoliaProvider));
+    verifyCommonContractInstances(contracts, arbitrumSepoliaProvider);
+    expect(contracts.chainlinkRng).to.not.be.null;
+    expect(contracts.randomizerRng).to.be.null;
+
+    // Verify all contract addresses
+    await verifyAllContractAddresses(contracts);
+    await verifyDeployedAddresses(contracts, NETWORKS.DEVNET, baseContractMapping);
+  });
+
+  it("should return correct contract instances for university", async () => {
+    const contracts = await getContracts(arbitrumSepoliaProvider, "university");
+
+    // Verify chain ID
+    const network = await arbitrumSepoliaProvider.getNetwork();
+    expect(network.chainId).to.equal(arbitrumSepolia.id);
+
+    // Verify contract instances
+    expect(contracts.klerosCore).to.be.instanceOf(
+      getConstructor(KlerosCoreUniversity__factory, arbitrumSepoliaProvider)
+    );
+    expect(contracts.sortition).to.be.instanceOf(
+      getConstructor(SortitionModuleUniversity__factory, arbitrumSepoliaProvider)
+    );
+    verifyCommonContractInstances(contracts, arbitrumSepoliaProvider);
+    expect(contracts.chainlinkRng).to.not.be.null;
+    expect(contracts.randomizerRng).to.be.null;
+
+    // Verify all contract addresses
+    await verifyAllContractAddresses(contracts);
+    await verifyDeployedAddresses(contracts, NETWORKS.DEVNET, universityContractMapping);
+  });
+
+  it("should return correct contract instances for testnet", async () => {
+    const contracts = await getContracts(arbitrumSepoliaProvider, "testnet");
+
+    // Verify chain ID
+    const network = await arbitrumSepoliaProvider.getNetwork();
+    expect(network.chainId).to.equal(arbitrumSepolia.id);
+
+    // Verify contract instances
+    expect(contracts.klerosCore).to.be.instanceOf(getConstructor(KlerosCore__factory, arbitrumSepoliaProvider));
+    expect(contracts.sortition).to.be.instanceOf(getConstructor(SortitionModule__factory, arbitrumSepoliaProvider));
+    verifyCommonContractInstances(contracts, arbitrumSepoliaProvider);
+    expect(contracts.chainlinkRng).to.not.be.null;
+    expect(contracts.randomizerRng).to.be.null;
+
+    // Verify all contract addresses
+    await verifyAllContractAddresses(contracts);
+    await verifyDeployedAddresses(contracts, NETWORKS.TESTNET, baseContractMapping);
+  });
+
+  it("should return correct contract instances for mainnetNeo", async () => {
+    const contracts = await getContracts(arbitrumProvider, "mainnetNeo");
+
+    // Verify chain ID
+    const network = await arbitrumProvider.getNetwork();
+    expect(network.chainId).to.equal(arbitrum.id);
+
+    // Verify contract instances
+    expect(contracts.klerosCore).to.be.instanceOf(getConstructor(KlerosCoreNeo__factory, arbitrumProvider));
+    expect(contracts.sortition).to.be.instanceOf(getConstructor(SortitionModuleNeo__factory, arbitrumProvider));
+    verifyCommonContractInstances(contracts, arbitrumProvider);
+    expect(contracts.chainlinkRng).to.not.be.null;
+    expect(contracts.randomizerRng).to.not.be.null;
+
+    // Verify all contract addresses
+    await verifyAllContractAddresses(contracts);
+    await verifyDeployedAddresses(contracts, NETWORKS.MAINNET, neoContractMapping);
+  });
+
+  it("should throw error for unsupported deployment", async () => {
+    // @ts-expect-error Testing invalid deployment
+    await expect(getContracts(arbitrumSepoliaProvider, "invalid")).to.be.rejectedWith(
+      /Unsupported deployment|Cannot destructure property/
+    );
+  });
+});
diff --git a/contracts/test/integration/getContractsViem.test.ts b/contracts/test/integration/getContractsViem.test.ts
new file mode 100644
index 000000000..58fdfd0c2
--- /dev/null
+++ b/contracts/test/integration/getContractsViem.test.ts
@@ -0,0 +1,220 @@
+import { expect } from "chai";
+import { createPublicClient, http } from "viem";
+import { arbitrum, arbitrumSepolia } from "viem/chains";
+import { getContracts } from "../../deployments/contractsViem";
+import { getActualAddress } from "../utils/getActualAddress";
+
+// Network names for deployments
+const NETWORKS = {
+  DEVNET: "arbitrumSepoliaDevnet",
+  TESTNET: "arbitrumSepolia",
+  MAINNET: "arbitrum",
+} as const;
+
+type NetworkType = (typeof NETWORKS)[keyof typeof NETWORKS];
+
+type ContractMapping = {
+  [K in keyof ReturnType<typeof getContracts>]: {
+    name: string;
+    optional?: boolean;
+  };
+};
+
+const baseContractMapping: ContractMapping = {
+  klerosCore: { name: "KlerosCore" },
+  sortition: { name: "SortitionModule" },
+  disputeKitClassic: { name: "DisputeKitClassic" },
+  disputeResolver: { name: "DisputeResolver" },
+  disputeTemplateRegistry: { name: "DisputeTemplateRegistry" },
+  evidence: { name: "EvidenceModule" },
+  policyRegistry: { name: "PolicyRegistry" },
+  transactionBatcher: { name: "TransactionBatcher" },
+  chainlinkRng: { name: "ChainlinkRNG", optional: true },
+  randomizerRng: { name: "RandomizerRNG", optional: true },
+  blockHashRng: { name: "BlockHashRNG" },
+  pnk: { name: "PNK" },
+  klerosCoreSnapshotProxy: { name: "KlerosCoreSnapshotProxy" },
+};
+
+const universityContractMapping: ContractMapping = {
+  klerosCore: { name: "KlerosCoreUniversity" },
+  sortition: { name: "SortitionModuleUniversity" },
+  disputeKitClassic: { name: "DisputeKitClassicUniversity" },
+  disputeResolver: { name: "DisputeResolverUniversity" },
+  disputeTemplateRegistry: { name: "DisputeTemplateRegistry" },
+  evidence: { name: "EvidenceModule" },
+  policyRegistry: { name: "PolicyRegistry" },
+  transactionBatcher: { name: "TransactionBatcher" },
+  chainlinkRng: { name: "ChainlinkRNG", optional: true },
+  randomizerRng: { name: "RandomizerRNG", optional: true },
+  blockHashRng: { name: "BlockHashRNG" },
+  pnk: { name: "PNK" },
+  klerosCoreSnapshotProxy: { name: "KlerosCoreSnapshotProxy" },
+};
+
+const neoContractMapping: ContractMapping = {
+  klerosCore: { name: "KlerosCoreNeo" },
+  sortition: { name: "SortitionModuleNeo" },
+  disputeKitClassic: { name: "DisputeKitClassicNeo" },
+  disputeResolver: { name: "DisputeResolverNeo" },
+  disputeTemplateRegistry: { name: "DisputeTemplateRegistry" },
+  evidence: { name: "EvidenceModule" },
+  policyRegistry: { name: "PolicyRegistry" },
+  transactionBatcher: { name: "TransactionBatcher" },
+  chainlinkRng: { name: "ChainlinkRNG", optional: false },
+  randomizerRng: { name: "RandomizerRNG", optional: false },
+  blockHashRng: { name: "BlockHashRNG" },
+  pnk: { name: "PNK" },
+  klerosCoreSnapshotProxy: { name: "KlerosCoreSnapshotProxy" },
+};
+
+describe("getContractsViem", () => {
+  // Create Viem clients for testing
+  const arbitrumSepoliaClient = createPublicClient({
+    chain: arbitrumSepolia,
+    transport: http("https://sepolia-rollup.arbitrum.io/rpc"),
+  });
+
+  const arbitrumClient = createPublicClient({
+    chain: arbitrum,
+    transport: http("https://arb1.arbitrum.io/rpc"),
+  });
+
+  // Helper to verify contract instance
+  function verifyContractInstance(contract: any) {
+    expect(contract).to.have.property("address");
+    expect(contract).to.have.property("abi");
+    expect(contract.address).to.match(/^0x[a-fA-F0-9]{40}$/);
+    expect(contract.address).to.not.equal("0x0000000000000000000000000000000000000000");
+  }
+
+  // Helper to verify all contract instances
+  function verifyAllContractInstances(contracts: ReturnType<typeof getContracts>) {
+    verifyContractInstance(contracts.klerosCore);
+    verifyContractInstance(contracts.sortition);
+    verifyContractInstance(contracts.disputeKitClassic);
+    verifyContractInstance(contracts.disputeResolver);
+    verifyContractInstance(contracts.disputeTemplateRegistry);
+    verifyContractInstance(contracts.evidence);
+    verifyContractInstance(contracts.policyRegistry);
+    verifyContractInstance(contracts.transactionBatcher);
+    verifyContractInstance(contracts.blockHashRng);
+    verifyContractInstance(contracts.pnk);
+    verifyContractInstance(contracts.klerosCoreSnapshotProxy);
+
+    if (contracts.chainlinkRng) {
+      verifyContractInstance(contracts.chainlinkRng);
+    }
+    if (contracts.randomizerRng) {
+      verifyContractInstance(contracts.randomizerRng);
+    }
+  }
+
+  // Helper to verify deployed addresses
+  async function verifyDeployedAddresses(
+    contracts: ReturnType<typeof getContracts>,
+    network: NetworkType,
+    contractMapping: ContractMapping
+  ) {
+    for (const [key, { name, optional }] of Object.entries(contractMapping)) {
+      const contract = contracts[key as keyof typeof contracts];
+      if (!contract) {
+        if (!optional) {
+          throw new Error(`Required contract ${name} is null`);
+        }
+        continue;
+      }
+      expect(contract.address).to.equal(await getActualAddress(network, name));
+    }
+  }
+
+  it("should return correct contract instances for devnet", async () => {
+    const contracts = getContracts({
+      publicClient: arbitrumSepoliaClient,
+      deployment: "devnet",
+    });
+
+    // Verify chain ID
+    expect(arbitrumSepoliaClient.chain.id).to.equal(arbitrumSepolia.id);
+
+    // Verify all contract instances
+    verifyAllContractInstances(contracts);
+
+    // Verify specific RNG instances
+    expect(contracts.chainlinkRng).to.not.be.undefined;
+    expect(contracts.randomizerRng).to.be.undefined;
+
+    // Verify deployed addresses
+    await verifyDeployedAddresses(contracts, NETWORKS.DEVNET, baseContractMapping);
+  });
+
+  it("should return correct contract instances for university", async () => {
+    const contracts = getContracts({
+      publicClient: arbitrumSepoliaClient,
+      deployment: "university",
+    });
+
+    // Verify chain ID
+    expect(arbitrumSepoliaClient.chain.id).to.equal(arbitrumSepolia.id);
+
+    // Verify all contract instances
+    verifyAllContractInstances(contracts);
+
+    // Verify specific RNG instances
+    expect(contracts.chainlinkRng).to.be.undefined;
+    expect(contracts.randomizerRng).to.be.undefined;
+
+    // Verify deployed addresses
+    await verifyDeployedAddresses(contracts, NETWORKS.DEVNET, universityContractMapping);
+  });
+
+  it("should return correct contract instances for testnet", async () => {
+    const contracts = getContracts({
+      publicClient: arbitrumSepoliaClient,
+      deployment: "testnet",
+    });
+
+    // Verify chain ID
+    expect(arbitrumSepoliaClient.chain.id).to.equal(arbitrumSepolia.id);
+
+    // Verify all contract instances
+    verifyAllContractInstances(contracts);
+
+    // Verify specific RNG instances
+    expect(contracts.chainlinkRng).to.not.be.undefined;
+    expect(contracts.randomizerRng).to.be.undefined;
+
+    // Verify deployed addresses
+    await verifyDeployedAddresses(contracts, NETWORKS.TESTNET, baseContractMapping);
+  });
+
+  it("should return correct contract instances for mainnetNeo", async () => {
+    const contracts = getContracts({
+      publicClient: arbitrumClient,
+      deployment: "mainnetNeo",
+    });
+
+    // Verify chain ID
+    expect(arbitrumClient.chain.id).to.equal(arbitrum.id);
+
+    // Verify all contract instances
+    verifyAllContractInstances(contracts);
+
+    // Verify specific RNG instances
+    expect(contracts.chainlinkRng).to.not.be.undefined;
+    expect(contracts.randomizerRng).to.not.be.undefined;
+
+    // Verify deployed addresses
+    await verifyDeployedAddresses(contracts, NETWORKS.MAINNET, neoContractMapping);
+  });
+
+  it("should throw error for unsupported deployment", () => {
+    expect(() =>
+      getContracts({
+        publicClient: arbitrumSepoliaClient,
+        // @ts-expect-error Testing invalid deployment
+        deployment: "invalid",
+      })
+    ).to.throw(/Cannot destructure property 'chainId'/);
+  });
+});
diff --git a/contracts/test/utils/getActualAddress.test.ts b/contracts/test/utils/getActualAddress.test.ts
new file mode 100644
index 000000000..c80569a07
--- /dev/null
+++ b/contracts/test/utils/getActualAddress.test.ts
@@ -0,0 +1,22 @@
+import { expect } from "chai";
+import { getActualAddress } from "./getActualAddress";
+
+describe("getActualAddress", () => {
+  it("should return the correct address for KlerosCore on arbitrumSepoliaDevnet", async () => {
+    const address = await getActualAddress("arbitrumSepoliaDevnet", "KlerosCore");
+    expect(address).to.match(/^0x[a-fA-F0-9]{40}$/);
+    expect(address).to.not.equal("0x0000000000000000000000000000000000000000");
+  });
+
+  it("should throw error for non-existent network", async () => {
+    await expect(getActualAddress("nonexistentNetwork", "KlerosCore")).to.be.rejectedWith(
+      "No deployment file found for KlerosCore on nonexistentNetwork"
+    );
+  });
+
+  it("should throw error for non-existent contract", async () => {
+    await expect(getActualAddress("arbitrumSepoliaDevnet", "NonExistentContract")).to.be.rejectedWith(
+      "No deployment file found for NonExistentContract on arbitrumSepoliaDevnet"
+    );
+  });
+});
diff --git a/contracts/test/utils/getActualAddress.ts b/contracts/test/utils/getActualAddress.ts
new file mode 100644
index 000000000..8e38e16b3
--- /dev/null
+++ b/contracts/test/utils/getActualAddress.ts
@@ -0,0 +1,23 @@
+/**
+ * Get the deployed address of a contract from its deployment JSON file
+ * @param network The network name (e.g., "arbitrumSepoliaDevnet")
+ * @param contractName The contract name (e.g., "KlerosCore")
+ * @returns The deployed contract address
+ * @throws Error if the deployment file doesn't exist or has no address
+ */
+export async function getActualAddress(network: string, contractName: string): Promise<string> {
+  try {
+    const deployment = await import(`../../deployments/${network}/${contractName}.json`, {
+      assert: { type: "json" },
+    });
+    if (!deployment.default.address) {
+      throw new Error(`No address found in deployment file for ${contractName} on ${network}`);
+    }
+    return deployment.default.address;
+  } catch (error) {
+    if (error instanceof Error && error.message.includes("Cannot find module")) {
+      throw new Error(`No deployment file found for ${contractName} on ${network}`);
+    }
+    throw error;
+  }
+}
diff --git a/contracts/tsconfig-release.json b/contracts/tsconfig-release.json
index 7464d1b94..a94f4d6f6 100644
--- a/contracts/tsconfig-release.json
+++ b/contracts/tsconfig-release.json
@@ -4,7 +4,6 @@
     "declaration": true
   },
   "include": [
-    "./src",
     "./typechain-types",
     "./deployments"
   ],
diff --git a/contracts/wagmi.config.devnet.ts b/contracts/wagmi.config.devnet.ts
index d562d4b66..7c8009cc1 100644
--- a/contracts/wagmi.config.devnet.ts
+++ b/contracts/wagmi.config.devnet.ts
@@ -1,5 +1,5 @@
 import { Config, defineConfig } from "@wagmi/cli";
-import IHomeGateway from "@kleros/kleros-v2-contracts/artifacts/src/gateway/interfaces/IHomeGateway.sol/IHomeGateway.json" assert { type: "json" };
+import IHomeGateway from "./artifacts/src/gateway/interfaces/IHomeGateway.sol/IHomeGateway.json" assert { type: "json" };
 import { getAbi, readArtifacts, merge } from "./scripts/wagmiHelpers";
 
 const getConfig = async (): Promise<Config> => {
diff --git a/contracts/wagmi.config.mainnet.ts b/contracts/wagmi.config.mainnet.ts
index b48c7f5d6..aa321eba0 100644
--- a/contracts/wagmi.config.mainnet.ts
+++ b/contracts/wagmi.config.mainnet.ts
@@ -1,5 +1,5 @@
 import { Config, defineConfig } from "@wagmi/cli";
-import IHomeGateway from "@kleros/kleros-v2-contracts/artifacts/src/gateway/interfaces/IHomeGateway.sol/IHomeGateway.json" assert { type: "json" };
+import IHomeGateway from "./artifacts/src/gateway/interfaces/IHomeGateway.sol/IHomeGateway.json" assert { type: "json" };
 import { getAbi, readArtifacts, merge } from "./scripts/wagmiHelpers";
 
 const getConfig = async (): Promise<Config> => {
diff --git a/contracts/wagmi.config.testnet.ts b/contracts/wagmi.config.testnet.ts
index aa1c4b108..93a00cdd0 100644
--- a/contracts/wagmi.config.testnet.ts
+++ b/contracts/wagmi.config.testnet.ts
@@ -1,5 +1,5 @@
 import { Config, defineConfig } from "@wagmi/cli";
-import IHomeGateway from "@kleros/kleros-v2-contracts/artifacts/src/gateway/interfaces/IHomeGateway.sol/IHomeGateway.json" assert { type: "json" };
+import IHomeGateway from "./artifacts/src/gateway/interfaces/IHomeGateway.sol/IHomeGateway.json" assert { type: "json" };
 import { getAbi, readArtifacts, merge } from "./scripts/wagmiHelpers";
 
 const getConfig = async (): Promise<Config> => {
diff --git a/yarn.lock b/yarn.lock
index 631f38963..6aa34039e 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -5565,6 +5565,7 @@ __metadata:
     "@types/chai": "npm:^4.3.20"
     "@types/mocha": "npm:^10.0.10"
     "@types/node": "npm:^20.17.6"
+    "@types/sinon": "npm:^17.0.4"
     "@wagmi/cli": "npm:^2.2.0"
     abitype: "npm:^0.10.3"
     chai: "npm:^4.5.0"
@@ -5590,6 +5591,7 @@ __metadata:
     prettier: "npm:^3.3.3"
     prettier-plugin-solidity: "npm:^1.4.2"
     shelljs: "npm:^0.8.5"
+    sinon: "npm:^20.0.0"
     solhint-plugin-prettier: "npm:^0.1.0"
     solidity-coverage: "npm:^0.8.13"
     ts-node: "npm:^10.9.2"
@@ -8589,6 +8591,24 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@sinonjs/commons@npm:^3.0.1":
+  version: 3.0.1
+  resolution: "@sinonjs/commons@npm:3.0.1"
+  dependencies:
+    type-detect: "npm:4.0.8"
+  checksum: 10/a0af217ba7044426c78df52c23cedede6daf377586f3ac58857c565769358ab1f44ebf95ba04bbe38814fba6e316ca6f02870a009328294fc2c555d0f85a7117
+  languageName: node
+  linkType: hard
+
+"@sinonjs/fake-timers@npm:^13.0.5":
+  version: 13.0.5
+  resolution: "@sinonjs/fake-timers@npm:13.0.5"
+  dependencies:
+    "@sinonjs/commons": "npm:^3.0.1"
+  checksum: 10/11ee417968fc4dce1896ab332ac13f353866075a9d2a88ed1f6258f17cc4f7d93e66031b51fcddb8c203aa4d53fd980b0ae18aba06269f4682164878a992ec3f
+  languageName: node
+  linkType: hard
+
 "@sinonjs/fake-timers@npm:^8.0.1":
   version: 8.1.0
   resolution: "@sinonjs/fake-timers@npm:8.1.0"
@@ -8598,6 +8618,17 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@sinonjs/samsam@npm:^8.0.1":
+  version: 8.0.2
+  resolution: "@sinonjs/samsam@npm:8.0.2"
+  dependencies:
+    "@sinonjs/commons": "npm:^3.0.1"
+    lodash.get: "npm:^4.4.2"
+    type-detect: "npm:^4.1.0"
+  checksum: 10/58ca9752e8e835a09ed275f8edf8da2720fe95c0c02f6bcb90ad7f86fdceb393f35f744194b705dd94216228646ec0aedbb814e245eb869b940dcf1266b7a533
+  languageName: node
+  linkType: hard
+
 "@socket.io/component-emitter@npm:~3.1.0":
   version: 3.1.0
   resolution: "@socket.io/component-emitter@npm:3.1.0"
@@ -9978,6 +10009,22 @@ __metadata:
   languageName: node
   linkType: hard
 
+"@types/sinon@npm:^17.0.4":
+  version: 17.0.4
+  resolution: "@types/sinon@npm:17.0.4"
+  dependencies:
+    "@types/sinonjs__fake-timers": "npm:*"
+  checksum: 10/286c34e66e3573673ba59a332ac81189e20dd591c5c5360c8ff3ed83a59a60bdb1d4c8f13ab8863a4d5ce636282e4b11c640b87f398663eee152988ca09b1933
+  languageName: node
+  linkType: hard
+
+"@types/sinonjs__fake-timers@npm:*":
+  version: 8.1.5
+  resolution: "@types/sinonjs__fake-timers@npm:8.1.5"
+  checksum: 10/3a0b285fcb8e1eca435266faa27ffff206608b69041022a42857274e44d9305822e85af5e7a43a9fae78d2ab7dc0fcb49f3ae3bda1fa81f0203064dbf5afd4f6
+  languageName: node
+  linkType: hard
+
 "@types/sockjs@npm:^0.3.33":
   version: 0.3.33
   resolution: "@types/sockjs@npm:0.3.33"
@@ -15793,6 +15840,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"diff@npm:^7.0.0":
+  version: 7.0.0
+  resolution: "diff@npm:7.0.0"
+  checksum: 10/e9b8e48d054c9c0c093c65ce8e2637af94b35f2427001607b14e5e0589e534ea3413a7f91ebe6d7c5a1494ace49cb7c7c3972f442ddd96a4767ff091999a082e
+  languageName: node
+  linkType: hard
+
 "diffie-hellman@npm:^5.0.3":
   version: 5.0.3
   resolution: "diffie-hellman@npm:5.0.3"
@@ -22967,6 +23021,13 @@ __metadata:
   languageName: node
   linkType: hard
 
+"lodash.get@npm:^4.4.2":
+  version: 4.4.2
+  resolution: "lodash.get@npm:4.4.2"
+  checksum: 10/2a4925f6e89bc2c010a77a802d1ba357e17ed1ea03c2ddf6a146429f2856a216663e694a6aa3549a318cbbba3fd8b7decb392db457e6ac0b83dc745ed0a17380
+  languageName: node
+  linkType: hard
+
 "lodash.isarguments@npm:^3.1.0":
   version: 3.1.0
   resolution: "lodash.isarguments@npm:3.1.0"
@@ -30112,6 +30173,19 @@ __metadata:
   languageName: node
   linkType: hard
 
+"sinon@npm:^20.0.0":
+  version: 20.0.0
+  resolution: "sinon@npm:20.0.0"
+  dependencies:
+    "@sinonjs/commons": "npm:^3.0.1"
+    "@sinonjs/fake-timers": "npm:^13.0.5"
+    "@sinonjs/samsam": "npm:^8.0.1"
+    diff: "npm:^7.0.0"
+    supports-color: "npm:^7.2.0"
+  checksum: 10/825cb36a58c0510cec03d9bef4fe66a12baf0e0cfdf1600423e3da1e6d57a03fe8161f4859340ea13d4c42e63da1724a260ef4c5ce119dc9ee075ad93b6e8bdd
+  languageName: node
+  linkType: hard
+
 "sirv@npm:^2.0.4":
   version: 2.0.4
   resolution: "sirv@npm:2.0.4"
@@ -31300,7 +31374,7 @@ __metadata:
   languageName: node
   linkType: hard
 
-"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0":
+"supports-color@npm:^7.0.0, supports-color@npm:^7.1.0, supports-color@npm:^7.2.0":
   version: 7.2.0
   resolution: "supports-color@npm:7.2.0"
   dependencies: