Skip to Content
APIDAO API

DAO API

Manage on-chain governance.

Get DAO Information

GET /dao/:realmId

Retrieve DAO details, treasury, and proposals.

Response

{ "realmId": "REALM123...", "name": "MyToken DAO", "symbol": "MTK", "type": "community", "governance": "GOV456...", "treasury": { "address": "TRES789...", "balances": { "MTK": 50000000, "SOL": 10.5 } }, "config": { "quorum": 10, "passThreshold": 60, "votingPeriodHours": 72, "minTokensToCreate": 1000000 }, "proposals": { "total": 5, "active": 1, "passed": 3, "failed": 1 }, "voters": 142 }

List Proposals

GET /dao/:realmId/proposals

List all proposals with optional filters.

Query Parameters

ParameterTypeDescription
statusstringactive, passed, failed, cancelled
limitnumberMax results (default: 20)
offsetnumberPagination offset

Response

{ "proposals": [ { "proposalId": "PROP123...", "title": "Increase Marketing Budget", "description": "...", "status": "active", "createdAt": "2026-02-15T12:00:00Z", "votingEndsAt": "2026-02-18T12:00:00Z", "creator": "8vP2...", "voteResults": { "yes": 500000000, "no": 100000000, "total": 600000000, "quorumMet": true } } ], "pagination": { "total": 5, "limit": 20, "offset": 0 } }

Get Proposal Details

GET /dao/:mint/proposals/:proposal

Detailed view of a proposal including votes.

{ "name": "Release tranche 1", "description": "Episode 1 shipped", "state": "voting", "yesVotes": "1200000000000", "noVotes": "0", "votingStartedAt": "2026-09-06T10:00:00.000Z", "votingEndsAt": null, "executedAt": null, "transfer": { "to": "8vP2...", "amount": 1500 }, "milestoneId": "ms-1" }
FieldDescription
transferThe treasury transfer stored as the proposal’s transaction #0 — to is the recipient wallet, amount in whole tokens — or null for a signalling proposal
milestoneIdStudio milestone id parsed from the [milestone:<id>] prefix that createProposal writes into the description (null when absent). The prefix is stripped from description

Vote weight base and launch-DAO safeguards

Launch DAOs created from this release on:

  • Max vote weight = Absolute(totalSupply − treasury allocation). Everything that can be bought from the pool or claimed from vesting counts; only the treasury (owned by the governance, it can never vote) is excluded. passThreshold % of that is the pass bar — spl-governance v3 has no separate quorum, so dao.quorum is accepted but ignored.
  • No early tipping. Every proposal stays in voting for the full votingPeriodHours.
  • Hold-up: a passed proposal’s transactions become executable after DAO_HOLD_UP_SECONDS (default 24 h).
  • Proposing needs 1 % of total supply deposited (the /tx builder deposits the shortfall).
  • Creator veto. The realm has a council mint with a supply of exactly 1 token, held by the launch beneficiary (mint authority revoked). The council can veto any community proposal while it is being voted on (action: "veto"), but cannot pass or create proposals.
  • Realm authority is handed from Etch’s wallet to the governance PDA, so realm changes need a (vetoable) proposal.

Realms created before this (circulating-supply SupplyFraction, no council) keep working for every read and user-signed action except veto.

Proposal detail also returns holdUpSeconds, councilVeto (boolean), vetoVotes (raw), and canTipEarly (false on launch DAOs).

Create Proposal

Removed. The custodial POST /api/dao/:mint/deposit, POST /api/dao/:mint/proposals and POST /api/dao/:mint/proposals/:proposal routes signed with Etch’s own wallet (so the launch owner could vote with Etch-held tokens). They now answer 410 { "error": "custodial_route_removed", "details": { "replacement": "/api/dao/:mint/tx" } }. Use the user-signed builder. The GET endpoints are unchanged.

POST /dao/:realmId/proposal

Submit a new governance proposal.

Request Body

{ "title": "Proposal Title (max 80 chars)", "description": "Detailed description...", "type": "treasury_transfer", "actions": [ { "type": "transfer", "amount": 100000000000, "recipient": "WALLET_ADDRESS...", "mint": "TOKEN_MINT..." } ], "options": { "votingPeriodHours": 48 } }

Proposal Types

TypeActionsDescription
treasury_transfertransferSend tokens from treasury
config_changeset_configModify governance params
generalnoneCommunity signaling

Actions

Transfer:

{ "type": "transfer", "amount": 100000000000, "recipient": "8vP2...", "mint": "TOKEN_MINT..." }

Set Config:

{ "type": "set_config", "config": { "quorum": 15, "passThreshold": 65 } }

Response

{ "proposalId": "PROP789...", "status": "active", "transaction": "5abc...", "votingEndsAt": "2026-02-18T12:00:00Z" }

Cast Vote

POST /dao/:realmId/vote

Vote on an active proposal.

Request Body

{ "proposal": "PROP789...", "vote": "yes", "weight": 1000000000 // Optional: specific token amount }

Vote Options

ValueDescription
yesApprove the proposal
noReject the proposal
abstainCount toward quorum, neutral outcome
vetoCouncil-only rejection

Response

{ "signature": "5ghi...", "vote": "yes", "weight": 1000000000, "proposal": "PROP789...", "votingPower": 1000000000 }

Cancel Proposal

POST /dao/:realmId/proposals/:proposalId/cancel

Cancel a proposal (only by creator before voting starts).

Response

{ "signature": "5jkl...", "status": "cancelled" }

Execute Proposal

POST /dao/:realmId/proposals/:proposalId/execute

Execute a passed proposal (anyone can trigger after pass).

Response

{ "signature": "5mno...", "status": "executed", "results": { "transfers": [ { "amount": 100000000000, "recipient": "8vP2..." } ] } }

Withdraw Governance Tokens

POST /dao/:realmId/withdraw

Withdraw tokens deposited for voting.

Request Body

{ "amount": 1000000000 }

Get Voting Power

GET /dao/:realmId/voting-power/:wallet

Get current voting power for a wallet.

Response

{ "wallet": "8vP2...", "tokenBalance": 1000000000, "votingPower": 1000000000, "isDeposited": true, "canCreateProposal": true }

Build a Governance Transaction (non-custodial)

POST /dao/:mint/tx

The only way to act in a DAO (the custodial endpoints above are gone). Etch builds an unsigned transaction for the authenticated wallet; the wallet signs it and the client sends it. See the Transactions API for the response object and the client-side signing flow.

There is no ownership check on this endpoint: any token holder may deposit, vote, propose, finalize, execute or cancel — exactly as on-chain governance allows. (Ownership was only enforced on the custodial routes because Etch signed there.) :mint is the token mint; the realm and governance are resolved from the launch.

Request Body

Discriminated on action:

{ "action": "deposit", "amount": 123.45 } { "action": "withdraw" } { "action": "createProposal", "name": "…", "description": "…", "options": ["Yes", "No"], "transfer": { "to": "<pubkey>", "amount": 1500 }, "milestoneId": "ms-1" } { "action": "vote", "proposal": "<pubkey>", "approve": true } { "action": "veto", "proposal": "<pubkey>" } { "action": "finalize", "proposal": "<pubkey>" } { "action": "execute", "proposal": "<pubkey>", "transactionIndex": 0 } { "action": "cancel", "proposal": "<pubkey>" }
ActionNotes
depositamount in whole tokens (9 decimals). Tokens are pulled from the wallet’s own associated token account. On the wallet’s first deposit the token-owner record is created in the same transaction.
withdrawWithdraws the wallet’s entire governance deposit back to its ATA.
createProposaloptions defaults to ["Approve", "Deny"]; description may be empty. The proposal is signed off in the same transaction so it is immediately votable. Response adds "proposal": "<pubkey>". transfer (optional, { to, amount } — recipient wallet + whole tokens) inserts an SPL transfer of amount from the DAO’s native treasury ATA to to’s ATA as proposal transaction #0, so execute with transactionIndex: 0 pays it out once the vote passes; the recipient ATA is created idempotently in the create transaction (paid by the proposer). Response then also carries "transactionIndex": 0. Requires the DAO to have a native treasury (404 dao_not_found otherwise). milestoneId (optional, 1-64 chars of [A-Za-z0-9_-], also accepted inside transfer) is stored as a [milestone:<id>] prefix on the description and echoed in the response and in proposal detail.
voteapprove: true votes for option 0; approve: false is a deny vote. (veto: true is treated as action: "veto".) Response adds "voteRecord".
vetoCouncil veto — only the wallet holding the DAO’s council token (the creator) can sign it. Deposits the council token into the realm first when it is not deposited yet, then casts a Veto vote; one veto ends the proposal as vetoed. Only while the proposal is being voted on. Response adds "voteRecord" and "councilMint". 400 no_council on DAOs created before the council; 403 not_council_member for any other wallet.
finalizeAfter the voting period ends. The proposer’s token-owner record is resolved from the proposal account.
executeExecutes the proposal’s transaction at transactionIndex (default 0). The inner instructions are read from the on-chain proposal-transaction account; 404 proposal_transaction_not_found when the proposal has no instruction at that index.
cancelOnly the proposer’s wallet can sign a valid cancel.

Response

{ "transaction": "<base64 legacy Transaction, unsigned>", "version": "legacy", "feePayer": "<wallet>", "blockhash": "…", "lastValidBlockHeight": 123, "signers": ["<wallet>"], "summary": "Deposit 123.45 TKN into DAO" }

Errors

StatuserrorMeaning
401unauthorizedMissing / invalid JWT
400invalid_requestUnknown action or invalid payload (details has the issues)
404dao_not_foundNo launch for this mint, or it has no DAO
404proposal_not_foundvote / veto / finalize: the proposal account could not be read
400no_councilveto on a DAO created without a council
403not_council_memberveto from a wallet that holds no council token
500build_failedRPC / SDK error while building (message)

Webhook Events

EventDescription
dao.proposal_createdNew proposal submitted
dao.vote_castVote recorded
dao.proposal_passedProposal passed quorum
dao.proposal_failedProposal failed/rejected
dao.proposal_executedProposal executed
dao.proposal_cancelledProposal cancelled

Examples

Create and Vote on Proposal

async function createAndVote( authToken: string, realmId: string, wallet: string ) { // Create proposal const proposal = await fetch( `https://etch.film.fun/api/dao/${realmId}/proposal`, { method: "POST", headers: { Authorization: `Bearer ${authToken}`, "Content-Type": "application/json" }, body: JSON.stringify({ title: "Community Grant #1", description: "Send 100K tokens to marketing team", type: "treasury_transfer", actions: [{ type: "transfer", amount: 100000000000, recipient: "MARKETING_WALLET...", mint: "YOUR_TOKEN_MINT..." }] }) } ).then(r => r.json()); console.log(`Created: ${proposal.proposalId}`); // Vote yes const vote = await fetch( `https://etch.film.fun/api/dao/${realmId}/vote`, { method: "POST", headers: { Authorization: `Bearer ${authToken}`, "Content-Type": "application/json" }, body: JSON.stringify({ proposal: proposal.proposalId, vote: "yes" }) } ).then(r => r.json()); console.log(`Voted: ${vote.signature}`); }

Error Codes

CodeDescription
proposal_not_foundProposal doesn’t exist
already_votedWallet already voted
voting_endedProposal period closed
insufficient_tokensNot enough tokens to create proposal
not_authorizedNot proposal creator
not_executableProposal conditions not met
Last updated on