From 67c66edd89543be22f6166b562e4f10f494f1df2 Mon Sep 17 00:00:00 2001
From: Stephan-Thomas
Date: Sat, 24 Oct 2026 02:24:27 +0100
Subject: [PATCH] feat(frontend): reflect escrow pause state and yield-pool
coverage across the app
Closes #2098
---
src/app/create/page.tsx | 25 ++-
src/app/dashboard/page.tsx | 52 ++++-
src/app/globals.css | 3 +
src/app/layout.tsx | 13 +-
src/components/PausedBanner.tsx | 25 +++
src/components/ProtocolStatusProvider.tsx | 151 +++++++++++++
src/components/YieldCoverageChip.tsx | 28 +++
.../__tests__/PausedBanner.test.tsx | 171 ++++++++++++++
.../__tests__/ProtocolStatusProvider.test.tsx | 192 ++++++++++++++++
.../__tests__/YieldCoverageChip.test.tsx | 36 +++
.../soroban/__tests__/protocol-status.test.ts | 135 +++++++++++
src/lib/soroban/errors.test.ts | 2 +-
src/lib/soroban/errors.ts | 14 ++
src/lib/soroban/protocol-status.ts | 209 ++++++++++++++++++
src/lib/soroban/tx.ts | 92 ++++++++
15 files changed, 1131 insertions(+), 17 deletions(-)
create mode 100644 src/components/PausedBanner.tsx
create mode 100644 src/components/ProtocolStatusProvider.tsx
create mode 100644 src/components/YieldCoverageChip.tsx
create mode 100644 src/components/__tests__/PausedBanner.test.tsx
create mode 100644 src/components/__tests__/ProtocolStatusProvider.test.tsx
create mode 100644 src/components/__tests__/YieldCoverageChip.test.tsx
create mode 100644 src/lib/soroban/__tests__/protocol-status.test.ts
create mode 100644 src/lib/soroban/protocol-status.ts
create mode 100644 src/lib/soroban/tx.ts
diff --git a/src/app/create/page.tsx b/src/app/create/page.tsx
index 5753f5a3..76482fbe 100644
--- a/src/app/create/page.tsx
+++ b/src/app/create/page.tsx
@@ -13,6 +13,8 @@ import {
type CommitmentType,
} from '@/lib/commitments';
import { useWallet } from '@/components/WalletProvider';
+import { useProtocolStatus } from '@/components/ProtocolStatusProvider';
+import { getActionPauseReason } from '@/lib/soroban/protocol-status';
import { ESCROW_CONTRACT_ID, explorerContractUrl, config } from '@/lib/config';
const ASSETS = ['USDC', 'XLM', 'EURC'];
@@ -20,6 +22,9 @@ const STEP_LABELS = ['Type', 'Configure', 'Review', 'Done'];
export default function CreatePage() {
const { address } = useWallet();
+ const protocolStatus = useProtocolStatus();
+ const isPaused = protocolStatus.status === 'ok' && protocolStatus.paused;
+ const pauseReason = getActionPauseReason('create_commitment', isPaused);
const [step, setStep] = useState(0);
const [type, setType] = useState('balanced');
const [asset, setAsset] = useState('USDC');
@@ -303,13 +308,25 @@ export default function CreatePage() {
{config.networkDetails.label.toLowerCase()} escrow contract is enabled once a contract ID is configured
{ESCROW_CONTRACT_ID ? '.' : ' (none set in this deployment).'}
-
+
-
+
+ {isPaused && (
+
+ {pauseReason}
+
+ )}
+
+
)}
diff --git a/src/app/dashboard/page.tsx b/src/app/dashboard/page.tsx
index e2d3f083..bea9cd20 100644
--- a/src/app/dashboard/page.tsx
+++ b/src/app/dashboard/page.tsx
@@ -18,6 +18,9 @@ import {
} from '@/lib/commitments';
import { ConfirmDialog } from '@/components/ConfirmDialog';
import { useWallet } from '@/components/WalletProvider';
+import { useProtocolStatus } from '@/components/ProtocolStatusProvider';
+import { getActionPauseReason } from '@/lib/soroban/protocol-status';
+import { YieldCoverageChip } from '@/components/YieldCoverageChip';
import { fetchXlmBalance } from '@/lib/wallet';
import type { DecodedEscrowEvent } from '@/lib/soroban/events';
import { config, explorerTx } from '@/lib/config';
@@ -45,6 +48,9 @@ type BalanceState =
export default function DashboardPage() {
const { status, address } = useWallet();
+ const protocolStatus = useProtocolStatus();
+ const isPaused = protocolStatus.status === 'ok' && protocolStatus.paused;
+ const exitPauseReason = getActionPauseReason('early_exit', isPaused);
const [list, setList] = useState(null);
const [pending, setPending] = useState<{ kind: 'exit' | 'remove'; id: string } | null>(null);
const [announcement, setAnnouncement] = useState('');
@@ -282,7 +288,22 @@ export default function DashboardPage() {
const st = effectiveStatus(c);
const pct = Math.round(progressOf(c) * 100);
const shown = st === 'settled' || st === 'exited' ? 100 : pct;
- const left =Math.max(0, Math.ceil((maturityOf(c) - Date.now()) / 86_400_000));
+ const left = Math.max(0, Math.ceil((maturityOf(c) - Date.now()) / 86_400_000));
+ const isNearOrMatured =
+ st === 'matured' ||
+ (st === 'active' && maturityOf(c) - Date.now() <= 7 * 86_400_000);
+ const isOnChain = !c.demo;
+ const accruedYield = BigInt(
+ (c as any).accrued_yield ??
+ (c as any).accruedYield ??
+ Math.round(projectedYield(c) * 10_000_000),
+ );
+ const showCoverageWarning =
+ isOnChain &&
+ isNearOrMatured &&
+ protocolStatus.status === 'ok' &&
+ protocolStatus.yieldPool < accruedYield;
+
return (
@@ -296,8 +317,14 @@ export default function DashboardPage() {
{c.id} · {specFor(c.type).name} · {c.apy}% APY · max
loss {c.maxLoss}%
+ {showCoverageWarning && (
+
+ )}
-
+
{st === 'matured' && (