diff --git a/sdk/README.md b/sdk/README.md index 729940a6..6b5f1f23 100644 --- a/sdk/README.md +++ b/sdk/README.md @@ -58,18 +58,18 @@ HypercertClientConfig is a configuration object used when initializing a new ins you to customize the client by setting your own providers or deployments. At it's simplest, you only need to provide `chain.id` to initalize the client in `readonly` mode. -| Field | Type | Description | -| --------------------------- | ------- | ---------------------------------------------------------------------------------------------- | -| `chain` | Object | Partial configuration for the blockchain network. | -| `contractAddress` | String | The address of the deployed contract. | -| `graphUrl` | String | The URL to the subgraph that indexes the contract events. Override for localized testing. | -| `graphName` | String | The name of the subgraph. | -| `easContractAddress` | String | The address of the EAS contract. | -| `publicClient` | Object | The PublicClient is inherently read-only and is used for reading data from the blockchain. | -| `walletClient` | Object | The WalletClient is used for signing and sending transactions. | -| `unsafeForceOverrideConfig` | Boolean | Boolean to force the use of overridden values. | -| `readOnly` | Boolean | Boolean to assert if the client is in read-only mode. | -| `readOnlyReason` | String | Reason for read-only mode. This is optional and can be used for logging or debugging purposes. | +| Field | Type | Description | +| --------------------------- | --------------------------------- | ---------------------------------------------------------------------------------------------- | ------------------------------- | +| `chain` | Object | Partial configuration for the blockchain network. | +| `contractAddress` | String | The address of the deployed contract. | +| `graphName` | String | The name of the subgraph. | +| `easContractAddress` | String | The address of the EAS contract. | +| `publicClient` | Object | The PublicClient is inherently read-only and is used for reading data from the blockchain. | +| `walletClient` | Object | The WalletClient is used for signing and sending transactions. | +| `unsafeForceOverrideConfig` | Boolean | Boolean to force the use of overridden values. | +| `readOnly` | Boolean | Boolean to assert if the client is in read-only mode. | +| `readOnlyReason` | String | Reason for read-only mode. This is optional and can be used for logging or debugging purposes. | +| `indexerEnvironment` | `'test' \| 'production' \| 'all'` | Determines which graphs should be read out when querying | The environment of the indexer. | ### Read-only mode diff --git a/sdk/package.json b/sdk/package.json index a13dffe3..d8970961 100644 --- a/sdk/package.json +++ b/sdk/package.json @@ -1,6 +1,6 @@ { "name": "@hypercerts-org/sdk", - "version": "1.5.0", + "version": "2.0.0-alpha.4", "description": "SDK for hypercerts protocol", "repository": "git@github.com:hypercerts-org/hypercerts.git", "author": "Hypercerts team", diff --git a/sdk/src/constants.ts b/sdk/src/constants.ts index 85582795..1fc4fdc1 100644 --- a/sdk/src/constants.ts +++ b/sdk/src/constants.ts @@ -6,6 +6,7 @@ import { Deployment, SupportedChainIds } from "./types"; import { deployments } from "@hypercerts-org/contracts"; const DEFAULT_GRAPH_BASE_URL = "https://api.thegraph.com/subgraphs/name/hypercerts-org"; +export const DEFAULT_INDEXER_ENVIRONMENT = "production"; // The APIs we expose diff --git a/sdk/src/indexer.ts b/sdk/src/indexer.ts index ae4019d8..5c62f9bf 100644 --- a/sdk/src/indexer.ts +++ b/sdk/src/indexer.ts @@ -34,7 +34,7 @@ import { parseClaimOrFractionId } from "./utils/parsing"; * Because of the autogenerated Graph client packed with the SDK, this class is not recommended for custom Graph deployments. * * @example - * const indexer = new HypercertIndexer({ graphUrl: 'your-graph-url', graphName: 'your-graph-name' }); + * const indexer = new HypercertIndexer({ indexerEnvironment: 'production' }); * const claims = await indexer.claimsByOwner('your-address'); */ export class HypercertIndexer implements HypercertIndexerInterface { @@ -48,16 +48,22 @@ export class HypercertIndexer implements HypercertIndexerInterface { * @param options The configuration options for the indexer. */ constructor(options: Partial) { - logger.info("Creating HypercertIndexer", "constructor", { name: options.graphName, url: options.graphUrl }); - if (!options.graphUrl) throw new Error("Missing graphUrl"); - this.environment = options.indexerEnvironment || "test"; + logger.info("Creating HypercertIndexer", "constructor (write)", { + environment: options.indexerEnvironment, + }); + + if (!options.indexerEnvironment) { + throw new Error("Missing indexer environment"); + } + this.environment = options.indexerEnvironment; const environments = HypercertIndexer.getDeploymentsForEnvironment(this.environment); + logger.info("Creating Graph clients", "constructor (read)", { environments }); this.graphClients = new Map(); for (const [chainId, deployment] of environments) { if (!deployment.graphUrl) { - console.log(`Missing graphUrl for chain ${chainId}`); + logger.info("Missing graphUrl for chain", "constructor (read)", { chainId }); continue; } this.graphClients.set( @@ -71,6 +77,7 @@ export class HypercertIndexer implements HypercertIndexerInterface { } static getDeploymentsForEnvironment(environment: IndexerEnvironment) { + logger.info("Indexer", "getDeploymentsForEnvironment", { environment }); return Object.entries(DEPLOYMENTS).filter(([_, deployment]) => { if (environment === "all") { return true; diff --git a/sdk/src/types/client.ts b/sdk/src/types/client.ts index 606f6894..dcd02794 100644 --- a/sdk/src/types/client.ts +++ b/sdk/src/types/client.ts @@ -68,7 +68,7 @@ export type Deployment = { /** * Configuration options for the Hypercert client. */ -export type HypercertClientConfig = Deployment & +export type HypercertClientConfig = Pick & HypercertStorageConfig & HypercertEvaluatorConfig & { /** The PublicClient is inherently read-only */ diff --git a/sdk/src/utils/config.ts b/sdk/src/utils/config.ts index 7ac30a8e..d58e2cb7 100644 --- a/sdk/src/utils/config.ts +++ b/sdk/src/utils/config.ts @@ -1,6 +1,6 @@ import { sepolia, optimism, celo, Chain, baseSepolia, base } from "viem/chains"; -import { DEPLOYMENTS } from "../constants"; +import { DEFAULT_INDEXER_ENVIRONMENT } from "../constants"; import { ConfigurationError, Deployment, @@ -34,7 +34,7 @@ import { deployments } from "../../src"; * @throws {UnsupportedChainError} Will throw an `UnsupportedChainError` if the default configuration for the provided chain ID is missing. */ export const getConfig = (overrides: Partial): Partial => { - // Get the chainId, first from overrides, then environment variables, then the constant + // Get the chainId of the writing chain, first from overrides, then environment variables, then the constant const chain = getChainConfig(overrides); if (!chain) { logger.warn("[getConfig]: No default config for chain found"); @@ -43,22 +43,20 @@ export const getConfig = (overrides: Partial): Partial & { unsafeForceOverrideConfig?: boolean }) | undefined; if (overrides.unsafeForceOverrideConfig) { - if (!overrides.chain?.id || !overrides.graphUrl) { + if (!overrides.chain?.id) { throw new InvalidOrMissingError( `attempted to override with chainId=${overrides.chain?.id}, but requires chainName, graphUrl, and contractAddress to be set`, { chainID: overrides.chain?.id?.toString(), - graphUrl: overrides.graphUrl, }, ); } baseDeployment = { chain: { ...chain, id: overrides.chain?.id }, - graphUrl: overrides.graphUrl, unsafeForceOverrideConfig: overrides.unsafeForceOverrideConfig, }; } else { - //TODO doo many casts + //TODO do many casts baseDeployment = overrides.chain?.id ? (getDeployment(overrides.chain?.id as SupportedChainIds) as Partial & { unsafeForceOverrideConfig?: boolean; @@ -81,8 +79,8 @@ export const getConfig = (overrides: Partial): Partial { return deployments[chainId]; }; +const getIndexerEnvironment = (overrides: Partial) => { + return { indexerEnvironment: overrides.indexerEnvironment || DEFAULT_INDEXER_ENVIRONMENT }; +}; + const getChainConfig = (overrides: Partial) => { const chainId = overrides?.chain?.id ? overrides.chain?.id : undefined; @@ -120,38 +122,6 @@ const getChainConfig = (overrides: Partial) => { return chain; }; -const getGraphUrl = (overrides: Partial) => { - let graphUrl; - if (overrides.unsafeForceOverrideConfig) { - if (!overrides.graphUrl) { - throw new ConfigurationError("A graphUrl must be specified when overriding configuration"); - } - try { - new URL(overrides.graphUrl); - } catch (error) { - throw new ConfigurationError("Invalid graph URL", { graphUrl: overrides.graphUrl }); - } - graphUrl = overrides.graphUrl; - return { graphUrl }; - } - - const chain = getChainConfig(overrides); - - graphUrl = DEPLOYMENTS[chain?.id as keyof typeof DEPLOYMENTS].graphUrl ?? process.env.GRAPH_URL; - if (!graphUrl) { - throw new UnsupportedChainError(`No Graph URL found in deployments or env vars`, { - chainID: chain?.toString(), - }); - } - try { - new URL(graphUrl); - } catch (error) { - throw new ConfigurationError("Invalid graph URL", { graphUrl }); - } - - return { graphUrl }; -}; - const getWalletClient = (overrides: Partial) => { const walletClient = overrides.walletClient; diff --git a/sdk/src/utils/parsing.ts b/sdk/src/utils/parsing.ts index 74ce0758..d699b8b4 100644 --- a/sdk/src/utils/parsing.ts +++ b/sdk/src/utils/parsing.ts @@ -4,7 +4,7 @@ export const parseClaimOrFractionId = (claimId: string) => { const [chainId, contractAddress, id] = claimId.split("-"); if (!chainId || !contractAddress || !id || !isAddress(contractAddress)) { - console.log("Invalid claimId format. Expected 'chainId-contractAddress-tokenId'"); + console.log(`Invalid claimId format (claimId given: ${claimId}}. Expected "chainId-contractAddress-tokenId`); throw new Error(`Invalid claimId format (claimId given: ${claimId}}. Expected "chainId-contractAddress-tokenId"`); } @@ -16,7 +16,7 @@ export const parseClaimOrFractionId = (claimId: string) => { throw new Error(`Invalid chainId while parsing: ${chainId}`); } - let idParsed: BigInt | undefined; + let idParsed: bigint | undefined; try { idParsed = BigInt(id); } catch (error) { diff --git a/sdk/test/indexer.test.ts b/sdk/test/indexer.test.ts index cc5d29d3..784e2d54 100644 --- a/sdk/test/indexer.test.ts +++ b/sdk/test/indexer.test.ts @@ -1,22 +1,21 @@ -import { describe, it } from "vitest"; - -import { expect } from "chai"; +import { describe, it, expect } from "vitest"; import { HypercertIndexer } from "../src/indexer"; +import { DEPLOYMENTS } from "../src/constants"; describe("HypercertsIndexer", () => { - it("should be able to create a new instance without valid graphName", () => { - const indexer = new HypercertIndexer({ graphUrl: "https://api.thegraph.com/subgraphs/name/hypercerts-testnet" }); - - expect(indexer).to.be.an.instanceOf(HypercertIndexer); + it("should only initialize with test environments", async () => { + const environments = HypercertIndexer.getDeploymentsForEnvironment("test"); + expect(environments.every(([_, deployment]) => deployment.isTestnet)).toBe(true); }); - it("should be able to create a new instance with valid graphName and url", () => { - const indexer = new HypercertIndexer({ - graphName: "hypercerts-testnet", - graphUrl: "https://api.thegraph.com/subgraphs/name/hypercerts-testnet", - }); + it("should only initialize with production environments", async () => { + const environments = HypercertIndexer.getDeploymentsForEnvironment("production"); + expect(environments.every(([_, deployment]) => !deployment.isTestnet)).toBe(true); + }); - expect(indexer).to.be.an.instanceOf(HypercertIndexer); + it("should only initialize with all environments", async () => { + const environments = HypercertIndexer.getDeploymentsForEnvironment("all"); + expect(environments.length).toEqual(Object.keys(DEPLOYMENTS).length); }); }); diff --git a/sdk/test/indexer/queries.test.ts b/sdk/test/indexer/queries.test.ts index 27096d01..9c4793f1 100644 --- a/sdk/test/indexer/queries.test.ts +++ b/sdk/test/indexer/queries.test.ts @@ -7,10 +7,7 @@ describe("HypercertIndexer", () => { let indexer: HypercertIndexer; beforeEach(() => { - indexer = new HypercertIndexer({ - graphName: "hypercerts-testnet", - graphUrl: "https://api.thegraph.com/subgraphs/name/hypercerts-testnet", - }); + indexer = new HypercertIndexer({ indexerEnvironment: "test" }); }); afterEach(() => { diff --git a/sdk/test/utils/config.test.ts b/sdk/test/utils/config.test.ts index 33f75089..f9d21fce 100644 --- a/sdk/test/utils/config.test.ts +++ b/sdk/test/utils/config.test.ts @@ -8,6 +8,7 @@ import { ConfigurationError, HypercertClientConfig, InvalidOrMissingError } from import { getConfig } from "../../src/utils/config"; import { reloadEnv } from "../../test/setup-env"; import { walletClient, publicClient } from "../helpers"; +import { DEFAULT_INDEXER_ENVIRONMENT } from "../../src/constants"; chai.use(chaiSubset); @@ -16,25 +17,23 @@ describe("Config: graphUrl", () => { reloadEnv(); }); - it("should return the default graphUrl when no overrides are specified", () => { + it("should return the default indexer environment when no overrides are specified", () => { const result = getConfig({ chain: { id: 11155111 } }); - expect(result.graphUrl).to.equal("https://api.thegraph.com/subgraphs/name/hypercerts-org/hypercerts-sepolia"); + expect(result.indexerEnvironment).to.equal(DEFAULT_INDEXER_ENVIRONMENT); }); it("should return the config specified by overrides", () => { const overrides: Partial = { chain: { id: 11155111 }, - graphUrl: "https://api.example.com", unsafeForceOverrideConfig: true, }; const result = getConfig(overrides); - expect(result.graphUrl).to.equal(overrides.graphUrl); + expect(result.chain?.id).to.equal(overrides.chain?.id); }); it("should throw an error when the graph URL specified by overrides is invalid", () => { const overrides: Partial = { chain: { id: 11155111 }, - graphUrl: "incorrect-url", unsafeForceOverrideConfig: true, };