> ## Documentation Index
> Fetch the complete documentation index at: https://dao.cafe/docs/llms.txt
> Use this file to discover all available pages before exploring further.

# Server Handler

> Mount the handler that reads CreateDAO data from the chain via RPC

# Server Handler

Since version 0.3, daocafe-sdk reads governance data directly from the CreateDAO
contracts over RPC. There is no hosted indexer: the hooks and query functions call
`/api/daocafe` on your own origin, and a handler from `daocafe-sdk/server` answers
from an in-memory cache that it keeps in sync with the chain.

<Note>
  Upgrading from 0.2.x? The hooks, query functions, types and IDs are unchanged.
  Mount the handler below and you're done. `https://dao.cafe/graphql` is retired.
</Note>

## Setup

The handler needs `viem`:

```bash theme={null}
npm install daocafe-sdk viem
```

<CodeGroup>
  ```ts Next.js App Router theme={null}
  // app/api/daocafe/route.ts
  import { createDaoCafeHandler } from 'daocafe-sdk/server';

  export const GET = createDaoCafeHandler({
    rpcUrls: {
      1: process.env.RPC_URL_ETHEREUM,
      42161: process.env.RPC_URL_ARBITRUM,
      11155111: process.env.RPC_URL_SEPOLIA,
    },
    cacheDir: process.env.DAOCAFE_CACHE_DIR, // optional
  });
  ```

  ```ts Next.js Pages Router theme={null}
  // pages/api/daocafe.ts
  import { createDaoCafeNodeHandler } from 'daocafe-sdk/server';

  export default createDaoCafeNodeHandler({
    rpcUrls: { 1: process.env.RPC_URL_ETHEREUM },
  });
  ```

  ```ts Express / node:http theme={null}
  import express from 'express';
  import { createDaoCafeNodeHandler } from 'daocafe-sdk/server';

  const app = express();
  app.get('/api/daocafe', createDaoCafeNodeHandler({ rpcUrls: { 1: process.env.RPC_URL_ETHEREUM } }));
  ```

  ```ts Vite dev server theme={null}
  // vite.config.ts
  import { defineConfig } from 'vite';
  import { createDaoCafeNodeHandler } from 'daocafe-sdk/server';

  export default defineConfig({
    plugins: [{
      name: 'daocafe-api',
      configureServer(server) {
        const handler = createDaoCafeNodeHandler({ rpcUrls: { 1: process.env.RPC_URL_ETHEREUM } });
        server.middlewares.use('/api/daocafe', (req, res) => void handler(req, res));
      },
    }],
  });
  ```
</CodeGroup>

Keep RPC URLs server-side (no `NEXT_PUBLIC_` prefix): the browser only talks to
your handler.

## Options

| Option | Default | Description |
| - | - | - |
| `rpcUrls` | — | RPC URL, or list of URLs, per chain id. Empty entries are skipped, so env vars can be passed directly. |
| `chains` | all known | Chain ids to serve |
| `publicFallback` | `true` | Try public RPCs after `rpcUrls` fail |
| `minSyncIntervalMs` | `15000` | Minimum time between two syncs of a chain |
| `minFreshIntervalMs` | `5000` | Same, for requests sent after `refreshDaoCafe()` |
| `cacheDir` | memory only | Directory to persist synced events in, so restarts don't re-scan history. In Docker, mount a volume here. |
| `fourByte` | `true` | Look up unknown calldata selectors on 4byte.directory |
| `warmup` | `true` | Start syncing every chain when the server is created |
| `cacheMaxAge` | `5` | `Cache-Control` max-age (seconds) of normal responses |
| `deployments` | built in | Add or override `{ factory, startBlock, confirmations }` per chain |

## Querying Without HTTP

In server components, route handlers or scripts, skip the HTTP hop:

```ts theme={null}
import { createDaoCafeServer } from 'daocafe-sdk/server';
import type { DAO, PaginatedResponse } from 'daocafe-sdk';

const daocafe = createDaoCafeServer({ rpcUrls: { 1: process.env.RPC_URL_ETHEREUM } });

const daos = (await daocafe.query('daos', {
  orderBy: 'createdAt',
  orderDirection: 'desc',
})) as PaginatedResponse<DAO>;
```

Operations: `daos`, `dao`, `proposals`, `proposal`, `votes`, `vote`, `delegates`,
`delegate`, `tokenHolders`, `tokenHolder`. They take the same parameters as the
matching query functions. Servers with the same config share one cache per process.

## Fresh Data After Transactions

Responses can be up to `minSyncIntervalMs` old. After a user's transaction is
mined, call `refreshDaoCafe` so their vote or proposal shows up immediately:

```tsx theme={null}
import { useQueryClient } from '@tanstack/react-query';
import { refreshDaoCafe } from 'daocafe-sdk';

const queryClient = useQueryClient();
// once the transaction receipt arrives
await refreshDaoCafe(queryClient);
```

It refetches every SDK query, and for the next 20 seconds the server re-syncs
before answering, at most once every `minFreshIntervalMs` per chain. If you
invalidate queries yourself, call `markFresh()` first.

## How It Works

<Steps>
  <Step title="Discover DAOs">
    `DAOCreated` events from the factory
    (`0xc852E5Cb44C50614a82050163aB7170cB88EB5F9`, same address on every chain).
  </Step>

  <Step title="Sync events">
    One `eth_getLogs` call covers the factory and every governor and token it
    created. The first sync starts at the factory deployment block; later syncs
    only fetch new blocks. If a provider rejects a block range, the range is
    split, using the limit the provider states when it gives one.
  </Step>

  <Step title="Re-read the head">
    The last \~minute of blocks is fetched again on every sync and replaces what
    was there. Providers sometimes report a block before its logs can be queried,
    and a single empty answer must not drop a proposal or vote.
  </Step>

  <Step title="Derive entities">
    DAOs, proposals (with tallies), votes, delegations and token holders (balances
    and voting power) are rebuilt from the events, with the same IDs and fields
    the indexer used.
  </Step>

  <Step title="Read live state">
    Governance settings (voting delay/period, threshold, quorum, manager, timelock
    delay) are read via multicall when a DAO appears and after events that change
    them. Proposal state comes from events and the block time. Once voting ends,
    `state()` is read once to decide `SUCCEEDED` vs `DEFEATED`, and the answer is cached.
  </Step>
</Steps>

### What It Costs

While someone is browsing, each chain costs at most one `eth_getBlockByNumber`
plus one `eth_getLogs` per `minSyncIntervalMs` (15 s by default). Any number of
concurrent requests share that sync. With no traffic there are no calls at all.
A cold start is about one `eth_getLogs` per chain with a provider that accepts
wide block ranges, such as dRPC. With public RPCs only, the first Arbitrum sync
takes a few hundred requests (\~30 s); `cacheDir` keeps that from repeating on restart.

## Decoded Actions

Each proposal includes `decodedActions`, a human-readable decoding of every
target/value/calldata triple:

* Common governance calls are decoded from built-in ABIs, with real argument names:
  ERC20 `transfer`/`approve`/`transferFrom`/`delegate`, governor `setVotingDelay`,
  `setVotingPeriod`, `setProposalThreshold`, `updateQuorumNumerator`, `setManager`,
  `updateTimelock`, `relay`, timelock `updateDelay`/`grantRole`/`revokeRole`, and
  ERC721/1155 `safeTransferFrom`.
* Other selectors are looked up on [4byte.directory](https://www.4byte.directory/)
  (cached per process); arguments are then named `arg0`, `arg1`, …
* Empty calldata is reported as `ETH Transfer`.

```json theme={null}
{
  "index": 0,
  "target": "0xa6be5ae3bc634cf44439845365a01c2cd705f32d",
  "value": "0",
  "calldata": "0xa9059cbb…",
  "decoded": {
    "status": "success",
    "functionName": "transfer",
    "signature": "transfer(address,uint256)",
    "args": [
      { "name": "to", "type": "address", "value": "0x90EF4A0D…9Cd711" },
      { "name": "amount", "type": "uint256", "value": "50000000000000000000000" }
    ],
    "summary": "transfer(0x90EF4A0D...9Cd711, 5000000000...000000)"
  }
}
```

When decoding fails, `decoded` is `{ status: "error", error, selector, hint }`.
See [Types](/docs/sdk/types) for the full definitions.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.