Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 3 additions & 1 deletion apps/web/app/app/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,7 @@ import {
stroopsToXlm,
} from '@/lib/api';
import { getUser, isAuthenticated } from '@/lib/auth';
import { CopyButton } from '@/components/CopyButton';
import { useYieldProjection } from '@/lib/workers/useYieldProjection';
import * as Sentry from '@sentry/nextjs';

Expand Down Expand Up @@ -592,14 +593,15 @@ const EventLogRow = memo(function EventLogRow({ event }: { event: ContractEvent
<p className="text-xs text-muted">{new Date(event.createdAt).toLocaleString()}</p>
</div>
<span className="text-sm font-mono">{amountLabel}</span>
<CopyButton value={event.txHash} label="transaction hash" size="sm" />
{event.txUrl ? (
<a
href={event.txUrl}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-accent hover:underline font-mono"
>
{event.txHash.slice(0, 8)}…
View
</a>
) : null}
</li>
Expand Down
41 changes: 41 additions & 0 deletions apps/web/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -43,3 +43,44 @@ body {
font-family: var(--font-sans);
-webkit-font-smoothing: antialiased;
}

/* MobileNavDrawer animations */
@keyframes drawer-slide-in {
from {
transform: translateX(-100%);
}
to {
transform: translateX(0);
}
}

@keyframes drawer-slide-out {
from {
transform: translateX(0);
}
to {
transform: translateX(-100%);
}
}

@keyframes backdrop-fade-in {
from {
opacity: 0;
}
to {
opacity: 1;
}
}

.drawer-enter {
animation: drawer-slide-in 200ms ease-out forwards;
}

.drawer-exit {
animation: drawer-slide-out 200ms ease-in forwards;
}

.drawer-backdrop {
animation: backdrop-fade-in 200ms ease-out forwards;
background: rgba(0, 0, 0, 0.5);
}
144 changes: 81 additions & 63 deletions apps/web/app/page.tsx
Original file line number Diff line number Diff line change
@@ -1,26 +1,36 @@
import Link from 'next/link';
import { Nav } from '@/components/Nav';
import { DemoDashboard } from '@/components/DemoDashboard';
import type { Metadata } from 'next';

export const metadata: Metadata = {
title: 'TariffShield - Real-Time Customs Bond Collateral Management on Stellar',
description: 'Yield-bearing USDC escrow on Stellar Soroban for customs bond collateral. Auto top-up, 4-5% APY, surety clawback. Built on Stellar testnet.',
};

function Card({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className="rounded-lg border border-border bg-card p-5">
<h3 className="text-sm font-semibold tracking-tight text-foreground">{title}</h3>
<p className="mt-2 text-sm text-muted leading-relaxed">{children}</p>
</div>
);
}

export default function Home() {
return (
<>
<Nav />
<main className="max-w-6xl mx-auto px-6 py-16">
{/* Hero */}
<section className="max-w-3xl">
<p className="text-sm font-medium text-accent uppercase tracking-wide">
Customs bond collateral · Soroban smart contracts · Stellar testnet
</p>
<h1 className="mt-3 text-4xl sm:text-5xl font-semibold tracking-tight text-foreground">
Your bond collateral shouldn&apos;t earn 0%.
Real-Time Customs Bond Collateral Management on Stellar
</h1>
<p className="mt-5 text-lg text-muted leading-relaxed">
US importers post <strong className="text-foreground">$3.6B</strong> in customs-bond
insufficiencies (FY25). Surety premiums up{' '}
<strong className="text-foreground">200%</strong> on tariff spikes. Importers lock{' '}
<strong className="text-foreground">50–100% cash collateral</strong> with sureties —
earning <strong className="text-foreground">0%</strong> for a 314-day average lock-up.
</p>
<p className="mt-4 text-lg text-muted leading-relaxed">
TariffShield replaces dead-weight cash with{' '}
<strong className="text-foreground">yield-bearing USDC</strong> in a Soroban escrow
contract. When tariffs spike, the contract auto-tops-up from your reserve bucket. Surety
Expand All @@ -31,14 +41,64 @@ export default function Home() {
href="/signup"
className="rounded-md bg-accent px-5 py-2.5 text-accent-foreground hover:opacity-90 text-sm font-medium"
>
Try the demo
Get Started
</Link>
<Link
href="/login"
<a
href="#demo"
className="rounded-md border border-border px-5 py-2.5 text-sm font-medium hover:bg-card"
>
Log in
</Link>
See Demo
</a>
</div>
</section>

{/* Interactive Demo */}
<section id="demo" className="mt-16 rounded-lg border border-border bg-card p-6">
<h2 className="text-lg font-semibold">Interactive Demo — Live Bond Monitoring</h2>
<p className="mt-1 text-sm text-muted">Simulated SSE stream animates collateral values every 3 seconds — no API calls, no sign-up required.</p>
<div className="mt-6">
<DemoDashboard />
</div>
</section>

{/* How It Works 3-step */}
<section className="mt-16">
<h2 className="text-2xl font-semibold tracking-tight text-center">How It Works</h2>
<div className="mt-8 grid gap-6 sm:grid-cols-3">
<div className="rounded-lg border border-border bg-card p-6 text-center">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-accent/20 text-accent text-xl">①</div>
<h3 className="mt-4 text-sm font-semibold">Register your bond</h3>
<p className="mt-2 text-sm text-muted">Create your importer profile and register your CBP bond ID on-chain. Platform funds a Stellar testnet account via friendbot.</p>
</div>
<div className="rounded-lg border border-border bg-card p-6 text-center">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-accent/20 text-accent text-xl">②</div>
<h3 className="mt-4 text-sm font-semibold">Post collateral</h3>
<p className="mt-2 text-sm text-muted">Deposit USDC into collateral and reserve buckets held by the Soroban escrow contract. Yield starts accruing immediately.</p>
</div>
<div className="rounded-lg border border-border bg-card p-6 text-center">
<div className="mx-auto flex h-12 w-12 items-center justify-center rounded-full bg-accent/20 text-accent text-xl">③</div>
<h3 className="mt-4 text-sm font-semibold">Earn yield</h3>
<p className="mt-2 text-sm text-muted">BENJI yield auto-compounds while tariffs are stable. Spike hits? Contract atomically top-ups from reserve — no paperwork.</p>
</div>
</div>
</section>

{/* Why Stellar */}
<section className="mt-16 rounded-lg border border-border bg-card p-6">
<h2 className="text-lg font-semibold">Why <span className="text-accent">Stellar</span></h2>
<div className="mt-4 grid gap-6 sm:grid-cols-3 text-sm">
<div>
<h3 className="font-semibold text-accent">On-chain transparency</h3>
<p className="mt-1 text-muted">Every deposit, top-up, and yield accrual is an immutable Soroban event. Auditable by importers, sureties, and regulators.</p>
</div>
<div>
<h3 className="font-semibold text-accent">5-second settlement</h3>
<p className="mt-1 text-muted">Stellar finality means collateral moves settle in seconds — no waiting for ACH, no port holds on tariff spikes.</p>
</div>
<div>
<h3 className="font-semibold text-accent">XLM yield</h3>
<p className="mt-1 text-muted">Escrowed USDC earns BENJI-backed 4–5% APY. Idle cash becomes productive float while staying fully collateralized.</p>
</div>
</div>
</section>

Expand All @@ -59,58 +119,16 @@ export default function Home() {
</Card>
</section>

<section className="mt-16 rounded-lg border border-border bg-card p-6">
<h2 className="text-lg font-semibold">How a tariff spike plays out</h2>
<ol className="mt-3 space-y-2 text-sm list-decimal pl-5">
<li>
You sign up as an importer + register your CBP bond ID. Platform funds a Stellar
account for you on testnet via friendbot.
</li>
<li>
You upload your ACE Portal CSV (or synthetic data at MVP). The platform computes
required collateral from annual duties × 10% × 50%.
</li>
<li>
You deposit USDC into your <em>collateral</em> bucket + a margin into your{' '}
<em>reserve</em> bucket. Both held by the Soroban contract.
</li>
<li>
Tariff schedule changes (Section 301 hike, reciprocal regime, AD/CVD order). Your
required collateral updates on-chain.
</li>
<li>
One contract call (<code className="text-accent">auto_top_up</code>) moves the
shortfall from reserve to collateral atomically. No paperwork. No re-underwriting. No
port hold.
</li>
<li>
BENJI yield accrues to your account every period. Withdrawals (above required) are one
contract call.
</li>
<li>
If you default, surety calls <code className="text-accent">clawback</code> — all funds
move to surety wallet, account freezes. Bond stays good.
</li>
</ol>
<p className="mt-3 text-xs text-muted">
MVP runs on Stellar testnet with synthetic CBP data. Live ACE API + surety partner
integration + real BENJI flow + mainnet config are scoped roadmap items.
</p>
</section>

<footer className="mt-20 text-xs text-muted">
MIT licensed · testnet demo · single Soroban contract
{/* Footer */}
<footer className="mt-20 flex flex-col sm:flex-row items-center justify-between gap-4 border-t border-border pt-6 text-xs text-muted">
<span>MIT licensed · testnet demo · single Soroban contract</span>
<div className="flex items-center gap-4">
<Link href="/login" className="hover:text-accent">Log in</Link>
<Link href="/signup" className="hover:text-accent">Sign up</Link>
<a href="mailto:contact@tariffshield.io" className="hover:text-accent">Contact</a>
</div>
</footer>
</main>
</>
);
}

function Card({ title, children }: { title: string; children: React.ReactNode }) {
return (
<div className="rounded-lg border border-border bg-card p-5">
<h3 className="text-sm font-semibold tracking-tight text-foreground">{title}</h3>
<p className="mt-2 text-sm text-muted leading-relaxed">{children}</p>
</div>
);
}
17 changes: 13 additions & 4 deletions apps/web/app/surety/[id]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { useCallback, useEffect, useState } from 'react';
import { Nav } from '@/components/Nav';
import { api, ApiError, type ImporterDetail, type ContractEvent, stroopsToXlm } from '@/lib/api';
import { getUser, isAuthenticated } from '@/lib/auth';
import { CopyButton } from '@/components/CopyButton';

export default function SuretyImporterDetail() {
const params = useParams<{ id: string }>();
Expand Down Expand Up @@ -82,9 +83,16 @@ export default function SuretyImporterDetail() {
<p className="mt-1 text-sm text-muted">
Bond <span className="font-mono">{detail.importer.bondId}</span>
</p>
<p className="mt-1 text-xs font-mono break-all text-muted">
{detail.importer.stellarAddress}
</p>
<div className="mt-1 flex items-center gap-2">
<p className="text-xs font-mono break-all text-muted">
{detail.importer.stellarAddress}
</p>
<CopyButton value={detail.importer.stellarAddress} label="stellar address" size="sm" />
</div>
<div className="mt-2 flex items-center gap-2">
<span className="text-xs text-muted">Importer ID:</span>
<CopyButton value={detail.importer.id} label="importer ID" size="sm" />
</div>
</div>

{onc.isClawbacked ? (
Expand Down Expand Up @@ -190,14 +198,15 @@ export default function SuretyImporterDetail() {
<span className="text-sm font-mono">
{e.amount ? `${stroopsToXlm(e.amount)} XLM` : '—'}
</span>
<CopyButton value={e.txHash} label="transaction hash" size="sm" />
{e.txUrl ? (
<a
href={e.txUrl}
target="_blank"
rel="noopener noreferrer"
className="text-xs text-accent hover:underline font-mono"
>
{e.txHash.slice(0, 8)}…
View
</a>
) : null}
</li>
Expand Down
37 changes: 17 additions & 20 deletions apps/web/app/surety/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { useRouter } from 'next/navigation';
import { useEffect, useState } from 'react';
import { Nav } from '@/components/Nav';
import { api, ApiError, type Importer, type ImporterMetrics, stroopsToXlm } from '@/lib/api';
import { ImporterComparisonTable, type ImporterRow } from '@/components/ImporterComparisonTable';
import { getUser, isAuthenticated } from '@/lib/auth';

export default function SuretyDashboard() {
Expand Down Expand Up @@ -82,26 +83,22 @@ export default function SuretyDashboard() {
</p>
</div>
) : (
<ul className="divide-y divide-border rounded-lg border border-border bg-card">
{importers.map((imp) => (
<li key={imp.id}>
<Link
href={`/surety/${imp.id}`}
onMouseEnter={() => api.prefetchImporter(imp.id)}
className="flex items-center justify-between gap-4 px-4 py-3 hover:bg-background"
>
<div className="min-w-0 flex-1">
<p className="font-medium">{imp.legalName}</p>
<p className="text-xs text-muted">
Bond <span className="font-mono">{imp.bondId}</span> · {imp.email}
</p>
<p className="text-xs text-muted font-mono break-all">{imp.stellarAddress}</p>
</div>
<span className="text-xs text-accent">manage →</span>
</Link>
</li>
))}
</ul>
<ImporterComparisonTable
importers={importers.map((imp) => ({
id: imp.id,
legalName: imp.legalName,
ein: imp.ein,
bondId: imp.bondId,
requiredCollateral: '0',
postedCollateral: '0',
utilization: 0,
reserve: '0',
yieldAccrued: '0',
healthScore: 100,
lastActivity: imp.createdAt,
stellarAddress: imp.stellarAddress,
} as ImporterRow))}
/>
)}
</div>
</main>
Expand Down
Loading