Files
zeekay 6b38df60a4 feat: MPC wallet dashboard - full-stack implementation
Complete MPC wallet management platform rivaling Fireblocks/Utila/Fordefi:

Backend (Go):
- REST API with 60+ endpoints on chi router (pkg/api/)
- PostgreSQL schema with 13 tables (pkg/db/)
- JWT + MFA auth, bcrypt passwords, API key management
- Policy engine (deny/approve/require_approval)
- Transaction signing flow with approval workflow
- Subscription scheduler for recurring payments
- Payment requests with public pay pages
- Safe + ERC-4337 smart wallet integration (pkg/smart/)
- Rate limiting, webhook delivery (HMAC-SHA256), audit logging
- 27 passing tests (auth, policy engine, middleware)

Frontend (Next.js 14):
- 27 routes with monochrome dark theme (Tailwind + Geist fonts)
- Full API client with JWT auto-refresh
- React Query hooks for all resources
- Pages: dashboard, vaults, wallets, transactions, policies,
  whitelist, team, settings, API keys, webhooks, audit,
  subscriptions, payments, smart wallets

Infrastructure:
- Dockerfiles for API (Go) and dashboard (Next.js standalone)
- compose.yml for local development (PostgreSQL + API + dashboard)
- K8s manifests for mpc.lux.network + cloud.lux.network
- GitHub Actions deploy workflow (build → push GHCR → kubectl apply)
- TLS via cert-manager, Traefik IngressRoute for both domains
2025-12-27 13:11:38 -08:00

208 lines
6.6 KiB
TypeScript

'use client'
import { useState } from 'react'
export type PolicyType = 'spend_limit' | 'whitelist' | 'time_lock' | 'quorum' | 'custom'
export interface PolicyCondition {
field: string
operator: string
value: string
}
export interface PolicyFormData {
name: string
type: PolicyType
conditions: PolicyCondition[]
appliedTo: string
}
export interface PolicyFormProps {
initialData?: Partial<PolicyFormData>
onSubmit: (data: PolicyFormData) => void
onCancel: () => void
submitting?: boolean
}
const policyTypes: { value: PolicyType; label: string }[] = [
{ value: 'spend_limit', label: 'Spend Limit' },
{ value: 'whitelist', label: 'Whitelist' },
{ value: 'time_lock', label: 'Time Lock' },
{ value: 'quorum', label: 'Quorum' },
{ value: 'custom', label: 'Custom' },
]
const operators = ['==', '!=', '>', '<', '>=', '<=', 'in', 'not_in']
const conditionFields: Record<PolicyType, string[]> = {
spend_limit: ['amount', 'token', 'period'],
whitelist: ['to_address', 'chain'],
time_lock: ['delay_seconds', 'window_start', 'window_end'],
quorum: ['required_approvals', 'approver_group'],
custom: ['field'],
}
export function PolicyForm({
initialData,
onSubmit,
onCancel,
submitting = false,
}: PolicyFormProps) {
const [name, setName] = useState(initialData?.name ?? '')
const [type, setType] = useState<PolicyType>(initialData?.type ?? 'spend_limit')
const [conditions, setConditions] = useState<PolicyCondition[]>(
initialData?.conditions ?? [{ field: '', operator: '==', value: '' }]
)
const [appliedTo, setAppliedTo] = useState(initialData?.appliedTo ?? '')
function addCondition() {
setConditions((c) => [...c, { field: '', operator: '==', value: '' }])
}
function updateCondition(index: number, patch: Partial<PolicyCondition>) {
setConditions((c) =>
c.map((cond, i) => (i === index ? { ...cond, ...patch } : cond))
)
}
function removeCondition(index: number) {
setConditions((c) => c.filter((_, i) => i !== index))
}
function handleSubmit(e: React.FormEvent) {
e.preventDefault()
onSubmit({ name, type, conditions, appliedTo })
}
const fields = conditionFields[type]
return (
<form onSubmit={handleSubmit} className="space-y-6">
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="policy-name">
Policy Name
</label>
<input
id="policy-name"
type="text"
required
value={name}
onChange={(e) => setName(e.target.value)}
placeholder="e.g. Daily Spend Limit"
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="policy-type">
Type
</label>
<select
id="policy-type"
value={type}
onChange={(e) => setType(e.target.value as PolicyType)}
className="w-full rounded-md border border-input bg-background px-3 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
>
{policyTypes.map((pt) => (
<option key={pt.value} value={pt.value}>
{pt.label}
</option>
))}
</select>
</div>
<div className="space-y-2">
<label className="text-sm font-medium" htmlFor="policy-applied">
Applied To (wallet or vault ID)
</label>
<input
id="policy-applied"
type="text"
value={appliedTo}
onChange={(e) => setAppliedTo(e.target.value)}
placeholder="wallet-... or vault-... or * for all"
className="w-full rounded-md border border-input bg-background px-3 py-2 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-ring"
/>
</div>
<div className="space-y-3">
<div className="flex items-center justify-between">
<label className="text-sm font-medium">Conditions</label>
<button
type="button"
onClick={addCondition}
className="text-sm font-medium text-primary hover:underline"
>
+ Add Condition
</button>
</div>
{conditions.map((cond, i) => (
<div key={i} className="flex items-start gap-2">
<select
value={cond.field}
onChange={(e) => updateCondition(i, { field: e.target.value })}
className="w-1/3 rounded-md border border-input bg-background px-2 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
>
<option value="">Field...</option>
{fields.map((f) => (
<option key={f} value={f}>
{f}
</option>
))}
</select>
<select
value={cond.operator}
onChange={(e) => updateCondition(i, { operator: e.target.value })}
className="w-24 rounded-md border border-input bg-background px-2 py-2 font-mono text-sm focus:outline-none focus:ring-2 focus:ring-ring"
>
{operators.map((op) => (
<option key={op} value={op}>
{op}
</option>
))}
</select>
<input
type="text"
value={cond.value}
onChange={(e) => updateCondition(i, { value: e.target.value })}
placeholder="Value"
className="flex-1 rounded-md border border-input bg-background px-2 py-2 text-sm focus:outline-none focus:ring-2 focus:ring-ring"
/>
{conditions.length > 1 && (
<button
type="button"
onClick={() => removeCondition(i)}
className="rounded-md border border-border px-2 py-2 text-sm text-muted-foreground hover:bg-accent"
>
Remove
</button>
)}
</div>
))}
</div>
<div className="flex gap-3 pt-2">
<button
type="button"
onClick={onCancel}
disabled={submitting}
className="flex-1 rounded-md border border-border px-4 py-2 text-sm font-medium hover:bg-accent disabled:opacity-50"
>
Cancel
</button>
<button
type="submit"
disabled={submitting || !name}
className="flex-1 rounded-md bg-primary px-4 py-2 text-sm font-medium text-primary-foreground hover:bg-primary/90 disabled:opacity-50"
>
{submitting ? 'Saving...' : 'Save Policy'}
</button>
</div>
</form>
)
}