> For the complete documentation index, see [llms.txt](https://docs.realms.today/llms.txt). Markdown versions of documentation pages are available by appending `.md` to page URLs; this page is available as [Markdown](https://docs.realms.today/developer-resources/custom-ui.md).

# Custom UI

## Custom UI Integration

Integrate Realms components into your application, and work with the Realms UI codebase.

### Important: Repository Access

* **SPL Governance** (the on-chain program and Rust/TS SDKs) is **open source** and freely available in this repository.
* **Realms UI** (the frontend application at realms.today) is a **private repository**. To access the UI codebase for integration, embedding, or forking purposes, **contact the Realms team** for repository access.

**Contact for Realms UI access:**

* Discord: [Realms](https://discord.gg/VsPbrK2hJk)

### Building Your Own Governance UI

#### Using the SDK Directly

You can build a complete governance UI using only the `@realms-today/spl-governance` SDK and `@solana/web3.js`:

```bash
npm install @realms-today/spl-governance @solana/web3.js
```

**Display Realm Info**

```typescript
import { getRealm, getAllGovernances } from '@realms-today/spl-governance';

async function RealmDashboard({ realmAddress }) {
  const realm = await getRealm(connection, realmAddress);

  return {
    name: realm.account.name,
    communityMint: realm.account.communityMint.toBase58(),
    authority: realm.account.authority?.toBase58(),
  };
}
```

**List Active Proposals**

```typescript
import { getAllProposals, ProposalState } from '@realms-today/spl-governance';

async function getActiveProposals(governanceAddress) {
  const proposals = await getAllProposals(
    connection, programId, governanceAddress
  );

  return proposals.flat().filter(
    p => p.account.state === ProposalState.Voting
  );
}
```

**Voting Component**

```typescript
import { withCastVote, Vote, YesNoVote } from '@realms-today/spl-governance';
import { TransactionInstruction, Transaction } from '@solana/web3.js';

async function voteOnProposal(proposal, approve: boolean) {
  const vote = approve
    ? Vote.fromYesNoVote(YesNoVote.Yes)
    : Vote.fromYesNoVote(YesNoVote.No);

  const instructions: TransactionInstruction[] = [];

  await withCastVote(
    instructions,
    programId, programVersion,
    realmAddress, governanceAddress,
    proposal.pubkey, proposalOwnerRecord,
    voterTokenOwnerRecord, walletAddress,
    governingTokenMint, vote,
    walletAddress,        // payer
    voterWeightRecord,    // optional
    maxVoterWeightRecord, // optional
  );

  // Send transaction with wallet adapter
  await sendTransaction(new Transaction().add(...instructions));
}
```

#### Plugin UI Integration

If your DAO uses a governance plugin (VSR, NFT Voter, etc.), your UI needs to implement the `VotePlugin` interface to handle deposits, withdrawals, and voter weight updates.

The plugin interface:

```typescript
interface VotePlugin {
  showDepositModal: boolean;

  // Initialize with realm/voter context
  initPlugin(rpcEndpoint, programId, voter, realm, mint, delegates): Promise<VotePlugin>;

  // Get current voting weight
  getVoterWeight(tokenOwnerRecord?): Promise<VoteWeight>;

  // Token deposit/withdraw instructions
  getDepositInstructions(amount): Promise<PluginInstructions>;
  getWithdrawInstructions(amount, tokenOwnerRecord): Promise<PluginInstructions>;

  // UI helpers
  getDepositMessage(): DepositMessage | null;
  getPluginDepositMint(): string | null;
  getDepositedTokenAmount(): BN | null;

  // Voter weight update (called before governance actions)
  getUpdateVoterWeightInstructions(
    tokenOwnerRecord?, actionTarget?, action?,
    governance?, proposal?
  ): Promise<UpdateInstructions>;
}
```

Each governance action (create proposal, cast vote, etc.) needs to include the voter weight update instructions in the same transaction:

```typescript
// Before casting a vote with a plugin
const { instructions: updateIxs, voterWeightRecordKey } =
  await plugin.getUpdateVoterWeightInstructions(
    tokenOwnerRecord,
    proposalAddress,
    VoterWeightAction.CastVote,
  );

await withCastVote(
  instructions,
  /* ... */
  new PublicKey(voterWeightRecordKey),  // pass the VWR
  /* ... */
);

const tx = new Transaction();
updateIxs.forEach(ix => tx.add(ix));
instructions.forEach(ix => tx.add(ix));
```

### Realms UI Architecture (Private Repo)

> Requires repository access - contact the Realms team.

The Realms UI is a Next.js application with the following structure:

```
src/
  actions/       # Transaction builders and signers
  components/    # React components (50+ components)
  constants/     # Network config, program IDs
  context/       # React context providers
  hooks/         # Custom React hooks (50+ hooks)
  lib/           # Core logic
    governance/  # Governance SDK wrappers
    sowellian/   # Sowellian prediction market logic
    token/       # Token utilities
    treasury.ts  # Treasury management
    votes/       # Vote computation
  pages/         # Next.js pages
    api/v1/      # API routes (DAOs, users, leaderboard)
    dao/         # DAO pages
    create-dao/  # DAO creation flow
    sowellian/   # Prediction market pages
  plugins/       # Governance plugin system
    plugins/     # Individual plugin implementations
      vsr/       # Voter Stake Registry
      nft-voter/ # NFT voting
      token-voter/ # Token voting
      gateway/   # Civic Gateway
      pyth/      # Pyth staking
      drift/     # Drift staking
      bonk/      # Bonk voting
      quadratic/ # Quadratic voting
      sowellian/ # Prediction market plugin
      core-voter/ # Metaplex Core NFTs
      bio-vsr/   # Bio VSR variant
      epicentral-vsr/ # Epicentral Labs staking
      parcl/     # Parcl voting
      token-haver/ # Token holder voting
    constants/   # Plugin registry and types
  stores/        # Zustand state stores
  styles/        # Tailwind CSS styles
```

#### Key Technologies

* **Next.js 15** with Pages Router
* **React 19**
* **TanStack React Query** for data fetching
* **Zustand** for state management
* **Tailwind CSS** for styling
* **@realms-today/spl-governance** SDK
* **@coral-xyz/anchor** for Anchor program interactions

#### Adding a New Plugin to the UI

If you've built a custom on-chain plugin and have UI repo access:

1. Create a new directory under `src/plugins/plugins/your-plugin/`
2. Add your Anchor IDL as `idl.json` and types as `idl.ts`
3. Implement the `VotePlugin` interface in `client.ts`
4. Register your plugin's program ID in `src/plugins/constants/index.ts`
5. Add your plugin instance to the plugins array in `src/plugins/index.ts`

```typescript
// src/plugins/constants/index.ts
export const YOUR_PLUGINS = ['YourProgramId11111111111111111111111111'];

// src/plugins/index.ts
import { YourPlugin } from './plugins/your-plugin/client';
export const plugins: VotePlugin[] = [
  // ... existing plugins
  new YourPlugin(defaultConnection),
];
```

### Embedding Governance Widgets

For lightweight integration, you can build standalone governance widgets that embed in any web page:

#### Proposal Status Widget

```typescript
// Minimal widget that shows proposal status
async function ProposalWidget({ proposalAddress }) {
  const proposal = await getProposal(connection, proposalAddress);
  const { account } = proposal;

  const yesVotes = account.options[0]?.voteWeight || new BN(0);
  const noVotes = account.denyVoteWeight || new BN(0);
  const total = yesVotes.add(noVotes);

  return {
    name: account.name,
    state: ProposalState[account.state],
    yesPercent: total.gt(new BN(0))
      ? yesVotes.mul(new BN(100)).div(total).toNumber()
      : 0,
    votingEndsAt: account.votingCompletedAt
      ? new Date(account.votingCompletedAt.toNumber() * 1000)
      : null,
  };
}
```

#### Treasury Balance Widget

```typescript
async function TreasuryWidget({ governanceAddress }) {
  const nativeTreasury = await getNativeTreasuryAddress(programId, governanceAddress);
  const balance = await connection.getBalance(nativeTreasury);

  return {
    address: nativeTreasury.toBase58(),
    solBalance: balance / 1e9,
  };
}
```

### Contact for UI Repo Access

The Realms UI repository is private. For access to the codebase for building custom integrations, embedding components, or deploying your own instance:

* **Discord**: [Realms](https://discord.gg/VsPbrK2hJk)
* Contact the Realms team directly to discuss your integration needs and get repository access.
