Skip to content

feat(kleros-sdk): Initial SDK documentation and README improvements #2006

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Draft
wants to merge 1 commit into
base: dev
Choose a base branch
from
Draft
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
159 changes: 156 additions & 3 deletions kleros-sdk/README.md
Original file line number Diff line number Diff line change
@@ -1,11 +1,164 @@
# @kleros/kleros-sdk

_Archon's successor_
**The official TypeScript SDK for interacting with the Kleros V2 protocol.**

To run the data mappings tests, at the root folder level, do:
_This SDK is the successor to Archon and provides developers with a comprehensive set of tools to build applications on top of Kleros._

## Table of Contents

- [Features](#features)
- [Installation](#installation)
- [Getting Started](#getting-started)
- [Configuration](#configuration)
- [Quick Start Example](#quick-start-example)
- [Core Concepts](#core-concepts)
- [Public Client](#public-client)
- [Data Mappings](#data-mappings)
- [Requests](#requests)
- [API Documentation](#api-documentation)
- [Examples](#examples)
- [Contributing](#contributing)
- [License](#license)

## Features

* **Viem Integration**: Leverages the power and efficiency of [Viem](httpsa://viem.sh/) for Ethereum blockchain interactions.
* **Type-Safe**: Fully written in TypeScript for robust type checking and improved developer experience.
* **Dispute Resolution**: Tools to fetch dispute details, and interact with the Kleros arbitration process.
* **Data Handling**: Utilities for working with Kleros-specific data structures and evidence.
* **Subgraph Interaction**: Functionality to query Kleros subgraphs for indexed data.
* **IPFS Support**: Helpers for fetching data stored on IPFS.

## Installation

You can install the Kleros SDK using npm or yarn:

```bash
# Using npm
npm install @kleros/kleros-sdk viem

# Using yarn
yarn add @kleros/kleros-sdk viem
```

**Note:** `@kleros/kleros-sdk` has `viem` as a peer dependency, so you need to install it separately in your project.

## Getting Started

### Configuration

Before you can use the SDK, you need to configure it with a `viem` Public Client instance. This client will be used for all blockchain interactions.

```typescript
import { configureSDK } from '@kleros/kleros-sdk';
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains'; // Or your desired chain

// Create a viem public client
const publicClient = createPublicClient({
chain: mainnet,
transport: http(), // Replace with your preferred transport (e.g., Infura, Alchemy)
});

// Configure the Kleros SDK
configureSDK({ client: publicClient });

console.log('Kleros SDK configured!');
```

### Quick Start Example

Here's a simple example of how to fetch details for a specific dispute:

```typescript
import { configureSDK, KlerosSDK } from '@kleros/kleros-sdk'; // Assuming KlerosSDK is the main class or namespace
import { createPublicClient, http } from 'viem';
import { mainnet } from 'viem/chains';

// Configure the SDK (as shown above)
const publicClient = createPublicClient({
chain: mainnet,
transport: http(),
});
configureSDK({ client: publicClient });

// Example: Fetching dispute details (Illustrative - actual API might differ)
// Replace with actual SDK usage once API is fully explored
async function getDisputeExample(disputeId: string) {
try {
// Placeholder: Actual function to get dispute details needs to be identified
// For now, we'll assume a function getDisputeDetails exists or similar
// const disputeDetails = await KlerosSDK.getDisputeDetails(disputeId);
// console.log('Dispute Details:', disputeDetails);

console.log(`Fetching details for dispute ${disputeId}... (Illustrative)`);
// This part will be updated once the actual API for fetching disputes is clear.
// For now, we refer to functions that might exist based on file names like 'getDispute.ts' or 'fetchDisputeDetails.ts'
// For example, if 'getDispute' is the correct function:
// import { getDispute } from '@kleros/kleros-sdk';
// const dispute = await getDispute(disputeId, publicClient); // publicClient might be implicitly used or passed
// console.log(dispute);


} catch (error) {
console.error('Error fetching dispute details:', error);
}
}

getDisputeExample('123'); // Replace '123' with an actual dispute ID
```
*Note: The Quick Start example is illustrative. The exact API usage, especially for fetching dispute details, will be refined as the SDK's public API is further clarified in subsequent steps.*

## Core Concepts

### Public Client

The SDK uses a `viem` Public Client for all on-chain interactions. You must provide this client during the SDK's configuration. This design gives you full control over the Ethereum connection (e.g., choice of RPC provider, chain).

### Data Mappings

The `dataMappings` module (found in `src/dataMappings`) is a powerful feature that allows for complex data retrieval and processing. It can execute a series of actions, such as:
* Calling smart contract functions
* Fetching JSON data from IPFS
* Querying Kleros subgraphs
It uses a template-based system to populate data structures based on the results of these actions. This is particularly useful for constructing the `metaEvidence` and `evidence` associated with disputes.

### Requests

The `requests` module (found in `src/requests`) provides functions for making specific queries, often to Kleros subgraphs via GraphQL. For example, `fetchDisputeDetails.ts` likely uses this module to retrieve detailed information about a dispute.

## API Documentation

Detailed API documentation will be generated from TSDoc comments and made available separately. (This will be addressed in Step 2 of the improvement plan).

For now, developers can explore the exported functions and types directly within their IDEs, leveraging TypeScript's autocompletion features.

## Examples

Additional runnable examples demonstrating various SDK features will be added to the `examples/` directory within this package. (This will be addressed in a later step of the improvement plan).

## Contributing

Contributions are welcome! If you find a bug, have a feature request, or want to contribute to the codebase, please:

1. Check the [issue tracker](https://github.com/kleros/kleros-v2/issues) for existing issues.
2. Open a new issue if yours isn't listed.
3. For code contributions, please fork the repository and submit a pull request to the `master` (or relevant development) branch.

We use ESLint for linting and Prettier for formatting. Please ensure your contributions adhere to these standards.

### Running Tests

To run the test suite (powered by Vitest):

```bash
# From the root of the kleros-v2 monorepo
yarn test packages/kleros-sdk

# Or, if you are inside the kleros-sdk package directory
yarn test
```

🚧 ⚖️ 🚧
## License

This SDK is licensed under the [MIT License](./LICENSE).
79 changes: 63 additions & 16 deletions kleros-sdk/src/requests/fetchDisputeDetails.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,31 @@
import { RequestError } from "../errors";
import { CombinedError, gql } from "@urql/core";
import { RequestError, KlerosSDKError } from "../errors"; // Assuming KlerosSDKError as a base
import { CombinedError, gql, type TypedDocumentNode } from "@urql/core";
import getClient from "./gqlClient";

/**
* Represents the structure of the response for a dispute details query.
* @internal
*/
type DisputeDetailsQueryResponse = {
dispute: {
/** The address of the arbitrable contract involved in the dispute. */
arbitrated: {
id: string;
};
/** The chain ID where the arbitrable contract resides. */
arbitrableChainId: number;
/** The identifier of the dispute within the context of the arbitrable contract. */
externalDisputeId: number;
/** The identifier of the dispute template associated with this dispute. */
templateId: number;
};
} | null; // Dispute can be null if not found
};

const query = gql`
/**
* GraphQL query to fetch core details of a dispute from a Kleros Core subgraph.
* @internal
*/
const query: TypedDocumentNode<DisputeDetailsQueryResponse, { id: string }> = gql`
query DisputeDetails($id: ID!) {
dispute(id: $id) {
arbitrated {
Expand All @@ -26,28 +38,63 @@ const query = gql`
}
`;

const fetchDisputeDetails = async (endpoint: string, id: bigint) => {
/**
* Fetches core details of a dispute from a specified Kleros Core subgraph.
* This function is intended for internal use by the SDK, typically called by higher-level
* functions like `getDispute`.
*
* @param {string} endpoint - The GraphQL endpoint URL of the Kleros Core subgraph.
* @param {bigint} id - The unique identifier of the dispute (as a BigInt).
* @returns {Promise<DisputeDetailsQueryResponse | undefined>} A promise that resolves to the dispute details
* from the subgraph. Returns `undefined` if the
* query is successful but yields no data.
* @throws {CombinedError} If Urql client encounters a GraphQL error (e.g., network issues, invalid query).
* This error object can contain multiple GraphQL errors.
* @throws {RequestError} If there's a non-GraphQL error during the request or if the response
* contains an error explicitly thrown by the promise chain.
* @throws {KlerosSDKError} For other unexpected errors during the execution.
*
* @example
* // Internal SDK usage:
* // const disputeId = 123n;
* // const coreSubgraphUrl = 'https://your-core-subgraph-url.com/graphql';
* // try {
* // const details = await fetchDisputeDetails(coreSubgraphUrl, disputeId);
* // if (details?.dispute) {
* // console.log('Arbitrated contract:', details.dispute.arbitrated.id);
* // } else {
* // console.log('Dispute not found.');
* // }
* // } catch (error) {
* // console.error('Failed to fetch dispute details:', error);
* // }
*/
const fetchDisputeDetails = async (endpoint: string, id: bigint): Promise<DisputeDetailsQueryResponse | undefined> => {
const variables = { id: id.toString() };

try {
const client = getClient(endpoint);
return client
.query<DisputeDetailsQueryResponse>(query, variables)
.toPromise()
.then((res) => {
if (res?.error) {
throw res.error;
}
return res?.data;
});
const result = await client
.query<DisputeDetailsQueryResponse, { id: string }>(query, variables)
.toPromise();

if (result.error) {
// Let CombinedError be caught by the catch block for uniform error handling
throw result.error;
}
return result.data;
} catch (error: unknown) {
if (error instanceof CombinedError) {
// Re-throw to allow specific handling upstream if needed, or wrap if preferred
throw error;
} else if (error instanceof Error) {
throw new RequestError(`Error querying Dispute Details: ${error.message}`, endpoint);
// Wrap other errors in RequestError for consistent SDK error types
throw new RequestError(`Error querying Dispute Details for ID ${id}: ${error.message}`, endpoint, { cause: error });
}
throw new RequestError("An unknown error occurred while querying Dispute Details", endpoint);
// Fallback for unknown error types
throw new KlerosSDKError(`An unknown error occurred while querying Dispute Details for ID ${id}`, { cause: error });
}
};

export default fetchDisputeDetails;
```
84 changes: 68 additions & 16 deletions kleros-sdk/src/requests/fetchDisputeTemplateFromId.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,30 @@
import { CombinedError, gql } from "@urql/core";
import { RequestError } from "../errors";
import { CombinedError, gql, type TypedDocumentNode } from "@urql/core";
import { RequestError, KlerosSDKError } from "../errors"; // Assuming KlerosSDKError as a base
import getClient from "./gqlClient";

/**
* Represents the structure of the response for a dispute template query from the DTR subgraph.
* @internal
*/
type DisputeTemplateQueryResponse = {
disputeTemplate: {
/**
* The raw template data, typically a JSON string defining the structure and questions of the dispute.
*/
templateData: string;
/**
* JSON string defining how to fetch and map additional data required by the template.
* This can include contract calls, IPFS fetches, or subgraph queries.
*/
templateDataMappings: string;
};
} | null; // disputeTemplate can be null if not found
};
const query = gql`

/**
* GraphQL query to fetch a dispute template from a Dispute Template Registry (DTR) subgraph.
* @internal
*/
const query: TypedDocumentNode<DisputeTemplateQueryResponse, { id: string }> = gql`
query DisputeTemplate($id: ID!) {
disputeTemplate(id: $id) {
templateData
Expand All @@ -17,28 +33,64 @@ const query = gql`
}
`;

const fetchDisputeTemplateFromId = async (endpoint: string, id: number) => {
/**
* Fetches a dispute template from a specified Dispute Template Registry (DTR) subgraph
* based on its template ID.
* This function is intended for internal use by the SDK, primarily by `getDispute`.
*
* @param {string} endpoint - The GraphQL endpoint URL of the DTR subgraph.
* @param {number} id - The unique identifier of the dispute template.
* @returns {Promise<DisputeTemplateQueryResponse | undefined>} A promise that resolves to the dispute template data.
* Returns `undefined` if the query is successful but
* yields no data (e.g., template not found).
* @throws {CombinedError} If Urql client encounters a GraphQL error (e.g., network issues, invalid query).
* This error object can contain multiple GraphQL errors.
* @throws {RequestError} If there's a non-GraphQL error during the request or if the response
* contains an error explicitly thrown by the promise chain.
* @throws {KlerosSDKError} For other unexpected errors during the execution.
*
* @example
* // Internal SDK usage:
* // const templateId = 1;
* // const dtrSubgraphUrl = 'https://your-dtr-subgraph-url.com/graphql';
* // try {
* // const templateResponse = await fetchDisputeTemplateFromId(dtrSubgraphUrl, templateId);
* // if (templateResponse?.disputeTemplate) {
* // console.log('Template Data:', templateResponse.disputeTemplate.templateData);
* // } else {
* // console.log('Dispute template not found.');
* // }
* // } catch (error) {
* // console.error('Failed to fetch dispute template:', error);
* // }
*/
const fetchDisputeTemplateFromId = async (endpoint: string, id: number): Promise<DisputeTemplateQueryResponse | undefined> => {
// Convert ID to string for GraphQL variables, as GraphQL IDs are typically strings or numbers.
const variables = { id: id.toString() };

try {
const client = getClient(endpoint);
return client
.query<DisputeTemplateQueryResponse>(query, variables)
.toPromise()
.then((res) => {
if (res?.error) {
throw res.error;
}
return res?.data;
});
const result = await client
.query<DisputeTemplateQueryResponse, { id: string }>(query, variables)
.toPromise();

if (result.error) {
// Let CombinedError be caught by the catch block for uniform error handling
throw result.error;
}
return result.data;
} catch (error: unknown) {
if (error instanceof CombinedError) {
// Re-throw to allow specific handling upstream if needed
throw error;
} else if (error instanceof Error) {
throw new RequestError(`Error querying Dispute Template: ${error.message}`, endpoint);
// Wrap other errors in RequestError for consistent SDK error types
throw new RequestError(`Error querying Dispute Template for ID ${id}: ${error.message}`, endpoint, { cause: error });
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Verification agent

🧩 Analysis chain

Inconsistency with RequestError constructor.

The RequestError constructor is being called with three arguments, but the RequestError class shown in the relevant snippets only accepts two parameters (message and endpoint). The third argument ({ cause: error }) might not be correctly handled.


🏁 Script executed:

#!/bin/bash
# Verify the RequestError constructor implementation
rg -A 10 "export class RequestError"

Length of output: 683


🏁 Script executed:

#!/bin/bash
# Inspect the CustomError class to see if it handles 'cause'
rg -A 10 "export class CustomError" kleros-sdk/src/errors/index.ts

Length of output: 302


Fix RequestError constructor or remove unsupported cause argument

The throw at kleros-sdk/src/requests/fetchDisputeTemplateFromId.ts:88 passes three arguments to RequestError, but its signature is only:

constructor(message: string, endpoint?: string)

The third { cause: error } is discarded at runtime. You should either:

  • Extend the RequestError (and possibly CustomError) constructor to accept an optional options?: { cause?: Error } parameter and assign this.cause = options.cause.
  • Or remove the unsupported { cause: error } argument from the throw and rely on chaining the original error manually if needed.

Locations to update:

  • kleros-sdk/src/requests/fetchDisputeTemplateFromId.ts (line 88)
  • kleros-sdk/src/errors/index.ts (update RequestError and/or CustomError constructors)
🤖 Prompt for AI Agents
In kleros-sdk/src/requests/fetchDisputeTemplateFromId.ts at line 88, the throw
statement passes three arguments to the RequestError constructor, but the
constructor only accepts two parameters (message and endpoint). To fix this,
either update the RequestError (and possibly CustomError) constructor in
kleros-sdk/src/errors/index.ts to accept an optional options parameter that
includes cause and assign this.cause accordingly, or remove the unsupported
third argument { cause: error } from the throw statement and handle error
chaining manually if needed.

throw new RequestError("An unknown error occurred while querying Dispute Template", endpoint);
// Fallback for unknown error types
throw new KlerosSDKError(`An unknown error occurred while querying Dispute Template for ID ${id}`, { cause: error });
}
};

export default fetchDisputeTemplateFromId;
```
Loading
Loading