diff --git a/README.md b/README.md index e215bc4..c48c6c3 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +# Futarchy + This is a [Next.js](https://nextjs.org) project bootstrapped with [`create-next-app`](https://nextjs.org/docs/app/api-reference/cli/create-next-app). ## Getting Started diff --git a/functions/realt-proxy.ts b/functions/realt-proxy.ts new file mode 100644 index 0000000..931ade0 --- /dev/null +++ b/functions/realt-proxy.ts @@ -0,0 +1,132 @@ +import { getStore } from "@netlify/blobs"; + +/** + * Cache TTL in milliseconds (24 hours). + */ +const CACHE_TTL_MS = 24 * 60 * 60 * 1000; + +/** + * RealT property UUIDs to fetch. + */ +const REALT_UUIDS = [ + "0x0f388d7e65a969dbcbfab21bc3ab6629af78f4cf", + "0x5c4e3fa9704d4212c6434190af6379cfbda47e13", + "0x854a0cfa24012937d3d15682ecc3d5b474bfa97e", + "0xd8b19f31186fc7350be018651aa1383175923bb3", + "0xc7697f5e86a102eaf4000719a2dc477d65beea7d", + "0x4ae9d3343bbc6a894b7ee7f843c224c953f1661b", + "0x90d280b6456f8233e115e6aabb2ca89249dafd39", + "0x19f824662ba9df78e368022f085b708fccc201c8", + "0xa83cbd26964ea953f86c741871a1ab2a256cb82d", +]; + +/** + * Edge route that proxies a rate-limited upstream API + * (allowed to be called once per 24 hours), caches the + * response using Netlify Blobs, and serves cached data + * to all clients. + * + * @returns RealT properties data + */ +export default async function handler(): Promise { + const store = getStore("realt-api-cache"); + const now = Date.now(); + type CachedResponse = { + data: Record; + fetchedAt: number; + }; + const apiUrl = process.env.REALT_API_URL; + const apiKey = process.env.REALT_API_KEY; + + if (!apiUrl || !apiKey) { + console.error("[realt-proxy] Missing required env vars", { + UPSTREAM_API_URL: !!apiUrl, + UPSTREAM_API_KEY: !!apiKey, + }); + + return new Response("Server misconfigured", { status: 500 }); + } + + // Read cached response + const cached = (await store.get("response", { + type: "json", + })) as CachedResponse | null; + + // Serve cached response if still valid + if (cached && now - cached.fetchedAt < CACHE_TTL_MS) { + console.log("[realt-proxy] Serving from cache"); + return Response.json(cached.data, { + headers: { + "Cache-Control": "public, max-age=300", + }, + }); + } + + try { + // Call realT API (should happen ~once per day) + console.log("[realt-proxy] Calling RealT API"); + + const res = await fetch(apiUrl, { + headers: { + "X-AUTH-REALT-TOKEN": apiKey, + }, + }); + + if (!res.ok) { + throw new Error(`RealT API failed with status ${res.status}`); + } + + const allProperties = (await res.json()) as Array<{ + uuid: string; + [key: string]: unknown; + }>; + + /** + * Filter only the UUIDs we need + */ + const uuidSet = new Set(REALT_UUIDS.map((uuid) => uuid.toLowerCase())); + + const filtered = allProperties.filter( + (item) => + typeof item.uuid === "string" && uuidSet.has(item.uuid.toLowerCase()), + ); + + const normalized = filtered.map((item) => ({ + ...item, + uuid: item.uuid.toLowerCase(), + })); + + const byUuid = Object.fromEntries( + normalized.map((item) => [item.uuid, item]), + ); + + console.log( + `[realt-proxy] Filtered ${filtered.length} / ${allProperties.length} properties`, + ); + + // Persist fresh response + await store.setJSON("response", { + data: byUuid, + fetchedAt: now, + } satisfies CachedResponse); + + return Response.json(byUuid, { + headers: { + "Cache-Control": "public, max-age=300", + }, + }); + } catch (error) { + console.error("[realt-proxy] RealT API fetch failed:", error); + + // serve stale cache if available + if (cached) { + return Response.json(cached.data, { + headers: { + "Cache-Control": "public, max-age=300", + }, + }); + } + + return new Response("RealT API unavailable", { status: 502 }); + } +} diff --git a/netlify.toml b/netlify.toml new file mode 100644 index 0000000..3302661 --- /dev/null +++ b/netlify.toml @@ -0,0 +1,2 @@ +[functions] + directory = "functions" \ No newline at end of file diff --git a/next.config.mjs b/next.config.mjs index 22a1d2c..b16669d 100644 --- a/next.config.mjs +++ b/next.config.mjs @@ -9,6 +9,13 @@ const nextConfig = { pathname: "/**", search: "", }, + { + protocol: "https", + hostname: "realt.co", + port: "", + pathname: "/**", + search: "", + }, ], }, webpack: (config) => { diff --git a/package.json b/package.json index b1a536a..63abe63 100644 --- a/package.json +++ b/package.json @@ -14,6 +14,7 @@ "@cowprotocol/cow-sdk": "^5.10.3", "@kleros/kleros-app": "^2.3.0", "@kleros/ui-components-library": "^3.7.0", + "@netlify/blobs": "^10.6.0", "@reown/appkit": "^1.7.11", "@reown/appkit-adapter-wagmi": "^1.7.11", "@swapr/sdk": "https://github.com/seer-pm/swapr-sdk/archive/6dea7e63f7e05c84a4374717ee1ad5baca86f7de.tar.gz", @@ -37,6 +38,7 @@ "react-jdenticon": "^1.4.0", "react-toastify": "^11.1.0", "react-use": "^17.6.0", + "swiper": "^12.0.2", "viem": "^2.31.4", "wagmi": "^2.15.6", "zustand": "^5.0.8" diff --git a/public/futarchy_kleros.png b/public/futarchy_kleros.png index 95b9995..cbff75c 100644 Binary files a/public/futarchy_kleros.png and b/public/futarchy_kleros.png differ diff --git a/public/icon1.png b/public/icon1.png index d63709d..888f441 100644 Binary files a/public/icon1.png and b/public/icon1.png differ diff --git a/src/app/(homepage)/components/AdvancedSection.tsx b/src/app/(homepage)/components/AdvancedSection.tsx index 449c1c5..f04bee4 100644 --- a/src/app/(homepage)/components/AdvancedSection.tsx +++ b/src/app/(homepage)/components/AdvancedSection.tsx @@ -1,12 +1,32 @@ +import { useCallback } from "react"; + import { Card } from "@kleros/ui-components-library"; import clsx from "clsx"; import Link from "next/link"; +import { useAllRealtMarketData } from "@/hooks/useRealtMarketData"; + +import LightButton from "@/components/LightButton"; import SeerLogo from "@/components/SeerLogo"; +import DownloadIcon from "@/assets/svg/download.svg"; import ExternalArrow from "@/assets/svg/external-arrow.svg"; +import { downloadCsvFile, generateRealtDataCsv } from "@/utils/csv"; + +import { markets, seerMarketLink } from "@/consts/markets"; + +const contractAddresses = markets.map((m) => m.realtContract); + const AdvancedSection: React.FC = () => { + const { data: allRealtData, isLoading } = useAllRealtMarketData(); + + const handleDownloadCsv = useCallback(() => { + if (!allRealtData) return; + const csv = generateRealtDataCsv(allRealtData, contractAddresses); + downloadCsvFile("realt-market-data.csv", csv); + }, [allRealtData]); + return ( { Check the opportunities if you want to LP or Trade specific outcome tokens in Seer.  { Check it out

+
+ + Download the latest data (updated in the last 24 hours) for the 9 + properties in CSV format + + + } + /> +
diff --git a/src/app/(homepage)/components/Chart/Legend.tsx b/src/app/(homepage)/components/Chart/Legend.tsx index f800415..01eac28 100644 --- a/src/app/(homepage)/components/Chart/Legend.tsx +++ b/src/app/(homepage)/components/Chart/Legend.tsx @@ -4,6 +4,8 @@ import clsx from "clsx"; import ChartBarIcon from "@/assets/svg/chart-bar.svg"; import StatsBarIcon from "@/assets/svg/stats-bar.svg"; +import { formatCompactUsd } from "@/utils/formatCompactUsd"; + import { type MarketsData } from "."; interface ILegend { @@ -38,7 +40,7 @@ const Legend: React.FC = ({ onMouseLeave={() => onHoverMarket?.(null)} > onToggleMarket(name)} className={clsx( diff --git a/src/app/(homepage)/components/Chart/index.tsx b/src/app/(homepage)/components/Chart/index.tsx index 034369a..304cfca 100644 --- a/src/app/(homepage)/components/Chart/index.tsx +++ b/src/app/(homepage)/components/Chart/index.tsx @@ -15,6 +15,7 @@ import { import { type IChartData } from "@/hooks/useChartData"; import { shortenName } from "@/utils"; +import { formatCompactUsd } from "@/utils/formatCompactUsd"; import { IMarket, startTime, endTime } from "@/consts/markets"; @@ -123,12 +124,13 @@ const Chart: React.FC<{ data: IChartData[] }> = ({ data }) => { Object.entries(marketsData).forEach(([marketName, marketInfo]) => { const { data, market } = marketInfo; - const maxValue = market.maxValue; + const { minValue, maxValue } = market; // Find valid start index (skip extreme values) let validStartIndex = 0; for (let i = 0; i < data.length; i++) { - const isExtreme = data[i].value === maxValue || data[i].value < 0.1; + const isExtreme = + data[i].value === maxValue || data[i].value < minValue + 0.1; if (!isExtreme && i > 0) { validStartIndex = i; break; @@ -188,7 +190,7 @@ const Chart: React.FC<{ data: IChartData[] }> = ({ data }) => { ensureEdgeTickMarksVisible: true, }, localization: { - priceFormatter: (val: number) => `${val.toFixed(2)}%`, + priceFormatter: (val: number) => formatCompactUsd(val), }, leftPriceScale: { borderVisible: false, @@ -252,7 +254,7 @@ const Chart: React.FC<{ data: IChartData[] }> = ({ data }) => { onHoverMarket={handleHoverMarket} />

- Market Estimate Scores + Market estimates (USD)

diff --git a/src/app/(homepage)/components/Header/index.tsx b/src/app/(homepage)/components/Header/index.tsx index d30ee6b..d0d580f 100644 --- a/src/app/(homepage)/components/Header/index.tsx +++ b/src/app/(homepage)/components/Header/index.tsx @@ -3,7 +3,6 @@ import Image from "next/image"; import { useWinningAnswers } from "@/hooks/useWinningAnswers"; -import ExternalLink from "@/components/ExternalLink"; import SeerLogo from "@/components/SeerLogo"; import SeerHeaderBackground from "@/assets/png/seer-header-bg.png"; @@ -56,14 +55,7 @@ const Header: React.FC = () => {

- You can look at{" "} - - , to get an idea of what he would like/dislike. + {marketMetadata.questionDescription}

{!isLoading && winningMarkets.length > 0 ? ( diff --git a/src/app/(homepage)/components/ParticipateSection/CsvUpload/index.tsx b/src/app/(homepage)/components/ParticipateSection/CsvUpload/index.tsx index 9d5da71..9f83b7d 100644 --- a/src/app/(homepage)/components/ParticipateSection/CsvUpload/index.tsx +++ b/src/app/(homepage)/components/ParticipateSection/CsvUpload/index.tsx @@ -31,8 +31,8 @@ const CsvUploadPopup: React.FC = ({ const csvText = await file.text(); const records = parseMarketCSV(csvText); - Object.entries(records).forEach(([marketId, score]) => { - setPrediction(marketId, score); + Object.entries(records).forEach(([marketId, predictionUsd]) => { + setPrediction(marketId, predictionUsd); }); toggleIsOpen(); @@ -48,7 +48,7 @@ const CsvUploadPopup: React.FC = ({ {...{ isOpen }} >
-

+

Upload CSV Predictions

= ({ marketName,score - Judge Dredd (1995),49.45 + 23750 W 7 Mile,1200000 - Bacurau (2019),53.52 + 18881 Mound,450000 ...
- Each row represents a prediction for a movie's score in the - Gnosis ecosystem. + Each row is a property name and a predicted price in USD (whole + dollars, within that property's min/max range).
diff --git a/src/app/(homepage)/components/ParticipateSection/TradeWallet/ProjectBalances/index.tsx b/src/app/(homepage)/components/ParticipateSection/TradeWallet/ProjectBalances/index.tsx index 69c8e5f..187705d 100644 --- a/src/app/(homepage)/components/ParticipateSection/TradeWallet/ProjectBalances/index.tsx +++ b/src/app/(homepage)/components/ParticipateSection/TradeWallet/ProjectBalances/index.tsx @@ -6,7 +6,7 @@ import clsx from "clsx"; import { useTradeWallet } from "@/context/TradeWalletContext"; import { useTokensBalances } from "@/hooks/useTokenBalances"; -import MovieIcon from "@/assets/svg/movie.svg"; +import HomeIcon from "@/assets/svg/homes.svg"; import { markets } from "@/consts/markets"; @@ -26,9 +26,9 @@ const ProjectBalances: React.FC = () => { { title: (
- +
), diff --git a/src/app/(homepage)/components/ParticipateSection/index.tsx b/src/app/(homepage)/components/ParticipateSection/index.tsx index 4f37812..d6d7049 100644 --- a/src/app/(homepage)/components/ParticipateSection/index.tsx +++ b/src/app/(homepage)/components/ParticipateSection/index.tsx @@ -18,11 +18,11 @@ const ParticipateSection: React.FC = () => {

{/* NOTE: project specific */} - Set estimates for the movies below. + Set estimates for the properties below. {" "}
- You can choose how many movies you want to predict.
- Note that the same capital can be used to predict on all movies at + You can choose how many properties you want to predict.
+ Note that the same capital can be used to predict on all properties at once.

diff --git a/src/app/(homepage)/components/PredictAll/PredictAllPopup/Header.tsx b/src/app/(homepage)/components/PredictAll/PredictAllPopup/Header.tsx index cd63d9a..06378e3 100644 --- a/src/app/(homepage)/components/PredictAll/PredictAllPopup/Header.tsx +++ b/src/app/(homepage)/components/PredictAll/PredictAllPopup/Header.tsx @@ -8,13 +8,14 @@ import { useMarketsStore } from "@/store/markets"; import { usePredictionMarkets } from "@/hooks/usePredictionMarkets"; import LightButton from "@/components/LightButton"; +import { ScrollFade } from "@/components/ScrollFade"; import CloseIcon from "@/assets/svg/close-icon.svg"; import ArrowDown from "@/assets/svg/long-arrow-down.svg"; import ArrowUp from "@/assets/svg/long-arrow-up.svg"; -import { formatWithPrecision, isUndefined } from "@/utils"; -import { ScrollFade } from "@/components/ScrollFade"; +import { isUndefined } from "@/utils"; +import { formatUsd } from "@/utils/marketRange"; const Header: React.FC = () => { const markets = usePredictionMarkets(); @@ -62,12 +63,12 @@ const Header: React.FC = () => {
{/* TODO: Changes per experiment */} - Score + Price {market.prediction - ? `${formatWithPrecision(market.prediction, market.precision)}%` - : "0%"} + ? formatUsd(market.prediction) + : formatUsd(0)}
diff --git a/src/app/(homepage)/components/ProjectFunding/Details.tsx b/src/app/(homepage)/components/ProjectFunding/Details.tsx deleted file mode 100644 index 73caded..0000000 --- a/src/app/(homepage)/components/ProjectFunding/Details.tsx +++ /dev/null @@ -1,30 +0,0 @@ -import React from "react"; - -import Link from "next/link"; - -import { IDetails } from "@/consts/markets"; - -const Details: React.FC = ({ imdbURL, posterURL, summary }) => ( -
- {posterURL ? ( - movie poster - ) : null} -
- {imdbURL ? ( - - IMDB - - ) : null} -

- {summary} -

-
-
-); - -export default Details; diff --git a/src/app/(homepage)/components/ProjectFunding/Details/DetailItem.tsx b/src/app/(homepage)/components/ProjectFunding/Details/DetailItem.tsx new file mode 100644 index 0000000..31f5de4 --- /dev/null +++ b/src/app/(homepage)/components/ProjectFunding/Details/DetailItem.tsx @@ -0,0 +1,21 @@ +import React from "react"; + +const DetailItem: React.FC<{ icon: string; label: string; value: string }> = ({ + icon, + label, + value, +}) => ( +
+ {icon} +
+ + {value} + + + {label} + +
+
+); + +export default DetailItem; diff --git a/src/app/(homepage)/components/ProjectFunding/Details/DetailsLayout.tsx b/src/app/(homepage)/components/ProjectFunding/Details/DetailsLayout.tsx new file mode 100644 index 0000000..970b15a --- /dev/null +++ b/src/app/(homepage)/components/ProjectFunding/Details/DetailsLayout.tsx @@ -0,0 +1,48 @@ +import React from "react"; + +import clsx from "clsx"; + +export const DetailsCategory: React.FC<{ + title: string; + children: React.ReactNode; +}> = ({ title, children }) => ( +
+
+ {title} +
+ {children} +
+); + +export const DetailsGrid: React.FC<{ children: React.ReactNode }> = ({ + children, +}) => ( +
+ {children} +
+); + +export const DetailsListItem: React.FC<{ + label: string; + value: React.ReactNode; + small?: boolean; +}> = ({ label, value, small }) => ( +
+ + {label} + + + {value} + +
+); diff --git a/src/app/(homepage)/components/ProjectFunding/Details/MiscData.tsx b/src/app/(homepage)/components/ProjectFunding/Details/MiscData.tsx new file mode 100644 index 0000000..f90af3c --- /dev/null +++ b/src/app/(homepage)/components/ProjectFunding/Details/MiscData.tsx @@ -0,0 +1,41 @@ +import React from "react"; + +import clsx from "clsx"; + +interface MiscDataProps { + data: Record; +} + +const MiscData: React.FC = ({ data }) => ( +
+
+ {Object.entries(data).map(([key, value]) => ( +
+ + {key} + + + {value === null || value === undefined || value === "" + ? "(empty)" + : typeof value === "object" + ? JSON.stringify(value) + : String(value)} + +
+ ))} +
+
+); + +export default MiscData; diff --git a/src/app/(homepage)/components/ProjectFunding/Details/MoreDetails.tsx b/src/app/(homepage)/components/ProjectFunding/Details/MoreDetails.tsx new file mode 100644 index 0000000..5fd1734 --- /dev/null +++ b/src/app/(homepage)/components/ProjectFunding/Details/MoreDetails.tsx @@ -0,0 +1,153 @@ +import React from "react"; + +import { RealtMarketData } from "@/hooks/useRealtMarketData"; + +import ExternalLink from "@/components/ExternalLink"; + +import { DetailsCategory, DetailsGrid, DetailsListItem } from "./DetailsLayout"; +import { + formatCurrency, + formatCurrencyDecimals, + formatDate, + formatNumber, +} from "./formatters"; + +interface MoreDetailsProps { + data: RealtMarketData; +} + +const MoreDetails: React.FC = ({ data }) => ( +
+ {/* Token Information */} + + + + + + + + } + /> + + + + {/* Financial Details */} + + + + + + + + + + 0 + ? `${formatCurrency(data.utilities)}/yr` + : "Tenant Paid" + } + /> + + + + + + + + {/* Property Details */} + + + + + + + + + + + + + + {/* Rental Information */} + + + + + + l.toUpperCase())} + /> + + +
+); + +export default MoreDetails; diff --git a/src/app/(homepage)/components/ProjectFunding/Details/displayKeys.ts b/src/app/(homepage)/components/ProjectFunding/Details/displayKeys.ts new file mode 100644 index 0000000..9b5828a --- /dev/null +++ b/src/app/(homepage)/components/ProjectFunding/Details/displayKeys.ts @@ -0,0 +1,49 @@ +// Keys already displayed in the main UI or in MoreDetails +export const DISPLAYED_KEYS = new Set([ + "fullName", + "shortName", + "totalInvestment", + "squareFeet", + "propertyType", + "propertyTypeName", + "grossRentYear", + "netRentYear", + "netRentMonth", + "netRentDay", + "initialLaunchDate", + "annualPercentageYield", + "coordinate", + "marketplaceLink", + "gnosisContract", + "imageLink", + "bedroomBath", + "totalUnits", + "rentedUnits", + "hasTenants", + "tokenPrice", + "totalTokens", + "symbol", + "seriesNumber", + "underlyingAssetPrice", + "propertyManagement", + "propertyManagementPercent", + "realtPlatform", + "realtPlatformPercent", + "insurance", + "propertyTaxes", + "utilities", + "initialMaintenanceReserve", + "renovationReserve", + "propertyMaintenanceMonthly", + "miscellaneousCosts", + "lotSize", + "constructionYear", + "constructionType", + "roofType", + "foundation", + "heating", + "cooling", + "neighborhood", + "rentStartDate", + "rentalType", +]); diff --git a/src/app/(homepage)/components/ProjectFunding/Details/formatters.ts b/src/app/(homepage)/components/ProjectFunding/Details/formatters.ts new file mode 100644 index 0000000..51a8770 --- /dev/null +++ b/src/app/(homepage)/components/ProjectFunding/Details/formatters.ts @@ -0,0 +1,29 @@ +export const formatCurrency = (value: number) => + new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(value); + +export const formatCurrencyDecimals = (value: number) => + new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: 2, + maximumFractionDigits: 2, + }).format(value); + +export const formatDate = (dateStr: string) => { + const date = new Date(dateStr); + return date.toLocaleDateString("en-US", { + year: "numeric", + month: "short", + day: "numeric", + }); +}; + +export const formatNumber = (value: number | null | undefined) => { + if (value === null || value === undefined) return "N/A"; + return new Intl.NumberFormat("en-US").format(value); +}; diff --git a/src/app/(homepage)/components/ProjectFunding/Details/index.tsx b/src/app/(homepage)/components/ProjectFunding/Details/index.tsx new file mode 100644 index 0000000..e00cef1 --- /dev/null +++ b/src/app/(homepage)/components/ProjectFunding/Details/index.tsx @@ -0,0 +1,252 @@ +import React from "react"; + +import { Accordion } from "@kleros/ui-components-library"; +import clsx from "clsx"; +import Image from "next/image"; +import { Navigation, Pagination } from "swiper/modules"; +import { Swiper, SwiperSlide } from "swiper/react"; +import "swiper/css"; +import "swiper/css/navigation"; +import "swiper/css/pagination"; + +import { useRealtMarketData } from "@/hooks/useRealtMarketData"; + +import ExternalLink from "@/components/ExternalLink"; + +import CalendarIcon from "@/assets/svg/calendar.svg"; +import ChartIcon from "@/assets/svg/chart-bar.svg"; +import DollarIcon from "@/assets/svg/dollar.svg"; +import GeoPin from "@/assets/svg/geo-pin.svg"; +import GnosisLogo from "@/assets/svg/gnosis.svg"; + +import DetailItem from "./DetailItem"; +import { DISPLAYED_KEYS } from "./displayKeys"; +import { formatCurrency, formatDate, formatNumber } from "./formatters"; +import MiscData from "./MiscData"; +import MoreDetails from "./MoreDetails"; + +type DetailsProps = { + contract: string; +}; + +const Details: React.FC = ({ contract }) => { + const { data: realtData, isLoading, isError } = useRealtMarketData(contract); + + if (isLoading) { + return ( +
+ Loading details... +
+ ); + } + + if (isError || !realtData) { + return ( +
+ Error loading details or no data available. +
+ ); + } + + const miscDetails = Object.entries(realtData).reduce( + (acc, [key, value]) => { + if (!DISPLAYED_KEYS.has(key)) { + acc[key] = value; + } + return acc; + }, + {} as Record, + ); + + return ( +
+ {/* Header: Address + Coordinates */} +
+ + + {realtData.fullName} + + + ({realtData.coordinate.lat}, {realtData.coordinate.lng}) + +
+ + {/* Main content: Stats + Gallery */} +
+ {/* Stats Section */} +
+ {/* Total Investment */} +
+
+ +
+
+
+ {formatCurrency(realtData.totalInvestment)} +
+
+ Total Investment +
+
+
+ + {/* Property Details Grid */} +
+ + + + +
+ + {/* Financial Row */} +
+
+
+ Gross Rent Year +
+
+ {formatCurrency(realtData.grossRentYear)} +
+
+
+
+ Net Rent Year +
+
+ {formatCurrency(realtData.netRentYear)} +
+
+
+ + {/* Yield Badge */} +
+ + + {realtData.annualPercentageYield.toFixed(2)}% + + + Annual Percentage Yield + +
+
+ + {/* Gallery Section */} +
+ {/* Launch Date Badge */} +
+ + {formatDate(realtData.initialLaunchDate.date)} + Launch Date +
+ + {realtData.imageLink.map((imageSrc) => ( + + House image + + ))} + +
+
+ + {/* Links Row */} +
+ + + | + +
+ + + Gnosis Contract + + + {realtData.gnosisContract} + +
+
+ + {/* More Details Accordion */} + , + }, + ]} + /> + + {/* Misc Data Accordion */} + {Object.keys(miscDetails).length > 0 && ( + , + }, + ]} + /> + )} +
+ ); +}; + +export default Details; diff --git a/src/app/(homepage)/components/ProjectFunding/PositionValue.tsx b/src/app/(homepage)/components/ProjectFunding/PositionValue.tsx index 77f893f..ebed17b 100644 --- a/src/app/(homepage)/components/ProjectFunding/PositionValue.tsx +++ b/src/app/(homepage)/components/ProjectFunding/PositionValue.tsx @@ -73,7 +73,7 @@ const PositionValue: React.FC = ({ } return ( -
+

{isResolved ? ( Position to redeem: @@ -83,7 +83,7 @@ const PositionValue: React.FC = ({

@@ -133,7 +133,7 @@ const PositionValue: React.FC = ({ target="_blank" rel="noopener noreferrer" className={clsx( - "flex items-center gap-1", + "flex w-fit items-center gap-1", "text-klerosUIComponentsPrimaryText justify-center text-sm", "hover:text-klerosUIComponentsPrimaryBlue transition-colors", )} diff --git a/src/app/(homepage)/components/ProjectFunding/PredictionSlider.tsx b/src/app/(homepage)/components/ProjectFunding/PredictionSlider.tsx index acf8094..84fd181 100644 --- a/src/app/(homepage)/components/ProjectFunding/PredictionSlider.tsx +++ b/src/app/(homepage)/components/ProjectFunding/PredictionSlider.tsx @@ -14,8 +14,10 @@ import { useWinningAnswers } from "@/hooks/useWinningAnswers"; import { Skeleton } from "@/components/Skeleton"; -import { formatWithPrecision, isUndefined } from "@/utils"; +import { isUndefined } from "@/utils"; +import { formatCompactUsd } from "@/utils/formatCompactUsd"; import { getReadableTextColor } from "@/utils/getReadableTextColor"; +import { formatUsd, predictionToNormalizedPrice } from "@/utils/marketRange"; const LoadingSkeleton: React.FC = () => (
@@ -43,7 +45,9 @@ const PredictionSliderContent: React.FC = () => { showEstimateVariant, hasLiquidity, } = useMarketContext(); - const { maxValue, minValue, precision, color } = market; + const { minValue, maxValue, initialInvestmentUsd, color } = market; + + const step = Math.max(1, Math.round(initialInvestmentUsd / 100)); const { winningMarkets } = useWinningAnswers(); const resolvedMarket = useMemo(() => { @@ -66,14 +70,15 @@ const PredictionSliderContent: React.FC = () => { "w-full", "[&_#slider-label]:!text-klerosUIComponentsPrimaryText [&_#slider-label]:font-semibold", )} - maxValue={maxValue * precision} - minValue={minValue * precision} + maxValue={maxValue} + minValue={minValue} + step={step} value={prediction} - leftLabel="" - rightLabel="" + leftLabel={formatCompactUsd(minValue)} + rightLabel={formatCompactUsd(maxValue)} aria-label="Slider" callback={setPrediction} - formatter={(value) => `${formatWithPrecision(value, precision)}`} + formatter={(value) => formatUsd(value)} // @ts-expect-error other values not needed theme={ showEstimateVariant @@ -86,7 +91,7 @@ const PredictionSliderContent: React.FC = () => { isDisabled={!hasLiquidity} />
{ }} > {/* TODO: updates for individual experiments */} - {`${formatWithPrecision(marketEstimate, precision)}%`} + {formatUsd(marketEstimate)}
- {isUndefined(resolvedMarket) ? null : ( + {isUndefined(resolvedMarket) || + isUndefined(resolvedMarket.finalAnswer) ? null : (
diff --git a/src/app/(homepage)/components/ProjectFunding/index.tsx b/src/app/(homepage)/components/ProjectFunding/index.tsx index 3d01047..db293ab 100644 --- a/src/app/(homepage)/components/ProjectFunding/index.tsx +++ b/src/app/(homepage)/components/ProjectFunding/index.tsx @@ -25,9 +25,11 @@ import { positionExplainerLink } from "@/consts/markets"; import Details from "./Details"; import PositionValue from "./PositionValue"; import PredictionSlider from "./PredictionSlider"; + const ProjectFunding: React.FC = () => { const { market } = useMarketContext(); - const { name, color, upToken, downToken, details, underlyingToken } = market; + const { name, color, upToken, downToken, realtContract, underlyingToken } = + market; const isSelected = useMarketsStore((s) => { const m = s.markets[market.marketId]; return !!m?.prediction && m.prediction !== m.marketEstimate; @@ -148,7 +150,7 @@ const ProjectFunding: React.FC = () => {
{tradeExecutor && !isMarketResolved ? ( -
+
@@ -160,8 +162,14 @@ const ProjectFunding: React.FC = () => { className={clsx( "w-full max-w-full", "[&_#expand-button]:bg-klerosUIComponentsLightBackground [&_#expand-button_p]:font-normal", + "[&_#body-wrapper]:max-sm:px-0", )} - items={[{ title: "Details", body:
}]} + items={[ + { + title: "Details", + body:
, + }, + ]} />
), diff --git a/src/app/apple-icon.png b/src/app/apple-icon.png index 5be3a9a..c968444 100644 Binary files a/src/app/apple-icon.png and b/src/app/apple-icon.png differ diff --git a/src/app/favicon.ico b/src/app/favicon.ico index 64aac39..59dac49 100644 Binary files a/src/app/favicon.ico and b/src/app/favicon.ico differ diff --git a/src/app/globals.css b/src/app/globals.css index 05f199b..9e75f1e 100644 --- a/src/app/globals.css +++ b/src/app/globals.css @@ -76,6 +76,20 @@ ::file-selector-button { border-color: var(--color-gray-200, currentcolor); } + + .swiper-button-next, + .swiper-button-prev { + width: 60px; + height: 60px; + border-radius: 100%; + background-color: #fff; + filter: drop-shadow(0 4px 4px rgba(0, 0, 0, 0.25)); + svg { + fill: #333333 !important; + width: 16px !important; + height: 16px !important; + } + } :root { --background: #ffffff; --foreground: #171717; diff --git a/src/app/icon0.svg b/src/app/icon0.svg index 2e9f294..faa9b95 100644 --- a/src/app/icon0.svg +++ b/src/app/icon0.svg @@ -1,3 +1 @@ - \ No newline at end of file + \ No newline at end of file diff --git a/src/app/icon1.png b/src/app/icon1.png index d63709d..888f441 100644 Binary files a/src/app/icon1.png and b/src/app/icon1.png differ diff --git a/src/assets/miniguides/first-visit/process-and-timeline.svg b/src/assets/miniguides/first-visit/process-and-timeline.svg index a721c3b..f4162bd 100644 --- a/src/assets/miniguides/first-visit/process-and-timeline.svg +++ b/src/assets/miniguides/first-visit/process-and-timeline.svg @@ -1,35 +1,245 @@ - + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - - - - + + + + + + + + + + + + + + + + + + + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + + + + + + + @@ -44,39 +254,79 @@ + - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - - + + + + + + + + + + + diff --git a/src/assets/miniguides/first-visit/profit-loss.svg b/src/assets/miniguides/first-visit/profit-loss.svg index c4b9bfd..80fef65 100644 --- a/src/assets/miniguides/first-visit/profit-loss.svg +++ b/src/assets/miniguides/first-visit/profit-loss.svg @@ -1,95 +1,112 @@ - - - - + + + + - - - - - + + + + + - - - - - - - + + + + + + + + + + + + + + + + + + - - - - - - - - + + + + + - - - + + + - - + + - - - - - - - - + + + + + + + + + + + + - - + + - - - - - - - + + + + + + + + + + + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - + + - - - - + + + + diff --git a/src/assets/miniguides/first-visit/slider.svg b/src/assets/miniguides/first-visit/slider.svg index 442cd14..2ed6464 100644 --- a/src/assets/miniguides/first-visit/slider.svg +++ b/src/assets/miniguides/first-visit/slider.svg @@ -1,12 +1,12 @@ - + - + - + @@ -20,7 +20,7 @@ - + @@ -28,12 +28,12 @@ - + - - + + @@ -41,18 +41,18 @@ - - + + - + - + - + - + diff --git a/src/assets/png/realt-logo-custom.png b/src/assets/png/realt-logo-custom.png new file mode 100644 index 0000000..ff02bc0 Binary files /dev/null and b/src/assets/png/realt-logo-custom.png differ diff --git a/src/assets/png/realt-logo-dark.png b/src/assets/png/realt-logo-dark.png new file mode 100644 index 0000000..0da82ce Binary files /dev/null and b/src/assets/png/realt-logo-dark.png differ diff --git a/src/assets/png/realt-logo.png b/src/assets/png/realt-logo.png new file mode 100644 index 0000000..78a7a83 Binary files /dev/null and b/src/assets/png/realt-logo.png differ diff --git a/src/assets/svg/badge-built-by-kleros-black.svg b/src/assets/svg/badge-built-by-kleros-black.svg new file mode 100644 index 0000000..e21c02e --- /dev/null +++ b/src/assets/svg/badge-built-by-kleros-black.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/calendar.svg b/src/assets/svg/calendar.svg new file mode 100644 index 0000000..9b0ee4e --- /dev/null +++ b/src/assets/svg/calendar.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/dollar.svg b/src/assets/svg/dollar.svg new file mode 100644 index 0000000..4b69fdf --- /dev/null +++ b/src/assets/svg/dollar.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/geo-pin.svg b/src/assets/svg/geo-pin.svg new file mode 100644 index 0000000..ee76d53 --- /dev/null +++ b/src/assets/svg/geo-pin.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/assets/svg/gnosis.svg b/src/assets/svg/gnosis.svg index b91f06e..2990efd 100644 --- a/src/assets/svg/gnosis.svg +++ b/src/assets/svg/gnosis.svg @@ -1,4 +1,4 @@ - + diff --git a/src/assets/svg/home.svg b/src/assets/svg/home.svg new file mode 100644 index 0000000..d704c7b --- /dev/null +++ b/src/assets/svg/home.svg @@ -0,0 +1,3 @@ + + + diff --git a/src/assets/svg/homes.svg b/src/assets/svg/homes.svg new file mode 100644 index 0000000..a05ed6e --- /dev/null +++ b/src/assets/svg/homes.svg @@ -0,0 +1,16 @@ + + + + + + + + + + + + + + + + diff --git a/src/components/Guides/FirstVisit/MarketInfo.tsx b/src/components/Guides/FirstVisit/MarketInfo.tsx index 8cae274..fd5f873 100644 --- a/src/components/Guides/FirstVisit/MarketInfo.tsx +++ b/src/components/Guides/FirstVisit/MarketInfo.tsx @@ -1,16 +1,17 @@ import React from "react"; export const Title: React.FC = () => { - return <>Predict and Trade Movie Markets; + return <>Predict and Trade Property Markets; }; export const SubTitle: React.FC = () => { return (

- Use the available data for each movie β€” click Details to view more - information β€” and try to predict the final price that ClΓ©ment will assign. - When you're ready, select the movie you want to predict and click the - Predict Selected button below to confirm your choice. + Use the available data for each property β€” click Details to view more + information β€” and try to predict the final price that the professional + evaluator will assign. When you’re ready, select the property you want to + predict and click the Predict Selected button below to confirm your + choice.

); }; diff --git a/src/components/Guides/FirstVisit/ProcessAndTimeline.tsx b/src/components/Guides/FirstVisit/ProcessAndTimeline.tsx index 52b7058..a3cbb8d 100644 --- a/src/components/Guides/FirstVisit/ProcessAndTimeline.tsx +++ b/src/components/Guides/FirstVisit/ProcessAndTimeline.tsx @@ -7,10 +7,11 @@ export const Title: React.FC = () => { export const SubTitle: React.FC = () => { return (

- Sixteen movies have been selected for this experiment. Only five of them - will be evaluated by ClΓ©ment. The trading period will last approximately - one month. At the end of this period, the market closes and the Final - Ratings for the 5 movies chosen are publicly revealed. + Nine properties have been selected for this experiment. Only one of them + will be evaluated by a professional expert. The trading period will last + approximately one month. +
At the end of this period, the market closes and the Final Price of + the single professionally evaluated property is publicly revealed.

); }; diff --git a/src/components/Guides/FirstVisit/ProfitOrLoss.tsx b/src/components/Guides/FirstVisit/ProfitOrLoss.tsx index 8b282d6..b35fcbe 100644 --- a/src/components/Guides/FirstVisit/ProfitOrLoss.tsx +++ b/src/components/Guides/FirstVisit/ProfitOrLoss.tsx @@ -1,7 +1,7 @@ import React from "react"; export const Title: React.FC = () => { - return <>Profit or Loss - Your Prediction on the Evaluated Movie; + return <>Profit or Loss – Your Prediction on the Expert-Evaluated Property; }; export const SubTitle: React.FC = () => { diff --git a/src/components/layout/Header/Logo.tsx b/src/components/layout/Header/Logo.tsx index 199e4db..414704e 100644 --- a/src/components/layout/Header/Logo.tsx +++ b/src/components/layout/Header/Logo.tsx @@ -6,8 +6,9 @@ import { useTheme } from "next-themes"; import Image from "next/image"; import Link from "next/link"; -import _LogoDark from "@/assets/png/movies-logo-dark.png"; -import _Logo from "@/assets/png/movies-logo.png"; +import _LogoDark from "@/assets/png/realt-logo-custom.png"; +import _Logo from "@/assets/png/realt-logo.png"; +import BuiltByKleros from "@/assets/svg/badge-built-by-kleros-black.svg"; const Logo: React.FC = () => { const { resolvedTheme } = useTheme(); @@ -19,28 +20,26 @@ const Logo: React.FC = () => { if (!mounted) { return ( -
- - Movies experiment logo - -
+ + RealT Distilled Judgement + + ); } return ( -
- - Movies experiment logo - -
+ + RealT Distilled Judgement + + ); }; diff --git a/src/consts/markets.ts b/src/consts/markets.ts index 2209eb7..f04259f 100644 --- a/src/consts/markets.ts +++ b/src/consts/markets.ts @@ -1,5 +1,7 @@ import { Address } from "viem"; +import { deriveMarketRangeFromInvestment } from "@/utils/marketRange"; + export const positionExplainerLink = "https://kleros.notion.site/Kleros-Foresight-Advanced-Guide-What-Actually-Happens-After-Your-First-Prediction-30d9a9db4f0880f8a44ecb13d34ad3c6#30d9a9db4f0881969c23e8152ab1146d"; @@ -17,27 +19,25 @@ export const advancedUserGuide = export const tgLink = "https://t.me/+HrYn_tzqTGFlYTc0"; +export const seerMarketLink = + "https://app.seer.pm/markets/100/which-property-will-be-selected-for-evaluation-as-part-of-the-realt-prediction-e-3"; + // TODO: update to latest -export const projectsChosen = 5; +export const projectsChosen = 1; export const parentMarket: Address = - "0x6f7ae2815e7e13c14a6560f4b382ae78e7b1493e"; + "0x9446c7cd29be9b0f5b7f05bfdc8a81cf83341a17"; export const parentConditionId = - "0x0d6c99d7eb9fa657236905b6cf464eaa938371ae5ce8cf153af450321377241d"; + "0x0737c9d5f79d0be9a628f4f2741bc8ab8bc44677246d71526575fd8282830443"; export const invalidMarket: Address = - "0x45F2d1Bfa638E0A5f04dFacAAdbDbd0c2044eae8"; + "0x769FF74Ab52cA375dA2B47C10116376421A8A64c"; // in unix timestamp, seconds -export const startTime: number = 1771871400; -export const endTime: number = 1775239200; -export const endDate: string = "Friday 3rd April 18:00 UTC"; -export interface IDetails { - imdbURL?: string; - posterURL?: string; - summary: string; -} +export const startTime: number = 1779327180; +export const endTime: number = 1781721000; +export const endDate: string = "Wednesday 17th June 00:00 UTC"; export interface IMarket { name: string; @@ -46,386 +46,166 @@ export interface IMarket { downToken: Address; underlyingToken: Address; invalidToken: Address; - minValue: number; - maxValue: number; - precision: number; marketId: Address; parentMarketOutcome: number; - details: IDetails; + realtContract: Address; conditionId: `0x${string}`; + initialInvestmentUsd: number; + minValue: number; + maxValue: number; + precision: number; } export const marketMetadata = { - name: "Session 1 - Movies Experiment", - question: - "If watched, what percentile score would ClΓ©ment give to the movie?", + name: "RealT Properties Predictions", + question: "If evaluated, what is the current price of the property?", + questionDescription: + "Which property will be selected for evaluation as a part of β€œRealT Properties Predictions”? \nAnd for the selected property, what price will that property be appraised at?", }; -export const markets: Array = [ +export const markets: IMarket[] = [ { - name: "Judge Dredd (1995)", + name: "23750 W 7 Mile", color: "#E6194B", - upToken: "0x0ee25eb2e22c01fa832dd5fea5637fba4cd5e870", - downToken: "0x4abea4bf9e35f4e957695374c388cee9f83ca1d0", - underlyingToken: "0xb72a1271caa3d84d3fbbbcbb0f63ee358b94f96a", - invalidToken: "0x11463F43181eB643bA8a584756CCB27a9B8f7B98", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0x105d957043ee12f7705efa072af11e718f8c5b83", + upToken: "0xbe2513792354e1b968e978b28ba533e670b10d7a", + downToken: "0xa31847efee83119d31ddc8d531ff96ec78528a26", + underlyingToken: "0x643e6708becd02164ff6e8ecdab518de5e8ea65c", + invalidToken: "0x8760c4f85092d0db379c37ede98c7ded8a402028", + marketId: "0x7acd59be3b18a94c91b9dddcae53d62f1410240b", parentMarketOutcome: 0, conditionId: - "0x3d963acd72df546f58bf4ea76fda6957c830e6e3f8965517c396fc76dc2c08a3", - details: { - imdbURL: - "https://www.imdb.com/title/tt0113492/?ref_=nv_sr_srsg_0_tt_8_nm_0_in_0_q_judge%2520dredd", - posterURL: - "https://resizing.flixster.com/BsX7kI5BwBsc9xSQPEt5ddA3PI4=/206x305/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p16918_p_v8_ae.jpg", - summary: - "In a dystopian future, Joseph Dredd, the most famous Judge (a police officer with instant field judiciary powers), is convicted for a crime he did not commit and must face his murderous counterpart.", - }, + "0x82226df23ab46ca8ac26a677cc548c92012294cceaad8fa5b8be002bb4081737", + realtContract: "0x0f388d7e65a969dbcbfab21bc3ab6629af78f4cf", + initialInvestmentUsd: 944_537, + precision: 1, + ...deriveMarketRangeFromInvestment(944_537), }, { - name: "Bacurau (2019)", + name: "18881 Mound", color: "#3CB44B", - upToken: "0x028ec9938471bbad5167c2e5281144a94d1acbe9", - downToken: "0x53f82c3f6836dcba9d35450d906286a6ea089a26", - underlyingToken: "0xcb1f243baaf93199742e09dc98b16fc8b714b67c", - invalidToken: "0x971bd2446cc32dFa26410Cc46978AA0c371Bc48e", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0x68af0afe82dda5c9c26e6a458a143caad35708d6", + upToken: "0xf6d130eaf7cb156d4af691b4e35181db2fe84a70", + downToken: "0x7e0c7fa477a82cfffa7367dd02aea96b82dc3263", + underlyingToken: "0x64c5fc69aaea54eedfefb1270af7cc893801e448", + invalidToken: "0xb7e7164e1de9154af1027bd20b7aa4de3438f767", + marketId: "0x1adc7298acb34cb59385ba007a542f59392ea9d5", parentMarketOutcome: 1, conditionId: - "0xa4cc97a4e4f6e02c546a5b3bb49e2c411dcb4c6dcd478cef9cd0c86605c59878", - details: { - imdbURL: - "https://www.imdb.com/title/tt2762506/?ref_=nv_sr_srsg_0_tt_7_nm_1_in_0_q_bacura", - posterURL: - "https://resizing.flixster.com/MUNwK1o6mdxwkgj-2v86bWf6xXM=/206x305/v2/https://resizing.flixster.com/-cGVSNCtYaLQDwteIiI9LUMoqJ0=/ems.cHJkLWVtcy1hc3NldHMvbW92aWVzL2Y3NWE5YWNjLTRlNzktNGEzYi05NTg5LWNhOTBiYTJlODM1OC53ZWJw", - summary: - "After the death of her grandmother, Teresa comes home to her matriarchal village in a near-future Brazil to find a succession of sinister events that mobilizes all of its residents.", - }, + "0x40935a2f9046721322018d6709b74094042a8eee7fc2db5fd0e0db8935544a16", + realtContract: "0x5c4e3fa9704d4212c6434190af6379cfbda47e13", + initialInvestmentUsd: 336_340, + precision: 1, + ...deriveMarketRangeFromInvestment(336_340), }, { - name: "The Hitchhiker's Guide to the Galaxy (2005)", + name: "14631-14633 Plymouth", color: "#FFD93D", - upToken: "0xad2248b8eaa3e3a405c1ba79dd436947f8b427df", - downToken: "0xdd510abc6a848662371c3455717949035cc24019", - underlyingToken: "0xfb06c25e59302d8a0318d6df41a2f29deeea1c8a", - invalidToken: "0x43D6E82de1E64531b5E47891b186227edA566344", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0xfdd8af90af2722d5fe39adf1002fbd069b8a76c0", + upToken: "0xb8bc865f8a7990ae37cee6e51260c015880ed7cc", + downToken: "0x18ed32e2aad7d445c8c72e3afee35e8357f5babd", + underlyingToken: "0x733f14dc8c1592f46df5073651513268791014b4", + invalidToken: "0x4beb629a302e65bac5f8e39fba7f924942424fb6", + marketId: "0x7dfa1ea4b31f5096e5674e4e17604ebd7c6fa947", parentMarketOutcome: 2, conditionId: - "0xe97f19928d4143377d3cb97043c90408ccb9c51788447f42d2df9d65694c8171", - details: { - imdbURL: - "https://www.imdb.com/title/tt0371724/?ref_=nv_sr_srsg_0_tt_7_nm_1_in_0_q_the%2520hitch", - posterURL: - "https://resizing.flixster.com/otfSVWc26cetfV0acq5Z5-E9A60=/206x305/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p35755_p_v8_am.jpg", - summary: `Mere seconds before the Earth is to be demolished by an alien construction crew, journeyman Arthur Dent is swept off the planet by his friend Ford Prefect, a researcher penning a new edition of "The Hitchhiker's Guide to the Galaxy."`, - }, + "0xfa1bdea018d1f7735354aadb531a8fbfd6cc7d2562c4950559406f5b481ccdf0", + realtContract: "0x854a0cfa24012937d3d15682ecc3d5b474bfa97e", + initialInvestmentUsd: 1_389_025, + precision: 1, + ...deriveMarketRangeFromInvestment(1_389_025), }, { - name: "Everything, Everywhere, All At Once (2022)", + name: "11373 Prest", color: "#6BCB77", - upToken: "0xfa020fcd05e0b91dae83a2a08c5b5533edf8c851", - downToken: "0x372d0798ffe8c3aa982a15258c0fea22c6a768df", - underlyingToken: "0xe85d556d1aaae2f6027336e468e9c981251a4bef", - invalidToken: "0x3Aa738505C22e670a074e60566bD7264e7D682B1", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0x1f2e76d66047e7f8e0deea373a0c04ffecab31df", + upToken: "0xcd4c6f4c2ca01a99b541c26cb6a9ff0fe6abea69", + downToken: "0x439d40c9addf88993e8b45ea7b9587cc7160cde9", + underlyingToken: "0x51ef0c299a67acdb28dd3e1ac71c81dcee91079d", + invalidToken: "0x0f2662a1d632748ea5462415ced47769e4426fb2", + marketId: "0xfdc2902d8562133073669624bc54320fc0ac975e", parentMarketOutcome: 3, conditionId: - "0xdc8f8277da182ee2d5293c754a1cfb8d3761720259cf17a65df61b7cb6983721", - details: { - imdbURL: "https://www.imdb.com/title/tt6710474/?ref_=fn_all_ttl_1", - posterURL: - "https://resizing.flixster.com/I2Z0zDTKJdvO7Akh819HROIhZQ4=/206x305/v2/https://resizing.flixster.com/mx-agGjjsUK1QMyuv3AJhHI3hgo=/ems.cHJkLWVtcy1hc3NldHMvbW92aWVzLzA3ZjU2MGU1LWMxODItNDlkMC1hYzJhLTY2YzMwOGZkMDhiZi5qcGc=", - summary: - "A middle-aged Chinese immigrant is swept up into an insane adventure in which she alone can save existence by exploring other universes and connecting with the lives she could have led.", - }, + "0xcc70777895ce8e0e6dc979509798021a1fb143d5b1c08f23e3f438464f648f0f", + realtContract: "0xd8b19f31186fc7350be018651aa1383175923bb3", + initialInvestmentUsd: 76_806, + precision: 1, + ...deriveMarketRangeFromInvestment(76_806), }, { - name: "12 angry men (1957)", + name: "16728-16730 Woodingham", color: "#4D96FF", - upToken: "0x7ee3806d16dc6a76bef2b11880b70cc70f74fa1a", - downToken: "0x34f8572eab463606a014c37ff68b78ac9361cacc", - underlyingToken: "0xb3933fd994af5db7ae985a0d62ed2dda918a839b", - invalidToken: "0x12c91f543a48F58e3E54c398f19BEc4b62aFD617", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0x2338ca7d59b7e15bd03dd81cf5f5bb59b6c6c6d4", + upToken: "0xab3fb717c036390fc199e19092100cc2d1f42d8e", + downToken: "0xebd2c06c341f617818d81344c96c8e11c27597e3", + underlyingToken: "0xab0b68be4638dad16f4e8c4493607438b99a73b0", + invalidToken: "0x82d044d344de4bf5b3e380e0f43ab1ad23fd092b", + marketId: "0xacfdbce48f1d217e9eaeeeb613bc69e59d1b8174", parentMarketOutcome: 4, conditionId: - "0xf857ab39ef39d99f00d38ab07a5676406dfd5382f6d2177c44642e147d8dd0ad", - details: { - imdbURL: - "https://www.imdb.com/title/tt0050083/?ref_=nv_sr_srsg_0_tt_8_nm_0_in_0_q_12%2520angry", - posterURL: - "https://resizing.flixster.com/FDNKxkwCqhqdzh-IvaGBfzqRb74=/206x305/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p2084_p_v8_ar.jpg", - summary: - "The jury in a New York City murder trial is frustrated by a single member whose skeptical caution forces them to more carefully consider the evidence before jumping to a hasty verdict.", - }, + "0x9101e472cce97d8f6d47c8abe51b9346c57e90eeefb3a6b7d0d893538d771d14", + realtContract: "0xc7697f5e86a102eaf4000719a2dc477d65beea7d", + initialInvestmentUsd: 143_897, + precision: 1, + ...deriveMarketRangeFromInvestment(143_897), }, { - name: "Alien (1979)", + name: "9518 Franklin", color: "#845EC2", - upToken: "0x37e70bae5e87327feece73a7c227446571f92137", - downToken: "0x31e3d82a613e5aeea7c3a65c3d657cacaaaf2674", - underlyingToken: "0x6d0407b5ae419fdd92ffdc64abf04c5f28950e02", - invalidToken: "0xe54422171C40aA14B0fc935DEA7AFb85BE15357d", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0x9a274ea86665d872fc58c8f26fd97a18b844c6ac", + upToken: "0x37181ffbd521a3e062a5fa833d5bea83e35050e3", + downToken: "0xb3125c6e54ba7fdfb2dd30e89e52eb71fa294f90", + underlyingToken: "0x55a43c4cec7664a6515df0b7df410a56938b49f2", + invalidToken: "0xf8b59a97bc632653187fa0fc93e2e14d2006b002", + marketId: "0x911e310bda048be01b2ed2b6c856b8f10ee4647a", parentMarketOutcome: 5, conditionId: - "0x8054990ae8221c8a08581381a0d2e3e5f23144a4d18a2398858be52dd94cc8c9", - details: { - imdbURL: - "https://www.imdb.com/title/tt0078748/?ref_=nv_sr_srsg_3_tt_8_nm_0_in_0_q_alien", - posterURL: - "https://resizing.flixster.com/5R4bkJZC-W_K-YjmIMKAXCbts5Y=/206x305/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p2571_p_v8_aw.jpg", - summary: - "After investigating a mysterious transmission of unknown origin, the crew of a commercial spacecraft encounters a deadly lifeform.", - }, + "0xa54a9d0ae7150d3f5fb637cce9ab2dc94a31d72d3acaf3814731b95c9135ed1d", + realtContract: "0x4ae9d3343bbc6a894b7ee7f843c224c953f1661b", + initialInvestmentUsd: 461_643, + precision: 1, + ...deriveMarketRangeFromInvestment(461_643), }, { - name: "Demolition Man (1993)", + name: "8034 Faith", color: "#FF9671", - upToken: "0x53a9011c5570bfb8148954c4f49a6625dc44077b", - downToken: "0x64974d3bf944fafec6fa19a900f3679a716b3a86", - underlyingToken: "0x20025021e440edd39d486f3c6a1d7adb9c269faf", - invalidToken: "0x406B8Ee2DF07c644414E852542dAB98BdDf39234", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0xc25af7d4a5cb36bb3ce9faf652a5f7f989a1d57a", + upToken: "0xa867ab33e8f982f980ed0c7d11c32da71fe2c393", + downToken: "0x1710bb5b7216481c37b0882ed447ffad5454be99", + underlyingToken: "0xdc0f60e48eb73eea5eb6cb163033f82dd928667c", + invalidToken: "0xbe0d50317118f413606c3bb8d3e4a43a4ad743f9", + marketId: "0x1db74195286b284ed6bdc7ed9ec0092fa7b90bd7", parentMarketOutcome: 6, conditionId: - "0xe35db6fb9992ab689e21751f036ccc9a8548b71dec3089874cf4a19a13cd34bb", - details: { - imdbURL: - "https://www.imdb.com/title/tt0106697/?ref_=nv_sr_srsg_0_tt_8_nm_0_in_0_q_demolition%2520man", - posterURL: - "https://resizing.flixster.com/e3iHOpnnUZKRPz_xHJVoLz8TkGM=/206x305/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p15098_p_v10_ab.jpg", - summary: - "A police officer is brought out of suspended animation in prison to pursue an old ultra-violent nemesis who is loose in a non-violent future society.", - }, + "0xa30d5f5f3ffd2d23ec1ffb08878f1b76052bef8ee6d9b19eaff0cbb0c35a67cf", + realtContract: "0x90d280b6456f8233e115e6aabb2ca89249dafd39", + initialInvestmentUsd: 327_296, + precision: 1, + ...deriveMarketRangeFromInvestment(327_296), }, { - name: "Barbie (2023)", + name: "1769 Cheryl", color: "#0081CF", - upToken: "0xaed0fad91e7149ec84bb4d0a2a77be819169275f", - downToken: "0x044e1b6d8aacbda5699423578bd200484f7473c3", - underlyingToken: "0x67d0f938ea12e7e30b8ccc24dd031d656cc3927d", - invalidToken: "0xA9099Baa3b74c1d602aCe8CeaC5933a16A0456C5", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0xd31d05158722f64b6a49e25bccc47d3203eecbe9", + upToken: "0x2ed11278a5da5f442b9de24ec1d2c798a6c12fb7", + downToken: "0x85450e10ddd065cc75343fffa9b658ebaac06123", + underlyingToken: "0xde8a6e7ebb1950ec1b5957bcc03675f2f42a607b", + invalidToken: "0xcace59ad51b6e3b13629333796c1d756a077b9d8", + marketId: "0x9e9aee3dc35a5d4fd9fac1f9b918aacfe9229cf7", parentMarketOutcome: 7, conditionId: - "0x3c102db4f274983b648bd27a4092866e1b81dbc08b8738a5c694a8d8c3948a81", - details: { - imdbURL: - "https://www.imdb.com/title/tt1517268/?ref_=nv_sr_srsg_1_tt_6_nm_1_in_0_q_barbie", - posterURL: - "https://resizing.flixster.com/r409CsU-O1gEcAP0VtU6tDD9sKI=/206x305/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p13472534_p_v8_am.jpg", - summary: - "Barbie and Ken are having the time of their lives in the seemingly perfect world of Barbie Land. However, when they get a chance to go to the outside world, they soon discover the joys and perils of living among regular humans.", - }, + "0x643bec3e8e883942e309f07b74a72a25129bf4530cd1c734c5510254641d8419", + realtContract: "0x19f824662ba9df78e368022f085b708fccc201c8", + initialInvestmentUsd: 307_272, + precision: 1, + ...deriveMarketRangeFromInvestment(307_272), }, { - name: "Eduardo e MΓ΄nica (2020)", + name: "9311 Bedford", color: "#FFC75F", - upToken: "0x9d64a3e7e55880f3c8f9c584ed32397bb6f0b9f6", - downToken: "0xe9d025d3cbd783d6a92626b650a32f7cbaca0e7d", - underlyingToken: "0x58ce7a53abeca1db90cec0e6b7dcbe3a36d986c4", - invalidToken: "0xcA4c82fd178aaf4b72ECe35774ce04B7Aa2E5361", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0x13d48a73811c01f574e1bfa4c58b7d95d2f590e4", + upToken: "0x15014f3c758a851ff57adeabff53639723ce0e85", + downToken: "0xef3922eb753e41c4e24a7877928c280e0bebd0bd", + underlyingToken: "0x8f20773ec4ba5f2e547282d043895680431cabf8", + invalidToken: "0x610b3028fbd70717db25ab666d965f177356198a", + marketId: "0x987226cc83d5d3baae096197f9708f3142c16691", parentMarketOutcome: 8, conditionId: - "0x2dcf754f36437ea0c298e5d27a0f3904dc2335a6e239b15a104f3ca7787c5926", - details: { - imdbURL: - "https://www.imdb.com/title/tt8747460/?ref_=nv_sr_srsg_0_tt_2_nm_0_in_0_q_Eduardo%2520e%2520M%25C3%25B4nica%2520(2020)", - posterURL: - "https://resizing.flixster.com/IaXbRF4gIPh9jireK_4VCPNfdKc=/200x0/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p18824656_k_v8_aa.jpg", - summary: - "On an unusual day, a series of coincidences lead Eduardo to meet Monica at a party. Curiosity is aroused between the two and, despite not being alike, they fall madly in love. This love needs to mature and learn to overcome differences.", - }, - }, - { - name: "Thor: The Dark World (2013)", - color: "#00C9A7", - upToken: "0x0c569fbc021119b778ea160efd718a5d592ef46c", - downToken: "0xd8d2dfe1912239451b5a4a0462006e95393f2151", - underlyingToken: "0x72ec9aade867b5b41705c6a83f66bc56485669b5", - invalidToken: "0xFa2e53b2E33309CEE9255b440f143308F92BbA83", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0x878a332b5efc0a4bf983036beece050352baa73d", - parentMarketOutcome: 9, - conditionId: - "0xb223aad8405c321b761e3cba872e556c1de3a8b552a38249d626bc5aff7c6ba2", - details: { - imdbURL: - "https://www.imdb.com/title/tt1981115/?ref_=nv_sr_srsg_0_tt_8_nm_0_in_0_q_thor%2520the%2520dark", - posterURL: - "https://resizing.flixster.com/HtozfP_2NYit3_l7s-cbtsiuWps=/206x305/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p9530219_p_v13_aa.jpg", - summary: - "When the Dark Elves attempt to plunge the universe into darkness, Thor must embark on a perilous and personal journey that will reunite him with doctor Jane Foster.", - }, - }, - { - name: "Talk to me (2022)", - color: "#C34A36", - upToken: "0xf3c17e909bd1f9367ecdc786d137465d7ee96b6a", - downToken: "0xf99be182b6b0e6d994509ecdced281b94100435f", - underlyingToken: "0x2b3a8ac53ba42da13f542a867d2859642fb1db44", - invalidToken: "0x94b6580034e1FFf008Ac8370dF69E180740469b0", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0xee4a77447069f32f555f3d75aaba18a4acb54ac4", - parentMarketOutcome: 10, - conditionId: - "0x715f9e8ccc373f85e2f9ec02bba8d23c5f87090b729750ca8adac5b0f969213e", - details: { - imdbURL: - "https://www.imdb.com/title/tt10638522/?ref_=nv_sr_srsg_0_tt_8_nm_0_in_0_q_talk%2520to%2520me", - posterURL: - "https://resizing.flixster.com/ejS3S8JOBfvZr_fQ_--6SyKKJpQ=/206x305/v2/https://resizing.flixster.com/9WxKriao1BmRamIaqig2k8hd5uM=/ems.cHJkLWVtcy1hc3NldHMvbW92aWVzL2YyZDQwYTM2LWZmYzEtNGUwMC05NzRkLTA3ODM0NThiNDE4Ny5qcGc=", - summary: - "When a group of friends discover how to conjure spirits using an embalmed hand, they become hooked on the new thrill, until one of them goes too far and unleashes terrifying supernatural forces.", - }, - }, - { - name: "Fast & Furious 6 (2013)", - color: "#9B51E0", - upToken: "0x850d2ffa4475296cfbbd76247894a773e3b1be6c", - downToken: "0xb28c716f63b0dd272f62e25765a914baeebab8c2", - underlyingToken: "0x71c3df5edcab48cfb6a1a99255eff063f33b6265", - invalidToken: "0xb3cE80d6b30DcC4d605B290f4dC1Fc3B8C2bcC3b", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0x38a2923cc391b9cd926e5a2d07462dc7d189c407", - parentMarketOutcome: 11, - conditionId: - "0x27f341cdecacbd7ff0e4bb7b28add74ddaa388ff9f16bc749e2828a71fe6a5f6", - details: { - imdbURL: - "https://www.imdb.com/title/tt1905041/?ref_=nv_sr_srsg_0_tt_8_nm_0_in_0_q_fast%2520%2526%2520furious%25206", - posterURL: - "https://resizing.flixster.com/dJUU6CNK8IBSjsImW4nXCxxUVwU=/206x305/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p9573130_p_v7_ab.jpg", - summary: - "Hobbs has Dominic and Brian reassemble their crew to take down a team of mercenaries, but Dominic unexpectedly gets sidetracked with facing his presumed deceased girlfriend, Letty.", - }, - }, - { - name: "Elysium (2013)", - color: "#2D4059", - upToken: "0xe9427a7a32daad2d29db2aad809b2a44060d8fc8", - downToken: "0x75b5cd86828f7c9009e30619a83b1b2da67f1342", - underlyingToken: "0xf52e0e144b73a0d5748bc53667efe3ba62fe5695", - invalidToken: "0x69641B6664a493ecF467D4D9aAB595A8b9Cc4a66", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0xc0dab34c6c2008391bdc742cec0bd0afb60d4d59", - parentMarketOutcome: 12, - conditionId: - "0x2d2ee6e67d4ffa2c2a14898a29d0afe3d3cdd8ad362811aad64770a90553cb3a", - details: { - imdbURL: - "https://www.imdb.com/title/tt1535108/?ref_=nv_sr_srsg_0_tt_8_nm_0_in_0_q_elysium", - posterURL: - "https://resizing.flixster.com/WlkdhZWddtMIv8U2Tmlb74rmBZ4=/206x305/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p9360879_p_v10_ar.jpg", - summary: - "In the year 2154, the very wealthy live on a man-made space station while the rest of the population resides on a ruined Earth. A man takes on a mission that could bring equality to the polarized worlds.", - }, - }, - { - name: "Session 9 (2001)", - color: "#F9F871", - upToken: "0xe080c03ad6bc9f8fd5b45b5d3bf14ebcfa1ec0b5", - downToken: "0x76cce8491785789c2c5542f043ec6c35b12cd909", - underlyingToken: "0x1086a95c224dd586809a7f4d875b4f09d2ac9290", - invalidToken: "0x4F2b7EC3aAC8Bb0Ffb272a4B27B758D2FFC31bc6", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0xa7cf69c4c93d2f6811a394e92320979c3cf86b37", - parentMarketOutcome: 13, - conditionId: - "0x6bc6c6fd532a02ec128e7c8dfe3e496295f677c861405a88b7da503f1882eef8", - details: { - imdbURL: - "https://www.imdb.com/title/tt0261983/?ref_=nv_sr_srsg_0_tt_8_nm_0_in_0_q_session%25209", - posterURL: - "https://resizing.flixster.com/pMiw8blJew0YXddZivo7mtYlUDg=/206x305/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p28177_p_v13_ac.jpg", - summary: - "Tensions rise within an asbestos cleaning crew as they work in an abandoned mental hospital with a horrific past that seems to be coming back.", - }, - }, - { - name: "Mamma Mia! (2008)", - color: "#B0A8B9", - upToken: "0xfa82984fc8ddeb71fdb2e6e471f30995178ad5f0", - downToken: "0x5d528dbec7e37927d8af41bfb1b54e7641dd3ccb", - underlyingToken: "0x11ed86c399f455819f495cda1256e9b52afd0971", - invalidToken: "0x756de0795875f925AC95ba37472D26bC4375c6a4", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0x96638d67ac5bc5f8223f9e2d60e92f4d8dcf3147", - parentMarketOutcome: 14, - conditionId: - "0x6e5b27306498d2917cdde6a3ea4791cd5a6fe8d8bf33d491c97524c431eda325", - details: { - imdbURL: - "https://www.imdb.com/title/tt0795421/?ref_=nv_sr_srsg_0_tt_7_nm_1_in_0_q_mamma%2520mia", - posterURL: - "https://resizing.flixster.com/sD29k0EMFXDWY0DPiFoQsaxqDNU=/206x305/v2/https://resizing.flixster.com/-XZAfHZM39UwaGJIFWKAE8fS0ak=/v3/t/assets/p176344_p_v8_al.jpg", - summary: - "Donna, an independent hotelier, is preparing for her daughter's wedding with the help of two old friends. Meanwhile Sophie, the spirited bride, has a plan. She invites three men from her mother's past in hope of meeting her real father.", - }, - }, - { - name: "Ethereum (2022)", - color: "#FF8066", - upToken: "0xf8313845248f2392a39bdcd50be0781c7cf497c1", - downToken: "0x3befdfbd7c2a7139acafc3005369d30ff2cd8f9a", - underlyingToken: "0x78c2edb5639af0ed4351f001c728c9026820887e", - invalidToken: "0x8eB59F4590fF6a0037a159ea1601D9d309aEa598", - minValue: 0, - maxValue: 100, - precision: 100, - marketId: "0x002c70343ddef063d0ad8da91104934318800d30", - parentMarketOutcome: 15, - conditionId: - "0x2b9e73d1da8dc051ffe4972114f59e61ad1bfd65fda93d88bcfb6644ffb07f4b", - details: { - imdbURL: - "https://www.imdb.com/title/tt22069858/?ref_=nv_sr_srsg_2_tt_8_nm_0_in_0_q_ethereum", - posterURL: - "https://play-lh.googleusercontent.com/ARlYF4lUWeSFL9CgcKmHIesZwjsRjB0qkCKyIcLYckxYdrAkmvz1RKLQ_RFPRQuedofL8xOeCBtz-MIStG8=w240-h480-rw", - summary: - "Learn About the hottest cryptocurrency around, Ethereum. This amazing documentary explores the history of Ethereum, a decentralized, open-source blockchain with smart contract functionality.", - }, + "0xee5437a6e77919789498fe0b50a89da4b999ad551ab9c325b884713ea73f69f2", + realtContract: "0xa83cbd26964ea953f86c741871a1ab2a256cb82d", + initialInvestmentUsd: 90_270, + precision: 1, + ...deriveMarketRangeFromInvestment(90_270), }, ]; diff --git a/src/consts/metadata.ts b/src/consts/metadata.ts index cef842c..d1bac15 100644 --- a/src/consts/metadata.ts +++ b/src/consts/metadata.ts @@ -1,7 +1,7 @@ import { Metadata } from "next"; -export const websiteUrl = "https://foresight.kleros.io"; -export const siteName = "foresight.kleros.io"; +export const websiteUrl = "https://realt.foresight.kleros.io"; +export const siteName = "realt.foresight.kleros.io"; export const metadata: Metadata = { title: "Foresight | Kleros", diff --git a/src/context/MarketContext.tsx b/src/context/MarketContext.tsx index b400ad2..c7734da 100644 --- a/src/context/MarketContext.tsx +++ b/src/context/MarketContext.tsx @@ -18,6 +18,7 @@ import { useGetWinningOutcomes } from "@/hooks/useGetWinningOutcomes"; import { useMarketPrice } from "@/hooks/useMarketPrice"; import { isUndefined } from "@/utils"; +import { outcomeFromPoolPrice, outcomeRangeSpan } from "@/utils/marketRange"; import { IMarket, parentConditionId } from "@/consts/markets"; @@ -66,7 +67,7 @@ const MarketContextProvider: React.FC = ({ const setMarketEstimate = useMarketsStore((state) => state.setMarketEstimate); - const { underlyingToken, upToken, downToken, maxValue, precision } = market; + const { underlyingToken, upToken, downToken } = market; const [localPrediction, setLocalPrediction] = useState( undefined, @@ -82,7 +83,6 @@ const MarketContextProvider: React.FC = ({ [localPrediction], ); - // initialize market useEffect(() => { setMarket(market); }, []); @@ -114,9 +114,9 @@ const MarketContextProvider: React.FC = ({ const marketEstimate = useMemo( () => typeof marketPrice !== "undefined" - ? Math.round(marketPrice * maxValue * precision) + ? outcomeFromPoolPrice(marketPrice, market) : 0, - [marketPrice, maxValue, precision], + [marketPrice, market], ); useEffect(() => { @@ -134,14 +134,13 @@ const MarketContextProvider: React.FC = ({ ) { setLocalPrediction(marketEstimate); } - }, [localPrediction, marketEstimate, market.precision, currentPrices]); + }, [localPrediction, marketEstimate, currentPrices]); const showEstimateVariant = useMemo(() => { if (isUndefined(localPrediction) || !hasLiquidity) return false; - return ( - Math.abs(localPrediction - marketEstimate) > - market.maxValue / market.precision / 100 - ); + const span = outcomeRangeSpan(market); + if (span === 0) return false; + return Math.abs(localPrediction - marketEstimate) > span / 100; }, [localPrediction, market, marketEstimate, hasLiquidity]); const { data: winningOutcomes } = useGetWinningOutcomes(market.conditionId); diff --git a/src/context/Web3Context.tsx b/src/context/Web3Context.tsx index 5589951..38b11ff 100644 --- a/src/context/Web3Context.tsx +++ b/src/context/Web3Context.tsx @@ -41,8 +41,8 @@ createAppKit({ projectId: reownProjectId, networks: [gnosis], defaultNetwork: gnosis, - metadata: metadata, enableCoinbase: false, + metadata: metadata, features: { analytics: false, }, diff --git a/src/hooks/useChartData.ts b/src/hooks/useChartData.ts index d0f26fa..781795d 100644 --- a/src/hooks/useChartData.ts +++ b/src/hooks/useChartData.ts @@ -3,6 +3,8 @@ import { useQuery } from "@tanstack/react-query"; import { formatUnits } from "viem"; +import { chartValueFromPoolPrice } from "@/utils/marketRange"; + import { IMarket } from "@/consts/markets"; interface IDataPoint { @@ -50,6 +52,7 @@ const getSqrtPrices = ( export const useChartData = (markets: Array) => useQuery({ queryKey: [`chart-${markets.map(({ marketId }) => marketId).join("-")}`], + enabled: markets.length > 0, queryFn: async () => { const { data }: { data: IReturn[] } = await fetch("api/market-chart", { next: { revalidate: 300 }, @@ -70,16 +73,16 @@ export const useChartData = (markets: Array) => ) { ({ token0Price, token1Price } = getSqrtPrices(sqrtPrice)); } + const poolPrice = parseFloat( + (dataPoint.pool.token0.id.toLowerCase() === + market.underlyingToken.toLowerCase() + ? token0Price + : token1Price + ).slice(0, 9), + ); return { timestamp: dataPoint.periodStartUnix, - value: - parseFloat( - (dataPoint.pool.token0.id.toLowerCase() === - market.underlyingToken.toLowerCase() - ? token0Price - : token1Price - ).slice(0, 9), - ) * market.maxValue, + value: chartValueFromPoolPrice(poolPrice, market), }; }, ); diff --git a/src/hooks/useRealtMarketData.ts b/src/hooks/useRealtMarketData.ts new file mode 100644 index 0000000..3afa412 --- /dev/null +++ b/src/hooks/useRealtMarketData.ts @@ -0,0 +1,199 @@ +import { useQuery } from "@tanstack/react-query"; + +export interface RealtMarketData { + fullName: string; + shortName: string; + symbol: string; + productType: string; + tokenPrice: number; + canal: string; + currency: string; + totalTokens: number; + totalTokensRegSummed: number; + uuid: string; + ethereumContract: string; + xDaiContract: string; + gnosisContract: string; + goerliContract: string | null; + totalInvestment: number; + grossRentYear: number; + grossRentMonth: number; + propertyManagement: number; + propertyManagementPercent: number; + realtPlatform: number; + realtPlatformPercent: number; + insurance: number; + propertyTaxes: number; + utilities: number; + initialMaintenanceReserve: number; + netRentDay: number; + netRentMonth: number; + netRentYear: number; + netRentDayPerToken: number; + netRentMonthPerToken: number; + netRentYearPerToken: number; + annualPercentageYield: number; + coordinate: { + lat: string; + lng: string; + }; + marketplaceLink: string; + imageLink: string[]; + propertyType: number; + propertyTypeName: string; + squareFeet: number; + lotSize: number; + bedroomBath: string; + hasTenants: boolean; + rentedUnits: number; + totalUnits: number; + termOfLease: string | null; + renewalDate: string | null; + section8paid: number; + subsidyStatus: string; + subsidyStatusValue: string | null; + subsidyBy: string | null; + sellPropertyTo: string; + secondaryMarketplace: { + UniswapV1: number; + UniswapV2: number; + }; + secondaryMarketplaces: Array<{ + chainId: number; + chainName: string; + dexName: string; + contractPool: string; + pair: { + contract: string; + symbol: string; + name: string; + }; + }>; + blockchainAddresses: { + ethereum: { + chainName: string; + chainId: number; + contract: string; + distributor: string; + maintenance: string; + }; + xDai: { + chainName: string; + chainId: number; + contract: string; + distributor: string; + rmmPoolAddress: number; + rmmV3WrapperAddress: string; + chainlinkPriceContract: string; + }; + sepolia: { + chainName: string; + chainId: number; + contract: number; + distributor: number; + rmmPoolAddress: number; + chainlinkPriceContract: number; + }; + }; + underlyingAssetPrice: number; + renovationReserve: number; + propertyMaintenanceMonthly: number; + rentStartDate: { + date: string; + timezone_type: number; + timezone: string; + }; + lastUpdate: { + date: string; + timezone_type: number; + timezone: string; + }; + originSecondaryMarketplaces: Array<{ + chainId: number; + chainName: string; + dexName: string; + contractPool: string; + }>; + initialLaunchDate: { + date: string; + timezone_type: number; + timezone: string; + }; + seriesNumber: number; + constructionYear: number; + constructionType: string; + roofType: string; + assetParking: string | null; + foundation: string; + heating: string; + cooling: string; + tokenIdRules: number; + rentCalculationType: string; + realtListingFeePercent: number; + realtListingFee: number; + miscellaneousCosts: number; + propertyStories: string | null; + rentalType: string; + tokenPrices: string | null; + renovationPoolMonthlyPercentFee: number; + renovationPoolInitialFee: string | null; + rentalHistory: Array<{ + date: string; + value: string; + adjustment_date: string; + gross_income_annual: { + old: string; + new: string; + }; + rented_units: { + old: string; + new: string; + }; + total_units: { + old: string; + new: string; + }; + monthly_subsidy: { + old: string; + new: string; + }; + payer_name: { + old: string; + new: string; + }; + }>; + neighborhood: string; + [key: string]: unknown; +} + +async function fetchAllRealtData(): Promise> { + const response = await fetch("/.netlify/functions/realt-proxy"); + if (!response.ok) { + throw new Error("Failed to fetch RealT market data"); + } + return response.json(); +} + +export function useAllRealtMarketData() { + return useQuery, Error>({ + queryKey: ["realtAllMarketData"], + queryFn: fetchAllRealtData, + }); +} + +export function useRealtMarketData(contractAddress: string) { + return useQuery, Error, RealtMarketData>({ + queryKey: ["realtAllMarketData"], + queryFn: fetchAllRealtData, + select: (allData) => { + const data = allData[contractAddress.toLowerCase()]; + if (!data) { + throw new Error( + `No data found for contract address: ${contractAddress}`, + ); + } + return data; + }, + enabled: !!contractAddress, + }); +} diff --git a/src/store/markets.ts b/src/store/markets.ts index 1f8485b..f3916dc 100644 --- a/src/store/markets.ts +++ b/src/store/markets.ts @@ -2,8 +2,9 @@ import { create } from "zustand"; import { persist } from "zustand/middleware"; import { isUndefined } from "@/utils"; +import { predictionToNormalizedPrice } from "@/utils/marketRange"; -import { IMarket } from "@/consts/markets"; +import { IMarket, parentMarket } from "@/consts/markets"; export interface PredictionMarket extends IMarket { prediction?: number; @@ -41,8 +42,10 @@ export const useMarketsStore = create()( const market = state.markets[marketId]; if (isUndefined(market)) return state; - const predictedPrice = - prediction / (market.maxValue * market.precision); + const predictedPrice = predictionToNormalizedPrice( + prediction, + market, + ); return { markets: { ...state.markets, @@ -74,8 +77,7 @@ export const useMarketsStore = create()( { ...market, prediction: estimate, - predictedPrice: - estimate / (market.maxValue * market.precision), + predictedPrice: predictionToNormalizedPrice(estimate, market), }, ]; }), @@ -89,18 +91,25 @@ export const useMarketsStore = create()( const market = state.markets[marketId]; if (isUndefined(market)) return state; + const estimate = market.marketEstimate; return { markets: { ...state.markets, // Setting the prediction to marketEstimate, // removes it from the predicable markets (@/hooks/usePredictionMarkets) - [marketId]: { ...market, prediction: market?.marketEstimate }, + [marketId]: { + ...market, + prediction: estimate, + predictedPrice: isUndefined(estimate) + ? undefined + : predictionToNormalizedPrice(estimate, market), + }, }, }; }), }), { - name: "futarchy-predictions", + name: `futarchy-realt-predictions-${parentMarket}`, partialize: (state) => ({ markets: Object.fromEntries( Object.entries(state.markets) diff --git a/src/utils/csv.ts b/src/utils/csv.ts index 7bfd161..6ad7052 100644 --- a/src/utils/csv.ts +++ b/src/utils/csv.ts @@ -2,9 +2,9 @@ import Papa from "papaparse"; import { PredictionMarket } from "@/store/markets"; -import marketFromName from "./marketIdFromName"; +import { formatUsdPlain } from "@/utils/marketRange"; -import { formatWithPrecision } from "."; +import marketFromName from "./marketIdFromName"; export const parseMarketCSV = (csvText: string): Record => { const parsed = Papa.parse<{ marketName: string; score: string }>(csvText, { @@ -55,29 +55,28 @@ export const parseMarketCSV = (csvText: string): Record => { const marketId = market.marketId; - // Validate score - const score = parseFloat(scoreStr); - if (isNaN(score)) { + const parsedUsd = parseFloat(scoreStr); + if (isNaN(parsedUsd)) { throw new Error( - `Row ${i + 2}: Score "${scoreStr}" is not a valid number`, + `Row ${i + 2}: Value "${scoreStr}" is not a valid number`, ); } - if (score < 0) { - throw new Error(`Row ${i + 2}: Score cannot be negative`); + const predictionUsd = Math.round(parsedUsd); + + if (predictionUsd < market.minValue) { + throw new Error( + `Row ${i + 2}: Price must be at least ${market.minValue} USD for property ${marketName}`, + ); } - const maxScore = formatWithPrecision( - market.maxValue * market.precision, - market.precision, - ); - if (score > +maxScore) { + if (predictionUsd > market.maxValue) { throw new Error( - `Row ${i + 2}: Score cannot be greater than the max value of ${maxScore}`, + `Row ${i + 2}: Price must be at most ${market.maxValue} USD for property ${marketName}`, ); } - result[marketId] = Math.round(score * market.precision); + result[marketId] = predictionUsd; } if (Object.values(result).length === 0) { @@ -90,10 +89,7 @@ export const parseMarketCSV = (csvText: string): Record => { export function generateMarketCsv(markets: Record) { const data = Object.values(markets).map((market) => ({ marketName: market.name, - score: formatWithPrecision( - market.prediction ?? market.marketEstimate ?? 0, - market.precision, - ), + score: formatUsdPlain(market.prediction ?? market.marketEstimate ?? 0), })); return Papa.unparse(data, { @@ -110,3 +106,74 @@ export function downloadCsvFile(filename: string, content: string) { link.click(); URL.revokeObjectURL(url); } + +function escapeCsvValue(value: unknown): string { + if (value === null || value === undefined) return ""; + + if (Array.isArray(value) || (typeof value === "object" && value !== null)) { + const json = JSON.stringify(value); + return `"${json.replace(/"/g, '""')}"`; + } + + const str = String(value); + if (str.includes(",") || str.includes('"') || str.includes("\n")) { + return `"${str.replace(/"/g, '""')}"`; + } + return str; +} + +function flattenObject( + obj: Record, + prefix = "", +): Record { + const result: Record = {}; + + for (const [key, value] of Object.entries(obj)) { + const flatKey = prefix ? `${prefix}.${key}` : key; + + if (Array.isArray(value)) { + result[flatKey] = value; + } else if ( + value !== null && + typeof value === "object" && + !(value instanceof Date) + ) { + Object.assign( + result, + flattenObject(value as Record, flatKey), + ); + } else { + result[flatKey] = value; + } + } + + return result; +} + +export function generateRealtDataCsv( + allData: Record>, + contractAddresses: string[], +): string { + const entries = contractAddresses + .map((addr) => allData[addr.toLowerCase()]) + .filter(Boolean) + .map((data) => flattenObject(data as Record)); + + if (entries.length === 0) return ""; + + // Collect all unique keys across all entries to ensure no field is missed + const allKeys = new Set(); + for (const entry of entries) { + for (const key of Object.keys(entry)) { + allKeys.add(key); + } + } + const columns = Array.from(allKeys); + + const header = columns.map(escapeCsvValue).join(","); + const rows = entries.map((entry) => + columns.map((col) => escapeCsvValue(entry[col])).join(","), + ); + + return [header, ...rows].join("\n"); +} diff --git a/src/utils/formatCompactUsd.ts b/src/utils/formatCompactUsd.ts new file mode 100644 index 0000000..9057eb0 --- /dev/null +++ b/src/utils/formatCompactUsd.ts @@ -0,0 +1,40 @@ +/** + * Short USD strings for charts: `2M$`, `100k$`, `$400`. + */ +export function formatCompactUsd(value: number): string { + const sign = value < 0 ? "-" : ""; + const absValue = Math.abs(value); + + if (absValue < 1000) { + const amountText = + absValue % 1 === 0 ? String(absValue) : absValue.toFixed(2); + return `${sign}$${amountText}`; + } + + let scaledPart: number; + let unitSuffix: string; + + if (absValue >= 1_000_000_000) { + scaledPart = absValue / 1_000_000_000; + unitSuffix = "B$"; + } else if (absValue >= 1_000_000) { + scaledPart = absValue / 1_000_000; + unitSuffix = "M$"; + } else { + scaledPart = absValue / 1000; + unitSuffix = "k$"; + if (scaledPart >= 999.5) { + scaledPart = absValue / 1_000_000; + unitSuffix = "M$"; + } + } + + const roundedText = + scaledPart >= 100 || Math.abs(scaledPart - Math.round(scaledPart)) < 0.0001 + ? String(Math.round(scaledPart)) + : scaledPart >= 10 + ? scaledPart.toFixed(1).replace(/\.0$/, "") + : scaledPart.toFixed(1).replace(/\.0$/, ""); + + return `${sign}${roundedText}${unitSuffix}`; +} diff --git a/src/utils/marketRange.ts b/src/utils/marketRange.ts new file mode 100644 index 0000000..1a5b82b --- /dev/null +++ b/src/utils/marketRange.ts @@ -0,0 +1,83 @@ +import type { IMarket } from "@/consts/markets"; + +type MarketRangeFields = Pick; + +/** + * Min/max USD from investment: βˆ’50% / +100% (rounded to whole dollars). + * @note RealT specific + */ +export function deriveMarketRangeFromInvestment(initialInvestmentUsd: number) { + return { + minValue: Math.round(0.5 * initialInvestmentUsd), + maxValue: Math.round(2 * initialInvestmentUsd), + }; +} + +/** + * `predictedPrice` in [0, 1] from stored USD prediction (for AMM / target price). + */ +export function predictionToNormalizedPrice( + predictionUsd: number, + market: MarketRangeFields, +) { + const range = market.maxValue - market.minValue; + if (range <= 0) { + return 0; + } + const clamped = Math.min( + market.maxValue, + Math.max(market.minValue, predictionUsd), + ); + return (clamped - market.minValue) / range; +} + +/** Inverse of {@link predictionToNormalizedPrice} (whole USD). */ +export function normalizedPriceToPrediction( + normalized: number, + market: MarketRangeFields, +) { + const range = market.maxValue - market.minValue; + if (range <= 0) { + return market.minValue; + } + return Math.round(market.minValue + normalized * range); +} + +export function formatUsd(dollars: number) { + return new Intl.NumberFormat("en-US", { + style: "currency", + currency: "USD", + minimumFractionDigits: 0, + maximumFractionDigits: 0, + }).format(Math.round(dollars)); +} + +export function formatUsdPlain(dollars: number) { + return String(Math.round(dollars)); +} + +export function outcomeRangeSpan(market: MarketRangeFields) { + return Math.max(0, market.maxValue - market.minValue); +} + +export function outcomeFromPoolPrice( + poolPriceUp: number, + market: MarketRangeFields, +) { + const range = market.maxValue - market.minValue; + if (range <= 0) { + return market.minValue; + } + return Math.round(market.minValue + poolPriceUp * range); +} + +export function chartValueFromPoolPrice( + poolPriceUp: number, + market: MarketRangeFields, +) { + const valueSpan = market.maxValue - market.minValue; + if (valueSpan === 0) { + return market.minValue; + } + return market.minValue + poolPriceUp * valueSpan; +} diff --git a/yarn.lock b/yarn.lock index 682c8a1..b9d161c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3756,6 +3756,60 @@ __metadata: languageName: node linkType: hard +"@netlify/blobs@npm:^10.6.0": + version: 10.6.0 + resolution: "@netlify/blobs@npm:10.6.0" + dependencies: + "@netlify/dev-utils": "npm:4.3.3" + "@netlify/otel": "npm:^5.1.1" + "@netlify/runtime-utils": "npm:2.3.0" + checksum: 10c0/289777612abfeb7e3eb940ceef409b7b048728171a9553b20019beb613363a8bfd5dc93088e08446a71e10d73cc71575acb4d2d464be29f8e8af04ac486dc171 + languageName: node + linkType: hard + +"@netlify/dev-utils@npm:4.3.3": + version: 4.3.3 + resolution: "@netlify/dev-utils@npm:4.3.3" + dependencies: + "@whatwg-node/server": "npm:^0.10.0" + ansis: "npm:^4.1.0" + chokidar: "npm:^4.0.1" + decache: "npm:^4.6.2" + dettle: "npm:^1.0.5" + dot-prop: "npm:9.0.0" + empathic: "npm:^2.0.0" + env-paths: "npm:^3.0.0" + image-size: "npm:^2.0.2" + js-image-generator: "npm:^1.0.4" + parse-gitignore: "npm:^2.0.0" + semver: "npm:^7.7.2" + tmp-promise: "npm:^3.0.3" + uuid: "npm:^13.0.0" + write-file-atomic: "npm:^5.0.1" + checksum: 10c0/60c95a0ce80193e79f300eb4c96f9e46bffde9b2b40370fcceb1d6d4a32d6f35d0675fe2727fe1782188f3387d688f301e7edb6ba84bb2ef4c2c9c55cce375c4 + languageName: node + linkType: hard + +"@netlify/otel@npm:^5.1.1": + version: 5.1.1 + resolution: "@netlify/otel@npm:5.1.1" + dependencies: + "@opentelemetry/api": "npm:1.9.0" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/instrumentation": "npm:^0.203.0" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/sdk-trace-node": "npm:1.30.1" + checksum: 10c0/aa0bf5e3ac57d683993a97a5dd305f9d388b4df0d942c4cb9422060df3412e8a44b4abf2b1969ea9ed9586d1c42e24ba4da9ecf873de581a4c1e971f80cf7327 + languageName: node + linkType: hard + +"@netlify/runtime-utils@npm:2.3.0": + version: 2.3.0 + resolution: "@netlify/runtime-utils@npm:2.3.0" + checksum: 10c0/03ef79d1cac8d30ba2d348cd975d391cbdb013080ec31308dc84fcaf279784066e2a9be56f54f0f7a4f754a7068ffd8af219f7de32d3180c8cb91ddd4fda5602 + languageName: node + linkType: hard + "@next/env@npm:14.2.35": version: 14.2.35 resolution: "@next/env@npm:14.2.35" @@ -4003,6 +4057,125 @@ __metadata: languageName: node linkType: hard +"@opentelemetry/api-logs@npm:0.203.0": + version: 0.203.0 + resolution: "@opentelemetry/api-logs@npm:0.203.0" + dependencies: + "@opentelemetry/api": "npm:^1.3.0" + checksum: 10c0/e7a0a0ff46aaeb62192a99f45ef4889222e4fea09be25cab6fea811afc2df95c02ea050b2c98dfc0fc5a6ec6a623d87096af2751fdf91ddbb3afcab61b5325da + languageName: node + linkType: hard + +"@opentelemetry/api@npm:1.9.0, @opentelemetry/api@npm:^1.3.0": + version: 1.9.0 + resolution: "@opentelemetry/api@npm:1.9.0" + checksum: 10c0/9aae2fe6e8a3a3eeb6c1fdef78e1939cf05a0f37f8a4fae4d6bf2e09eb1e06f966ece85805626e01ba5fab48072b94f19b835449e58b6d26720ee19a58298add + languageName: node + linkType: hard + +"@opentelemetry/context-async-hooks@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/context-async-hooks@npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10c0/3e8114d360060a5225226d2fcd8df08cd542246003790a7f011c0774bc60b8a931f46f4c6673f3977a7d9bba717de6ee028cae51b752c2567053d7f46ed3eba3 + languageName: node + linkType: hard + +"@opentelemetry/core@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/core@npm:1.30.1" + dependencies: + "@opentelemetry/semantic-conventions": "npm:1.28.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10c0/4c25ba50a6137c2ba9ca563fb269378f3c9ca6fd1b3f15dbb6eff78eebf5656f281997cbb7be8e51c01649fd6ad091083fcd8a42dd9b5dfac907dc06d7cfa092 + languageName: node + linkType: hard + +"@opentelemetry/instrumentation@npm:^0.203.0": + version: 0.203.0 + resolution: "@opentelemetry/instrumentation@npm:0.203.0" + dependencies: + "@opentelemetry/api-logs": "npm:0.203.0" + import-in-the-middle: "npm:^1.8.1" + require-in-the-middle: "npm:^7.1.1" + peerDependencies: + "@opentelemetry/api": ^1.3.0 + checksum: 10c0/b9de27ea7b42c54b1d0dab15dac62d4fc71c781bb6a48e90fa4ce8ce97be1b78e1fa9f05f58c39f68ca0e4a5590b8538d04209482f6b0632958926f7e80a28c1 + languageName: node + linkType: hard + +"@opentelemetry/propagator-b3@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/propagator-b3@npm:1.30.1" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10c0/aeeaa6325e2d970a207a396b98562e05578688ffd047e64544c441456702c593a74b614216c0360ee0f63bb7c3cf39b63f74c0f59c8580a1aac067970cee9bc2 + languageName: node + linkType: hard + +"@opentelemetry/propagator-jaeger@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/propagator-jaeger@npm:1.30.1" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10c0/e828d67768150bb23b4e75589bc6e9a3ae28e50a6ba6f6e737cf14fd33ab4108fb0aa84d363045e7e591b89a55bef4b8823fbd1734f64f7bb918338b78b86881 + languageName: node + linkType: hard + +"@opentelemetry/resources@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/resources@npm:1.30.1" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/semantic-conventions": "npm:1.28.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10c0/688e73258283c80662bfa9a858aaf73bf3b832a18d96e546d0dddfa6dcec556cdfa087a1d0df643435293406009e4122d7fb7eeea69aa87b539d3bab756fba74 + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-base@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/sdk-trace-base@npm:1.30.1" + dependencies: + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/resources": "npm:1.30.1" + "@opentelemetry/semantic-conventions": "npm:1.28.0" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10c0/77019dc3efaeceb41b4c54dd83b92f0ccd81ecceca544cbbe8e0aee4b2c8727724bdb9dcecfe00622c16d60946ae4beb69a5c0e7d85c4bc7ef425bd84f8b970c + languageName: node + linkType: hard + +"@opentelemetry/sdk-trace-node@npm:1.30.1": + version: 1.30.1 + resolution: "@opentelemetry/sdk-trace-node@npm:1.30.1" + dependencies: + "@opentelemetry/context-async-hooks": "npm:1.30.1" + "@opentelemetry/core": "npm:1.30.1" + "@opentelemetry/propagator-b3": "npm:1.30.1" + "@opentelemetry/propagator-jaeger": "npm:1.30.1" + "@opentelemetry/sdk-trace-base": "npm:1.30.1" + semver: "npm:^7.5.2" + peerDependencies: + "@opentelemetry/api": ">=1.0.0 <1.10.0" + checksum: 10c0/8ae1c2b49389d45bc9419e106c47fa3b91cb39708281dc7dfb7dab8e4d98d5bb27c1758a5521722840bca37bb825d4b8b1571e19ab88a7884867f994e29a5989 + languageName: node + linkType: hard + +"@opentelemetry/semantic-conventions@npm:1.28.0": + version: 1.28.0 + resolution: "@opentelemetry/semantic-conventions@npm:1.28.0" + checksum: 10c0/deb8a0f744198071e70fea27143cf7c9f7ecb7e4d7b619488c917834ea09b31543c1c2bcea4ec5f3cf68797f0ef3549609c14e859013d9376400ac1499c2b9cb + languageName: node + linkType: hard + "@openzeppelin/contracts@npm:3.4.1-solc-0.7-2": version: 3.4.1-solc-0.7-2 resolution: "@openzeppelin/contracts@npm:3.4.1-solc-0.7-2" @@ -7889,6 +8062,16 @@ __metadata: languageName: node linkType: hard +"@whatwg-node/fetch@npm:^0.10.13": + version: 0.10.13 + resolution: "@whatwg-node/fetch@npm:0.10.13" + dependencies: + "@whatwg-node/node-fetch": "npm:^0.8.3" + urlpattern-polyfill: "npm:^10.0.0" + checksum: 10c0/afce42c44e9c5572ac5800615bac3a03865923af53af99098d2e931b40f6db556ad5d4a3e08c29e51ecf871809f0860fb11f2b024891daa26646a309f8b07fc1 + languageName: node + linkType: hard + "@whatwg-node/node-fetch@npm:^0.8.0": version: 0.8.1 resolution: "@whatwg-node/node-fetch@npm:0.8.1" @@ -7901,6 +8084,18 @@ __metadata: languageName: node linkType: hard +"@whatwg-node/node-fetch@npm:^0.8.3": + version: 0.8.5 + resolution: "@whatwg-node/node-fetch@npm:0.8.5" + dependencies: + "@fastify/busboy": "npm:^3.1.1" + "@whatwg-node/disposablestack": "npm:^0.0.6" + "@whatwg-node/promise-helpers": "npm:^1.3.2" + tslib: "npm:^2.6.3" + checksum: 10c0/9f0d944476cc40f5cfed79057cff269ddacf52bd4dda36017fe922cbf3e0a98850f26cb9c4e7990e87e6097f2f9dd94a20c6cc11f95d57652a516e99a5ccafc2 + languageName: node + linkType: hard + "@whatwg-node/promise-helpers@npm:^1.0.0, @whatwg-node/promise-helpers@npm:^1.2.1, @whatwg-node/promise-helpers@npm:^1.2.4, @whatwg-node/promise-helpers@npm:^1.3.0, @whatwg-node/promise-helpers@npm:^1.3.2": version: 1.3.2 resolution: "@whatwg-node/promise-helpers@npm:1.3.2" @@ -7910,6 +8105,19 @@ __metadata: languageName: node linkType: hard +"@whatwg-node/server@npm:^0.10.0": + version: 0.10.18 + resolution: "@whatwg-node/server@npm:0.10.18" + dependencies: + "@envelop/instrumentation": "npm:^1.0.0" + "@whatwg-node/disposablestack": "npm:^0.0.6" + "@whatwg-node/fetch": "npm:^0.10.13" + "@whatwg-node/promise-helpers": "npm:^1.3.2" + tslib: "npm:^2.6.3" + checksum: 10c0/794c4776c8cb432d2f607f5a36392bec59649bbee6aa116b52866555e4852f00378e92d80702c1aa67cf32abe147b3eb96a999b7352237d64126b6e7e33acc6c + languageName: node + linkType: hard + "@xobotyi/scrollbar-width@npm:^1.9.5": version: 1.9.5 resolution: "@xobotyi/scrollbar-width@npm:1.9.5" @@ -7964,6 +8172,15 @@ __metadata: languageName: node linkType: hard +"acorn-import-attributes@npm:^1.9.5": + version: 1.9.5 + resolution: "acorn-import-attributes@npm:1.9.5" + peerDependencies: + acorn: ^8 + checksum: 10c0/5926eaaead2326d5a86f322ff1b617b0f698aa61dc719a5baa0e9d955c9885cc71febac3fb5bacff71bbf2c4f9c12db2056883c68c53eb962c048b952e1e013d + languageName: node + linkType: hard + "acorn-jsx@npm:^5.3.2": version: 5.3.2 resolution: "acorn-jsx@npm:5.3.2" @@ -7973,7 +8190,7 @@ __metadata: languageName: node linkType: hard -"acorn@npm:^8.9.0": +"acorn@npm:^8.14.0, acorn@npm:^8.9.0": version: 8.15.0 resolution: "acorn@npm:8.15.0" bin: @@ -8069,6 +8286,13 @@ __metadata: languageName: node linkType: hard +"ansis@npm:^4.1.0": + version: 4.2.0 + resolution: "ansis@npm:4.2.0" + checksum: 10c0/cd6a7a681ecd36e72e0d79c1e34f1f3bcb1b15bcbb6f0f8969b4228062d3bfebbef468e09771b00d93b2294370b34f707599d4a113542a876de26823b795b5d2 + languageName: node + linkType: hard + "anymatch@npm:^3.1.3, anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -8730,6 +8954,13 @@ __metadata: languageName: node linkType: hard +"callsite@npm:^1.0.0": + version: 1.0.0 + resolution: "callsite@npm:1.0.0" + checksum: 10c0/8b23d5ed879984b66fe3da381994d6c4b741e561226abc48b40c99c4896f7125db395ea4aa989071a7eb0712c3f83bc32fb1e798fdf54967acdf4af176e48572 + languageName: node + linkType: hard + "callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" @@ -8913,7 +9144,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^4.0.3": +"chokidar@npm:^4.0.1, chokidar@npm:^4.0.3": version: 4.0.3 resolution: "chokidar@npm:4.0.3" dependencies: @@ -8941,6 +9172,13 @@ __metadata: languageName: node linkType: hard +"cjs-module-lexer@npm:^1.2.2": + version: 1.4.3 + resolution: "cjs-module-lexer@npm:1.4.3" + checksum: 10c0/076b3af85adc4d65dbdab1b5b240fe5b45d44fcf0ef9d429044dd94d19be5589376805c44fb2d4b3e684e5fe6a9b7cf3e426476a6507c45283c5fc6ff95240be + languageName: node + linkType: hard + "clean-stack@npm:^2.0.0": version: 2.2.0 resolution: "clean-stack@npm:2.2.0" @@ -9402,7 +9640,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4.4.3": +"debug@npm:4.4.3, debug@npm:^4.3.5": version: 4.4.3 resolution: "debug@npm:4.4.3" dependencies: @@ -9435,6 +9673,15 @@ __metadata: languageName: node linkType: hard +"decache@npm:^4.6.2": + version: 4.6.2 + resolution: "decache@npm:4.6.2" + dependencies: + callsite: "npm:^1.0.0" + checksum: 10c0/7a27260a0bfc51b913db4956e8fe596d72151c0d4cb437daa30787950c274b3fa5c81235a334742b1e32f87ee55d7eb2a0d960ecdadf3583ef23b8f796aebad3 + languageName: node + linkType: hard + "decamelize-keys@npm:^1.1.0": version: 1.1.1 resolution: "decamelize-keys@npm:1.1.1" @@ -9590,6 +9837,13 @@ __metadata: languageName: node linkType: hard +"dettle@npm:^1.0.5": + version: 1.0.5 + resolution: "dettle@npm:1.0.5" + checksum: 10c0/015faadbbaf30c0f63386f0a5dd1fea5b32c3e34bc3c269146866bc34d4ce7d9eae0568a368971a40e2e247322a76b4f310fa42c01be31b7d1f6d889afc3a327 + languageName: node + linkType: hard + "dijkstrajs@npm:^1.0.1": version: 1.0.3 resolution: "dijkstrajs@npm:1.0.3" @@ -9672,6 +9926,15 @@ __metadata: languageName: node linkType: hard +"dot-prop@npm:9.0.0": + version: 9.0.0 + resolution: "dot-prop@npm:9.0.0" + dependencies: + type-fest: "npm:^4.18.2" + checksum: 10c0/4bac49a2f559156811862ac92813906f70529c50da918eaab81b38dd869743c667d578e183607f5ae11e8ae2a02e43e98e32c8a37bc4cae76b04d5b576e3112f + languageName: node + linkType: hard + "dotenv-cli@npm:^10.0.0": version: 10.0.0 resolution: "dotenv-cli@npm:10.0.0" @@ -9825,6 +10088,13 @@ __metadata: languageName: node linkType: hard +"empathic@npm:^2.0.0": + version: 2.0.0 + resolution: "empathic@npm:2.0.0" + checksum: 10c0/7d3b14b04a93b35c47bcc950467ec914fd241cd9acc0269b0ea160f13026ec110f520c90fae64720fde72cc1757b57f3f292fb606617b7fccac1f4d008a76506 + languageName: node + linkType: hard + "encode-utf8@npm:^1.0.3": version: 1.0.3 resolution: "encode-utf8@npm:1.0.3" @@ -9894,6 +10164,13 @@ __metadata: languageName: node linkType: hard +"env-paths@npm:^3.0.0": + version: 3.0.0 + resolution: "env-paths@npm:3.0.0" + checksum: 10c0/76dec878cee47f841103bacd7fae03283af16f0702dad65102ef0a556f310b98a377885e0f32943831eb08b5ab37842a323d02529f3dfd5d0a40ca71b01b435f + languageName: node + linkType: hard + "err-code@npm:^2.0.2": version: 2.0.3 resolution: "err-code@npm:2.0.3" @@ -11112,6 +11389,7 @@ __metadata: "@graphql-codegen/typescript-operations": "npm:^5.0.2" "@kleros/kleros-app": "npm:^2.3.0" "@kleros/ui-components-library": "npm:^3.7.0" + "@netlify/blobs": "npm:^10.6.0" "@reown/appkit": "npm:^1.7.11" "@reown/appkit-adapter-wagmi": "npm:^1.7.11" "@svgr/webpack": "npm:^8.1.0" @@ -11150,6 +11428,7 @@ __metadata: react-jdenticon: "npm:^1.4.0" react-toastify: "npm:^11.1.0" react-use: "npm:^17.6.0" + swiper: "npm:^12.0.2" tailwindcss: "npm:^4.1.4" typescript: "npm:^5" viem: "npm:^2.31.4" @@ -11728,6 +12007,15 @@ __metadata: languageName: node linkType: hard +"image-size@npm:^2.0.2": + version: 2.0.2 + resolution: "image-size@npm:2.0.2" + bin: + image-size: bin/image-size.js + checksum: 10c0/f09dd0f7cf8511cd20e4f756bdb5a7cb6d2240de3323f41bde266bed8373392a293892bf12e907e2995f52833fd88dd27cf6b1a52ab93968afc716cb78cd7b79 + languageName: node + linkType: hard + "immutable@npm:~3.7.6": version: 3.7.6 resolution: "immutable@npm:3.7.6" @@ -11752,6 +12040,18 @@ __metadata: languageName: node linkType: hard +"import-in-the-middle@npm:^1.8.1": + version: 1.15.0 + resolution: "import-in-the-middle@npm:1.15.0" + dependencies: + acorn: "npm:^8.14.0" + acorn-import-attributes: "npm:^1.9.5" + cjs-module-lexer: "npm:^1.2.2" + module-details-from-path: "npm:^1.0.3" + checksum: 10c0/43d4efbe75a89c04343fd052ca5d2193adc0e2df93325e50d8b32c31403b2f089a5e2b6e47f4e5413bc4058b9781aaaf61bfe3f0e5e6d7f9487eb112fd095e0d + languageName: node + linkType: hard + "imurmurhash@npm:^0.1.4": version: 0.1.4 resolution: "imurmurhash@npm:0.1.4" @@ -12494,6 +12794,13 @@ __metadata: languageName: node linkType: hard +"jpeg-js@npm:^0.4.2": + version: 0.4.4 + resolution: "jpeg-js@npm:0.4.4" + checksum: 10c0/4d0d5097f8e55d8bbce6f1dc32ffaf3f43f321f6222e4e6490734fdc6d005322e3bd6fb992c2df7f5b587343b1441a1c333281dc3285bc9116e369fd2a2b43a7 + languageName: node + linkType: hard + "js-cookie@npm:^2.2.1": version: 2.2.1 resolution: "js-cookie@npm:2.2.1" @@ -12501,6 +12808,15 @@ __metadata: languageName: node linkType: hard +"js-image-generator@npm:^1.0.4": + version: 1.0.4 + resolution: "js-image-generator@npm:1.0.4" + dependencies: + jpeg-js: "npm:^0.4.2" + checksum: 10c0/1fc64fa8aab0cecbb13ac312fd152f1fcf5dc6fa1abc7cb6bc8708adfe4de05516eda687f01d0ccbf05351b314783e0fd7ce24269dac27e0392c3014cfef7fcb + languageName: node + linkType: hard + "js-sha3@npm:0.8.0, js-sha3@npm:^0.8.0": version: 0.8.0 resolution: "js-sha3@npm:0.8.0" @@ -13449,6 +13765,13 @@ __metadata: languageName: node linkType: hard +"module-details-from-path@npm:^1.0.3": + version: 1.0.4 + resolution: "module-details-from-path@npm:1.0.4" + checksum: 10c0/10863413e96dab07dee917eae07afe46f7bf853065cc75a7d2a718adf67574857fb64f8a2c0c9af12ac733a9a8cf652db7ed39b95f7a355d08106cb9cc50c83b + languageName: node + linkType: hard + "moment@npm:^2.19.3": version: 2.30.1 resolution: "moment@npm:2.30.1" @@ -14244,6 +14567,13 @@ __metadata: languageName: node linkType: hard +"parse-gitignore@npm:^2.0.0": + version: 2.0.0 + resolution: "parse-gitignore@npm:2.0.0" + checksum: 10c0/d42d8512ad1737fbe47bd1ecdd685bb08efb777136cafcf1344eba9fd3104f79c14e9d3d1b313427b900250d99827ef124e0dc06ff6e9883b2d3617e56b2cbbc + languageName: node + linkType: hard + "parse-json@npm:^5.0.0, parse-json@npm:^5.2.0": version: 5.2.0 resolution: "parse-json@npm:5.2.0" @@ -15218,6 +15548,17 @@ __metadata: languageName: node linkType: hard +"require-in-the-middle@npm:^7.1.1": + version: 7.5.2 + resolution: "require-in-the-middle@npm:7.5.2" + dependencies: + debug: "npm:^4.3.5" + module-details-from-path: "npm:^1.0.3" + resolve: "npm:^1.22.8" + checksum: 10c0/43a2dac5520e39d13c413650895715e102d6802e6cc6ff322017bd948f12a9657fe28435f7cbbcba437b167f02e192ac7af29fa35cabd5d0c375d071c0605e01 + languageName: node + linkType: hard + "require-main-filename@npm:^2.0.0": version: 2.0.0 resolution: "require-main-filename@npm:2.0.0" @@ -15266,6 +15607,19 @@ __metadata: languageName: node linkType: hard +"resolve@npm:^1.22.8": + version: 1.22.11 + resolution: "resolve@npm:1.22.11" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/f657191507530f2cbecb5815b1ee99b20741ea6ee02a59c57028e9ec4c2c8d7681afcc35febbd554ac0ded459db6f2d8153382c53a2f266cee2575e512674409 + languageName: node + linkType: hard + "resolve@npm:^2.0.0-next.5": version: 2.0.0-next.5 resolution: "resolve@npm:2.0.0-next.5" @@ -15292,6 +15646,19 @@ __metadata: languageName: node linkType: hard +"resolve@patch:resolve@npm%3A^1.22.8#optional!builtin": + version: 1.22.11 + resolution: "resolve@patch:resolve@npm%3A1.22.11#optional!builtin::version=1.22.11&hash=c3c19d" + dependencies: + is-core-module: "npm:^2.16.1" + path-parse: "npm:^1.0.7" + supports-preserve-symlinks-flag: "npm:^1.0.0" + bin: + resolve: bin/resolve + checksum: 10c0/ee5b182f2e37cb1165465e58c6abc797fec0a80b5ba3231607beb4677db0c9291ac010c47cf092b6daa2b7f518d69a0e21888e7e2b633f68d501a874212a8c63 + languageName: node + linkType: hard + "resolve@patch:resolve@npm%3A^2.0.0-next.5#optional!builtin": version: 2.0.0-next.5 resolution: "resolve@patch:resolve@npm%3A2.0.0-next.5#optional!builtin::version=2.0.0-next.5&hash=c3c19d" @@ -15532,6 +15899,15 @@ __metadata: languageName: node linkType: hard +"semver@npm:^7.5.2, semver@npm:^7.7.2": + version: 7.7.4 + resolution: "semver@npm:7.7.4" + bin: + semver: bin/semver.js + checksum: 10c0/5215ad0234e2845d4ea5bb9d836d42b03499546ddafb12075566899fc617f68794bb6f146076b6881d755de17d6c6cc73372555879ec7dce2c2feee947866ad2 + languageName: node + linkType: hard + "sentence-case@npm:^3.0.4": version: 3.0.4 resolution: "sentence-case@npm:3.0.4" @@ -16285,6 +16661,13 @@ __metadata: languageName: node linkType: hard +"swiper@npm:^12.0.2": + version: 12.0.2 + resolution: "swiper@npm:12.0.2" + checksum: 10c0/e7826eb5973828c622a30f321dc7740300a32666d26810b381e2498ad60f2f84e070058a1aa0ee32acce8a6d31f2d2919c388b2e5921db45690c59af41b1c7b8 + languageName: node + linkType: hard + "sync-fetch@npm:0.6.0-2": version: 0.6.0-2 resolution: "sync-fetch@npm:0.6.0-2" @@ -16429,6 +16812,22 @@ __metadata: languageName: node linkType: hard +"tmp-promise@npm:^3.0.3": + version: 3.0.3 + resolution: "tmp-promise@npm:3.0.3" + dependencies: + tmp: "npm:^0.2.0" + checksum: 10c0/23b47dcb2e82b14bbd8f61ed7a9d9353cdb6a6f09d7716616cfd27d0087040cd40152965a518e598d7aabe1489b9569bf1eebde0c5fadeaf3ec8098adcebea4e + languageName: node + linkType: hard + +"tmp@npm:^0.2.0": + version: 0.2.5 + resolution: "tmp@npm:0.2.5" + checksum: 10c0/cee5bb7d674bb4ba3ab3f3841c2ca7e46daeb2109eec395c1ec7329a91d52fcb21032b79ac25161a37b2565c4858fefab927af9735926a113ef7bac9091a6e0e + languageName: node + linkType: hard + "to-regex-range@npm:^5.0.1": version: 5.0.1 resolution: "to-regex-range@npm:5.0.1" @@ -16573,6 +16972,13 @@ __metadata: languageName: node linkType: hard +"type-fest@npm:^4.18.2": + version: 4.41.0 + resolution: "type-fest@npm:4.41.0" + checksum: 10c0/f5ca697797ed5e88d33ac8f1fec21921839871f808dc59345c9cf67345bfb958ce41bd821165dbf3ae591cedec2bf6fe8882098dfdd8dc54320b859711a2c1e4 + languageName: node + linkType: hard + "type@npm:^2.7.2": version: 2.7.3 resolution: "type@npm:2.7.3" @@ -17069,6 +17475,15 @@ __metadata: languageName: node linkType: hard +"uuid@npm:^13.0.0": + version: 13.0.0 + resolution: "uuid@npm:13.0.0" + bin: + uuid: dist-node/bin/uuid + checksum: 10c0/950e4c18d57fef6c69675344f5700a08af21e26b9eff2bf2180427564297368c538ea11ac9fb2e6528b17fc3966a9fd2c5049361b0b63c7d654f3c550c9b3d67 + languageName: node + linkType: hard + "uuid@npm:^8.3.2": version: 8.3.2 resolution: "uuid@npm:8.3.2" @@ -17403,6 +17818,16 @@ __metadata: languageName: node linkType: hard +"write-file-atomic@npm:^5.0.1": + version: 5.0.1 + resolution: "write-file-atomic@npm:5.0.1" + dependencies: + imurmurhash: "npm:^0.1.4" + signal-exit: "npm:^4.0.1" + checksum: 10c0/e8c850a8e3e74eeadadb8ad23c9d9d63e4e792bd10f4836ed74189ef6e996763959f1249c5650e232f3c77c11169d239cbfc8342fc70f3fe401407d23810505d + languageName: node + linkType: hard + "ws@npm:8.18.0": version: 8.18.0 resolution: "ws@npm:8.18.0"