Skip to main content

DAO Governance: Members, Editors, Proposals

Anything that publishes to a DAO space needs to know its governance state — who can vote, how many members exist, what proposals are open or closed. The pattern below covers the three core reads: counts, member/editor lookups, and proposal status.

Counts at a glance

query GovCounts($spaceId: UUID!) {
membersConnection(first: 1, filter: { spaceId: { is: $spaceId } }) {
totalCount
}
editorsConnection(first: 1, filter: { spaceId: { is: $spaceId } }) {
totalCount
}
proposalsConnection(first: 1, filter: { spaceId: { is: $spaceId } }) {
totalCount
}
}
{ "spaceId": "41e851610e13a19441c4d980f2f2ce6b" }
{
"data": {
"membersConnection": { "totalCount": 233 },
"editorsConnection": { "totalCount": 15 },
"proposalsConnection": { "totalCount": 312 }
}
}

The AI space currently has 233 members, 15 editors, 312 lifetime proposals.

Loading interactive query runner…

List proposals (most recent first)

query SpaceProposals($spaceId: UUID!) {
proposals(
first: 10
filter: { spaceId: { is: $spaceId } }
orderBy: CREATED_AT_DESC
) {
id
name
proposedBy
startTime
endTime
executedAt
yesCount
noCount
abstainCount
}
}
{
"data": {
"proposals": [
{
"id": "0083d2a4b69642658e359eb8fed91aa1",
"name": "Fix stale relations from wrong-space bug",
"proposedBy": "f3dab79cb5a3d9d1759656dd5361d1c6",
"startTime": "1774634797",
"endTime": "1774721197",
"executedAt":"1774634803",
"yesCount": "1",
"noCount": "0",
"abstainCount": "0"
}
]
}
}
Loading interactive query runner…

Status is implicit in the timestamps:

  • executedAt set → executed (yes-vote passed and the on-chain action ran)
  • endTime past with no executedAtfailed or expired (vote closed, didn't execute)
  • endTime in the future → active (still accepting votes)

Members of a specific space

query SpaceMembers($spaceId: UUID!) {
members(filter: { spaceId: { is: $spaceId } }, first: 100) {
memberSpaceId
}
}

memberSpaceId is the personal-space ID of each member. To resolve those to wallet addresses or person entities, fan out queries on the result set.

Check whether a wallet is a member or editor

query IsAuthorized($spaceId: UUID!, $personalSpaceId: UUID!) {
members(filter: {
spaceId: { is: $spaceId }
memberSpaceId: { is: $personalSpaceId }
}) {
memberSpaceId
}
editors(filter: {
spaceId: { is: $spaceId }
memberSpaceId: { is: $personalSpaceId }
}) {
memberSpaceId
}
}

Any non-empty array means yes; empty means no. Useful before showing a "Propose Edit" button in your UI.

Vote breakdown on a proposal

The proposal record already includes vote counts (yesCount, noCount, abstainCount — note: returned as string-encoded BigInt). For the individual votes:

query ProposalVotes($proposalId: UUID!) {
proposalVotes(filter: { proposalId: { is: $proposalId } }) {
voterId
vote
createdAt
}
}

vote is the vote value (yes / no / abstain), voterId is the voter's personal space ID.

Notes

  • Counts are strings, not numbers. yesCount: "1" is a BigInt. Coerce with BigInt(p.yesCount) or Number(p.yesCount) depending on what you need.
  • Personal spaces vs. wallet addresses: members are tracked by their personal space ID, not the wallet address directly. The personal space is the identity used in DAO governance.
  • Filter shapes are different from entities: MemberFilter uses spaceId and memberSpaceId; the EntityFilter uses spaceIds (plural). Easy to mix up — check the reference for the exact shape.
  • Proposal name is human-readable but proposers control it — don't rely on a structured format.
  • proposedBy is the proposer's personal space ID, same as memberSpaceId semantics.