This document explains how the "Active Milestones" section on the homepage fetches, transforms, and displays market event data from Supabase.
┌─────────────────────────────────────────────────────────────────────────┐
│ CompaniesPage.jsx │
│ (Homepage / /companies) │
└─────────────────────────────────────────────────────────────────────────┘
│
│ useEffect on mount
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ EventsHighlightDataTransformer.jsx │
│ fetchEventHighlightData("all") │
└─────────────────────────────────────────────────────────────────────────┘
│
┌───────────────┴───────────────┐
↓ ↓
┌───────────────────┐ ┌────────────────────┐
│ getAvailableCompanies()│ │ │
│ Returns: [9, 10, 11]│ │ │
└───────────────────┘ └────────────────────┘
│
│ For each company ID
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ ProposalsPageDataTransformer.jsx │
│ fetchCompanyData(companyId, false) │
│ │
│ ┌────────────────────────────────────────────────────────────────┐ │
│ │ Supabase Query: │ │
│ │ supabase.from('market_event') │ │
│ │ .select('*') │ │
│ │ .eq('company_id', companyId) │ │
│ │ .order('created_at', {ascending: false}) │ │
│ └────────────────────────────────────────────────────────────────┘ │
│ │
│ Returns raw market_event records from Supabase │
└─────────────────────────────────────────────────────────────────────────┘
│
│ Transform data
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ transformCompanyData() │
│ │
│ Maps Supabase fields → Proposal format: │
│ • id → proposalID │
│ • title → proposalTitle │
│ • approval_status → approvalStatus │
│ • created_at → timestamp │
│ • end_date → endTime │
│ • metadata.prediction_pools → predictionPools │
│ • metadata.conditional_pools → poolAddresses │
│ • pool_yes, pool_no → fallback pool addresses │
│ │
│ Calculates: │
│ • impact = ((yesPriceNum - noPriceNum) / maxPrice) * 100 │
│ • eventProbability = (yesPriceNum / (yesPriceNum + noPriceNum)) * 100 │
│ │
│ Optional: Fetches live prices from blockchain if useNewPrices=true │
│ • SDAI rate from contract │
│ • Pool prices via Uniswap V3 / Algebra │
└─────────────────────────────────────────────────────────────────────────┘
│
│ Return to EventsHighlightDataTransformer
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ EventsHighlightDataTransformer (continued) │
│ │
│ For each company's proposals: │
│ 1. Filter active milestones: │
│ • approvalStatus === "ongoing" OR "on_going" OR "pending_review" │
│ • resolution_status !== "resolved" │
│ │
│ 2. Transform to eventHighlight format via createEventHighlight() │
│ │
│ 3. Combine all companies' events │
│ 4. Sort by startTime (most recent first) │
└─────────────────────────────────────────────────────────────────────────┘
│
│ Return array of event highlights
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ EventsHighlightCarousel.jsx │
│ │
│ 1. Receives events array │
│ 2. Filters out "pending_review" (unless debugMode=true) │
│ 3. Maps to Swiper slides │
└─────────────────────────────────────────────────────────────────────────┘
│
│ For each event
↓
┌─────────────────────────────────────────────────────────────────────────┐
│ EventHighlightCard.jsx │
│ │
│ Receives props: │
│ • eventId │
│ • companyLogo │
│ • proposalTitle │
│ • initialStats {yesPrice, noPrice} │
│ • predictionPools {yes: {address, tokenBaseSlot}, no: {...}} │
│ • poolAddresses {yes, no} │
│ • startTime, endTime, timeProgress │
│ • status, resolutionStatus │
│ • metadata (full metadata from Supabase) │
│ │
│ Card responsibilities: │
│ • Display initial data │
│ • Fetch live prices from pools (using predictionPools) │
│ • Update countdown timer │
│ • Recalculate impact/probability with live data │
└─────────────────────────────────────────────────────────────────────────┘
File: src/components/futarchyFi/companyList/page/CompaniesPage.jsx
useEffect(() => {
const loadEventHighlights = async () => {
await fetchEventHighlightData("all");
setIsEventsCarouselLoading(false);
};
loadEventHighlights();
}, []);File: src/components/futarchyFi/companyList/page/EventsHighlightDataTransformer.jsx
// When companyId === "all"
const { getAvailableCompanies } = await import("...");
const availableCompanyIds = await getAvailableCompanies();
// Returns: [9, 10, 11] (Gnosis, Kleros, Tesla)Supabase Query:
supabase
.from('market_event')
.select('company_id')
.order('company_id')
// Returns distinct company_id valuesFile: src/components/futarchyFi/proposalsList/page/proposalsPage/ProposalsPageDataTransformer.jsx
For each company ID, fetch all market events:
const { data, error } = await supabase
.from('market_event')
.select('*')
.eq('company_id', effectiveCompanyId)
.order('created_at', { ascending: false });Supabase Table Schema (market_event):
{
id: number,
company_id: number,
title: string,
approval_status: "ongoing" | "on_going" | "pending_review" | "approved" | "refused",
resolution_status: "unresolved" | "resolved",
created_at: ISO timestamp,
end_date: ISO timestamp,
pool_yes: address,
pool_no: address,
condition_id: string,
question_id: string,
tokens: object,
metadata: {
prediction_pools: {
yes: { address, tokenBaseSlot },
no: { address, tokenBaseSlot }
},
conditional_pools: {
yes: { address, tokenCompanySlot },
no: { address, tokenCompanySlot }
},
token_images: {
company: url,
currency: url
}
},
proposal_markdown: string,
tags: string[]
}File: src/components/futarchyFi/proposalsList/page/proposalsPage/ProposalsPageDataTransformer.jsx
const companyData = {
company_id: effectiveCompanyId,
name: "Gnosis DAO", // Hardcoded based on ID
logo: "/assets/gnosis-dao-logo.png",
currency_token: "GNO",
proposals: data.map(event => ({
proposal_id: event.id,
proposal_title: event.title,
approval_status: event.approval_status,
timestamp: convertToTimestamp(event.created_at),
end_time: convertToTimestamp(event.end_date),
prices: {
approval: `${event.approval_price} SDAI`,
refusal: `${event.refusal_price} SDAI`
},
metadata: event.metadata,
pool_yes: event.pool_yes,
pool_no: event.pool_no,
predictionPools: event.metadata?.prediction_pools
}))
};Then calls transformCompanyData() which:
-
Parses timestamps:
endTimeDate = new Date(proposal.end_date); timestamp = new Date(proposal.created_at);
-
Extracts pool configurations:
predictionPools = { yes: { address: metadata.prediction_pools.yes.address, tokenBaseSlot: metadata.prediction_pools.yes.token_slot }, no: { /* same structure */ } }; poolAddresses = { yes: metadata.conditional_pools?.yes?.address || pool_yes, no: metadata.conditional_pools?.no?.address || pool_no };
-
Fetches live prices (if useNewPrices=true):
// Fetch SDAI rate const sdaiRate = await fetchSdaiRate(provider); // Fetch pool prices via Uniswap V3 const v3Yes = await fetchUniswapV3Price( predictionPools.yes.address, predictionPools.yes.tokenBaseSlot, provider );
-
Calculates metrics:
// Impact const maxPrice = Math.max(yesPriceNum, noPriceNum); impact = ((yesPriceNum - noPriceNum) / maxPrice) * 100; // Event probability eventProbability = (yesPriceNum / (yesPriceNum + noPriceNum)) * 100;
File: src/components/futarchyFi/companyList/page/EventsHighlightDataTransformer.jsx
const activeProposals = companyData.proposals.filter(p =>
(p.approvalStatus === "ongoing" ||
p.approvalStatus === "on_going" ||
p.approvalStatus === "pending_review") &&
p.resolution_status !== "resolved"
);File: src/components/futarchyFi/companyList/page/EventsHighlightDataTransformer.jsx
const eventData = {
eventId: proposal.proposalID,
eventTitle: proposal.proposalTitle,
companyLogo: companyData.logo,
authorName: companyData.name,
stats: {
yesPrice: proposal.prices?.approval,
noPrice: proposal.prices?.refusal
},
predictionPools: proposal.predictionPools,
poolAddresses: {
yes: proposal.metadata?.conditional_pools?.yes?.address || proposal.pool_yes,
no: proposal.metadata?.conditional_pools?.no?.address || proposal.pool_no
},
startTime: proposal.timestamp,
endTime: proposal.endTime,
timeProgress: calculateTimeProgress(proposal.timestamp, proposal.endTime),
status: proposal.approvalStatus,
resolutionStatus: proposal.resolution_status,
metadata: proposal.metadata,
companyId: companyId
};File: src/components/futarchyFi/companyList/components/EventsHighlightCarousel.jsx
// Filter out pending_review unless debug mode
const newFilteredEvents = debugMode
? events
: events.filter(event => event.status !== 'pending_review');
// Map to Swiper slides
{filteredEvents.map((event, index) => (
<SwiperSlide key={index}>
<EventHighlightCard
eventId={event.eventId}
companyLogo={event.companyLogo}
proposalTitle={event.eventTitle}
initialStats={event.stats}
predictionPools={event.predictionPools}
poolAddresses={event.poolAddresses}
startTime={event.startTime}
endTime={event.endTime}
timeProgress={event.timeProgress}
status={event.status}
metadata={event.metadata}
/>
</SwiperSlide>
))}File: src/components/futarchyFi/companyList/cards/highlightCards/EventHighlightCard.jsx
The card receives the data and:
- Displays initial data from props
- Fetches live prices from blockchain using
predictionPools - Updates countdown timer using
startTimeandendTime - Recalculates metrics (impact, probability) with live prices
- Shows status based on
statusandresolutionStatus
Cards receive pool data from multiple sources with fallback priority:
-
predictionPools- Used for live price fetching- Source:
metadata.prediction_pools - Contains:
{yes: {address, tokenBaseSlot}, no: {...}}
- Source:
-
poolAddresses- Alternative pool addresses- Source:
metadata.conditional_pools→ fallback topool_yes/pool_no - Contains:
{yes: address, no: address}
- Source:
- Initial/Static prices: From Supabase
approval_priceandrefusal_price - Transformer prices: Optionally fetched during transformation if
useNewPrices=true - Card prices: Fetched live by the card component using Algebra/Uniswap V3 pools
NEXT_PUBLIC_SUPABASE_URL- Supabase project URLNEXT_PUBLIC_SUPABASE_ANON_KEY- Supabase anonymous keyNEXT_PUBLIC_DEBUG_MODE- Use mock data instead of SupabaseNEXT_PUBLIC_USE_DEFAULT_MOCK_METADATA- Use default pool metadata when missingNEXT_PUBLIC_ALLOW_MOCK_FALLBACK- Fall back to mock data on errorsNEXT_PUBLIC_RPC_URL- RPC endpoint for blockchain queries
- Parallel fetching: All companies fetched in parallel using
Promise.all() - Price fetching strategy:
- Transformer:
useNewPrices=falseto skip expensive blockchain calls - Card: Fetches own prices asynchronously after render
- Transformer:
- Caching: No caching currently implemented
- Supabase query: Single query per company, ordered by
created_at
- Supabase errors: Falls back to mock data if
NEXT_PUBLIC_ALLOW_MOCK_FALLBACK=true - Price fetch errors: Card continues with initial/static prices
- Missing metadata: Uses fallback pool addresses from root fields (
pool_yes,pool_no) - Invalid timestamps: Regex parsing from proposal title as fallback