Skip to content

Commit

Permalink
(feat): update tests and docs
Browse files Browse the repository at this point in the history
  • Loading branch information
Jipperism committed Apr 14, 2024
1 parent cc330a4 commit a1cb9e2
Show file tree
Hide file tree
Showing 10 changed files with 55 additions and 82 deletions.
24 changes: 12 additions & 12 deletions sdk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
2 changes: 1 addition & 1 deletion sdk/package.json
Original file line number Diff line number Diff line change
@@ -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",
Expand Down
1 change: 1 addition & 0 deletions sdk/src/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
17 changes: 12 additions & 5 deletions sdk/src/indexer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -48,16 +48,22 @@ export class HypercertIndexer implements HypercertIndexerInterface {
* @param options The configuration options for the indexer.
*/
constructor(options: Partial<HypercertClientConfig>) {
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<number, Client>();
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(
Expand All @@ -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;
Expand Down
2 changes: 1 addition & 1 deletion sdk/src/types/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ export type Deployment = {
/**
* Configuration options for the Hypercert client.
*/
export type HypercertClientConfig = Deployment &
export type HypercertClientConfig = Pick<Deployment, "addresses" | "chain"> &
HypercertStorageConfig &
HypercertEvaluatorConfig & {
/** The PublicClient is inherently read-only */
Expand Down
48 changes: 9 additions & 39 deletions sdk/src/utils/config.ts
Original file line number Diff line number Diff line change
@@ -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,
Expand Down Expand Up @@ -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<HypercertClientConfig>): Partial<HypercertClientConfig> => {
// 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");
Expand All @@ -43,22 +43,20 @@ export const getConfig = (overrides: Partial<HypercertClientConfig>): Partial<Hy
let baseDeployment: (Partial<Deployment> & { 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<Deployment> & {
unsafeForceOverrideConfig?: boolean;
Expand All @@ -81,8 +79,8 @@ export const getConfig = (overrides: Partial<HypercertClientConfig>): Partial<Hy
// Let the user override from environment variables
...getWalletClient(overrides),
...getPublicClient(overrides),
...getGraphUrl(overrides),
...getEasContractAddress(overrides),
...getIndexerEnvironment(overrides),
};

const missingKeys = [];
Expand All @@ -102,6 +100,10 @@ const getDeployment = (chainId: SupportedChainIds) => {
return deployments[chainId];
};

const getIndexerEnvironment = (overrides: Partial<HypercertClientConfig>) => {
return { indexerEnvironment: overrides.indexerEnvironment || DEFAULT_INDEXER_ENVIRONMENT };
};

const getChainConfig = (overrides: Partial<HypercertClientConfig>) => {
const chainId = overrides?.chain?.id ? overrides.chain?.id : undefined;

Expand All @@ -120,38 +122,6 @@ const getChainConfig = (overrides: Partial<HypercertClientConfig>) => {
return chain;
};

const getGraphUrl = (overrides: Partial<HypercertClientConfig>) => {
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<HypercertClientConfig>) => {
const walletClient = overrides.walletClient;

Expand Down
4 changes: 2 additions & 2 deletions sdk/src/utils/parsing.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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"`);
}

Expand All @@ -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) {
Expand Down
25 changes: 12 additions & 13 deletions sdk/test/indexer.test.ts
Original file line number Diff line number Diff line change
@@ -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);
});
});
5 changes: 1 addition & 4 deletions sdk/test/indexer/queries.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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(() => {
Expand Down
9 changes: 4 additions & 5 deletions sdk/test/utils/config.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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);

Expand All @@ -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<HypercertClientConfig> = {
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<HypercertClientConfig> = {
chain: { id: 11155111 },
graphUrl: "incorrect-url",
unsafeForceOverrideConfig: true,
};

Expand Down

0 comments on commit a1cb9e2

Please sign in to comment.