Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
664 changes: 0 additions & 664 deletions PROJECT_SPEC.md

This file was deleted.

38 changes: 37 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,43 @@

Wallet-gated accounting dashboard for RaidGuild treasury reporting.

The project plan and product specification live in [PROJECT_SPEC.md](./PROJECT_SPEC.md).
## Product Scope

RaidGuild Accounting links treasury holdings, manually entered accounting
activity, DAO proposal data, RIPs, and imported reporting data into member-facing
quarterly and annual financial reports.

The app uses cash-basis accounting: money counts when it moves. Taxable raid
revenue is gross client revenue, with spoils tracked as 10% of gross raid
revenue and subcontractor payouts tracked as expenses for P&L and raid
accounting.

Treasury portfolio views are read-only and focus on current holdings, asset
allocation, account breakdowns, sync freshness, and stablecoin rebalancing
guidance. Stablecoin allocation targets 25% stables and 75% other assets, with a
5 percentage point tolerance.

## Architecture

- Next.js App Router renders the wallet-gated dashboard.
- Drizzle ORM models accounting records, treasury accounts, balance snapshots,
quarter workflow state, audit records, and entity/address book records.
- Treasury balance snapshots are synced from environment-configured accounts and
cached for member-visible reads.
- Published quarter reports and exports are member-visible. Draft,
ready-for-review, reopened, and administrative accounting workflows remain
restricted to the appropriate roles.
- Sensitive configuration and real financial data are environment-driven. The
public repo must not hardcode treasury addresses, DAO addresses, RPC URLs, API
keys, bank data, or classified accounting records.

## Permissions

- Member access is wallet-gated through SIWE and current RaidGuild DAO shares.
- Admin/write access is derived from Angry Dwarf Hat ownership.
- Cleric access is database-managed by admins and is limited to raid-oriented
accounting workflows.
- Portfolio and published report views are read-only for authorized users.

## Getting Started

Expand Down
3 changes: 3 additions & 0 deletions src/app/admin/providers/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { and, eq, inArray, sql } from "drizzle-orm";

import { AppHeader } from "@/components/app-header";
import { CopyableAddress } from "@/components/copyable-address";
import { DashboardBackLink } from "@/components/dashboard-back-link";
import { Button } from "@/components/ui/button";
import { getDb } from "@/db";
import { ledgerEntries } from "@/db/schema";
Expand Down Expand Up @@ -638,6 +639,8 @@ export default async function ProvidersPage({
<AppHeader initialSession={sessionState} />

<section className="container-custom grid gap-8 py-8 md:py-12">
<DashboardBackLink />

<ProviderLauncher canManage={canManage} />
<ProviderRankingTable
emptyLabel="No service providers yet."
Expand Down
3 changes: 3 additions & 0 deletions src/app/admin/quarters/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ import {
} from "@/app/admin/quarters/actions";
import { PublishQuarterConfirmation } from "@/app/admin/quarters/publish-quarter-confirmation";
import { AppHeader } from "@/components/app-header";
import { DashboardBackLink } from "@/components/dashboard-back-link";
import { QuarterWorkflowProgress } from "@/components/quarters/quarter-workflow-progress";
import { Button } from "@/components/ui/button";
import { getAuthSession, serializeSession } from "@/lib/auth/session";
Expand Down Expand Up @@ -351,6 +352,8 @@ export default async function QuartersPage() {
<AppHeader initialSession={sessionState} />

<section className="container-custom grid gap-8 py-8 md:py-12">
<DashboardBackLink />

{canManage && !q1Exists ? (
<section className="rounded-lg border border-border bg-card p-6 shadow-sm">
<div className="flex flex-wrap items-center justify-between gap-4">
Expand Down
3 changes: 3 additions & 0 deletions src/app/admin/treasury-accounts/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import type { ReactNode } from "react";

import { AppHeader } from "@/components/app-header";
import { CopyableAddress } from "@/components/copyable-address";
import { DashboardBackLink } from "@/components/dashboard-back-link";
import { Button } from "@/components/ui/button";
import { getAuthSession, serializeSession } from "@/lib/auth/session";
import { getTreasuryBalanceSnapshot } from "@/lib/treasury/balances";
Expand Down Expand Up @@ -605,6 +606,8 @@ export default async function TreasuryAccountsPage({
<AppHeader initialSession={sessionState} />

<section className="container-custom grid gap-8 py-8 md:py-12">
<DashboardBackLink />

<AccountLauncher canManage={canManage} />
<AccountRankingTable
accounts={[...activeAccounts, ...archivedAccounts]}
Expand Down
25 changes: 21 additions & 4 deletions src/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,10 @@
@apply mx-auto box-border w-full max-w-7xl px-3 sm:px-6 lg:px-8;
}

.container-custom > * {
min-width: 0;
}

.type-label {
@apply text-xs font-semibold tracking-normal;
}
Expand Down Expand Up @@ -254,7 +258,7 @@
}

.mobile-card-table tr {
@apply grid gap-2 rounded-md border border-border bg-background p-3 md:table-row md:rounded-none md:border-0 md:bg-transparent md:p-0;
@apply grid gap-1.5 rounded-md border border-border bg-background p-3 md:table-row md:rounded-none md:border-0 md:bg-transparent md:p-0;
}

.mobile-card-table tbody tr {
Expand All @@ -271,7 +275,7 @@
}

.mobile-card-table td {
@apply grid min-w-0 grid-cols-[7.5rem_minmax(0,1fr)] gap-3 py-0 text-right md:table-cell md:text-left;
@apply grid min-w-0 grid-cols-[6.25rem_minmax(0,1fr)] gap-2 py-0 text-right leading-snug md:table-cell md:text-left md:leading-normal;

overflow-wrap: anywhere;
}
Expand All @@ -293,6 +297,10 @@
@apply sr-only;
}

.mobile-card-table td > a.block {
@apply px-0 py-0 md:px-3 md:py-3;
}

.mobile-card-table-lg {
@apply w-full text-left text-sm;
}
Expand All @@ -306,7 +314,7 @@
}

.mobile-card-table-lg tr {
@apply grid gap-2 rounded-md border border-border bg-background p-3 lg:table-row lg:rounded-none lg:border-0 lg:bg-transparent lg:p-0;
@apply grid gap-1.5 rounded-md border border-border bg-background p-3 lg:table-row lg:rounded-none lg:border-0 lg:bg-transparent lg:p-0;
}

.mobile-card-table-lg tbody tr {
Expand All @@ -323,7 +331,7 @@
}

.mobile-card-table-lg td {
@apply grid min-w-0 grid-cols-[7.5rem_minmax(0,1fr)] gap-3 py-0 text-right lg:table-cell lg:text-left;
@apply grid min-w-0 grid-cols-[6.25rem_minmax(0,1fr)] gap-2 py-0 text-right leading-snug lg:table-cell lg:text-left lg:leading-normal;

overflow-wrap: anywhere;
}
Expand All @@ -349,4 +357,13 @@
.mobile-card-table-lg td[data-full="true"]::before {
@apply sr-only;
}

.mobile-card-table-lg td > a.block {
@apply px-0 py-0 lg:px-3 lg:py-3;
}

.portfolio-asset-table td,
.portfolio-asset-table th {
@apply lg:py-2;
}
}
3 changes: 3 additions & 0 deletions src/app/membership/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import Link from "next/link";

import { AppHeader } from "@/components/app-header";
import { CopyableAddress } from "@/components/copyable-address";
import { DashboardBackLink } from "@/components/dashboard-back-link";
import { grantClericRole, revokeClericRole } from "@/app/membership/actions";
import { getAuthSession, serializeSession } from "@/lib/auth/session";
import { listClericRoles, type ClericRoleRow } from "@/lib/cleric-roles";
Expand Down Expand Up @@ -482,6 +483,8 @@ export default async function MembershipPage({
<AppHeader initialSession={session} />

<section className="container-custom grid gap-8 py-8 md:py-12">
<DashboardBackLink />

{canManageClerics ? (
<ClericAccessSection
clericRoles={clericRoles}
Expand Down
Loading