From 683996801ce68b316ab4448da2f12643b392e20f Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Thu, 19 Mar 2026 13:28:48 +0000 Subject: [PATCH 1/2] [#352] Add Trading History section on reader dashboard - Add user_address column to trade_history (migration 00019) - Update trade indexer (POST + cron) to save user address from Mint/Burn event args - Add Trading History section above Donation History on reader dashboard with Buy/Sell labels, reserve amount, Basescan tx links, and Load more pagination (10 per batch) Co-Authored-By: Claude Opus 4.6 (1M context) --- lib/supabase.ts | 4 + src/app/api/cron/trade-history/route.ts | 1 + src/app/api/index/trade/route.ts | 2 + src/app/dashboard/reader/page.tsx | 101 +++++++++++++++++- .../00019_trade_history_user_address.sql | 6 ++ 5 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 supabase/migrations/00019_trade_history_user_address.sql diff --git a/lib/supabase.ts b/lib/supabase.ts index ed1a4811..8a7e7d62 100644 --- a/lib/supabase.ts +++ b/lib/supabase.ts @@ -357,6 +357,7 @@ export interface Database { tx_hash: string; log_index: number; contract_address: string; + user_address: string | null; }; Insert: { id?: never; @@ -371,6 +372,7 @@ export interface Database { tx_hash: string; log_index: number; contract_address: string; + user_address?: string | null; }; Update: { id?: never; @@ -385,6 +387,7 @@ export interface Database { tx_hash?: string; log_index?: number; contract_address?: string; + user_address?: string | null; }; Relationships: []; }; @@ -413,3 +416,4 @@ export type Plot = Database["public"]["Tables"]["plots"]["Row"]; export type Donation = Database["public"]["Tables"]["donations"]["Row"]; export type Rating = Database["public"]["Tables"]["ratings"]["Row"]; export type Comment = Database["public"]["Tables"]["comments"]["Row"]; +export type TradeHistory = Database["public"]["Tables"]["trade_history"]["Row"]; diff --git a/src/app/api/cron/trade-history/route.ts b/src/app/api/cron/trade-history/route.ts index 402b6877..7516bc44 100644 --- a/src/app/api/cron/trade-history/route.ts +++ b/src/app/api/cron/trade-history/route.ts @@ -215,6 +215,7 @@ async function processTradeEvent( tx_hash: log.transactionHash!.toLowerCase(), log_index: log.logIndex!, contract_address: MCV2_BOND.toLowerCase(), + user_address: args.user.toLowerCase(), }; const { error } = await supabase diff --git a/src/app/api/index/trade/route.ts b/src/app/api/index/trade/route.ts index 011e5d19..a211d446 100644 --- a/src/app/api/index/trade/route.ts +++ b/src/app/api/index/trade/route.ts @@ -65,6 +65,7 @@ export async function POST(req: Request) { const args = decoded.args as { token: `0x${string}`; + user: `0x${string}`; amountMinted?: bigint; amountBurned?: bigint; reserveAmount?: bigint; @@ -107,6 +108,7 @@ export async function POST(req: Request) { tx_hash: txHash.toLowerCase(), log_index: log.logIndex!, contract_address: MCV2_BOND.toLowerCase(), + user_address: args.user.toLowerCase(), }; const { error: dbError } = await supabase diff --git a/src/app/dashboard/reader/page.tsx b/src/app/dashboard/reader/page.tsx index a76f4a83..b79cc847 100644 --- a/src/app/dashboard/reader/page.tsx +++ b/src/app/dashboard/reader/page.tsx @@ -3,7 +3,8 @@ import { useEffect, useState } from "react"; import { useAccount } from "wagmi"; import { useQuery } from "@tanstack/react-query"; -import { supabase, type Donation } from "../../../../lib/supabase"; +import { supabase, type Donation, type TradeHistory } from "../../../../lib/supabase"; +import { formatPrice } from "../../../../lib/format"; import { ReaderPortfolio } from "../../../components/ReaderPortfolio"; import { WriterIdentityClient } from "../../../components/WriterIdentityClient"; import { formatUnits } from "viem"; @@ -106,6 +107,9 @@ export default function ReaderDashboard() { + {/* --- Trading History --- */} + + {/* --- Donation History --- */}

@@ -188,3 +192,98 @@ function DonationRow({ donation, decimals }: { donation: Donation; decimals: num ); } + +const TRADE_PAGE_SIZE = 10; + +function TradingHistory({ address }: { address: string }) { + const [limit, setLimit] = useState(TRADE_PAGE_SIZE); + const [prevAddress, setPrevAddress] = useState(address); + if (address !== prevAddress) { + setPrevAddress(address); + setLimit(TRADE_PAGE_SIZE); + } + + const { data, isLoading } = useQuery({ + queryKey: ["reader-trades", address, limit], + queryFn: async () => { + if (!supabase) return { rows: [], totalCount: 0 }; + const { data: rows, count } = await supabase + .from("trade_history") + .select("*", { count: "exact" }) + .eq("user_address", address.toLowerCase()) + .order("block_timestamp", { ascending: false }) + .range(0, limit - 1) + .returns(); + return { rows: rows ?? [], totalCount: count ?? 0 }; + }, + }); + + const trades = data?.rows ?? []; + const totalCount = data?.totalCount ?? 0; + const hasMore = trades.length < totalCount; + + return ( +
+

Trading History

+

+ {totalCount} {totalCount === 1 ? "trade" : "trades"} +

+ + {isLoading &&

Loading...

} + +
+ {trades.map((t) => ( +
+
+ + {t.event_type === "mint" ? "Buy" : "Sell"} + + Story #{t.storyline_id} + {t.block_timestamp && ( + + )} +
+
+ + {formatPrice(t.reserve_amount)} {RESERVE_LABEL} + + {t.tx_hash && ( + + ↗ + + )} +
+
+ ))} + {!isLoading && trades.length === 0 && ( +

+ No trades yet. +

+ )} +
+ + {hasMore && ( + + )} +
+ ); +} diff --git a/supabase/migrations/00019_trade_history_user_address.sql b/supabase/migrations/00019_trade_history_user_address.sql new file mode 100644 index 00000000..eeb234ea --- /dev/null +++ b/supabase/migrations/00019_trade_history_user_address.sql @@ -0,0 +1,6 @@ +-- Add user_address column to trade_history for filtering by trader +ALTER TABLE trade_history ADD COLUMN IF NOT EXISTS user_address text; + +-- Index for reader dashboard queries +CREATE INDEX IF NOT EXISTS idx_trade_history_user_address + ON trade_history (user_address, block_timestamp DESC); From e4d93bf966a018422a34e8f644fc60bbb105e333 Mon Sep 17 00:00:00 2001 From: Cho Young-Hwi Date: Thu, 19 Mar 2026 13:30:47 +0000 Subject: [PATCH 2/2] [#352] Add storyline link and token amount to trading history rows Address T2a review: Story #{id} is now a clickable link to the story page. Token amount (tokens traded) computed from reserve_amount / price_per_token and displayed alongside the reserve amount. Co-Authored-By: Claude Opus 4.6 (1M context) --- src/app/dashboard/reader/page.tsx | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/src/app/dashboard/reader/page.tsx b/src/app/dashboard/reader/page.tsx index b79cc847..69cea98c 100644 --- a/src/app/dashboard/reader/page.tsx +++ b/src/app/dashboard/reader/page.tsx @@ -6,6 +6,7 @@ import { useQuery } from "@tanstack/react-query"; import { supabase, type Donation, type TradeHistory } from "../../../../lib/supabase"; import { formatPrice } from "../../../../lib/format"; import { ReaderPortfolio } from "../../../components/ReaderPortfolio"; +import Link from "next/link"; import { WriterIdentityClient } from "../../../components/WriterIdentityClient"; import { formatUnits } from "viem"; import { ConnectWallet } from "../../../components/ConnectWallet"; @@ -241,7 +242,12 @@ function TradingHistory({ address }: { address: string }) { {t.event_type === "mint" ? "Buy" : "Sell"} - Story #{t.storyline_id} + + Story #{t.storyline_id} + {t.block_timestamp && (