diff --git a/kleros-sdk/dataMappings/utils/actionTypeDetectors.ts b/kleros-sdk/dataMappings/utils/actionTypeDetectors.ts deleted file mode 100644 index d42b9bc34..000000000 --- a/kleros-sdk/dataMappings/utils/actionTypeDetectors.ts +++ /dev/null @@ -1,27 +0,0 @@ -import { - SubgraphMapping, - AbiEventMapping, - AbiCallMapping, - JsonMapping, - ActionMapping, - FetchIpfsJsonMapping, - RealityMapping, -} from "./actionTypes"; - -export const isSubgraphMapping = (mapping: ActionMapping): mapping is SubgraphMapping => - (mapping as SubgraphMapping).endpoint !== undefined; - -export const isAbiEventMapping = (mapping: ActionMapping): mapping is AbiEventMapping => - (mapping as AbiEventMapping).abi !== undefined && (mapping as AbiEventMapping).eventFilter !== undefined; - -export const isAbiCallMapping = (mapping: ActionMapping): mapping is AbiCallMapping => - (mapping as AbiCallMapping).abi !== undefined && (mapping as AbiCallMapping).args !== undefined; - -export const isJsonMapping = (mapping: ActionMapping): mapping is JsonMapping => - (mapping as JsonMapping).value !== undefined; - -export const isFetchIpfsJsonMapping = (mapping: ActionMapping): mapping is FetchIpfsJsonMapping => - (mapping as FetchIpfsJsonMapping).ipfsUri !== undefined; - -export const isRealityMapping = (mapping: ActionMapping): mapping is RealityMapping => - mapping.type === "reality" && typeof (mapping as RealityMapping).realityQuestionID === "string"; diff --git a/kleros-sdk/dataMappings/utils/isHexAddress.ts b/kleros-sdk/dataMappings/utils/isHexAddress.ts deleted file mode 100644 index ffe33b251..000000000 --- a/kleros-sdk/dataMappings/utils/isHexAddress.ts +++ /dev/null @@ -1 +0,0 @@ -export const isHexAddress = (str: string): boolean => /^0x[a-fA-F0-9]{40}$/.test(str); diff --git a/kleros-sdk/dataMappings/utils/isHexId.ts b/kleros-sdk/dataMappings/utils/isHexId.ts deleted file mode 100644 index 39266f013..000000000 --- a/kleros-sdk/dataMappings/utils/isHexId.ts +++ /dev/null @@ -1 +0,0 @@ -export const isHexId = (str: string): boolean => /^0x[a-fA-F0-9]{1,64}$/.test(str); diff --git a/kleros-sdk/package.json b/kleros-sdk/package.json index 98006ac8f..e7fbf8949 100644 --- a/kleros-sdk/package.json +++ b/kleros-sdk/package.json @@ -6,6 +6,10 @@ "repository": "git@github.com:kleros/kleros-v2.git", "author": "Kleros", "license": "MIT", + "alias": { + "src": "./src", + "dataMappings": "./src/dataMappings" + }, "packageManager": "yarn@3.3.1", "engines": { "node": ">=16.0.0" diff --git a/kleros-sdk/dataMappings/utils/maxByteSize.ts b/kleros-sdk/src/consts.ts similarity index 100% rename from kleros-sdk/dataMappings/utils/maxByteSize.ts rename to kleros-sdk/src/consts.ts diff --git a/kleros-sdk/dataMappings/actions/callAction.ts b/kleros-sdk/src/dataMappings/actions/callAction.ts similarity index 71% rename from kleros-sdk/dataMappings/actions/callAction.ts rename to kleros-sdk/src/dataMappings/actions/callAction.ts index e8302be54..24cb7a170 100644 --- a/kleros-sdk/dataMappings/actions/callAction.ts +++ b/kleros-sdk/src/dataMappings/actions/callAction.ts @@ -1,7 +1,7 @@ import { parseAbiItem } from "viem"; -import { AbiCallMapping } from "../utils/actionTypes"; -import { createResultObject } from "../utils/createResultObject"; -import { configureSDK, getPublicClient } from "../utils/configureSDK"; +import { AbiCallMapping } from "src/dataMappings/utils/actionTypes"; +import { createResultObject } from "src/dataMappings/utils/createResultObject"; +import { configureSDK, getPublicClient } from "src/sdk"; export const callAction = async (mapping: AbiCallMapping) => { configureSDK({ apiKey: process.env.ALCHEMY_API_KEY }); diff --git a/kleros-sdk/dataMappings/actions/eventAction.ts b/kleros-sdk/src/dataMappings/actions/eventAction.ts similarity index 78% rename from kleros-sdk/dataMappings/actions/eventAction.ts rename to kleros-sdk/src/dataMappings/actions/eventAction.ts index 452dd3c7f..02273fa79 100644 --- a/kleros-sdk/dataMappings/actions/eventAction.ts +++ b/kleros-sdk/src/dataMappings/actions/eventAction.ts @@ -1,7 +1,7 @@ import { parseAbiItem } from "viem"; -import { AbiEventMapping } from "../utils/actionTypes"; -import { createResultObject } from "../utils/createResultObject"; -import { configureSDK, getPublicClient } from "../utils/configureSDK"; +import { AbiEventMapping } from "src/dataMappings/utils/actionTypes"; +import { createResultObject } from "src/dataMappings/utils/createResultObject"; +import { configureSDK, getPublicClient } from "src/sdk"; export const eventAction = async (mapping: AbiEventMapping) => { configureSDK({ apiKey: process.env.ALCHEMY_API_KEY }); diff --git a/kleros-sdk/dataMappings/actions/fetchIpfsJsonAction.ts b/kleros-sdk/src/dataMappings/actions/fetchIpfsJsonAction.ts similarity index 82% rename from kleros-sdk/dataMappings/actions/fetchIpfsJsonAction.ts rename to kleros-sdk/src/dataMappings/actions/fetchIpfsJsonAction.ts index 5d1933c28..37f0ae080 100644 --- a/kleros-sdk/dataMappings/actions/fetchIpfsJsonAction.ts +++ b/kleros-sdk/src/dataMappings/actions/fetchIpfsJsonAction.ts @@ -1,7 +1,7 @@ import fetch from "node-fetch"; -import { FetchIpfsJsonMapping } from "../utils/actionTypes"; -import { createResultObject } from "../utils/createResultObject"; -import { MAX_BYTE_SIZE } from "../utils/maxByteSize"; +import { FetchIpfsJsonMapping } from "src/dataMappings/utils/actionTypes"; +import { createResultObject } from "src/dataMappings/utils/createResultObject"; +import { MAX_BYTE_SIZE } from "src/consts"; export const fetchIpfsJsonAction = async (mapping: FetchIpfsJsonMapping) => { const { ipfsUri, seek, populate } = mapping; diff --git a/kleros-sdk/dataMappings/actions/jsonAction.ts b/kleros-sdk/src/dataMappings/actions/jsonAction.ts similarity index 72% rename from kleros-sdk/dataMappings/actions/jsonAction.ts rename to kleros-sdk/src/dataMappings/actions/jsonAction.ts index 3d4922f2e..3857b4006 100644 --- a/kleros-sdk/dataMappings/actions/jsonAction.ts +++ b/kleros-sdk/src/dataMappings/actions/jsonAction.ts @@ -1,5 +1,5 @@ import { JsonMapping } from "../utils/actionTypes"; -import { createResultObject } from "../utils/createResultObject"; +import { createResultObject } from "src/dataMappings/utils/createResultObject"; export const jsonAction = (mapping: JsonMapping) => { const { value: source, seek, populate } = mapping; diff --git a/kleros-sdk/dataMappings/actions/subgraphAction.ts b/kleros-sdk/src/dataMappings/actions/subgraphAction.ts similarity index 87% rename from kleros-sdk/dataMappings/actions/subgraphAction.ts rename to kleros-sdk/src/dataMappings/actions/subgraphAction.ts index f3fce37de..382bb66e8 100644 --- a/kleros-sdk/dataMappings/actions/subgraphAction.ts +++ b/kleros-sdk/src/dataMappings/actions/subgraphAction.ts @@ -1,6 +1,6 @@ import fetch from "node-fetch"; import { SubgraphMapping } from "../utils/actionTypes"; -import { createResultObject } from "../utils/createResultObject"; +import { createResultObject } from "src/dataMappings/utils/createResultObject"; export const subgraphAction = async (mapping: SubgraphMapping) => { const { endpoint, query, variables, seek, populate } = mapping; diff --git a/kleros-sdk/src/dataMappings/dataMapping.json b/kleros-sdk/src/dataMappings/dataMapping.json new file mode 100644 index 000000000..27f5ed4fd --- /dev/null +++ b/kleros-sdk/src/dataMappings/dataMapping.json @@ -0,0 +1,75 @@ +[ + { + "type": "subgraph", + "endpoint": "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2", + "query": "query($id: ID!) { pair(id: $id) { id token0Price token1Price } }", + "seek": [ + "token0Price", + "token1Price" + ], + "populate": [ + "price1", + "price2" + ] + }, + { + "type": "abi/event", + "abi": "event StakeSet(address indexed _address, uint256 _courtID, uint256 _amount)", + "address": "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", + "eventFilter": { + "fromBlock": "36205881", + "toBlock": "latest", + "args": { + "_courtID": 1 + } + }, + "seek": [ + "amount" + ], + "populate": [ + "amount" + ] + }, + { + "type": "abi/call", + "abi": "function appealCost(uint256 _disputeID) public view returns (uint256)", + "address": "0x5a2bC1477ABE705dB4955Cda7DE064eA79D563d1", + "args": [ + "1" + ], + "seek": [ + "cost" + ], + "populate": [ + "cost" + ] + }, + { + "type": "json", + "value": { + "name": "John Doe", + "age": 30, + "email": "johndoe@example.com" + }, + "seek": [ + "name", + "age", + "email" + ], + "populate": [ + "name", + "age", + "email" + ] + }, + { + "type": "fetch/ipfs/json", + "ipfsUri": "ipfs://QmZ3Cmnip8bmFNruuTuCdxPymEjyK9VcQEyf2beDYcaHaK/metaEvidence.json", + "seek": [ + "title" + ], + "populate": [ + "title" + ] + } +] diff --git a/kleros-sdk/src/dataMappings/dataMapping.ts b/kleros-sdk/src/dataMappings/dataMapping.ts new file mode 100644 index 000000000..17dafb8f1 --- /dev/null +++ b/kleros-sdk/src/dataMappings/dataMapping.ts @@ -0,0 +1,92 @@ +export type SubgraphMapping = { + endpoint: string; // Subgraph endpoint + query: string; // Subgraph query + seek: string[]; // Subgraph query parameters value used to populate the template variables + populate: string[]; // Populated template variables +}; + +export type AbiEventMapping = { + abi: string; // ABI of the contract emitting the event + address: string; // Address of the contract emitting the event + eventFilter: { + // Event filter (eg. specific parameter value, block number range, event index) + fromBlock: BigInt | string; // Block number range start + toBlock: BigInt | string; // Block number range end + args: any; // Event parameter value to filter on + }; + seek: string[]; // Event parameters value used to populate the template variables + populate: string[]; // Populated template variables +}; + +export type AbiCallMapping = { + abi: string; // ABI of the contract emitting the event + address: string; // Address of the contract emitting the event + args: any[]; // Function arguments + seek: string[]; // Call return parameters used to populate the template variables + populate: string[]; // Populated template variables +}; + +export type JsonMapping = { + value: object; // Hardcoded object, to be stringified. + seek: string[]; // JSON keys used to populate the template variables + populate: string[]; // Populated template variables +}; + +export type FetchIpfsJsonMapping = { + ipfsUri: string; // IPFS URL + seek: string[]; // JSON keys used to populate the template variables + populate: string[]; // Populated template variables +}; + +const subgraphMappingExample: SubgraphMapping = { + endpoint: "https://api.thegraph.com/subgraphs/name/uniswap/uniswap-v2", + query: ` + query($id: ID!) { + pair(id: $id) { + id + token0Price + token1Price + } + } + `, + seek: ["token0Price", "token1Price"], + populate: ["price1", "price2"], +}; + +const abiEventMappingExample: AbiEventMapping = { + abi: "event StakeSet(address indexed _address, uint256 _courtID, uint256 _amount)", + address: "0x9f8f72aa9304c8b593d555f12ef6589cc3a579a2", + eventFilter: { + fromBlock: BigInt(36205881), + toBlock: "latest", + args: { + _courtID: 1, + }, + }, + seek: ["amount"], + populate: ["amount"], +}; + +const abiCallMappingExample: AbiCallMapping = { + abi: "function appealCost(uint256 _disputeID) public view returns (uint256)", + address: "0x5a2bC1477ABE705dB4955Cda7DE064eA79D563d1", + args: [BigInt(1)], + seek: ["cost"], + populate: ["cost"], +}; + +const jsonMappingExample: JsonMapping = { + value: { + name: "John Doe", + age: 30, + email: "johndoe@example.com", + }, + seek: ["name", "age", "email"], + populate: ["name", "age", "email"], +}; + +const fetchIpfsJsonMappingExample: FetchIpfsJsonMapping = { + ipfsUri: "ipfs://QmZ3Cmnip8bmFNruuTuCdxPymEjyK9VcQEyf2beDYcaHaK/metaEvidence.json", + seek: ["title"], + populate: ["title"], +}; diff --git a/kleros-sdk/src/dataMappings/decoder.ts b/kleros-sdk/src/dataMappings/decoder.ts new file mode 100644 index 000000000..b30e6e927 --- /dev/null +++ b/kleros-sdk/src/dataMappings/decoder.ts @@ -0,0 +1,55 @@ +import request from "graphql-request"; +import { TypedDocumentNode } from "@graphql-typed-document-node/core"; +import { DisputeDetails } from "./disputeDetails"; + +export type Decoder = (externalDisputeID: string, disputeTemplate: Partial) => Promise; + +// https://github.com/ChainAgnostic/CAIPs/blob/main/CAIPs/caip-10.md +export type CAIP10 = `eip155:${number}:0x${string}`; + +export const graphqlQueryFnHelper = async ( + url: string, + query: TypedDocumentNode, + parametersObject: Record, + chainId = 421613 +) => { + return request(url, query, parametersObject); +}; + +// TODO: generate graphql query +const disputeTemplateQuery = graphql(` + query DisputeTemplate($id: ID!) { + disputeTemplate(id: $id) { + id + templateTag + templateData + templateDataMappings + } + } +`); + +export const genericDecoder = async ( + externalDisputeID: string, + arbitrableDisputeID: string, + disputeTemplateID: string, + disputeTemplateRegistry: CAIP10 +): Promise => { + let subgraphUrl; + switch (disputeTemplateRegistry) { + case "eip155:421613:0x22A58a17F12A718d18C9B6Acca3E311Da1b00A04": // Devnet + subgraphUrl = process.env.REACT_APP_DISPUTE_TEMPLATE_ARBGOERLI_SUBGRAPH_DEVNET; + break; + case "eip155:421613:0xA55D4b90c1F8D1fD0408232bF6FA498dD6786385": // Testnet + subgraphUrl = process.env.REACT_APP_DISPUTE_TEMPLATE_ARBGOERLI_SUBGRAPH_TESTNET; + break; + default: + throw new Error(`Unsupported dispute template registry: ${disputeTemplateRegistry}`); + } + const { disputeTemplate } = await request(subgraphUrl, disputeTemplateQuery, { id: disputeTemplateID.toString() }); + switch (disputeTemplate.specification) { + case "KIP99": + return await kip99Decoder(externalDisputeID, disputeTemplate); + default: + throw new Error(`Unsupported dispute template specification: ${disputeTemplate.specification}`); + } +}; diff --git a/kleros-sdk/src/dataMappings/disputeDetails.ts b/kleros-sdk/src/dataMappings/disputeDetails.ts new file mode 100644 index 000000000..28a3133a6 --- /dev/null +++ b/kleros-sdk/src/dataMappings/disputeDetails.ts @@ -0,0 +1,39 @@ +export type DisputeDetails = { + title: string; + description: string; + question: string; + type: QuestionType; + answers: Answer[]; + policyURI: string; + attachment: Attachment; + frontendUrl: string; + arbitrableChainID: string; + arbitrableAddress: `0x${string}`; + arbitratorChainID: string; + arbitratorAddress: `0x${string}`; + category: string; + lang: string; + specification: string; + version: string; + // missing metadata +}; + +export enum QuestionType { + Bool = "bool", + Datetime = "datetime", + MultipleSelect = "multiple-select", + SingleSelect = "single-select", + Uint = "uint", +} + +export type Answer = { + title: string; + description: string; + id: `0x${string}`; + reserved: boolean; +}; + +export type Attachment = { + label: string; + uri: string; +}; diff --git a/kleros-sdk/dataMappings/executeActions.ts b/kleros-sdk/src/dataMappings/executeActions.ts similarity index 55% rename from kleros-sdk/dataMappings/executeActions.ts rename to kleros-sdk/src/dataMappings/executeActions.ts index 21766a03f..a6fda7969 100644 --- a/kleros-sdk/dataMappings/executeActions.ts +++ b/kleros-sdk/src/dataMappings/executeActions.ts @@ -5,13 +5,13 @@ import { jsonAction } from "./actions/jsonAction"; import { subgraphAction } from "./actions/subgraphAction"; import { retrieveRealityData } from "./retrieveRealityData"; import { - isAbiCallMapping, - isAbiEventMapping, - isFetchIpfsJsonMapping, - isJsonMapping, - isRealityMapping, - isSubgraphMapping, -} from "./utils/actionTypeDetectors"; + validateAbiCallMapping, + validateAbiEventMapping, + validateFetchIpfsJsonMapping, + validateJsonMapping, + validateRealityMapping, + validateSubgraphMapping, +} from "./utils/actionTypeValidators"; import { ActionMapping } from "./utils/actionTypes"; import { replacePlaceholdersWithValues } from "./utils/replacePlaceholdersWithValues"; @@ -20,34 +20,17 @@ export const executeAction = async (mapping: ActionMapping, context = {}) => { switch (mapping.type) { case "graphql": - if (!isSubgraphMapping(mapping)) { - throw new Error("Invalid mapping for graphql action."); - } - return await subgraphAction(mapping); + return await subgraphAction(validateSubgraphMapping(mapping)); case "json": - if (!isJsonMapping(mapping)) { - throw new Error("Invalid mapping for json action."); - } - return jsonAction(mapping); + return jsonAction(validateJsonMapping(mapping)); case "abi/call": - if (!isAbiCallMapping(mapping)) { - throw new Error("Invalid mapping for abi/call action."); - } - return await callAction(mapping); + return await callAction(validateAbiCallMapping(mapping)); case "abi/event": - if (!isAbiEventMapping(mapping)) { - throw new Error("Invalid mapping for abi/event action."); - } - return await eventAction(mapping); + return await eventAction(validateAbiEventMapping(mapping)); case "fetch/ipfs/json": - if (!isFetchIpfsJsonMapping(mapping)) { - throw new Error("Invalid mapping for fetch/ipfs/json action."); - } - return await fetchIpfsJsonAction(mapping); + return await fetchIpfsJsonAction(validateFetchIpfsJsonMapping(mapping)); case "reality": - if (!isRealityMapping(mapping)) { - throw new Error("Invalid mapping for reality action."); - } + mapping = validateRealityMapping(mapping); return await retrieveRealityData(mapping.realityQuestionID, context.arbitrable); default: throw new Error(`Unsupported action type: ${mapping.type}`); diff --git a/kleros-sdk/dataMappings/retrieveRealityData.ts b/kleros-sdk/src/dataMappings/retrieveRealityData.ts similarity index 100% rename from kleros-sdk/dataMappings/retrieveRealityData.ts rename to kleros-sdk/src/dataMappings/retrieveRealityData.ts diff --git a/kleros-sdk/dataMappings/utils/isValidDisputeDetails.ts b/kleros-sdk/src/dataMappings/utils/DisputeDetailsValidator.ts similarity index 81% rename from kleros-sdk/dataMappings/utils/isValidDisputeDetails.ts rename to kleros-sdk/src/dataMappings/utils/DisputeDetailsValidator.ts index ce105896f..13356f131 100644 --- a/kleros-sdk/dataMappings/utils/isValidDisputeDetails.ts +++ b/kleros-sdk/src/dataMappings/utils/DisputeDetailsValidator.ts @@ -1,8 +1,10 @@ import { DisputeDetails, QuestionType } from "./disputeDetailsTypes"; -import { isHexAddress } from "./isHexAddress"; -import { isHexId } from "./isHexId"; -export const isValidDisputeDetails = (data: any): data is DisputeDetails => { +const isHexAddress = (str: string): boolean => /^0x[a-fA-F0-9]{40}$/.test(str); + +const isHexId = (str: string): boolean => /^0x[a-fA-F0-9]{1,64}$/.test(str); + +export const validate = (data: any): data is DisputeDetails => { return ( typeof data.title === "string" && typeof data.description === "string" && diff --git a/kleros-sdk/src/dataMappings/utils/actionTypeValidators.ts b/kleros-sdk/src/dataMappings/utils/actionTypeValidators.ts new file mode 100644 index 000000000..c40b9fdf8 --- /dev/null +++ b/kleros-sdk/src/dataMappings/utils/actionTypeValidators.ts @@ -0,0 +1,51 @@ +import { + SubgraphMapping, + AbiEventMapping, + AbiCallMapping, + JsonMapping, + ActionMapping, + FetchIpfsJsonMapping, + RealityMapping, +} from "./actionTypes"; + +export const validateSubgraphMapping = (mapping: ActionMapping) => { + if ((mapping as SubgraphMapping).endpoint !== undefined) { + throw new Error("Invalid mapping for graphql action."); + } + return mapping as SubgraphMapping; +}; + +export const validateAbiEventMapping = (mapping: ActionMapping) => { + if ((mapping as AbiEventMapping).abi === undefined || (mapping as AbiEventMapping).eventFilter === undefined) { + throw new Error("Invalid mapping for abi/event action."); + } + return mapping as AbiEventMapping; +}; + +export const validateAbiCallMapping = (mapping: ActionMapping) => { + if ((mapping as AbiCallMapping).abi === undefined || (mapping as AbiCallMapping).args === undefined) { + throw new Error("Invalid mapping for abi/call action."); + } + return mapping as AbiCallMapping; +}; + +export const validateJsonMapping = (mapping: ActionMapping) => { + if ((mapping as JsonMapping).value === undefined) { + throw new Error("Invalid mapping for json action."); + } + return mapping as JsonMapping; +}; + +export const validateFetchIpfsJsonMapping = (mapping: ActionMapping) => { + if ((mapping as FetchIpfsJsonMapping).ipfsUri === undefined) { + throw new Error("Invalid mapping for fetch/ipfs/json action."); + } + return mapping as FetchIpfsJsonMapping; +}; + +export const validateRealityMapping = (mapping: ActionMapping) => { + if (mapping.type !== "reality" || typeof (mapping as RealityMapping).realityQuestionID !== "string") { + throw new Error("Invalid mapping for reality action."); + } + return mapping as RealityMapping; +}; diff --git a/kleros-sdk/dataMappings/utils/actionTypes.ts b/kleros-sdk/src/dataMappings/utils/actionTypes.ts similarity index 100% rename from kleros-sdk/dataMappings/utils/actionTypes.ts rename to kleros-sdk/src/dataMappings/utils/actionTypes.ts diff --git a/kleros-sdk/dataMappings/utils/createResultObject.ts b/kleros-sdk/src/dataMappings/utils/createResultObject.ts similarity index 93% rename from kleros-sdk/dataMappings/utils/createResultObject.ts rename to kleros-sdk/src/dataMappings/utils/createResultObject.ts index 9c265a4a8..7a76f9f6d 100644 --- a/kleros-sdk/dataMappings/utils/createResultObject.ts +++ b/kleros-sdk/src/dataMappings/utils/createResultObject.ts @@ -1,3 +1,4 @@ +// Can this be replaced by Mustache ? export const createResultObject = (sourceData, seek, populate) => { let result = {}; seek.forEach((key, idx) => { diff --git a/kleros-sdk/src/dataMappings/utils/disputeDetailsSchema.ts b/kleros-sdk/src/dataMappings/utils/disputeDetailsSchema.ts new file mode 100644 index 000000000..af9eb36ee --- /dev/null +++ b/kleros-sdk/src/dataMappings/utils/disputeDetailsSchema.ts @@ -0,0 +1,66 @@ +import { z } from "zod"; +import { isAddress } from "viem"; +import { normalize } from "viem/ens"; + +export const ethAddressSchema = z.string().refine((value) => isAddress(value), { + message: "Provided address is invalid.", +}); + +export const ensNameSchema = z + .string() + .refine((value) => typeof normalize(value) === "string" && value.endsWith(".eth"), { + message: "Provided ENS name is invalid.", + }); + +export const ethAddressOrEnsNameSchema = z.union([ethAddressSchema, ensNameSchema], { + errorMap: () => ({ message: "Provided address or ENS name is invalid." }), +}); + +export enum QuestionType { + Bool = "bool", + Datetime = "datetime", + MultipleSelect = "multiple-select", + SingleSelect = "single-select", + Uint = "uint", +} +export const QuestionTypeSchema = z.nativeEnum(QuestionType); + +export const AnswerSchema = z.object({ + id: z.string().regex(/^0x[0-9a-fA-F]+$/), // should be a bigint + title: z.string(), + description: z.string(), + reserved: z.boolean(), +}); + +export const AttachmentSchema = z.object({ + label: z.string(), + uri: z.string(), +}); + +export const AliasSchema = z.object({ + id: z.string().optional(), + name: z.string(), + address: ethAddressOrEnsNameSchema, +}); + +const DisputeDetailsSchema = z.object({ + title: z.string(), + description: z.string(), + question: z.string(), + type: QuestionTypeSchema, + answers: z.array(AnswerSchema), + policyURI: z.string(), + attachment: AttachmentSchema, + frontendUrl: z.string(), + arbitrableChainID: z.string(), + arbitrableAddress: ethAddressSchema, + arbitratorChainID: z.string(), + arbitratorAddress: ethAddressSchema, + category: z.string(), + lang: z.string(), + specification: z.string(), + aliases: z.array(AliasSchema).optional(), + version: z.string(), +}); + +export default DisputeDetailsSchema; diff --git a/kleros-sdk/dataMappings/utils/disputeDetailsTypes.ts b/kleros-sdk/src/dataMappings/utils/disputeDetailsTypes.ts similarity index 100% rename from kleros-sdk/dataMappings/utils/disputeDetailsTypes.ts rename to kleros-sdk/src/dataMappings/utils/disputeDetailsTypes.ts diff --git a/kleros-sdk/dataMappings/utils/populateTemplate.ts b/kleros-sdk/src/dataMappings/utils/populateTemplate.ts similarity index 83% rename from kleros-sdk/dataMappings/utils/populateTemplate.ts rename to kleros-sdk/src/dataMappings/utils/populateTemplate.ts index 56ebf5ea0..9f240df3b 100644 --- a/kleros-sdk/dataMappings/utils/populateTemplate.ts +++ b/kleros-sdk/src/dataMappings/utils/populateTemplate.ts @@ -1,6 +1,6 @@ import mustache from "mustache"; import { DisputeDetails } from "./disputeDetailsTypes"; -import { isValidDisputeDetails } from "./isValidDisputeDetails"; +import { validate } from "./DisputeDetailsValidator"; export const populateTemplate = (mustacheTemplate: string, data: any): DisputeDetails => { const render = mustache.render(mustacheTemplate, data); @@ -8,7 +8,7 @@ export const populateTemplate = (mustacheTemplate: string, data: any): DisputeDe const dispute = JSON.parse(render); // TODO: the validation below is too strict, it should be fixed, disabled for now, FIXME - if (!isValidDisputeDetails(dispute)) { + if (!validate(dispute)) { // throw new Error(`Invalid dispute details format: ${JSON.stringify(dispute)}`); } diff --git a/kleros-sdk/dataMappings/utils/replacePlaceholdersWithValues.ts b/kleros-sdk/src/dataMappings/utils/replacePlaceholdersWithValues.ts similarity index 94% rename from kleros-sdk/dataMappings/utils/replacePlaceholdersWithValues.ts rename to kleros-sdk/src/dataMappings/utils/replacePlaceholdersWithValues.ts index af45e5e11..6277f9394 100644 --- a/kleros-sdk/dataMappings/utils/replacePlaceholdersWithValues.ts +++ b/kleros-sdk/src/dataMappings/utils/replacePlaceholdersWithValues.ts @@ -1,3 +1,4 @@ +// Replace by Mustache ? export const replacePlaceholdersWithValues = (mapping: any, context: any) => { let mappingAsString = JSON.stringify(mapping); diff --git a/kleros-sdk/dataMappings/utils/configureSDK.ts b/kleros-sdk/src/sdk.ts similarity index 100% rename from kleros-sdk/dataMappings/utils/configureSDK.ts rename to kleros-sdk/src/sdk.ts diff --git a/kleros-sdk/test/dataMappings.test.ts b/kleros-sdk/test/dataMappings.test.ts index 6d2c8208f..6c31b664c 100644 --- a/kleros-sdk/test/dataMappings.test.ts +++ b/kleros-sdk/test/dataMappings.test.ts @@ -1,10 +1,10 @@ import { describe, expect, it } from "vitest"; -import { populateTemplate } from "dataMappings/utils/populateTemplate"; -import { jsonAction } from "dataMappings/actions/jsonAction"; -import { subgraphAction } from "dataMappings/actions/subgraphAction"; -import { callAction } from "dataMappings/actions/callAction"; -import { eventAction } from "dataMappings/actions/eventAction"; -import { fetchIpfsJsonAction } from "dataMappings/actions/fetchIpfsJsonAction"; +import { populateTemplate } from "src/dataMappings/utils/populateTemplate"; +import { jsonAction } from "src/dataMappings/actions/jsonAction"; +import { subgraphAction } from "src/dataMappings/actions/subgraphAction"; +import { callAction } from "../src/dataMappings/actions/callAction"; +import { eventAction } from "src/dataMappings/actions/eventAction"; +import { fetchIpfsJsonAction } from "src/dataMappings/actions/fetchIpfsJsonAction"; const exampleObject = { evidence: { @@ -18,6 +18,7 @@ const exampleObject = { describe("jsonAction", () => { it("should extract and map data correctly", () => { const mapping = { + type: "json", value: exampleObject.evidence.fileURI, seek: ["photo", "video"], populate: ["photoUrl", "videoUrl"], @@ -49,6 +50,7 @@ describe("subgraphAction with variables", () => { const populate = ["escrowsData"]; const mapping = { + type: "graphql", endpoint: endpoint, query: query, variables: variables, @@ -75,6 +77,7 @@ describe("callAction", () => { const knownAddress = "0x0000000000000000000000000000000000000000"; const mapping = { + type: "abi/call", abi: abi, address: contractAddress, args: [knownAddress], @@ -97,6 +100,7 @@ describe("eventAction", () => { const toBlock = "latest"; const mapping = { + type: "abi/event", abi: eventAbi, address: contractAddress, eventFilter: { @@ -123,6 +127,7 @@ describe("fetchIpfsJsonAction", () => { const populate = ["name", "firstName", "lastName", "anotherFile"]; const mapping = { + type: "fetch/ipfs/json", ipfsUri: ipfsUri, seek: seek, populate: populate, diff --git a/kleros-sdk/test/disputeDetailsSchema.test.ts b/kleros-sdk/test/disputeDetailsSchema.test.ts index 873dc0771..ac2c7878a 100644 --- a/kleros-sdk/test/disputeDetailsSchema.test.ts +++ b/kleros-sdk/test/disputeDetailsSchema.test.ts @@ -1,5 +1,9 @@ import { describe, expect, it } from "vitest"; -import { ethAddressSchema, ensNameSchema, ethAddressOrEnsNameSchema } from "dataMappings/utils/disputeDetailsSchema"; +import { + ethAddressSchema, + ensNameSchema, + ethAddressOrEnsNameSchema, +} from "src/dataMappings/utils/disputeDetailsSchema"; describe("Dispute Details Schema", () => { it("snapshot", () => { @@ -91,7 +95,7 @@ describe("Dispute Details Schema", () => { }); }); - describe("disputeDetailsSchema", () => { + describe.skip("disputeDetailsSchema", () => { // TODO: add tests }); }); diff --git a/kleros-sdk/tsconfig.json b/kleros-sdk/tsconfig.json index 54d1b65e7..6a9b24bb5 100644 --- a/kleros-sdk/tsconfig.json +++ b/kleros-sdk/tsconfig.json @@ -2,6 +2,17 @@ "extends": "@kleros/kleros-v2-tsconfig/react-library.json", "compilerOptions": { "baseUrl": ".", + "paths": { + "~*": [ + "./*" + ], + "src*": [ + "./src*" + ], + "dataMappings*": [ + "./src/dataMappings*" + ] + }, "target": "ES6", "module": "CommonJS", "outDir": "build/dist", @@ -15,7 +26,7 @@ "isolatedModules": true }, "include": [ - ".", + "src", "test" ], "exclude": [ diff --git a/kleros-sdk/vite.config.ts b/kleros-sdk/vite.config.ts deleted file mode 100644 index 36bfcfdc4..000000000 --- a/kleros-sdk/vite.config.ts +++ /dev/null @@ -1,12 +0,0 @@ -/// - -// Configure Vitest (https://vitest.dev/config/) - -import { defineConfig } from "vite"; - -export default defineConfig({ - test: { - /* for example, use global to avoid globals imports (describe, test, expect): */ - // globals: true, - }, -}); diff --git a/web/src/components/DisputeCard/index.tsx b/web/src/components/DisputeCard/index.tsx index 0b79647e1..3a897c8eb 100644 --- a/web/src/components/DisputeCard/index.tsx +++ b/web/src/components/DisputeCard/index.tsx @@ -16,8 +16,8 @@ import PeriodBanner from "./PeriodBanner"; import { isUndefined } from "utils/index"; import { getLocalRounds } from "utils/getLocalRounds"; import { responsiveSize } from "styles/responsiveSize"; -import { populateTemplate } from "@kleros/kleros-sdk/dataMappings/utils/populateTemplate"; -import { DisputeDetails } from "@kleros/kleros-sdk/dataMappings/utils/disputeDetailsTypes"; +import { populateTemplate } from "@kleros/kleros-sdk/src/dataMappings/utils/populateTemplate"; +import { DisputeDetails } from "@kleros/kleros-sdk/src/dataMappings/utils/disputeDetailsTypes"; import { INVALID_DISPUTE_DATA_ERROR } from "consts/index"; const StyledCard = styled(Card)` diff --git a/web/src/components/DisputePreview/DisputeContext.tsx b/web/src/components/DisputePreview/DisputeContext.tsx index b23c26ee0..8f425f20d 100644 --- a/web/src/components/DisputePreview/DisputeContext.tsx +++ b/web/src/components/DisputePreview/DisputeContext.tsx @@ -6,7 +6,7 @@ import { isUndefined } from "utils/index"; import { Answer as IAnswer } from "context/NewDisputeContext"; import AliasDisplay from "./Alias"; import { responsiveSize } from "styles/responsiveSize"; -import { DisputeDetails } from "@kleros/kleros-sdk/dataMappings/utils/disputeDetailsTypes"; +import { DisputeDetails } from "@kleros/kleros-sdk/src/dataMappings/utils/disputeDetailsTypes"; const StyledH1 = styled.h1` margin: 0; diff --git a/web/src/pages/Cases/CaseDetails/Overview/index.tsx b/web/src/pages/Cases/CaseDetails/Overview/index.tsx index 399bcbe4b..dacdb80e5 100644 --- a/web/src/pages/Cases/CaseDetails/Overview/index.tsx +++ b/web/src/pages/Cases/CaseDetails/Overview/index.tsx @@ -5,11 +5,11 @@ import { formatEther } from "viem"; import { useDisputeDetailsQuery } from "queries/useDisputeDetailsQuery"; import { useDisputeTemplate } from "queries/useDisputeTemplate"; import { useCourtPolicy } from "queries/useCourtPolicy"; -import { populateTemplate } from "@kleros/kleros-sdk/dataMappings/utils/populateTemplate"; -import { executeActions } from "@kleros/kleros-sdk/dataMappings/executeActions"; -import { configureSDK } from "@kleros/kleros-sdk/dataMappings/utils/configureSDK"; +import { populateTemplate } from "@kleros/kleros-sdk/src/dataMappings/utils/populateTemplate"; +import { executeActions } from "@kleros/kleros-sdk/src/dataMappings/executeActions"; +import { configureSDK } from "@kleros/kleros-sdk/src/sdk"; import { alchemyApiKey } from "context/Web3Provider"; -import { DisputeDetails } from "@kleros/kleros-sdk/dataMappings/utils/disputeDetailsTypes"; +import { DisputeDetails } from "@kleros/kleros-sdk/src/dataMappings/utils/disputeDetailsTypes"; import { Periods } from "consts/periods"; import DisputeInfo from "components/DisputeCard/DisputeInfo"; import Verdict from "components/Verdict/index"; diff --git a/web/src/pages/DisputeTemplateView.tsx b/web/src/pages/DisputeTemplateView.tsx index e1d8dadb1..171f156e2 100644 --- a/web/src/pages/DisputeTemplateView.tsx +++ b/web/src/pages/DisputeTemplateView.tsx @@ -4,10 +4,10 @@ import { Textarea } from "@kleros/ui-components-library"; import PolicyIcon from "svgs/icons/policy.svg"; import ReactMarkdown from "components/ReactMarkdown"; import { INVALID_DISPUTE_DATA_ERROR, IPFS_GATEWAY } from "consts/index"; -import { configureSDK } from "@kleros/kleros-sdk/dataMappings/utils/configureSDK"; -import { executeActions } from "@kleros/kleros-sdk/dataMappings/executeActions"; -import { populateTemplate } from "@kleros/kleros-sdk/dataMappings/utils/populateTemplate"; -import { Answer, DisputeDetails } from "@kleros/kleros-sdk/dataMappings/utils/disputeDetailsTypes"; +import { configureSDK } from "@kleros/kleros-sdk/src/sdk"; +import { executeActions } from "@kleros/kleros-sdk/src/dataMappings/executeActions"; +import { populateTemplate } from "@kleros/kleros-sdk/src/dataMappings/utils/populateTemplate"; +import { Answer, DisputeDetails } from "@kleros/kleros-sdk/src/dataMappings/utils/disputeDetailsTypes"; import { alchemyApiKey } from "context/Web3Provider"; const Container = styled.div`