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
180 changes: 0 additions & 180 deletions app/build/dao/cast-vote.tsx

This file was deleted.

60 changes: 15 additions & 45 deletions app/build/dao/dao-chain.ts
Original file line number Diff line number Diff line change
@@ -1,13 +1,15 @@
/**
* Client-side DAO chain config + on-chain reads shared by the voting-power and
* cast-vote controls. Both governance chains expose the same OZ Governor surface
* but different vote tokens:
* Client-side DAO chain config + on-chain reads for the governance-intelligence
* panel. lightnode reads + decodes governance state; the actual vote/delegate
* transactions happen on LightChain's official UIs (see DAO_VOTE_UI), so there
* are no writes here. The two chains share the OZ Governor surface but differ on
* the vote token:
* - Ethereum -> LCAIB, an ERC20Votes token (balanceOf works).
* - LightChain -> a native genesis predeploy (balanceOf reverts; voting weight
* tracks the native LCAI balance, so we read getBalance there instead).
*/
import { createPublicClient, http } from "viem";
import { DAO_ADDRESSES, GOVERNOR_ABI, VOTES_ABI } from "lightnode-sdk";
import { DAO_ADDRESSES, VOTES_ABI } from "lightnode-sdk";
import { NETWORKS } from "@/lib/network";

export type DaoChain = "ethereum" | "lightchain";
Expand All @@ -17,11 +19,6 @@ export const DAO_RPC: Record<DaoChain, string> = {
lightchain: NETWORKS.mainnet.rpc,
};

export const DAO_CHAIN_ID: Record<DaoChain, number> = {
ethereum: 1,
lightchain: NETWORKS.mainnet.chainId,
};

export const DAO_EXPLORER: Record<DaoChain, string> = {
ethereum: "https://etherscan.io",
lightchain: NETWORKS.mainnet.explorer,
Expand All @@ -33,11 +30,6 @@ export const VOTE_TOKEN: Record<DaoChain, `0x${string}`> = {
lightchain: DAO_ADDRESSES.lightchain.ballots as `0x${string}`,
};

export const GOVERNOR: Record<DaoChain, `0x${string}`> = {
ethereum: DAO_ADDRESSES.ethereum.governor,
lightchain: DAO_ADDRESSES.lightchain.governor,
};

// Both governors count voting in BLOCKS (CLOCK_MODE=blocknumber), so block time
// is needed to turn votingDelay/votingPeriod + deadline distance into real time.
// Measured on mainnet: Ethereum ~12.04s (slot time), LightChain ~6.00s.
Expand All @@ -46,6 +38,15 @@ export const SECONDS_PER_BLOCK: Record<DaoChain, number> = {
lightchain: 6,
};

// lightnode is an ecosystem intelligence layer, NOT a governance app: the actual
// vote / delegate / execute transactions happen on LightChain's own official UIs.
// We read + decode everything here and link out for the writes.
export const DAO_VOTE_UI = "https://dao.lightchain.ai";
export const DELEGATION_UI: Record<DaoChain, string | null> = {
ethereum: "https://ballots.lightchain.ai", // wrap LCAI -> LCAIB + delegate
lightchain: null, // native voting self-delegates; no wrapper UI
};

export function daoPublicClient(chain: DaoChain) {
return createPublicClient({ transport: http(DAO_RPC[chain]) });
}
Expand Down Expand Up @@ -78,34 +79,3 @@ export async function loadVotingPower(chain: DaoChain, address: `0x${string}`):
]);
return { votesWei, delegate, balanceWei };
}

// LightChain's gas is tiny enough that MetaMask renders the fee red and blocks
// confirmation unless we pin chain-estimated values. Returns one fee arm or the
// other (never both) so viem's writeContract accepts it. Ethereum renders fine
// natively, so callers only pin for LightChain.
export type PinnedFees = { maxFeePerGas: bigint; maxPriorityFeePerGas: bigint } | { gasPrice: bigint } | undefined;

export async function pinnedFees(pub: ReturnType<typeof daoPublicClient>): Promise<PinnedFees> {
try {
const f = await pub.estimateFeesPerGas();
return f?.maxFeePerGas
? { maxFeePerGas: f.maxFeePerGas, maxPriorityFeePerGas: f.maxPriorityFeePerGas ?? f.maxFeePerGas }
: { gasPrice: await pub.getGasPrice() };
} catch {
try {
return { gasPrice: await pub.getGasPrice() };
} catch {
return undefined;
}
}
}

export async function readHasVoted(chain: DaoChain, proposalId: bigint, address: `0x${string}`): Promise<boolean> {
const pub = daoPublicClient(chain);
return pub.readContract({
address: GOVERNOR[chain],
abi: GOVERNOR_ABI,
functionName: "hasVoted",
args: [proposalId, address],
}) as Promise<boolean>;
}
32 changes: 26 additions & 6 deletions app/build/dao/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,18 +2,17 @@

import { useCallback, useEffect, useState } from "react";
import Image from "next/image";
import { Loader2, RefreshCw, ChevronDown, ExternalLink } from "lucide-react";
import { Loader2, RefreshCw, ChevronDown, ExternalLink, Landmark, Vote } from "lucide-react";
import { ConsolePanel } from "@/components/build/console/panel";
import { CodeTabs } from "@/components/build/console/code-tabs";
import { Notice, short } from "@/components/build/console/panel-kit";
import { Badge } from "@/components/ui/badge";
import { cn } from "@/lib/utils";
import { DAO_EXPLORER, SECONDS_PER_BLOCK, type DaoChain } from "./dao-chain";
import { DAO_EXPLORER, DAO_VOTE_UI, SECONDS_PER_BLOCK, type DaoChain } from "./dao-chain";
import { humanizeDuration } from "./dao-math";
import { TreasuryBar } from "./treasury-bar";
import { VotingPowerCard } from "./voting-power-card";
import { QuorumLine } from "./quorum-line";
import { CastVote } from "./cast-vote";

const CHAIN_META: Record<DaoChain, { label: string; icon: string }> = {
ethereum: { label: "Ethereum", icon: "/logos/eth.svg" },
Expand Down Expand Up @@ -167,8 +166,8 @@ export default function DaoPanel() {
<div className="space-y-10">
<ConsolePanel
kicker="Capability · DAO"
title="Governance"
subtitle="Read live LCAIGovernor proposals (OpenZeppelin Governor v5) and their on-chain vote tallies. Casting votes, proposing, queueing, and executing sign with your wallet - shown in the snippet."
title="Governance intelligence"
subtitle="An ecosystem lens over LightChain governance - decoded proposal actions, quorum distance, treasury flow, lifecycle timing, and your on-chain standing, read straight from the registries. Voting and delegation happen on LightChain's own DAO; lightnode surfaces what the official tools don't and links you there to act."
actions={
<div className="flex items-center gap-2">
<div className="inline-flex items-center rounded-full border border-bdr-soft bg-surface-base-subtle p-0.5 text-xs font-medium">
Expand Down Expand Up @@ -206,6 +205,20 @@ export default function DaoPanel() {
}
>
<div className="mb-4 space-y-3">
<a
href={DAO_VOTE_UI}
target="_blank"
rel="noopener noreferrer"
className="flex items-center justify-between gap-3 rounded-2xl border border-primary/25 bg-primary/6 px-4 py-3 transition-colors hover:border-primary/40"
>
<span className="flex items-center gap-2.5 text-sm text-content-default">
<Landmark className="size-4 shrink-0 text-primary" />
Vote, delegate, and execute on the official LightChain DAO
</span>
<span className="inline-flex shrink-0 items-center gap-1 text-xs font-semibold text-primary">
dao.lightchain.ai <ExternalLink className="size-3.5" />
</span>
</a>
<TreasuryBar chain={chain} />
<VotingPowerCard chain={chain} />
</div>
Expand Down Expand Up @@ -282,7 +295,14 @@ export default function DaoPanel() {
<QuorumLine chain={chain} votesFor={p.votesFor} votesAbstain={p.votesAbstain} quorumWei={p.quorumWei} />

{p.stateLabel.toLowerCase() === "active" && (
<CastVote chain={chain} proposalId={p.id} onVoted={() => void load(chain, limit)} />
<a
href={DAO_VOTE_UI}
target="_blank"
rel="noopener noreferrer"
className="inline-flex items-center gap-1.5 rounded-lg bg-gradient-primary px-3.5 py-2 text-xs font-semibold text-white shadow-[0_2px_10px_-2px_rgba(112,100,233,0.6)] transition-all hover:brightness-110"
>
<Vote className="size-3.5" /> Vote on this proposal at the LightChain DAO <ExternalLink className="size-3" />
</a>
)}

{(() => {
Expand Down
46 changes: 0 additions & 46 deletions app/build/dao/use-dao-wallet.ts

This file was deleted.

Loading
Loading