light-curate-data-service
Version:
A TypeScript library for interacting with LightGeneralizedTCR contracts
454 lines (388 loc) • 14.8 kB
Markdown
# Prompt 1 : Display Items from Light Curate Registry
## Setup
This frontend is structured around the light-curate-data-service NPM package, which retrieves data from and interacts with a Kleros Curate smart contract.
```bash
yarn add light-curate-data-service
```
```typescript
import { fetchItems, SUPPORTED_CHAINS } from "light-curate-data-service";
const REGISTRY_ADDRESS = "0xda03509bb770061a61615ad8fc8e1858520ebd86"; // store as global constant for reference from different parts of the code
const CHAIN_ID = SUPPORTED_CHAINS.ETHEREUM_MAINNET; // 1
```
## Fetch Data
```typescript
const { items, stats } = await fetchItems(REGISTRY_ADDRESS, CHAIN_ID, {
onProgress: ({ loaded, total }) => {
console.log(`Loaded ${loaded} items${total ? ` of ${total}` : ""}`);
},
maxBatches: 100, // Optional: Limit initial load for pagination
});
```
## Data Structure
Each item in the returned `items` array contains:
- `itemID`: Unique identifier
- `status`: Current status of the item ("Absent" | "RegistrationRequested" | "Registered" | "ClearingRequested" | "Challenged")
- `disputed`: Boolean flag if item is disputed
- `latestRequestSubmissionTime`: Unix timestamp of latest activity
- `metadata.props`: Array of structured properties:
```typescript
// Each property in the metadata.props array follows this structure:
{
description: string; // Property description (e.g., "Project name", "Repository URL")
isIdentifier: boolean; // If true, this property uniquely identifies the item
label: string; // Display label shown in UI
type: string; // Data type (e.g., "text", "link", etc.)
value: string; // Actual value of the property
}
```
- `requests`: Array of request objects:
```typescript
// Each request contains:
{
challenger: string; // Address of the challenger if disputed
deposit: string; // Deposit amount in Wei
disputeID: string; // ID of the dispute if challenged
disputed: boolean; // Whether this request is disputed
requester: string; // Address that made the request
resolutionTime: string; // When the request was resolved (Unix timestamp)
resolved: boolean; // Whether the request is resolved
requestType: string; // Type of request
evidenceGroup: { // Evidence submitted for this request
id: string;
evidences: [{
id: string; // Unique identifier for evidence
URI: string; // IPFS path to evidence content
party: string; // Address that submitted evidence
timestamp: string; // When evidence was submitted (Unix timestamp)
}]
}
rounds: [{ // Appeal rounds if disputed
appealed: boolean;
amountPaidChallenger: string; // Appeal fees paid by challenger
amountPaidRequester: string; // Appeal fees paid by requester
appealPeriodEnd: string; // When appeal period ends (Unix timestamp)
appealPeriodStart: string; // When appeal period starts (Unix timestamp)
hasPaidChallenger: boolean; // If challenger has fully funded appeal
hasPaidRequester: boolean; // If requester has fully funded appeal
ruling: number; // None = No ruling yet, 0 = Refuse to Arbitrate, 1 = Accept, 2 = Reject
}]
}
```
## Display Requirements
1. Create a grid/list of item cards
2. For each item, show:
- Status badge (prominent)
- Identifier properties (from props where isIdentifier=true)
- Submission timestamp (formatted)
- Disputed status indicator if applicable
3. Make cards clickable for detailed view
4. Support pagination if maxBatches was used
## Example Card Layout
```typescript
function ItemCard({ item }) {
const identifiers = item.metadata.props.filter(p => p.isIdentifier);
const timestamp = new Date(parseInt(item.latestRequestSubmissionTime) * 1000);
return (
<Card onClick={() => showDetails(item.itemID)}>
<StatusBadge>{item.status}</StatusBadge>
{item.disputed && <DisputedIndicator />}
{identifiers.map(prop => (
<PropertyDisplay label={prop.label} value={prop.value} />
))}
<TimeStamp date={timestamp} />
</Card>
);
}
```
The example above are actual and the data structures should be adhered to strictly.
Note: All currency values are in ETH since we're using Ethereum Mainnet (chainId=1).
# Prompt 2: Add Submission Flow to Light Curate Registry Frontend
Now that we have the basic display of registry items working, let's add the ability for users to submit new items.
## Setup
Use the same registry configuration from Prompt 1:
```typescript
import { LightCurateRegistry, SUPPORTED_CHAINS, uploadJSONToIPFS } from "light-curate-data-service";
const REGISTRY_ADDRESS = "0xda03509bb770061a61615ad8fc8e1858520ebd86";
const CHAIN_ID = SUPPORTED_CHAINS.ETHEREUM_MAINNET;
const registry = new LightCurateRegistry(REGISTRY_ADDRESS, CHAIN_ID);
```
## Requirements
1. Add a "Submit New Item" button to the registry view
2. Create a form modal that opens when the button is clicked
3. Dynamically generate form fields based on the registry's MetaEvidence
4. Handle submission to IPFS and the registry contract
## Implementation Steps
1. Fetch form structure from registry:
```typescript
const { registrationMetaEvidence } = await registry.getLatestMetaEvidence();
const metaData = await fetchFromIPFS(registrationMetaEvidence);
const formFields = metaData.metadata.columns;
```
2. Create submission form component:
```typescript
function SubmissionForm() {
const [values, setValues] = useState({});
const handleSubmit = async (e) => {
e.preventDefault();
// Get deposit information
const depositInfo = await registry.getSubmissionDepositAmount();
// depositInfo returns:
// {
// depositAmount: string; // Total deposit in ETH
// depositInWei: string; // Total deposit in Wei
// breakdown: {
// baseDeposit: string; // Base deposit in ETH
// arbitrationCost: string; // Arbitration cost in ETH
// total: string; // Total deposit in ETH
// },
// challengePeriodDays: number // Challenge period duration in days
// }
const submission = {
columns: formFields, // Include the original columns from MetaEvidence
values: values // Keys in values match the labels from formFields
};
// Upload to IPFS and submit to registry
const ipfsPath = await uploadJSONToIPFS(submission);
await registry.submitToRegistry(ipfsPath);
};
return (
<form onSubmit={handleSubmit}>
{formFields.map(field => (
<FormField
key={field.label}
label={field.label}
description={field.description}
type={field.type}
required={!field.label.includes("optional")}
onChange={(value) => setValues({...values, [field.label]: value})}
/>
))}
<button type="submit">Submit</button>
</form>
);
}
```
3. Add submission button and modal to the registry view from Prompt 1:
```typescript
function RegistryView() {
// ... existing code from Prompt 1 ...
const [showForm, setShowForm] = useState(false);
return (
<div>
<button onClick={() => setShowForm(true)}>
Submit New Item
</button>
{showForm && (
<Modal onClose={() => setShowForm(false)}>
<SubmissionForm />
</Modal>
)}
{/* Existing item cards from Prompt 1 */}
{items.map(item => (
<ItemCard key={item.itemID} item={item} />
))}
</div>
);
}
```
## Data Format
The submission must follow this structure, where the keys in the `values` object exactly match the `label` fields from the MetaEvidence columns:
```typescript
{
"columns": metaData.metadata.columns, // Direct copy from MetaEvidence
"values": {
[field.label]: fieldValue, // For each field in columns array
}
}
```
For example, if the MetaEvidence contains these columns:
```typescript
columns: [
{ label: "Project Title", type: "text", description: "..." },
{ label: "Repository", type: "link", description: "..." }
]
```
Your submission should look like:
```typescript
{
columns: [/* same columns as above */],
values: {
"Project Title": "My Project",
"Repository": "https://github.com/..."
}
}
```
In the submission form, use the getSubmissionDepositAmount function in the registry object to display the breakdown of the deposit.
Note: This builds upon the item display functionality from Prompt 1, adding the submission flow while maintaining the same registry connection and configuration. The form fields and submission format are dynamically determined by the registry's MetaEvidence.
# Prompt 3: Item Detail Page
Create a detailed view for registry items that shows all item information and enables interaction with the item's lifecycle.
## Requirements
1. Display item details from the item data structure:
- Status badge
- All metadata properties (both identifier and non-identifier)
- Submission timestamp
- Disputed status
- Evidence for each request
- Appeal information if disputed
2. Add action buttons based on item state:
- Remove button for registered items
- Challenge button for items with pending requests
- Appeal buttons for disputed items in appeal period
## Data Structures
### Item Details
Use `fetchItemsById` to get detailed item information ( or just pass the data from the initial fetchItems request):
```typescript
const { items } = await fetchItemsById(registryAddress, [itemID], chainId);
const item = items[0];
// Item structure returned:
// {
// itemID: string;
// status: "Absent" | "RegistrationRequested" | "Registered" | "ClearingRequested" | "Challenged";
// disputed: boolean;
// latestRequestSubmissionTime: string; // Unix timestamp
// metadata: {
// props: {
// description: string; // Property description
// isIdentifier: boolean; // If property uniquely identifies item
// label: string; // Display label
// type: string; // Data type
// value: string; // Actual value
// }[];
// };
// requests: {
// evidenceGroup: {
// evidences: {
// party: string; // Ethereum address of submitter
// URI: string; // IPFS path to evidence
// timestamp: string; // Unix timestamp
// id: string; // Unique identifier
// }[];
// };
// rounds: {
// appealPeriodStart: string; // Unix timestamp
// appealPeriodEnd: string; // Unix timestamp
// appealed: boolean;
// ruling: number; // 0 = None, 1 = Accept, 2 = Reject
// }[];
// }[];
// }
```
### Evidence Content
When displaying evidence, fetch the each evidence's content from IPFS:
```typescript
const evidenceContent = await fetchFromIPFS(evidence.URI);
// Evidence content structure:
// {
// title: string;
// description: string;
// }
```
### Appeal Information
For disputed items, get appeal costs and funding status:
```typescript
// Get appeal costs
const appealCost = await registry.getAppealCost(itemID);
// Appeal cost structure:
// {
// requesterAppealFee: string; // Cost in ETH/xDAI
// challengerAppealFee: string; // Cost in ETH/xDAI
// requesterAppealFeeWei: string; // Cost in Wei
// challengerAppealFeeWei: string; // Cost in Wei
// currentRuling: number; // 0 = None, 1 = Accept, 2 = Reject
// }
// Get funding status
const fundingStatus = await registry.getAppealFundingStatus(itemID);
// Funding status structure:
// {
// requesterFunded: boolean;
// challengerFunded: boolean;
// requesterAmountPaid: string; // Amount in ETH/xDAI
// challengerAmountPaid: string; // Amount in ETH/xDAI
// requesterAmountPaidWei: string; // Amount in Wei
// challengerAmountPaidWei: string; // Amount in Wei
// requesterRemainingToFund: string; // Amount in ETH/xDAI
// challengerRemainingToFund: string; // Amount in ETH/xDAI
// requesterRemainingToFundWei: string; // Amount in Wei
// challengerRemainingToFundWei: string; // Amount in Wei
// appealed: boolean;
// currentRuling: number; // 0 = None, 1 = Accept, 2 = Reject
// roundIndex: number; // Current round (0-based)
// }
```
## Implementation Steps
1. Create item detail component:
```typescript
function ItemDetail({ itemID }) {
const [item, setItem] = useState<Item | null>(null);
const [appealCost, setAppealCost] = useState<AppealCost | null>(null);
const [fundingStatus, setFundingStatus] = useState<AppealFundingStatus | null>(null);
useEffect(() => {
const loadItem = async () => {
const { items } = await fetchItemsById(registryAddress, [itemID], chainId);
setItem(items[0]);
if (items[0].disputed) {
const cost = await registry.getAppealCost(itemID);
const status = await registry.getAppealFundingStatus(itemID);
setAppealCost(cost);
setFundingStatus(status);
}
};
loadItem();
}, [itemID]);
// ... render logic ...
}
```
2. Display item metadata and status:
```typescript
<StatusBadge status={item.status} disputed={item.disputed} />
{item.metadata.props.map(prop => (
<PropertyDisplay
key={prop.label}
label={prop.label}
value={prop.value}
isIdentifier={prop.isIdentifier}
/>
))}
<TimeStamp date={new Date(parseInt(item.latestRequestSubmissionTime) * 1000)} />
```
3. Display evidence:
```typescript
{item.requests[0].evidenceGroup?.evidences.map(async (evidence) => {
const content = await fetchFromIPFS(evidence.URI);
return (
<EvidenceCard
key={evidence.id}
title={content.title}
description={content.description}
submitter={evidence.party}
timestamp={new Date(parseInt(evidence.timestamp) * 1000)}
/>
);
})}
```
4. Add appeal interface for disputed items:
```typescript
{item.disputed && appealCost && fundingStatus && (
<AppealSection
appealCost={appealCost}
fundingStatus={fundingStatus}
onFundRequester={(amount) => registry.fundAppeal(itemID, 0, 1, amount)}
onFundChallenger={(amount) => registry.fundAppeal(itemID, 0, 2, amount)}
/>
)}
```
5. Add action buttons based on item state:
```typescript
{item.status === "Registered" && (
<Button onClick={() => registry.removeItem(itemID)}>
Remove Item
</Button>
)}
{(item.status === "RegistrationRequested" ||
item.status === "ClearingRequested") && (
<Button onClick={() => registry.challengeRequest(itemID)}>
Challenge Request
</Button>
)}
```
Note: All currency values (deposits, appeal costs, etc.) are in ETH when using Ethereum Mainnet (chainId=1) and in xDAI when using Gnosis Chain (chainId=100).