diff --git a/kleros-sdk/config/v2-disputetemplate/reality/example4/DisputeMappings.json.mustache b/kleros-sdk/config/v2-disputetemplate/reality/example4/DisputeMappings.json.mustache new file mode 100644 index 000000000..6c7832dcf --- /dev/null +++ b/kleros-sdk/config/v2-disputetemplate/reality/example4/DisputeMappings.json.mustache @@ -0,0 +1,40 @@ +[ + { + "type": "graphql", + "endpoint": "https://api.thegraph.com/subgraphs/name/kemuru/escrow-v2-devnet", + "query": "query GetTransaction($transactionId: ID!) { escrow(id: $transactionId) { transactionUri buyer seller amount asset deadline } }", + "variables": { + "transactionId": "{{externalDisputeID}}" + }, + "seek": [ + "escrow.transactionUri", + "escrow.buyer", + "escrow.seller", + "escrow.amount", + "escrow.asset", + "escrow.deadline" + ], + "populate": [ + "transactionUri", + "address", + "sendingRecipientAddress", + "amount", + "asset", + "deadline" + ] + }, + { + "type": "fetch/ipfs/json", + "ipfsUri": "{{transactionUri}}", + "seek": [ + "title", + "description", + "extraDescriptionUri" + ], + "populate": [ + "escrowTitle", + "deliverableText", + "extraDescriptionUri" + ] + } +] \ No newline at end of file diff --git a/kleros-sdk/config/v2-disputetemplate/reality/example4/DisputeTemplate.json.mustache b/kleros-sdk/config/v2-disputetemplate/reality/example4/DisputeTemplate.json.mustache new file mode 100644 index 000000000..b305e190d --- /dev/null +++ b/kleros-sdk/config/v2-disputetemplate/reality/example4/DisputeTemplate.json.mustache @@ -0,0 +1,40 @@ +{ + "title": "{{escrowTitle}}", + "description": "{{deliverableText}}", + "question": "Which party abided by the terms of the contract?", + "answers": [ + { + "title": "Refund the Buyer", + "description": "Select this to return the funds to the Buyer." + }, + { + "title": "Pay the Seller", + "description": "Select this to release the funds to the Seller." + } + ], + "policyURI": "ipfs://TODO", + "attachment": { + "label": "Transaction Terms", + "uri": "{{extraDescriptionUri}}" + }, + "frontendUrl": "https://escrow-v2.kleros.builders/#/myTransactions/{{externalDisputeID}}", + "arbitrableChainID": "421614", + "arbitrableAddress": "{{arbitrator}}", + "arbitratorChainID": "421614", + "arbitratorAddress": "{{arbitrable}}", + "metadata": { + "buyer": "{{address}}", + "seller": "{{sendingRecipientAddress}}", + "amount": "{{sendingQuantity}}", + "asset": "{{asset}}", + "deadline": "{{deadline}}", + "transactionUri": "{{transactionUri}}" + }, + "category": "Escrow", + "specification": "KIPXXX", + "aliases": { + "Buyer": "{{address}}", + "Seller": "{{sendingRecipientAddress}}" + }, + "version": "1.0" +} \ No newline at end of file diff --git a/kleros-sdk/src/dataMappings/utils/actionTypeValidators.ts b/kleros-sdk/src/dataMappings/utils/actionTypeValidators.ts index c40b9fdf8..b95296043 100644 --- a/kleros-sdk/src/dataMappings/utils/actionTypeValidators.ts +++ b/kleros-sdk/src/dataMappings/utils/actionTypeValidators.ts @@ -9,7 +9,7 @@ import { } from "./actionTypes"; export const validateSubgraphMapping = (mapping: ActionMapping) => { - if ((mapping as SubgraphMapping).endpoint !== undefined) { + if ((mapping as SubgraphMapping).endpoint === undefined) { throw new Error("Invalid mapping for graphql action."); } return mapping as SubgraphMapping; diff --git a/kleros-sdk/src/dataMappings/utils/createResultObject.ts b/kleros-sdk/src/dataMappings/utils/createResultObject.ts index cc910c063..184965e4f 100644 --- a/kleros-sdk/src/dataMappings/utils/createResultObject.ts +++ b/kleros-sdk/src/dataMappings/utils/createResultObject.ts @@ -1,12 +1,26 @@ // Can this be replaced by Mustache ? export const createResultObject = (sourceData, seek, populate) => { const result = {}; + seek.forEach((key, idx) => { - let foundValue; - if (typeof sourceData !== "object" || key === "0") { - foundValue = sourceData; + let foundValue = sourceData; + + if (key.includes(".")) { + const keyParts = key.split("."); + for (const part of keyParts) { + if (foundValue[part] !== undefined) { + foundValue = foundValue[part]; + } else { + foundValue = undefined; + break; + } + } } else { - foundValue = sourceData[key]; + if (typeof sourceData !== "object" || key === "0") { + foundValue = sourceData; + } else { + foundValue = sourceData[key]; + } } console.log(`Seek key: ${key}, Found value:`, foundValue); diff --git a/web/src/hooks/useContractAddress.tsx b/web/src/hooks/useContractAddress.tsx index 474ce3e78..ad0cb8ecf 100644 --- a/web/src/hooks/useContractAddress.tsx +++ b/web/src/hooks/useContractAddress.tsx @@ -1,7 +1,16 @@ import { Abi, PublicClient } from "viem"; import { usePublicClient } from "wagmi"; import { GetContractArgs, GetContractResult } from "wagmi/actions"; -import { getPinakionV2, pinakionV2ABI, getWeth, getPnkFaucet, wethABI, pnkFaucetABI } from "./contracts/generated"; +import { + getPinakionV2, + pinakionV2ABI, + getWeth, + getPnkFaucet, + wethABI, + pnkFaucetABI, + klerosCoreABI, + getKlerosCore, +} from "./contracts/generated"; type Config = Omit, "abi" | "address"> & { chainId?: any; @@ -25,3 +34,7 @@ export const useWETHAddress = () => { export const usePNKFaucetAddress = () => { return useContractAddress(getPnkFaucet)?.address; }; + +export const useKlerosCoreAddress = () => { + return useContractAddress(getKlerosCore)?.address; +}; diff --git a/web/src/layout/Header/TestnetBanner.tsx b/web/src/layout/Header/TestnetBanner.tsx index e0ed68e3a..3e9edc4dc 100644 --- a/web/src/layout/Header/TestnetBanner.tsx +++ b/web/src/layout/Header/TestnetBanner.tsx @@ -9,11 +9,11 @@ const Container = styled.div` background-color: ${({ theme }) => theme.tintPurple}; color: ${({ theme }) => theme.primaryText}; - padding: 6px 2px; + padding: 5px 2px; ${landscapeStyle( () => css` - padding: 8px 10px; + padding: 5px 10px; ` )} `; diff --git a/web/src/layout/Header/navbar/Explore.tsx b/web/src/layout/Header/navbar/Explore.tsx index dccdf73a1..812862c45 100644 --- a/web/src/layout/Header/navbar/Explore.tsx +++ b/web/src/layout/Header/navbar/Explore.tsx @@ -48,6 +48,10 @@ const StyledLink = styled(Link)<{ isActive: boolean }>` )}; `; +const HiddenLink = styled(StyledLink)<{ isActive: boolean }>` + color: ${({ isActive, theme }) => (isActive ? theme.primaryText : theme.primaryPurple)}; +`; + const links = [ { to: "/", text: "Home" }, { to: "/cases/display/1/desc/all", text: "Cases" }, @@ -73,6 +77,15 @@ const Explore: React.FC = () => { ))} + + + Dev + + ); }; diff --git a/web/src/pages/DisputeTemplateView.tsx b/web/src/pages/DisputeTemplateView.tsx index 171f156e2..b6d945dc3 100644 --- a/web/src/pages/DisputeTemplateView.tsx +++ b/web/src/pages/DisputeTemplateView.tsx @@ -1,6 +1,6 @@ import React, { useEffect, useState } from "react"; import styled from "styled-components"; -import { Textarea } from "@kleros/ui-components-library"; +import { Field, 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"; @@ -8,10 +8,12 @@ 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 { useKlerosCoreAddress } from "hooks/useContractAddress"; import { alchemyApiKey } from "context/Web3Provider"; +import { useDebounce } from "react-use"; +import Skeleton from "react-loading-skeleton"; const Container = styled.div` - width: 50%; height: auto; display: flex; flex-direction: column; @@ -69,7 +71,7 @@ const LinkContainer = styled.div` justify-content: space-between; `; -const Wrapper = styled.div` +const LongTextSections = styled.div` min-height: calc(100vh - 144px); margin: 24px; display: flex; @@ -77,58 +79,198 @@ const Wrapper = styled.div` `; const StyledTextArea = styled(Textarea)` - width: 50%; + width: 30vw; height: calc(100vh - 300px); + font-family: "Roboto Mono", monospace; `; -const DisputeTemplateView: React.FC = () => { +const StyledForm = styled.form` + display: flex; + flex-direction: column; + justify-content: center; + margin-top: 24px; + margin-left: 24px; +`; + +const StyledRow = styled.div` + display: flex; + flex-direction: row; + gap: 24px; +`; + +const StyledP = styled.p` + font-family: "Roboto Mono", monospace; +`; + +const StyledHeader = styled.h2` + margin-top 24px; +`; + +const LongText = styled.div` + display: flex; + flex-direction: column; + width: auto; +`; + +const DisputeTemplateView = () => { + const klerosCoreAddress = useKlerosCoreAddress(); const [disputeDetails, setDisputeDetails] = useState(undefined); const [disputeTemplateInput, setDisputeTemplateInput] = useState(""); const [dataMappingsInput, setDataMappingsInput] = useState(""); + const [arbitrator, setArbitrator] = useState(klerosCoreAddress as string); + const [arbitrable, setArbitrable] = useState("0x10f7A6f42Af606553883415bc8862643A6e63fdA"); // Escrow devnet + const [arbitrableDisputeID, setArbitrableDisputeID] = useState("0"); + const [externalDisputeID, setExternalDisputeID] = useState("0"); + const [templateID, setTemplateID] = useState("0"); + const [templateUri, setTemplateUri] = useState(""); - // TODO: add some input fields for the IArbitrableV2.DisputeRequest event which is available to the SDK in a real case - // - arbitrable (= the address which emitted DisputeRequest) - // - the DisputeRequest event params: arbitrator, arbitrableDisputeID, externalDisputeID, templateId, templateUri - const arbitrable = "0xdaf749DABE7be6C6894950AE69af35c20a00ABd9"; + const [debouncedArbitrator, setDebouncedArbitrator] = useState(arbitrator); + const [debouncedArbitrable, setDebouncedArbitrable] = useState(arbitrable); + const [debouncedArbitrableDisputeID, setDebouncedArbitrableDisputeID] = useState(arbitrableDisputeID); + const [debouncedExternalDisputeID, setDebouncedExternalDisputeID] = useState(externalDisputeID); + const [debouncedTemplateID, setDebouncedTemplateID] = useState(templateID); + const [debouncedTemplateUri, setDebouncedTemplateUri] = useState(templateUri); + const [loading, setLoading] = useState(false); + + useDebounce(() => setDebouncedArbitrator(arbitrator), 350, [arbitrator]); + useDebounce(() => setDebouncedArbitrable(arbitrable), 350, [arbitrable]); + useDebounce(() => setDebouncedArbitrableDisputeID(arbitrableDisputeID), 350, [arbitrableDisputeID]); + useDebounce(() => setDebouncedExternalDisputeID(externalDisputeID), 350, [externalDisputeID]); + useDebounce(() => setDebouncedTemplateID(templateID), 350, [templateID]); + useDebounce(() => setDebouncedTemplateUri(templateUri), 350, [templateUri]); useEffect(() => { configureSDK({ apiKey: alchemyApiKey }); - const initialContext = { - arbitrable: arbitrable, - }; - if (!disputeTemplateInput || !dataMappingsInput) return; - - const fetchData = async () => { - try { - const parsedMappings = JSON.parse(dataMappingsInput); - const data = await executeActions(parsedMappings, initialContext); - const finalDisputeDetails = populateTemplate(disputeTemplateInput, data); - setDisputeDetails(finalDisputeDetails); - console.log("finalTemplate: ", finalDisputeDetails); - } catch (e) { - console.error(e); - setDisputeDetails(undefined); + let isFetchDataScheduled = false; + + const scheduleFetchData = () => { + if (!isFetchDataScheduled) { + isFetchDataScheduled = true; + + setLoading(true); + + setTimeout(() => { + const initialContext = { + arbitrator: debouncedArbitrator, + arbitrable: debouncedArbitrable, + arbitrableDisputeID: debouncedArbitrableDisputeID, + externalDisputeID: debouncedExternalDisputeID, + templateID: debouncedTemplateID, + templateUri: debouncedTemplateUri, + }; + + const fetchData = async () => { + try { + const parsedMappings = JSON.parse(dataMappingsInput); + const data = await executeActions(parsedMappings, initialContext); + const finalDisputeDetails = populateTemplate(disputeTemplateInput, data); + setDisputeDetails(finalDisputeDetails); + } catch (e) { + console.error(e); + setDisputeDetails(undefined); + } finally { + setLoading(false); + } + }; + + fetchData(); + + isFetchDataScheduled = false; + }, 350); } }; - fetchData(); - }, [disputeTemplateInput, dataMappingsInput, arbitrable]); - + if ( + disputeTemplateInput || + dataMappingsInput || + debouncedArbitrator || + debouncedArbitrable || + debouncedArbitrableDisputeID || + debouncedExternalDisputeID || + debouncedTemplateID || + debouncedTemplateUri + ) { + scheduleFetchData(); + } + }, [ + disputeTemplateInput, + dataMappingsInput, + debouncedArbitrator, + debouncedArbitrable, + debouncedArbitrableDisputeID, + debouncedExternalDisputeID, + debouncedTemplateID, + debouncedTemplateUri, + ]); return ( - - setDisputeTemplateInput(e.target.value)} - placeholder="Enter dispute template" - /> - setDataMappingsInput(e.target.value)} - placeholder="Enter data mappings" - /> - - + <> + + Dispute Request event parameters + + {"{{ arbitrator }}"} + setArbitrator(e.target.value)} placeholder="0x..." /> + + + {"{{ arbitrable }}"} + setArbitrable(e.target.value)} placeholder="0x..." /> + + + {"{{ arbitrableDisputeID }}"} + setArbitrableDisputeID(e.target.value)} + placeholder="0" + /> + + + {"{{ externalDisputeID }}"} + setExternalDisputeID(e.target.value)} + placeholder="0" + /> + + + {"{{ templateID }}"} + setTemplateID(e.target.value)} placeholder="0" /> + + + {"{{ templateUri }}"} + setTemplateUri(e.target.value)} + placeholder="ipfs://... (optional)" + /> + + + + + Template + setDisputeTemplateInput(e.target.value)} + placeholder="Enter dispute template" + /> + + + Data Mapping + setDataMappingsInput(e.target.value)} + placeholder="Enter data mappings" + /> + + + Dispute Preview +
+ {loading ? : } +
+
+ ); };