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
12 changes: 6 additions & 6 deletions app/build/dao/cast-vote.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,7 @@ const CHOICES: { label: string; support: Support; cls: string }[] = [
];

export function CastVote({ chain, proposalId, onVoted }: { chain: DaoChain; proposalId: string; onVoted?: () => void }) {
const { address, isConnected, walletClient, open, onTargetChain, ensureChain } = useDaoWallet(chain);
const { address, isConnected, open, getSigner } = useDaoWallet(chain);
const [voted, setVoted] = useState<boolean | null>(null);
const [powerWei, setPowerWei] = useState<bigint | null>(null);
const [vote, setVote] = useState<Vote>({ phase: "idle" });
Expand Down Expand Up @@ -55,12 +55,12 @@ export function CastVote({ chain, proposalId, onVoted }: { chain: DaoChain; prop
}, [isConnected, address, chain, proposalId, check]);

const castVote = async (support: Support) => {
if (!walletClient || !address) return open();
setVote({ phase: "working", support, msg: onTargetChain ? "Confirm your vote in your wallet..." : "Switch network in your wallet..." });
if (!isConnected || !address) return open();
setVote({ phase: "working", support, msg: "Confirm in your wallet (you may be asked to switch network first)..." });
try {
await ensureChain();
const signer = await getSigner();
const fees = chain === "lightchain" ? await pinnedFees(daoPublicClient(chain)) : undefined;
const hash = await walletClient.writeContract({
const hash = await signer.writeContract({
address: GOVERNOR[chain],
abi: GOVERNOR_ABI,
functionName: "castVote",
Expand All @@ -78,7 +78,7 @@ export function CastVote({ chain, proposalId, onVoted }: { chain: DaoChain; prop
};

return (
<div className="rounded-lg border border-primary/25 bg-primary/[0.06] p-3">
<div className="rounded-lg border border-primary/25 bg-primary/6 p-3">
<p className="mb-2 text-[10px] font-semibold uppercase tracking-wider text-content-soft">Cast your vote</p>
<CastBody
connected={isConnected && !!address}
Expand Down
30 changes: 20 additions & 10 deletions app/build/dao/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -44,19 +44,29 @@ interface DaoResponse {
proposals: Proposal[];
}

const SNIPPET = `import { createPublicClient, http } from "viem";
function snippetFor(chain: DaoChain): string {
const rpcVar = chain === "ethereum" ? "ETH_RPC" : "LIGHTCHAIN_RPC";
const tokenNote =
chain === "ethereum"
? `// Ethereum: LCAIB is an ERC20Votes token - delegate once to activate power.`
: `// LightChain: native voting via the genesis predeploy - stake self-delegates.`;
return `import { createPublicClient, http } from "viem";
import { DAO } from "lightnode-sdk";

const dao = new DAO(createPublicClient({ transport: http(ETH_RPC) }), "ethereum");
${tokenNote}
const dao = new DAO(createPublicClient({ transport: http(${rpcVar}) }), "${chain}");

// Read a proposal + your standing:
const p = await dao.proposal(proposalId); // state, tallies, deadline
const quorum = await dao.quorum(p.snapshot); // wei needed to pass
const power = await dao.getVotes(me, p.snapshot); // your weight at snapshot
// Live reads (no wallet needed):
const p = await dao.proposal(proposalId); // state, tallies, snapshot
const quorum = await dao.quorum(p.snapshot); // wei needed to reach quorum
const power = await dao.getVotes(me, p.snapshot); // your weight at the snapshot
const voted = await dao.hasVoted(proposalId, me); // already voted?
const delegate = await dao.getDelegate(me); // who holds your voting power

// Writes sign with your wallet (pass a wallet client):
// await dao.delegate(me); // activate your voting power
// await dao.castVote(proposalId, 1); // 0 against, 1 for, 2 abstain`;
// Writes sign with your wallet: new DAO(rpc, "${chain}", walletClient)
// await dao.delegate(me); // activate your voting power
// await dao.castVote(proposalId, 1); // 0 against, 1 for, 2 abstain`;
}

function toneFor(label: string): "brand" | "success" | "danger" | "warning" | "muted" {
const l = label.toLowerCase();
Expand Down Expand Up @@ -281,7 +291,7 @@ export default function DaoPanel() {

<section className="space-y-3">
<h2 className="text-sm font-semibold uppercase tracking-wider text-content-soft">The SDK behind it</h2>
<CodeTabs tabs={[{ label: "TypeScript", code: SNIPPET }]} />
<CodeTabs tabs={[{ label: "TypeScript", code: snippetFor(chain) }]} />
</section>
</div>
);
Expand Down
24 changes: 18 additions & 6 deletions app/build/dao/use-dao-wallet.ts
Original file line number Diff line number Diff line change
@@ -1,34 +1,46 @@
"use client";

import { useAccount, useWalletClient, useSwitchChain, useChainId } from "wagmi";
import { useAccount, useSwitchChain, useChainId } from "wagmi";
import { getWalletClient } from "@wagmi/core";
import { useAppKit } from "@reown/appkit/react";
import { wagmiConfig } from "@/lib/wagmi";
import { DAO_CHAIN_ID, type DaoChain } from "./dao-chain";

/**
* Shared wallet wiring for the DAO write controls (delegate, cast vote). Uses
* `open()` from AppKit for connect rather than the global ConnectButton, which
* treats Ethereum as "unsupported" and would nag the user back to LightChain.
*
* `getSigner` is the load-bearing piece: the app's network toggle drives wagmi's
* tracked chain (e.g. LightChain 9200) independently of the wallet's real chain,
* so the reactive `useWalletClient()` can stay bound to the wrong chain and viem
* then rejects the write ("wallet chain ... does not match target chain ..."). We
* instead switch to the target chain and fetch a FRESH client bound to it from
* the connector's actual state.
*/
export function useDaoWallet(chain: DaoChain) {
const { address, isConnected } = useAccount();
const { data: walletClient } = useWalletClient();
const { switchChainAsync } = useSwitchChain();
const { open } = useAppKit();
const chainId = useChainId();
const targetChainId = DAO_CHAIN_ID[chain];

const ensureChain = async () => {
if (chainId !== targetChainId) await switchChainAsync({ chainId: targetChainId });
const getSigner = async () => {
// Switching to the chain you're already on is a no-op (no wallet prompt),
// and it realigns wagmi's tracked chain with the connector before we read it.
await switchChainAsync({ chainId: targetChainId });
const signer = await getWalletClient(wagmiConfig, { chainId: targetChainId });
if (!signer) throw new Error("Could not get a wallet client for this network. Reconnect and try again.");
return signer;
};

return {
address,
isConnected,
walletClient,
open,
chainId,
targetChainId,
onTargetChain: chainId === targetChainId,
ensureChain,
getSigner,
};
}
19 changes: 14 additions & 5 deletions app/build/dao/voting-power-card.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ type Tx = { phase: "idle" | "working" | "submitted" | "confirmed" | "error"; msg
const SYMBOL: Record<DaoChain, string> = { ethereum: "LCAIB", lightchain: "LCAI" };

export function VotingPowerCard({ chain }: { chain: DaoChain }) {
const { address, isConnected, walletClient, open, onTargetChain, ensureChain } = useDaoWallet(chain);
const { address, isConnected, open, getSigner } = useDaoWallet(chain);
const [reads, setReads] = useState<VotingPowerReads | null>(null);
const [loading, setLoading] = useState(false);
const [tx, setTx] = useState<Tx>({ phase: "idle" });
Expand All @@ -47,12 +47,12 @@ export function VotingPowerCard({ chain }: { chain: DaoChain }) {
}, [isConnected, address, chain, refresh]);

const delegateToSelf = async () => {
if (!walletClient || !address) return open();
setTx({ phase: "working", msg: onTargetChain ? "Confirm the delegation in your wallet..." : "Switch network in your wallet..." });
if (!isConnected || !address) return open();
setTx({ phase: "working", msg: "Confirm in your wallet (you may be asked to switch network first)..." });
try {
await ensureChain();
const signer = await getSigner();
const fees = chain === "lightchain" ? await pinnedFees(daoPublicClient(chain)) : undefined;
const hash = await walletClient.writeContract({
const hash = await signer.writeContract({
address: VOTE_TOKEN[chain],
abi: VOTES_ABI,
functionName: "delegate",
Expand Down Expand Up @@ -137,6 +137,15 @@ function DelegationRow({
</p>
);
}
// Nothing to delegate: holding zero balance on this chain. Don't push a
// pointless "delegate to activate" prompt.
if (reads.balanceWei === 0n) {
return (
<p className="mt-3 text-xs text-content-soft">
You hold no {SYMBOL[chain]} on this chain, so you have no voting power here.
</p>
);
}
const msg =
status.kind === "undelegated"
? `You hold ${formatLcaiWei(reads.balanceWei, 2)} ${SYMBOL[chain]} but 0 voting power. Delegate to yourself to activate it.`
Expand Down
Loading