From 03c43d7f11e28b4da40896488bf877b384d49763 Mon Sep 17 00:00:00 2001 From: neumattock <152253273+newmattock@users.noreply.github.com> Date: Tue, 26 May 2026 14:18:19 -0700 Subject: [PATCH] feat: add oracle verification badge --- app/governance/page.tsx | 16 +++++--- app/profile/[address]/page.tsx | 7 +++- src/components/LPDashboard.tsx | 40 ++++++++++--------- src/components/PayerIdentity.tsx | 23 +++++++++++ src/components/TokenSelector.tsx | 4 +- src/components/VerificationBadge.tsx | 38 ++++++++++++++++++ .../__tests__/VerificationBadge.test.tsx | 38 ++++++++++++++++++ .../__tests__/oracleVerification.test.ts | 22 ++++++++++ src/utils/federation.ts | 3 +- src/utils/oracleVerification.ts | 19 +++++++++ 10 files changed, 182 insertions(+), 28 deletions(-) create mode 100644 src/components/PayerIdentity.tsx create mode 100644 src/components/VerificationBadge.tsx create mode 100644 src/components/__tests__/VerificationBadge.test.tsx create mode 100644 src/utils/__tests__/oracleVerification.test.ts create mode 100644 src/utils/oracleVerification.ts diff --git a/app/governance/page.tsx b/app/governance/page.tsx index 6573981..a742347 100644 --- a/app/governance/page.tsx +++ b/app/governance/page.tsx @@ -29,6 +29,7 @@ function StatusBadge({ status }: { status: ProposalStatus }) { Failed: { color: "bg-red-500/15 text-red-500 border-red-500/30", icon: "cancel" }, Executed: { color: "bg-purple-500/15 text-purple-500 border-purple-500/30", icon: "rocket_launch" }, Pending: { color: "bg-amber-500/15 text-amber-500 border-amber-500/30", icon: "schedule" }, + Vetoed: { color: "bg-red-500/15 text-red-500 border-red-500/30", icon: "gavel" }, }; const { color, icon } = config[status]; return ( @@ -194,9 +195,10 @@ export default function GovernancePage() { const [votingPower, setVotingPower] = useState(0); const load = useCallback(async () => { - const data = await fetchProposals(); - setProposals(data); - setLoading(false); + fetchProposals().then((data) => { + setProposals(data); + setLoading(false); + }); }, []); useEffect(() => { @@ -208,10 +210,14 @@ export default function GovernancePage() { useEffect(() => { if (!isConnected || !address) { - setVotingPower(0); + Promise.resolve().then(() => { + setVotingPower(0); + }); return; } - getVotingPower(address).then(setVotingPower); + getVotingPower(address).then((power) => { + setVotingPower(power); + }); }, [address, isConnected]); const sorted = useMemo( diff --git a/app/profile/[address]/page.tsx b/app/profile/[address]/page.tsx index 41d6392..35fca7e 100644 --- a/app/profile/[address]/page.tsx +++ b/app/profile/[address]/page.tsx @@ -12,8 +12,10 @@ import { } from "@/utils/soroban"; import { resolveFederatedAddress } from "@/utils/federation"; import { formatDate } from "@/utils/format"; +import { isOracleVerifiedAddress } from "@/utils/oracleVerification"; import ProfileActivityChart from "@/components/ProfileActivityChart"; import ProfileRecentInvoices from "@/components/ProfileRecentInvoices"; +import VerificationBadge from "@/components/VerificationBadge"; interface ScoreHistoryPoint { period: string; @@ -151,7 +153,10 @@ export default function ProfilePage() {
Public reputation profile
-{address}
{resolvedAddress !== address
diff --git a/src/components/LPDashboard.tsx b/src/components/LPDashboard.tsx
index 9e7497f..d857dbc 100644
--- a/src/components/LPDashboard.tsx
+++ b/src/components/LPDashboard.tsx
@@ -32,9 +32,11 @@ import LastUpdated from "./LastUpdated";
import InvoiceStatusBadge from "./InvoiceStatusBadge";
import FundConfirmModal from "./FundConfirmModal";
import type { DataTableColumn } from "./DataTable";
+import PayerIdentity from "./PayerIdentity";
type Tab = "discovery" | "my-funded" | "watchlist";
+type DisplayInvoice = Invoice & { watchAddedAt?: number };
@@ -76,8 +78,9 @@ export default function LPDashboard() {
} else {
addToast({ type: "success", title: "Removed from Watchlist" });
}
- } catch (error: any) {
- addToast({ type: "error", title: "Watchlist Error", message: error.message });
+ } catch (error) {
+ const message = error instanceof Error ? error.message : "Unable to update watchlist.";
+ addToast({ type: "error", title: "Watchlist Error", message });
}
};
@@ -114,7 +117,7 @@ export default function LPDashboard() {
useEffect(() => {
if (!selectedInvoice || !address) return;
- void refreshAllowance(selectedInvoice, address);
+ void Promise.resolve().then(() => refreshAllowance(selectedInvoice, address));
}, [address, refreshAllowance, selectedInvoice]);
const toggleInvoiceSelection = (id: string) => {
@@ -180,7 +183,7 @@ export default function LPDashboard() {
);
- const sortedInvoices = useMemo(() => [...filteredInvoices].sort((a: any, b: any) => {
+ const sortedInvoices = useMemo(() => [...filteredInvoices].sort((a, b) => {
if (sortKey === "risk") {
const ra = RISK_SORT_ORDER[payerRisks.get(a.payer) ?? "Unknown"];
const rb = RISK_SORT_ORDER[payerRisks.get(b.payer) ?? "Unknown"];
@@ -195,6 +198,9 @@ export default function LPDashboard() {
}
const aVal = a[sortKey];
const bVal = b[sortKey];
+ if (aVal == null && bVal == null) return 0;
+ if (aVal == null) return sortOrder === "asc" ? -1 : 1;
+ if (bVal == null) return sortOrder === "asc" ? 1 : -1;
if (aVal < bVal) return sortOrder === "asc" ? -1 : 1;
if (aVal > bVal) return sortOrder === "asc" ? 1 : -1;
return 0;
@@ -219,7 +225,7 @@ export default function LPDashboard() {
}
};
- const handleKeyDown = (e: React.KeyboardEvent
{formatAddress(invoice.freelancer)}
-
+
{t("lpDashboard.tableHeaders.payer")}:{" "}
-
- {formatAddress(invoice.payer)}
-
+
@@ -600,7 +602,7 @@ export default function LPDashboard() {
{activeTab === "watchlist" && (
- {new Date(invoice.watchAddedAt).toLocaleDateString()}
+ {new Date(invoice.watchAddedAt ?? 0).toLocaleDateString()}
)}
{activeTab === "discovery" && (
diff --git a/src/components/PayerIdentity.tsx b/src/components/PayerIdentity.tsx
new file mode 100644
index 0000000..e6ddf17
--- /dev/null
+++ b/src/components/PayerIdentity.tsx
@@ -0,0 +1,23 @@
+"use client";
+
+import Link from "next/link";
+import { formatAddress } from "@/utils/format";
+import { isOracleVerifiedAddress } from "@/utils/oracleVerification";
+import VerificationBadge from "./VerificationBadge";
+
+export default function PayerIdentity({
+ address,
+ className = "",
+}: {
+ address: string;
+ className?: string;
+}) {
+ return (
+
+
+ {formatAddress(address)}
+
+