Skip to main content

Decoded Actions

The DAO Café indexer automatically decodes proposal calldata into human-readable format. This feature uses the 4bytes.directory API to look up function signatures and decode the parameters. Endpoint: https://dao.cafe/graphql

How It Works

When a proposal is created on-chain, the calldata (raw encoded function calls) is decoded into a structured format:
  1. Extract Selector: The first 4 bytes of the calldata are extracted as the function selector
  2. Lookup Signature: The selector is looked up in the 4bytes.directory API
  3. Decode Parameters: Using the signature, the remaining calldata is decoded into typed parameters
  4. Store Result: The decoded actions are stored alongside the raw calldata

Query Decoded Actions

Fetch a proposal with its decoded actions:
query GetProposalWithDecodedActions($id: String!) {
  proposal(id: $id) {
    id
    description
    state
    targets
    values
    calldatas
    decodedActions
  }
}

Response Structure

The decodedActions field returns an array of decoded actions, one for each target/calldata pair in the proposal:

Successful Decoding

{
  "decodedActions": [
    {
      "index": 0,
      "target": "0xe2bfada6771b3e2d0a2b8a1d13e27090fa1a4c71",
      "value": "0",
      "calldata": "0x0dbec5ea00000000000000000000000000000000000000000000000000000000000001f4",
      "decoded": {
        "status": "success",
        "functionName": "setFee",
        "signature": "setFee(uint256)",
        "args": [
          {
            "name": "arg0",
            "type": "uint256",
            "value": "500"
          }
        ],
        "summary": "setFee(500)"
      }
    }
  ]
}

Failed Decoding

When the function selector is not found in 4bytes.directory:
{
  "decodedActions": [
    {
      "index": 0,
      "target": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
      "value": "1000000000000000000",
      "calldata": "0xabc12345...",
      "decoded": {
        "status": "error",
        "error": "Function selector not found in 4bytes directory",
        "selector": "0xabc12345",
        "hint": "Submit the contract ABI at https://www.4byte.directory/api/v1/import-solidity/ to enable decoding"
      }
    }
  ]
}

ETH Transfer (Empty Calldata)

When a proposal action is a pure ETH transfer:
{
  "decodedActions": [
    {
      "index": 0,
      "target": "0x742d35Cc6634C0532925a3b844Bc9e7595f...",
      "value": "1000000000000000000",
      "calldata": "0x",
      "decoded": {
        "status": "success",
        "functionName": "transfer",
        "signature": "transfer()",
        "args": [],
        "summary": "ETH Transfer"
      }
    }
  ]
}

Multi-Action Proposals

Proposals can have multiple actions. Each action is decoded independently:
query GetMultiActionProposal($id: String!) {
  proposal(id: $id) {
    id
    description
    decodedActions
  }
}
Example response with 3 actions:
{
  "proposal": {
    "id": "11155111_0xf3dfd...efc6da8_302905...",
    "description": "# Treasury Management\n\nUpdate fee structure and transfer funds",
    "decodedActions": [
      {
        "index": 0,
        "target": "0xe2bfada6771b3e2d0a2b8a1d13e27090fa1a4c71",
        "value": "0",
        "calldata": "0x0dbec5ea...",
        "decoded": {
          "status": "success",
          "functionName": "setFee",
          "signature": "setFee(uint256)",
          "args": [{ "name": "arg0", "type": "uint256", "value": "500" }],
          "summary": "setFee(500)"
        }
      },
      {
        "index": 1,
        "target": "0xA0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48",
        "value": "0",
        "calldata": "0xa9059cbb...",
        "decoded": {
          "status": "success",
          "functionName": "transfer",
          "signature": "transfer(address,uint256)",
          "args": [
            { "name": "arg0", "type": "address", "value": "0x742d35Cc6634C0532925a3b844Bc9e7595f..." },
            { "name": "arg1", "type": "uint256", "value": "1000000000" }
          ],
          "summary": "transfer(0x742d35Cc...595f, 1000000000)"
        }
      },
      {
        "index": 2,
        "target": "0x7a250d5630B4cF539739dF2C5dAcb4c659F2488D",
        "value": "1000000000000000000",
        "calldata": "0xabc12345...",
        "decoded": {
          "status": "error",
          "error": "Function selector not found in 4bytes directory",
          "selector": "0xabc12345",
          "hint": "Submit the contract ABI at https://www.4byte.directory/api/v1/import-solidity/ to enable decoding"
        }
      }
    ]
  }
}

DecodedAction Type Reference

DecodedAction

FieldTypeDescription
indexIntOrder of action in proposal (0-indexed)
targetStringTarget contract address
valueStringETH value in wei
calldataStringOriginal raw calldata (hex)
decodedObjectDecoded data or error info

Decoded (Success)

FieldTypeDescription
statusStringAlways "success"
functionNameStringFunction name (e.g., "setFee")
signatureStringFull signature (e.g., "setFee(uint256)")
argsArrayDecoded arguments
summaryStringHuman-readable one-liner

Decoded (Error)

FieldTypeDescription
statusStringAlways "error"
errorStringError message
selectorStringThe 4-byte function selector
hintStringHow to fix the issue

DecodedArg

FieldTypeDescription
nameStringGeneric name (arg0, arg1, etc.)
typeStringSolidity type (uint256, address, etc.)
valueStringStringified value

Adding Missing Signatures

If a function selector is not found in 4bytes.directory, you can submit it:

Via API

curl -X POST https://www.4byte.directory/api/v1/signatures/ \
  -H "Content-Type: application/json" \
  -d '{"text_signature": "myFunction(uint256,address)"}'

Via Contract ABI

curl -X POST https://www.4byte.directory/api/v1/import-solidity/ \
  -H "Content-Type: application/json" \
  -d '{"contract_abi": "[{\"type\":\"function\",\"name\":\"myFunction\",...}]"}'

Via Solidity File

curl -F [email protected] https://www.4byte.directory/api/v1/import-solidity/
After submitting your signature, future proposals using that function will be decoded automatically.

UI Display Example

Here’s how you might display decoded actions in your frontend:
function ProposalActions({ decodedActions }) {
  return (
    <div className="actions-list">
      {decodedActions.map((action) => (
        <div key={action.index} className="action-card">
          <div className="action-header">
            Action {action.index + 1}
            {action.decoded.status === 'success' ? (
              <span className="badge success">{action.decoded.summary}</span>
            ) : (
              <span className="badge error">Unknown Function</span>
            )}
          </div>
          
          <div className="action-details">
            <div>Target: {action.target}</div>
            <div>Value: {action.value} wei</div>
            
            {action.decoded.status === 'success' && (
              <div className="args">
                {action.decoded.args.map((arg, i) => (
                  <div key={i}>
                    {arg.name}: {arg.value} ({arg.type})
                  </div>
                ))}
              </div>
            )}
            
            {action.decoded.status === 'error' && (
              <div className="error-hint">
                {action.decoded.hint}
              </div>
            )}
          </div>
        </div>
      ))}
    </div>
  );
}

SDK Support

The decodedActions field is available through the daocafe-sdk:
import { getProposal } from 'daocafe-sdk';

const proposal = await getProposal('11155111_0xf3dfd...efc6da8_302905...');

// Access decoded actions
proposal.decodedActions?.forEach(action => {
  if (action.decoded.status === 'success') {
    console.log(`${action.index}: ${action.decoded.summary}`);
  } else {
    console.log(`${action.index}: Unknown function (${action.decoded.selector})`);
  }
});