Skip to content
Merged
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: 2 additions & 2 deletions app-portal/src/app/privacy/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@
<h1 className="mt-3 text-4xl font-black tracking-tight">Privacy Policy</h1>
<p className="mt-3 text-sm text-white/50">Last updated: 7 September 2026</p>
<div className="mt-10 space-y-8 text-sm leading-7 text-white/75">
<section><h2 className="text-xl font-bold text-white">1. Who operates the Portal</h2><p className="mt-3">The service is operated under the name Decentralized Rights Protocol (DRP). The public contact address for privacy and service questions is <a className="text-[#00e5cc] underline" href="mailto:contact@decentralizedrights.com">contact@decentralizedrights.com</a>. If DRP is later operated by a registered legal entity, this notice should be updated with that entity's legal name, registered address and applicable registration details.</p></section>
<section><h2 className="text-xl font-bold text-white">1. Who operates the Portal</h2><p className="mt-3">The service is operated under the name Decentralized Rights Protocol (DRP). The public contact address for privacy and service questions is <a className="text-[#00e5cc] underline" href="mailto:contact@decentralizedrights.com">contact@decentralizedrights.com</a>. If DRP is later operated by a registered legal entity, this notice should be updated with that entity&apos;s legal name, registered address and applicable registration details.</p></section>
<section><h2 className="text-xl font-bold text-white">2. Data we collect</h2><p className="mt-3">We aim to collect only data needed to provide and secure the Portal. Depending on the feature you use, this may include a wallet/public address, activity or proof information you submit, evidence files you choose to upload, transaction or verification identifiers, basic account/contact information when you voluntarily provide it, and technical/security information needed to operate the service.</p><p className="mt-3">Do not submit passwords, private keys, seed phrases, government identification numbers, health information, or other sensitive information unless a feature explicitly requests it and the applicable notice explains why it is needed.</p></section>
<section><h2 className="text-xl font-bold text-white">3. Why we use it</h2><p className="mt-3">We use information to authenticate and secure access, process proof submissions, provide verification and rewards features, prevent abuse, maintain audit and security records, provide support, and meet legal obligations. We do not sell personal data.</p></section>
<section><h2 className="text-xl font-bold text-white">4. Blockchain and decentralized records</h2><p className="mt-3">Some records may be written to blockchain, decentralized storage or other distributed systems. Such records can be difficult or impossible to alter or delete after publication. You should not submit personal information that you do not want associated with a potentially persistent public record. Where possible, DRP should use hashes, identifiers and minimised data rather than unnecessary personal information.</p></section>
<section><h2 className="text-xl font-bold text-white">5. Service providers</h2><p className="mt-3">The Portal may use infrastructure providers such as Vercel, Convex, Render, Storacha/IPFS, wallet providers and blockchain/network infrastructure. They may process information on DRP's behalf or as independent providers. Their own terms and privacy notices may also apply.</p></section>
<section><h2 className="text-xl font-bold text-white">5. Service providers</h2><p className="mt-3">The Portal may use infrastructure providers such as Vercel, Convex, Render, Storacha/IPFS, wallet providers and blockchain/network infrastructure. They may process information on DRP&apos;s behalf or as independent providers. Their own terms and privacy notices may also apply.</p></section>
<section><h2 className="text-xl font-bold text-white">6. Retention and security</h2><p className="mt-3">We retain information only for as long as reasonably necessary for the stated purpose, security, dispute handling and legal obligations. We use access controls, encryption in transit and other reasonable safeguards, but no online system can be guaranteed completely secure.</p></section>
<section><h2 className="text-xl font-bold text-white">7. Your rights</h2><p className="mt-3">Depending on applicable law, you may have rights to access, correct, delete, restrict or object to certain processing, and to withdraw consent where consent is the legal basis. Some rights may be limited where information is required for security, legal compliance, or has already been irreversibly committed to a decentralized network.</p></section>
<section><h2 className="text-xl font-bold text-white">8. Ghana data protection</h2><p className="mt-3">DRP is designed with Ghana's Data Protection Act, 2012 (Act 843) in mind. Where Ghana data-protection obligations apply, processing should be fair, lawful, transparent and limited to necessary purposes. The Ghana Data Protection Commission is the relevant regulator.</p></section>

Check failure on line 16 in app-portal/src/app/privacy/page.tsx

View workflow job for this annotation

GitHub Actions / lint

`'` can be escaped with `&apos;`, `&lsquo;`, `&#39;`, `&rsquo;`
<section><h2 className="text-xl font-bold text-white">9. International processing</h2><p className="mt-3">Infrastructure providers may process data outside Ghana. Where applicable law requires safeguards for international transfers, DRP will use appropriate contractual, technical or other safeguards.</p></section>
<section><h2 className="text-xl font-bold text-white">10. Contact</h2><p className="mt-3">For privacy requests or questions, email <a className="text-[#00e5cc] underline" href="mailto:contact@decentralizedrights.com">contact@decentralizedrights.com</a>.</p></section>
</div>
Expand Down
2 changes: 1 addition & 1 deletion app-portal/src/app/terms/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ export default function TermsPage() {
<section><h2 className="text-xl font-bold text-white">2. What DRP provides</h2><p className="mt-3">The Portal provides software for submitting, viewing and verifying activity or status claims and interacting with DRP infrastructure. Features may change, be unavailable, or remain in testing. Descriptions of planned protocol capabilities are not promises that a feature is currently available.</p></section>
<section><h2 className="text-xl font-bold text-white">3. No guarantee of verification or rewards</h2><p className="mt-3">Submitting a proof does not guarantee approval, verification, eligibility, rewards, token value, governance rights, or any particular outcome. Verification decisions may depend on protocol rules, evidence quality, network availability and applicable governance processes.</p></section>
<section><h2 className="text-xl font-bold text-white">4. Wallets and blockchain transactions</h2><p className="mt-3">You are responsible for your wallet, device, credentials and transaction approvals. DRP will never ask you to disclose a private key or seed phrase. Blockchain transactions can be irreversible and may incur network fees. Confirm transaction details before signing.</p></section>
<section><h2 className="text-xl font-bold text-white">5. User submissions</h2><p className="mt-3">You must have the right to submit any text, files, images, video or other evidence you provide. Do not upload another person's private information without a lawful basis and appropriate permission. Do not submit unlawful, fraudulent, defamatory, infringing, malicious or abusive material.</p></section>
<section><h2 className="text-xl font-bold text-white">5. User submissions</h2><p className="mt-3">You must have the right to submit any text, files, images, video or other evidence you provide. Do not upload another person&apos;s private information without a lawful basis and appropriate permission. Do not submit unlawful, fraudulent, defamatory, infringing, malicious or abusive material.</p></section>
<section><h2 className="text-xl font-bold text-white">6. Intellectual property</h2><p className="mt-3">DRP software, branding, documentation and content are protected by applicable intellectual-property laws unless a specific licence states otherwise. You retain rights you already have in your submissions, subject to the permissions needed to operate verification and display features.</p></section>
<section><h2 className="text-xl font-bold text-white">7. Prohibited conduct</h2><p className="mt-3">Do not attempt to compromise the Portal, bypass access controls, submit fabricated evidence, manipulate verification, abuse rewards, impersonate another person, upload malware, or use the service in violation of applicable law.</p></section>
<section><h2 className="text-xl font-bold text-white">8. Disclaimers</h2><p className="mt-3">The Portal is provided on an availability basis. DRP does not guarantee uninterrupted operation, error-free verification, blockchain finality, or absolute security. Nothing on the Portal is legal, financial, investment, medical or human-rights legal advice.</p></section>
Expand Down
35 changes: 35 additions & 0 deletions explorer/app/globals.css
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
:root {
color-scheme: dark;
--bg: #070a12;
--panel: #101724;
--border: rgba(174, 199, 220, .15);
--text: #edf7fb;
--muted: #94a7b7;
--teal: #00e5cc;
--blue: #6aa9ff;
}
* { box-sizing: border-box; }
body { margin: 0; background: radial-gradient(ellipse at 15% 0%, rgba(0,229,204,.11), transparent 38%), radial-gradient(ellipse at 90% 10%, rgba(106,169,255,.12), transparent 34%), var(--bg); color: var(--text); font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; }
a { color: var(--teal); text-decoration: none; }
a:hover { text-decoration: underline; }
main { width: min(1180px, calc(100% - 32px)); margin: 0 auto; padding: 36px 0 64px; }
.eyebrow { color: var(--teal); text-transform: uppercase; letter-spacing: .2em; font-size: .72rem; font-weight: 800; }
h1 { margin: 12px 0; font-size: clamp(2.3rem, 6vw, 4.5rem); letter-spacing: -.055em; line-height: 1.03; }
.lede { color: var(--muted); max-width: 760px; line-height: 1.7; }
.topline { display:flex; justify-content:space-between; align-items:center; gap:18px; flex-wrap:wrap; margin-bottom:28px; }
.status { border:1px solid var(--border); border-radius:999px; padding:9px 13px; font-size:.8rem; color:var(--muted); }
.grid { display:grid; grid-template-columns:repeat(4,minmax(0,1fr)); gap:12px; margin:28px 0; }
.panel { border:1px solid var(--border); border-radius:18px; background:linear-gradient(140deg,rgba(255,255,255,.045),rgba(255,255,255,.018)); padding:18px; min-width:0; }
.panel h2 { font-size:1rem; margin:0 0 14px; }
.metric-label { color:var(--muted); font-size:.76rem; text-transform:uppercase; letter-spacing:.08em; }
.metric-value { font-size:1.25rem; font-weight:800; margin-top:8px; overflow-wrap:anywhere; }
.section-grid { display:grid; grid-template-columns:1fr 1fr; gap:14px; }
.list { display:grid; gap:10px; }
.item { padding:12px 0; border-top:1px solid var(--border); min-width:0; }
.item:first-child { border-top:0; padding-top:0; }
.item-title { font-weight:700; overflow-wrap:anywhere; }
.item-meta { margin-top:5px; color:var(--muted); font-size:.78rem; line-height:1.55; overflow-wrap:anywhere; }
.empty { color:var(--muted); font-size:.88rem; line-height:1.6; }
.error { color:#ffc3c3; }
footer { margin-top:26px; padding-top:20px; border-top:1px solid var(--border); color:var(--muted); font-size:.78rem; line-height:1.6; }
@media (max-width: 760px) { .grid { grid-template-columns:repeat(2,minmax(0,1fr)); } .section-grid { grid-template-columns:1fr; } main { width:min(100% - 24px,1180px); padding-top:24px; } .panel { padding:15px; } }
15 changes: 15 additions & 0 deletions explorer/app/layout.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
import type { Metadata } from "next";
import "./globals.css";

export const metadata: Metadata = {
title: "DRP Explorer | Decentralized Rights Protocol",
description: "Explore DRP network activity, blocks, transactions, and verification infrastructure using live API data.",
};

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
return (
<html lang="en">
<body>{children}</body>
</html>
);
}
96 changes: 96 additions & 0 deletions explorer/app/page.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
export const dynamic = "force-dynamic";

type ApiRecord = Record<string, unknown>;
type ApiResult<T> = { data: T | null; error: string | null };

const API_URL = (process.env.DRP_EXPLORER_API_URL || process.env.NEXT_PUBLIC_API_URL || "https://api.decentralizedrights.com").replace(/\/+$/, "");
const API_BASE = API_URL.endsWith("/api/v1/explorer") ? API_URL : `${API_URL}/api/v1/explorer`;

async function load<T>(path: string): Promise<ApiResult<T>> {
try {
const response = await fetch(`${API_BASE}${path}`, {
cache: "no-store",
signal: AbortSignal.timeout(5000),
headers: { accept: "application/json" },
});
if (!response.ok) return { data: null, error: `API returned HTTP ${response.status}` };
return { data: await response.json() as T, error: null };
} catch {
return { data: null, error: "The DRP API is currently unreachable. No live values are being substituted." };
}
}

function isRecord(value: unknown): value is ApiRecord {
return typeof value === "object" && value !== null && !Array.isArray(value);
}

function valueText(value: unknown): string {
if (value === null || value === undefined || value === "") return "—";
if (typeof value === "string" || typeof value === "number" || typeof value === "boolean") return String(value);
try { return JSON.stringify(value) ?? String(value); } catch { return String(value); }
}

function first(item: ApiRecord, keys: string[]): unknown {
for (const key of keys) if (item[key] !== undefined && item[key] !== null) return item[key];
return undefined;
}

function ListSection({ title, items, error, kind }: { title: string; items: ApiRecord[] | null; error: string | null; kind: "block" | "transaction" | "activity" }) {
return (
<section className="panel">
<h2>{title}</h2>
{error && <p className="empty error">{error}</p>}
{!error && (!items || items.length === 0) && <p className="empty">No records were returned by the API.</p>}
{items && items.length > 0 && <div className="list">{items.slice(0, 8).map((item, index) => {
const titleValue = kind === "block"
? first(item, ["block_number", "number", "height"])
: kind === "transaction"
? first(item, ["tx_hash", "hash", "id"])
: first(item, ["title", "type", "id"]);
const meta = kind === "block"
? [first(item, ["block_hash", "hash"]), first(item, ["transaction_count", "transactions"]), first(item, ["timestamp"])].filter(v => v !== undefined).map(valueText).join(" · ")
: kind === "transaction"
? [first(item, ["status"]), first(item, ["type", "token_type"]), first(item, ["timestamp"])].filter(v => v !== undefined).map(valueText).join(" · ")
: [first(item, ["description"]), first(item, ["actor"]), first(item, ["timestamp"])].filter(v => v !== undefined).map(valueText).join(" · ");
return <div className="item" key={String(titleValue ?? index)}><div className="item-title">{valueText(titleValue)}</div><div className="item-meta">{meta || "Record returned; additional fields may be available in the API response."}</div></div>;
})}</div>}
</section>
);
}

export default async function ExplorerPage() {
const [statsResult, blocksResult, transactionsResult, activityResult] = await Promise.all([
load<unknown>("/stats"),
load<ApiRecord[]>("/blocks?limit=8"),
load<ApiRecord[]>("/transactions?limit=8"),
load<ApiRecord[]>("/activity?limit=8"),
]);
const stats = isRecord(statsResult.data) ? statsResult.data : null;
const metrics = stats ? Object.entries(stats).filter(([, value]) => ["string", "number", "boolean"].includes(typeof value)).slice(0, 4) : [];
const blocks = Array.isArray(blocksResult.data) ? blocksResult.data : null;
const transactions = Array.isArray(transactionsResult.data) ? transactionsResult.data : null;
const activity = Array.isArray(activityResult.data) ? activityResult.data : null;
const errors = [statsResult.error, blocksResult.error, transactionsResult.error, activityResult.error].filter(Boolean);
return (
<main>
<div className="topline">
<span className="eyebrow">Decentralized Rights Protocol</span>
<span className="status">{errors.length === 0 ? "API responding" : "Live data connection needs attention"}</span>
</div>
<header>
<h1>DRP Network Explorer</h1>
<p className="lede">A transparent view of network activity and verification infrastructure. This explorer displays records returned by the configured DRP API; it does not invent missing blocks, transactions, or proof counts.</p>
</header>
<div className="grid">
{metrics.length ? metrics.map(([key, value]) => <section className="panel" key={key}><div className="metric-label">{key.replace(/[_-]/g, " ")}</div><div className="metric-value">{valueText(value)}</div></section>) : <section className="panel" style={{gridColumn:"1 / -1"}}><div className="metric-label">Network summary</div><div className="metric-value">Waiting for live API data</div><p className="empty">{statsResult.error || "The API did not return summary metrics."}</p></section>}
</div>
<div className="section-grid">
<ListSection title="Recent blocks" items={blocks} error={blocksResult.error} kind="block" />
<ListSection title="Recent transactions" items={transactions} error={transactionsResult.error} kind="transaction" />
<ListSection title="Activity feed" items={activity} error={activityResult.error} kind="activity" />
<section className="panel"><h2>Verification principles</h2><div className="list"><div className="item"><div className="item-title">Proof of Activity (PoAT)</div><div className="item-meta">Evidence and source provenance matter; a submitted claim is not automatically verified.</div></div><div className="item"><div className="item-title">Proof of Status (PoST)</div><div className="item-meta">Status claims should identify their issuer, scope, validity period, and revocation state.</div></div><div className="item"><div className="item-title">Accountable verification</div><div className="item-meta">A cryptographic signature confirms who signed a payload, not that the real-world claim is true.</div></div></div></section>
</div>
<footer>API base: {API_BASE}. Configure <code>DRP_EXPLORER_API_URL</code> in Vercel to use a different API origin. Raw evidence and private personal data should not be published to a public ledger.</footer>
</main>
);
}
5 changes: 5 additions & 0 deletions explorer/next-env.d.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
/// <reference types="next" />
/// <reference types="next/image-types/global" />

// NOTE: This file should not be edited
// see https://nextjs.org/docs/app/api-reference/config/typescript for more information.
27 changes: 27 additions & 0 deletions explorer/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
{
"name": "drp-blockchain-explorer",
"version": "0.1.0",
"private": true,
"scripts": {
"dev": "next dev",
"build": "next build",
"start": "next start"
},
"dependencies": {
"next": "15.5.24",
"react": "18.3.1",
"react-dom": "18.3.1"
},
"devDependencies": {
"@types/node": "^20",
"@types/react": "^18",
"@types/react-dom": "^18",
"typescript": "^5"
},
"postcss": {
"plugins": {}
},
"engines": {
"node": "22.x"
}
}
35 changes: 35 additions & 0 deletions explorer/tsconfig.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
{
"compilerOptions": {
"target": "ES2017",
"lib": [
"dom",
"dom.iterable",
"esnext"
],
"allowJs": true,
"skipLibCheck": true,
"strict": true,
"noEmit": true,
"esModuleInterop": true,
"module": "esnext",
"moduleResolution": "bundler",
"resolveJsonModule": true,
"isolatedModules": true,
"jsx": "preserve",
"incremental": true,
"plugins": [
{
"name": "next"
}
]
},
"include": [
"next-env.d.ts",
".next/types/**/*.ts",
"**/*.ts",
"**/*.tsx"
],
"exclude": [
"node_modules"
]
}
Loading
Loading