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
26 changes: 25 additions & 1 deletion app/api/dao-overview/route.ts
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,16 @@ const QUORUM_ABI = parseAbi([
"function quorumNumerator() view returns (uint256)",
"function quorumDenominator() view returns (uint256)",
]);
// LightChain's Governor overrides quorum() to use INativeVotes.getTotalVotingPower
// (the STAKED-EXCLUDED supply), not IVotes.getPastTotalSupply. So the real quorum
// is 3% of the votable base, not 3% of raw supply. Read both to show the truth.
const NATIVE_QUORUM_ABI = parseAbi([
"function quorum(uint256 timepoint) view returns (uint256)",
"function clock() view returns (uint48)",
]);
const NATIVE_VOTES_ABI = parseAbi([
"function getTotalVotingPower(uint256 timepoint) view returns (uint256)",
]);
const SCHEDULE_ABI = parseAbi([
"function votingDelay() view returns (uint256)",
"function votingPeriod() view returns (uint256)",
Expand Down Expand Up @@ -107,16 +117,30 @@ async function readEthereumOverview(pub: Pub) {
async function readLightchainOverview(pub: Pub) {
const a = DAO_ADDRESSES.lightchain;
// Native balances: treasury + FeePool hold native LCAI (no ERC-20 wrapper).
const [treasuryWei, feePoolWei, quorum] = await Promise.all([
const [treasuryWei, feePoolWei, quorum, clockRaw] = await Promise.all([
pub.getBalance({ address: a.treasury }).catch(() => 0n),
pub.getBalance({ address: FEE_POOL }).catch(() => 0n),
readQuorumConfig(pub, a.governor),
pub.readContract({ address: a.governor, abi: NATIVE_QUORUM_ABI, functionName: "clock" }).catch(() => 0n),
]);
// Evaluate the real quorum + votable base at the latest finalized timepoint.
const clock = BigInt(clockRaw as bigint | number);
const ref = clock > 0n ? clock - 1n : 0n;
const [quorumWei, votableWei] = await Promise.all([
pub.readContract({ address: a.governor, abi: NATIVE_QUORUM_ABI, functionName: "quorum", args: [ref] }).catch(() => null),
a.ballots
? pub.readContract({ address: a.ballots, abi: NATIVE_VOTES_ABI, functionName: "getTotalVotingPower", args: [ref] }).catch(() => null)
: Promise.resolve(null),
]);
return {
treasuryWei: treasuryWei.toString(),
feePoolWei: feePoolWei.toString(),
voteToken: { address: a.ballots, symbol: "LCAI", totalSupplyWei: null as string | null },
quorum,
// The actual quorum threshold + the staked-excluded base it applies to.
quorumWei: quorumWei != null ? (quorumWei as bigint).toString() : null,
votableSupplyWei: votableWei != null ? (votableWei as bigint).toString() : null,
stakeExcludedFromQuorum: true as boolean,
};
}

Expand Down
24 changes: 20 additions & 4 deletions app/build/dao/treasury-bar.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,11 @@ interface Overview {
feePoolWei: string | null;
voteToken: { address: string; symbol: string; totalSupplyWei: string | null };
quorum: { numerator: string; denominator: string };
// LightChain only: the actual on-chain quorum threshold + the staked-excluded
// base it applies to (worker + validator stake are not in the votable supply).
quorumWei?: string | null;
votableSupplyWei?: string | null;
stakeExcludedFromQuorum?: boolean;
schedule?: Schedule;
error?: string;
}
Expand Down Expand Up @@ -89,10 +94,21 @@ export function TreasuryBar({ chain }: { chain: DaoChain }) {
const qPct = quorumPercent(data.quorum.numerator, data.quorum.denominator);
const supply = data.voteToken.totalSupplyWei ? `${formatLcaiWei(BigInt(data.voteToken.totalSupplyWei), 0)} ${sym}` : "native stake";

const quorumValue = qPct ? `${qPct % 1 === 0 ? qPct : qPct.toFixed(1)}% of supply` : "n/a";
const quorumHint = data.voteToken.totalSupplyWei
? `${quorumValue} - needs ${formatLcaiWei((BigInt(data.voteToken.totalSupplyWei) * BigInt(data.quorum.numerator)) / BigInt(data.quorum.denominator || "1"), 0)} ${VOTE_SYMBOL[chain]} of For+Abstain to be valid`
: `${quorumValue} of the native vote supply`;
const pctLabel = qPct ? `${qPct % 1 === 0 ? qPct : qPct.toFixed(1)}%` : "3%";
// LightChain reports the REAL quorum (quorumWei): 3% of the staked-excluded
// votable supply, not "% of supply". Worker + validator stake are not in the
// votable base. Fall back to the "% of supply" label only where no real quorum
// amount is available (e.g. Ethereum's ERC20Votes reads).
const quorumValue = data.quorumWei
? `${formatLcaiWei(BigInt(data.quorumWei), 0)} ${VOTE_SYMBOL[chain]}`
: qPct
? `${pctLabel} of supply`
: "n/a";
const quorumHint = data.quorumWei
? `${pctLabel} of the votable supply${data.stakeExcludedFromQuorum ? " (staked LCAI excluded)" : ""}${data.votableSupplyWei ? ` = ${formatLcaiWei(BigInt(data.votableSupplyWei), 0)} ${VOTE_SYMBOL[chain]} base` : ""} - needs this much For+Abstain to pass`
: data.voteToken.totalSupplyWei
? `${quorumValue} - needs ${formatLcaiWei((BigInt(data.voteToken.totalSupplyWei) * BigInt(data.quorum.numerator)) / BigInt(data.quorum.denominator || "1"), 0)} ${VOTE_SYMBOL[chain]} of For+Abstain to be valid`
: `${quorumValue} of the native vote supply`;
return (
<div className="space-y-3 rounded-2xl border border-bdr-soft bg-card/60 px-4 py-3.5 backdrop-blur-sm">
<div className="grid grid-cols-2 gap-4 sm:grid-cols-4">
Expand Down
Loading