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
6 changes: 3 additions & 3 deletions .claude/commands/design-org.md
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ Generate the following files in order. After each file, briefly describe what it

Follow the pattern in **Appendix C** and `<REF_ROOT>/governance/examples/OptimisticExecution.s.sol`. Also read `<REF_ROOT>/governance/claude/global-environmental-movement/Deploy.s.sol` as a concrete same-folder example. Key rules:
- Contract name: `Deploy`
- Use `MAJOR=0, MINOR=1, PATCH=8` for registry lookups
- Use `MAJOR=0, MINOR=1, PATCH = 9` for registry lookups
- **Metadata URI**: use the URI supplied by the user as the second argument to the `Powers` constructor. If no URI was provided, use an empty string with a TODO comment:
```solidity
new Powers(
Expand Down Expand Up @@ -885,7 +885,7 @@ Draw on excerpts surfaced by the `search_governance_sources` MCP tool when expla
- **Actions script:** `<FOUNDRY_ROOT>/governance/<org-name>/Actions.s.sol`
- **Runners script:** `<FOUNDRY_ROOT>/governance/<org-name>/Runners.s.sol`
- **Test file:** `<FOUNDRY_ROOT>/governance/<org-name>/Test.t.sol`
- **Mandate version:** MAJOR=0, MINOR=1, PATCH=8
- **Mandate version:** MAJOR=0, MINOR=1, PATCH = 9
- **Mandate nameDescription strings must match exactly across all four files.**
```

Expand Down Expand Up @@ -937,7 +937,7 @@ contract Deploy is DeployHelpers {
// Always use these constants — they select the correct version from the registry.
uint16 constant MAJOR = 0;
uint16 constant MINOR = 1;
uint16 constant PATCH = 8;
uint16 constant PATCH = 9;

// ── run() ────────────────────────────────────────────────────────────────
function run() external returns (Powers) {
Expand Down
5 changes: 5 additions & 0 deletions During this Arbitrum Founder OpenHouse - text.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
During this Arbitrum Founder Openhouse we refined our Product Value Proposition and GTM strategy. Specifically, we defined a crypto specific market strategy focused on Powers providing a governance solution that is fit for purpose, in the broader current general recognition that existing solutions are not. Win which e also added a tiered payment structure, introducing the possibility for mandates to be paid.




Binary file added Pitch Slides - Openhouse.pdf
Binary file not shown.
33 changes: 33 additions & 0 deletions Script for pitch 1 Dao.txt
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
Script for pitch: 1:
Dao governance is collapsing and is in a total crisis. Billions on dollars have been held in DAO treasuries such as ENS DAO, uniswap, tally... hats protocol even tried to provide another system but this was too rigid ....this graph gathered from data on DeepDAO shows that the infrastructure so far has been not fit for purpose.

2: 
Our target customers are blockchain startups. They want no headaches , being able to focus on their work. They fear drama, disputes, lack of security. They need something that has easy on boarding, low maintenance cost, flexibility to change things after deployment, and simply works to do what it should do.

3: 
Powers Protocol is governance infrastructure that provides startups the possibility to create bespoke on-chain governance flows that allows to separate powers, create checks and balances, and encode nested relationships within organisations, and has role-based-access-control for different deliberation channels.


4: Until today, that was all not possible in a DAO. This is what a governance flow looks like on our front-end user interface - it is a process made up of individual mandates which are smart contracts that execute activity. Each one is customisable, and you can see that specific actions are tied to specific roles, with checks being made before one step can proceed onto the next step with the governance flow. And on top of that, in our forum each of these steps has its own deliberation channel.

5: 
so you can have rules that define rights & responsibilities of the roles within your organisation.
1 account = 1 vote + gated access to roles. There’s no token voting here…you can’t buy votes.
Only vote on proposed actions for your roles.
It’s decision making and deliberation side by side, on one interface.
Only discuss actions for your roles.
All actions (including votes) go through this governance system, and so they are logged.
Everything is transparent and time-stamped on-chain.
This could reduce a lot of workload, you could run scripts to analyse data about decisions that you’ve made. You can follow where the money goes.
And lastly, it’s accessible - we have native Account Abstraction support.

6:
Here are our offerings, separated into 3 tiers.
1: free, with basic access to our catalogue of mandates which would have been pre-audited. To help us build the organisations, which previously took months, we built a Claude skill called /design-org —> it knows all about governance because we used the RAG technique where it retrieves all kinds of info from academic literature such as Elinor Ostrom’s polycentric governance and more. It can even specialise to your jurisdiction, and can pull from specific information such as different types of legal entities in the UK where it would look at legislation such as the the Companies Act 2006.
Tier 2: Having paid for credits, you can have full access to our whole catalogue of mandates where you can build out your organisation’s governance infrastructure and then once built, we won’t charge for running costs but if you do want to reform your organisation at a later date then you’ll need to purchase more credits to continue building.
Tier 3: We are a consultancy who offers bespoke solutions and legal advice on compliance.

7: Fast, affordable, has liquidity. That brings in lots of founders who are our target customer.

8: So for crypto startups we bring normal governance to crypto where governance infrastructure is lacking. But this could also be applied to the wider field: We bring crypto trust to governance where social and institutional trust is lacking.

9: You can go to our website to explore different use-cases for Powers Protocol, view them as example organisations in our front end interface, and read our documentation.

We hope to hear from you soon!
23 changes: 14 additions & 9 deletions frontend/app/forum/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import { useEffectiveAddress } from "@/hooks/useEffectiveAddress";

import { NavigationDropdownMenu } from './NavigationDropdownMenu';
import { ThemeToggle } from '@/components/ThemeToggle';
import { ShareQrButton } from '@/components/ShareQrButton';
import { ChevronRightIcon } from "@heroicons/react/24/solid";
import { useAddressDisplay } from "@/hooks/useAddressDisplay";

Expand Down Expand Up @@ -110,7 +111,7 @@ export default function ForumLayout({ children }: Readonly<{ children: React.Rea

return (
<div className="h-screen w-screen flex flex-col bg-background scanlines overflow-hidden">
<header className="hidden sm:flex w-full flex-col items-center border-b border-border px-3 sm:px-4 py-4 flex-shrink-0">
<header className="hidden min-[700px]:flex w-full flex-col items-center border-b border-border px-3 sm:px-4 py-4 flex-shrink-0">
<div className="w-full flex flex-nowrap items-center justify-between max-w-4xl gap-2 sm:gap-3">
<div className="flex items-center gap-2 sm:gap-4">
<Link href="/forum" className="font-mono text-base sm:text-lg text-foreground tracking-wider truncate hover:text-foreground/80 transition-colors">{
Expand Down Expand Up @@ -158,6 +159,7 @@ export default function ForumLayout({ children }: Readonly<{ children: React.Rea
<span className="text-muted-foreground">NOT CONNECTED</span>
</button>
}
<ShareQrButton />
<ThemeToggle />
</div>
</div>
Expand Down Expand Up @@ -194,7 +196,7 @@ export default function ForumLayout({ children }: Readonly<{ children: React.Rea
</div>

{/* Hamburger button - only visible on mobile */}
<div className="flex items-center gap-1 sm:hidden">
<div className="flex items-center gap-1 min-[700px]:hidden">
<button
onClick={() => setMobileMenuOpen(true)}
className="p-2 text-foreground hover:text-foreground/80 transition-colors cursor-pointer"
Expand All @@ -215,7 +217,7 @@ export default function ForumLayout({ children }: Readonly<{ children: React.Rea

{/* Mobile Slide-out Menu */}
<div
className={`fixed inset-0 z-50 sm:hidden transition-transform duration-300 ease-in-out ${
className={`fixed inset-0 z-50 min-[700px]:hidden transition-transform duration-300 ease-in-out ${
mobileMenuOpen ? 'translate-x-0' : 'translate-x-full'
}`}
>
Expand All @@ -236,9 +238,6 @@ export default function ForumLayout({ children }: Readonly<{ children: React.Rea

{/* Menu content */}
<div className="flex-1 overflow-y-auto p-4 space-y-6">
{/* Divider */}
<div className="border-t border-border" />

{/* Wallet Connection */}
<div className="space-y-3">
<span className="text-xs text-muted-foreground font-mono uppercase">Wallet</span>
Expand Down Expand Up @@ -297,9 +296,6 @@ export default function ForumLayout({ children }: Readonly<{ children: React.Rea
{/* Divider */}
<div className="border-t border-border" />

{/* Divider */}
<div className="border-t border-border" />

{/* Theme Toggle */}
<div className="space-y-3">
<span className="text-xs text-muted-foreground font-mono uppercase">Theme</span>
Expand All @@ -308,6 +304,15 @@ export default function ForumLayout({ children }: Readonly<{ children: React.Rea
<span className="text-sm font-mono text-muted-foreground">Toggle theme</span>
</div>
</div>

{/* Divider */}
<div className="border-t border-border" />

{/* Share */}
<div className="space-y-3">
<span className="text-xs text-muted-foreground font-mono uppercase">Share</span>
<ShareQrButton variant="menu" />
</div>
</div>
</div>
</div>
Expand Down
22 changes: 1 addition & 21 deletions frontend/app/globals.css
Original file line number Diff line number Diff line change
Expand Up @@ -71,26 +71,6 @@ html, body {
50% { box-shadow: 0 0 8px 2px rgba(205, 94, 32, 0.5); }
}

.scanlines {
background-image: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(0, 0, 0, 0.015) 2px,
rgba(0, 0, 0, 0.015) 4px
);
}

.dark .scanlines {
background-image: repeating-linear-gradient(
0deg,
transparent,
transparent 2px,
rgba(255, 255, 255, 0.02) 2px,
rgba(255, 255, 255, 0.02) 4px
);
}

.border-pulse {
border-color: #CD5E20;
animation: border-pulse 2.5s ease-in-out infinite;
Expand All @@ -110,7 +90,7 @@ body {
padding-top: env(safe-area-inset-top); /* Standard */
padding-bottom: constant(safe-area-inset-bottom); /* iOS < 11.2 */
padding-bottom: env(safe-area-inset-bottom); /* Standard */
background: #f1f5f9; /* slate-100 */
background: hsl(var(--background));
font-family: 'Commit Mono', 'Courier New', monospace;
color: var(--text-color);
display: flex;
Expand Down
6 changes: 6 additions & 0 deletions frontend/app/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,12 @@ export const viewport: Viewport = {
initialScale: 1,
maximumScale: 1,
userScalable: false,
viewportFit: "cover",
// NOTE: theme-color is managed dynamically by <ThemeColorMeta/> so it can
// follow next-themes' resolvedTheme. Do NOT also declare `themeColor` here:
// that makes React/Next render its own theme-color meta nodes, which
// ThemeColorMeta then removes from the DOM, causing a "removeChild of null"
// crash in React's commit phase on the next navigation.
};

export default function RootLayout({ children }: Readonly<{ children: React.ReactNode }>) {
Expand Down
6 changes: 4 additions & 2 deletions frontend/app/overview/[chainId]/[powers]/FlowNodes.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ import {
} from 'reactflow'
import { Mandate, Powers, Action, Status, Flow } from '@/context/types'
import { toFullDateFormat, toEurTimeFormat } from '@/utils/toDates'
import { useBlocks } from '@/hooks/useBlocks'
import { useBlocks, L2_TO_L1_CHAIN_MAP } from '@/hooks/useBlocks'
import { parseChainId } from '@/utils/parsers'
import { fromFutureBlockToDateTime } from '@/public/organisations/helpers'
import { useBlockNumber } from 'wagmi'
Expand Down Expand Up @@ -108,7 +108,9 @@ export interface MandateSchemaNodeData {
const MandateSchemaNode: React.FC<NodeProps<MandateSchemaNodeData>> = ({ data }) => {
const { mandate, powers, onNodeClick, chainActionData, chainId, isHighlighted, isDimmed } = data
const { timestamps, fetchTimestamps } = useBlocks()
const { data: blockNumber } = useBlockNumber()
const parsedChainIdForBlock = parseChainId(chainId) as number
const blockChainId = (L2_TO_L1_CHAIN_MAP[parsedChainIdForBlock] ?? parsedChainIdForBlock) as number
const { data: blockNumber } = useBlockNumber({ chainId: blockChainId })
const cond = mandate.conditions

const nameParts = mandate.nameDescription?.split(':') ?? []
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ import { parseProposalStatus, shorterDescription } from "@/utils/parsers";
import { toFullDateFormat, toEurTimeFormat } from "@/utils/toDates";
import { useBlocks } from "@/hooks/useBlocks";
import { OrgBanner } from "@/components/OrgBanner";
import { callDataToActionParams } from "@/utils/callDataToActionParams";

const STATE_STYLES: Record<number, string> = {
0: 'text-muted-foreground bg-muted',
Expand Down Expand Up @@ -48,6 +49,16 @@ export default function ActionDetailPage() {
const primaryOccurrence = occurrences[0]?.action
const primaryMandate = powers?.mandates?.find(m => m.index === occurrences[0]?.mandateId)

// Decode the encoded callData into displayable input values against the mandate's params
const decodedInputs = React.useMemo(() => {
if (!primaryOccurrence?.callData || !primaryMandate?.params?.length) return []
try {
return callDataToActionParams(primaryOccurrence, powers)
} catch {
return []
}
}, [primaryOccurrence?.callData, primaryMandate?.index, powers])

useEffect(() => {
if (occurrences.length > 0) {
setHighlightMode({
Expand Down Expand Up @@ -100,9 +111,7 @@ export default function ActionDetailPage() {
<span className="text-muted-foreground w-32 truncate">{param.varName}</span>
<span className="text-foreground/50 text-[10px]">{param.dataType}</span>
<span className="text-foreground ml-auto truncate max-w-48">
{primaryOccurrence.paramValues?.[i] !== undefined
? String(primaryOccurrence.paramValues[i])
: '—'}
{decodedInputs[i] !== undefined ? String(decodedInputs[i]) : '—'}
</span>
</div>
))}
Expand Down
2 changes: 2 additions & 0 deletions frontend/app/overview/layout.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ import { usePowers } from "@/hooks/usePowers";
import { usePowersLive } from "@/hooks/usePowersLive";
import { parseChainId } from "@/utils/parsers";
import { ThemeToggle } from '@/components/ThemeToggle';
import { ShareQrButton } from '@/components/ShareQrButton';
import { useAddressDisplay } from "@/hooks/useAddressDisplay";
import { ArrowRightStartOnRectangleIcon, CheckCircleIcon, ArrowLeftIcon } from '@heroicons/react/24/outline';

Expand Down Expand Up @@ -134,6 +135,7 @@ export default function OverviewLayout({ children }: OverviewLayoutProps) {
</Link>
</div>
<div className="flex items-center gap-2 sm:gap-4 flex-shrink-0">
<ShareQrButton />
<ThemeToggle />
</div>
</div>
Expand Down
113 changes: 113 additions & 0 deletions frontend/components/ShareQrButton.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,113 @@
'use client'

import { useEffect, useState } from 'react';
import { usePathname } from 'next/navigation';
import QRCode from 'react-qr-code';
import { QrCodeIcon, XMarkIcon, ClipboardDocumentIcon, CheckIcon } from '@heroicons/react/24/outline';
import { ForumModal } from '@/components/ForumModal';

interface ShareQrButtonProps {
/**
* 'icon' - a bordered icon button for the desktop nav bar (default).
* 'menu' - a full-width row with icon + label for the mobile slide-out menu.
*/
variant?: 'icon' | 'menu';
}

/**
* ShareQrButton - Shows a QR code of the current page URL in a popup, with the
* full URL written out underneath and a "copy link" button. The URL is built
* client-side (window.location.origin + pathname) so it reflects the exact page
* the user is on.
*/
export function ShareQrButton({ variant = 'icon' }: ShareQrButtonProps) {
const pathname = usePathname();
const [open, setOpen] = useState(false);
const [url, setUrl] = useState('');
const [copied, setCopied] = useState(false);

// Resolve the full URL client-side whenever the modal opens or the path
// changes. window is undefined during SSR, so this must stay in an effect.
useEffect(() => {
if (open && typeof window !== 'undefined') {
// eslint-disable-next-line react-hooks/set-state-in-effect
setUrl(`${window.location.origin}${pathname}`);
}
}, [open, pathname]);

const copyUrl = () => {
if (!url) return;
navigator.clipboard.writeText(url);
setCopied(true);
setTimeout(() => setCopied(false), 2000);
};

return (
<>
{variant === 'icon' ? (
<button
onClick={() => setOpen(true)}
className="p-2 border border-border hover:bg-foreground hover:text-background transition-colors cursor-pointer"
aria-label="Share this page"
>
<QrCodeIcon className="h-4 w-4" />
</button>
) : (
<button
onClick={() => setOpen(true)}
className="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground font-mono transition-colors cursor-pointer"
aria-label="Share this page"
>
<QrCodeIcon className="h-4 w-4" />
<span>Share this page</span>
</button>
)}

<ForumModal open={open} onOpenChange={setOpen} className="font-mono max-w-sm">
<div className="flex items-center justify-between mb-4">
<h3 className="text-sm text-foreground uppercase tracking-wider">Share this page</h3>
<button
onClick={() => setOpen(false)}
className="p-1 text-muted-foreground hover:text-foreground transition-colors cursor-pointer"
aria-label="Close"
>
<XMarkIcon className="h-5 w-5" />
</button>
</div>

{/* QR code on a fixed white panel so it scans in both light and dark themes */}
<div className="flex items-center justify-center">
<div className="bg-white p-4">
{url ? (
<QRCode value={url} size={192} />
) : (
<div className="h-48 w-48" />
)}
</div>
</div>

{/* Full URL written out underneath */}
<div className="mt-4 border border-border p-3">
<p className="font-mono text-xs break-all text-muted-foreground">{url}</p>
</div>

<button
onClick={copyUrl}
className="mt-3 w-full flex items-center justify-center gap-2 px-3 py-2 border border-border cursor-pointer hover:bg-foreground hover:text-background transition-all text-xs uppercase font-mono leading-none"
>
{copied ? (
<>
<CheckIcon className="h-4 w-4 text-green-500" />
<span className="leading-none">Copied</span>
</>
) : (
<>
<ClipboardDocumentIcon className="h-4 w-4" />
<span className="leading-none">Copy link</span>
</>
)}
</button>
</ForumModal>
</>
);
}
12 changes: 7 additions & 5 deletions frontend/components/ThemeColorMeta.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,13 @@ export function ThemeColorMeta() {
const { resolvedTheme } = useTheme();

useEffect(() => {
// Remove any existing theme-color meta tags
const existingMeta = document.querySelector('meta[name="theme-color"]');
if (existingMeta) {
existingMeta.remove();
}
// Remove any theme-color meta tags this component previously created so we
// never leave duplicates. (The viewport export intentionally does NOT emit a
// theme-color meta — see app/layout.tsx — so these are only our own nodes,
// never React-managed ones.)
document
.querySelectorAll('meta[name="theme-color"]')
.forEach((m) => m.remove());

// Create new meta tag with the appropriate color
const meta = document.createElement("meta");
Expand Down
Loading
Loading