Skip to Content
APITransactions API

Transactions API

Etch builds, the user signs, Signal sends. For DAO governance and vesting claims Etch never signs on the user’s behalf. Instead it returns an unsigned transaction whose fee payer and every authority is the authenticated wallet. The client signs it with the user’s wallet, sends it, and (optionally) asks Etch to confirm it.

Signal ── POST /api/dao/:mint/tx ──────▶ Etch builds tx (user = fee payer) ◀── { transaction, blockhash, … } ─┘ Signal: deserialize → wallet.signTransaction → sendRawTransaction Signal ── POST /api/tx/confirm ────────▶ Etch polls → { confirmed, slot }

The custodial routes (POST /dao/:mint/deposit, /proposals, /proposals/:proposal, POST /vesting/:contract) remain available as an owner-gated fallback and are only used when a /tx endpoint returns 501.

Built transaction response

Every /tx endpoint returns the same object:

{ "transaction": "<base64 legacy Transaction, unsigned by the user>", "version": "legacy", "feePayer": "<owner pubkey>", "blockhash": "…", "lastValidBlockHeight": 123, "signers": ["<owner pubkey>"], "summary": "Deposit 123.45 TKN into DAO" }
FieldDescription
transactionBase64-encoded legacy Transaction, serialised with requireAllSignatures: false. Compute-budget instructions (CU limit + priority fee) are prepended.
versionAlways "legacy" today; a 0 would indicate a VersionedTransaction. Deserialize by this field.
feePayerThe authenticated wallet. It is also every owner / authority in the instructions.
blockhash, lastValidBlockHeightPass both to confirmTransaction (or to POST /api/tx/confirm).
signersPublic keys that must sign client-side. Only the user — any ephemeral keypair an instruction needs is already partially signed by Etch.
summaryHuman-readable description for the wallet prompt.

Some actions add extra fields (proposal on createProposal, voteRecord on vote); they never remove any of the above.

Signing on the client

import { Transaction, VersionedTransaction } from "@solana/web3.js"; const built = await etch.post(`/api/dao/${mint}/tx`, { action: "deposit", amount: 123.45 }); const tx = built.version === "legacy" ? Transaction.from(Buffer.from(built.transaction, "base64")) : VersionedTransaction.deserialize(Buffer.from(built.transaction, "base64")); const signed = await wallet.signTransaction(tx); const signature = await connection.sendRawTransaction(signed.serialize()); await connection.confirmTransaction({ signature, blockhash: built.blockhash, lastValidBlockHeight: built.lastValidBlockHeight, });

Confirm a transaction

POST /api/tx/confirm

Optional helper: polls a signature the client already sent to confirmed commitment using the same polling loop Etch uses for its own transactions. Clients may confirm locally instead.

Request Body

{ "signature": "5abc…", "blockhash": "…", "lastValidBlockHeight": 123 }

Response

{ "confirmed": true, "slot": 284219311, "signature": "5abc…" }

Errors

StatuserrorMeaning
401unauthorizedMissing / invalid JWT
400invalid_requestBody failed validation
400transaction_failedThe cluster processed the transaction and it errored (message has the error)
408expiredBlock height passed lastValidBlockHeight before confirmation — rebuild and resend
504confirm_timeoutPolling gave up (~30s). Safe to retry.

Endpoints that return built transactions

EndpointDoc
POST /api/dao/:mint/txDAO API → Build a governance transaction
POST /api/vesting/:contract/txVesting API → Build a claim transaction
Last updated on