Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions src/controllers/BlueprintController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,8 +21,8 @@ import { SupabaseDataService } from "../services/SupabaseDataService.js";
import { verifyAuthSignedData } from "../utils/verifyAuthSignedData.js";
import { isAddress } from "viem";
import { Json } from "../types/supabaseData.js";
import { getEvmClient } from "../utils/getRpcUrl.js";
import { waitForTxThenMintBlueprint } from "../utils/waitForTxThenMintBlueprint.js";
import { EvmClientFactory } from "../utils/evmClient.js";

@Route("v1/blueprints")
@Tags("Blueprints")
Expand Down Expand Up @@ -367,7 +367,7 @@ export class BlueprintController extends Controller {
};
}

const client = getEvmClient(chain_id);
const client = EvmClientFactory.createClient(chain_id);
const transaction = await client.getTransaction({
hash: tx_hash as `0x${string}`,
});
Expand Down
6 changes: 4 additions & 2 deletions src/controllers/MarketplaceController.ts
Original file line number Diff line number Diff line change
Expand Up @@ -19,10 +19,10 @@ import { z } from "zod";
import { isAddress, verifyMessage } from "viem";
import { SupabaseDataService } from "../services/SupabaseDataService.js";
import { getFractionsById } from "../utils/getFractionsById.js";
import { getRpcUrl } from "../utils/getRpcUrl.js";
import { isParsableToBigInt } from "../utils/isParsableToBigInt.js";
import { getHypercertTokenId } from "../utils/tokenIds.js";
import { BaseResponse } from "../types/api.js";
import { EvmClientFactory } from "../utils/evmClient.js";

export interface CreateOrderRequest {
signature: string;
Expand Down Expand Up @@ -148,7 +148,9 @@ export class MarketplaceController extends Controller {
const hec = new HypercertExchangeClient(
chainId,
// @ts-expect-error Typing issue with provider
new ethers.JsonRpcProvider(getRpcUrl(chainId)),
new ethers.JsonRpcProvider(
EvmClientFactory.getFirstAvailableUrl(chainId),
),
);
const typedData = hec.getTypedDataDomain();

Expand Down
5 changes: 2 additions & 3 deletions src/lib/safe-signature-verification/SafeSignatureVerifier.ts
Original file line number Diff line number Diff line change
@@ -1,15 +1,14 @@
import { getAddress, hashTypedData, type HashTypedDataParameters } from "viem";
import Safe from "@safe-global/protocol-kit";

import { getRpcUrl } from "../../utils/getRpcUrl.js";
import { EvmClientFactory } from "../../utils/evmClient.js";

export default abstract class SafeSignatureVerifier {
protected chainId: number;
protected safeAddress: `0x${string}`;
protected rpcUrl: string;

constructor(chainId: number, safeAddress: `0x${string}`) {
const rpcUrl = getRpcUrl(chainId);
const rpcUrl = EvmClientFactory.getFirstAvailableUrl(chainId);

if (!rpcUrl) {
throw new Error(`Unsupported chain ID: ${chainId}`);
Expand Down
6 changes: 4 additions & 2 deletions src/services/SupabaseDataService.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,6 @@ import {
OrderValidatorCode,
} from "@hypercerts-org/marketplace-sdk";
import { ethers } from "ethers";
import { getRpcUrl } from "../utils/getRpcUrl.js";
import { singleton } from "tsyringe";
import { GetUserArgs } from "../graphql/schemas/args/userArgs.js";
import type { DataDatabase as KyselyDataDatabase } from "../types/kyselySupabaseData.js";
Expand All @@ -23,6 +22,7 @@ import { GetBlueprintArgs } from "../graphql/schemas/args/blueprintArgs.js";
import { sql } from "kysely";
import { GetSignatureRequestArgs } from "../graphql/schemas/args/signatureRequestArgs.js";
import { GetCollectionsArgs } from "../graphql/schemas/args/collectionArgs.js";
import { EvmClientFactory } from "../utils/evmClient.js";

@singleton()
export class SupabaseDataService extends BaseSupabaseService<KyselyDataDatabase> {
Expand Down Expand Up @@ -278,7 +278,9 @@ export class SupabaseDataService extends BaseSupabaseService<KyselyDataDatabase>
const hec = new HypercertExchangeClient(
chainId,
// @ts-expect-error Typing issue with provider
new ethers.JsonRpcProvider(getRpcUrl(chainId)),
new ethers.JsonRpcProvider(
EvmClientFactory.getFirstAvailableUrl(chainId),
),
);
const validationResults = await hec.checkOrdersValidity(matchingOrders);

Expand Down
47 changes: 47 additions & 0 deletions src/utils/chainFactory.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import { Chain } from "viem";
import {
arbitrum,
arbitrumSepolia,
base,
baseSepolia,
celo,
filecoin,
filecoinCalibration,
mainnet,
optimism,
sepolia,
} from "viem/chains";

const SUPPORTED_CHAINS = [
mainnet,
optimism,
base,
arbitrum,
celo,
sepolia,
arbitrumSepolia,
baseSepolia,
filecoin,
filecoinCalibration,
];

// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class ChainFactory {
static getChain(chainId: number): Chain {
const chains = SUPPORTED_CHAINS.reduce(
(acc, chain) => {
acc[chain.id] = chain;
return acc;
},
{} as Record<number, Chain>,
);

const chain = chains[chainId];
if (!chain) throw new Error(`Unsupported chain ID: ${chainId}`);
return chain;
}

static getSupportedChains(): number[] {
return SUPPORTED_CHAINS.map((chain) => chain.id);
}
}
1 change: 1 addition & 0 deletions src/utils/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,3 +16,4 @@ export const infuraApiKey = getRequiredEnvVar("INFURA_API_KEY");
export const drpcApiPkey = getRequiredEnvVar("DRPC_API_KEY");
export const cachingDatabaseUrl = getRequiredEnvVar("CACHING_DATABASE_URL");
export const dataDatabaseUrl = getRequiredEnvVar("DATA_DATABASE_URL");
export const filecoinApiKey = getRequiredEnvVar("FILECOIN_API_KEY");
105 changes: 105 additions & 0 deletions src/utils/evmClient.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,105 @@
import { PublicClient, createPublicClient, fallback } from "viem";
import { ChainFactory } from "./chainFactory.js";
import { UnifiedRpcClientFactory } from "./rpcClientFactory.js";
import { alchemyApiKey, drpcApiPkey, infuraApiKey } from "./constants.js";

interface RpcProvider {
getUrl(chainId: number): string | undefined;
}

class AlchemyProvider implements RpcProvider {
getUrl(chainId: number): string | undefined {
if (!alchemyApiKey) return undefined;
const urls: Record<number, string> = {
10: `https://opt-mainnet.g.alchemy.com/v2/${alchemyApiKey}`,
8453: `https://base-mainnet.g.alchemy.com/v2/${alchemyApiKey}`,
42161: `https://arb-mainnet.g.alchemy.com/v2/${alchemyApiKey}`,
421614: `https://arb-sepolia.g.alchemy.com/v2/${alchemyApiKey}`,
84532: `https://base-sepolia.g.alchemy.com/v2/${alchemyApiKey}`,
11155111: `https://eth-sepolia.g.alchemy.com/v2/${alchemyApiKey}`,
};
return urls[chainId];
}
}

class InfuraProvider implements RpcProvider {
getUrl(chainId: number): string | undefined {
if (!infuraApiKey) return undefined;
const urls: Record<number, string> = {
10: `https://optimism-mainnet.infura.io/v3/${infuraApiKey}`,
42220: `https://celo-mainnet.infura.io/v3/${infuraApiKey}`,
42161: `https://arbitrum-mainnet.infura.io/v3/${infuraApiKey}`,
421614: `https://arbitrum-sepolia.infura.io/v3/${infuraApiKey}`,
};
return urls[chainId];
}
}

class DrpcProvider implements RpcProvider {
getUrl(chainId: number): string | undefined {
if (!drpcApiPkey) return undefined;
const networks: Record<number, string> = {
10: "optimism",
8453: "base",
42220: "celo",
42161: "arbitrum",
421614: "arbitrum-sepolia",
};
const network = networks[chainId];
return network
? `https://lb.drpc.org/ogrpc?network=${network}&dkey=${drpcApiPkey}`
: undefined;
}
}

class GlifProvider implements RpcProvider {
getUrl(chainId: number): string | undefined {
const urls: Record<number, string> = {
314: `https://node.glif.io/space07/lotus/rpc/v1`,
314159: `https://calibration.node.glif.io/archive/lotus/rpc/v1`,
};
return urls[chainId];
}
}

// eslint-disable-next-line @typescript-eslint/no-extraneous-class
export class EvmClientFactory {
private static readonly providers: RpcProvider[] = [
new AlchemyProvider(),
new InfuraProvider(),
new DrpcProvider(),
new GlifProvider(),
];

static createClient(chainId: number): PublicClient {
const urls = EvmClientFactory.getAllAvailableUrls(chainId);
if (urls.length === 0)
throw new Error(`No RPC URL available for chain ${chainId}`);

const transports = urls.map((url) =>
UnifiedRpcClientFactory.createViemTransport(chainId, url),
);

return createPublicClient({
chain: ChainFactory.getChain(chainId),
transport: fallback(transports),
});
}

static getAllAvailableUrls(chainId: number): string[] {
return EvmClientFactory.providers
.map((provider) => provider.getUrl(chainId))
.filter((url): url is string => url !== undefined);
}

// Keep this for backward compatibility
static getFirstAvailableUrl(chainId: number): string | undefined {
return EvmClientFactory.getAllAvailableUrls(chainId)[0];
}
}

export const getRpcUrl = (chainId: number): string => {
const url = EvmClientFactory.getFirstAvailableUrl(chainId);
if (!url) throw new Error(`No RPC URL available for chain ${chainId}`);
return url;
};
126 changes: 0 additions & 126 deletions src/utils/getRpcUrl.ts

This file was deleted.

4 changes: 2 additions & 2 deletions src/utils/getTokenPriceInUSD.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,15 +4,15 @@ import {
Currency,
} from "@hypercerts-org/marketplace-sdk";
import { formatUnits, getAddress } from "viem";
import { getEvmClient } from "./getRpcUrl.js";
import { AggregatorV3Abi } from "../abis/AggregatorV3Abi.js";
import { LRUCache } from "lru-cache";
import { EvmClientFactory } from "./evmClient.js";

export const getTokenPriceInUSD = async (
chainId: ChainId,
tokenAddress: string,
) => {
const client = getEvmClient(chainId);
const client = EvmClientFactory.createClient(chainId);

// The address of the contract which will provide the price of ETH
const feedAddress = tokenAddressToFeedAddress(chainId, tokenAddress);
Expand Down
Loading
Loading