Skip to main content

DAO Agent Contract

On this page, you'll look at the DAO smart contract that uses the yield and resume pattern to enable the Shade Agent to vote on proposals within a single transaction flow.

The AI DAO contract is a fork of the default agent contract, modified to remove the request_signature function and implement DAO-specific functionality. This page focuses on the DAO-specific code, as agent registration follows the default contract.


Contract Structure

The DAO agent contract extends the default contract with additional state:

  • The DAO's manifesto
  • A map of pending proposals
  • A map of finalized proposals
  • The current proposal ID

Manifesto

The manifesto consists of two components: the manifesto text that defines the DAO's decision-making principles and a hash of the manifesto for verifying that the agent uses the correct manifesto when voting.

The manifesto and its hash are initialized as empty strings.

Pending Proposals

This stores all proposals that are awaiting a vote from the agent. Each proposal request includes the proposal text and yield ID - a unique identifier for each yielded promise (each active pending proposal request).

The map is initialized as empty.

Finalized Proposals

This stores all proposals that the agent has voted on. Each finalized proposal contains the proposal text, proposal result (enum of Approved or Rejected), and reasoning for the vote. The result and reasoning are provided by the agent.

The map is initialized as empty.

Current Proposal ID

The current proposal ID is an integer identifier that increments with each proposal request and is used to identify different proposals. If a proposal is not voted on by the agent, then the proposal ID will still increment, leading to non-consecutive proposal IDs within the finalized proposals map. Note that the proposal ID is different to the yield ID.


Setting the Manifesto

The contract provides a function to set the manifesto, which only the contract owner can call. The owner provides the manifesto text, which is hashed and stored along with the text in the contract's state. In production, the owner would typically be a multisig contract.


Creating a Proposal

When a user calls create_proposal with the proposal text, the function creates a yielded promise. The promise will call the specified function,return_external_response, with the arguments of proposal_id and proposal_text when the promise resolves. The promise resolves when the agent produces a valid response or the promise times out - after 200 blocks (~2 minutes).

The function then reads the yield ID from the register for the created promise. The yield ID is a unique hash identifier that ensures responses are matched to the correct pending proposal. The yield ID is generated by the register, which takes an integer identifier that specifies which register is being used, since there is just one yield-resume register here, it's set to zero.

A new proposal request is created and inserted into the pending proposals map, allowing the agent to fetch proposals that it needs to respond to.

Lastly, the function returns the yielded promise making it ready to be resumed.


Agent Response and Validation

Once the agent makes its decision, it calls the agent_vote function. This function checks whether the response is valid and resumes the yielded promise if so.

The agent responds with the yield ID for the promise it intends to resume, the proposal ID it's voting on, the hash of the proposal, the hash of the manifesto, the vote, and the reasoning behind the vote.

Most importantly, the function checks if the caller is a valid registered agent, ensuring the DAO only makes decisions through the expected process (that's defined by the verifiable agent).

The function verifies that the manifesto hash and proposal hash submitted by the agent match those stored in the contract. This verification ensures the agent used the correct manifesto and proposal when voting, removing trust in the RPC used to fetch proposals and manifesto data. Otherwise, there could be a bug in the RPC causing it to fetch the wrong details and the RPC or a malicious intermediary could intentionally provide the wrong details to try to corrupt the vote.

If any of these checks fail then the function panics and the promise is not be resumed (it could be resumed later with valid arguments before timeout). If all checks pass, then the function resumes the promise with the specified yield ID and the agent's response as an argument.


Proposal Finalization

When the yielded promise resolves (either resumed or timed out), the return_external_response function is called. This function is private and can only be called by the yielded promise, not by external accounts.

The function receives arguments from both when the promise was created and when it was resumed.

The function first removes the proposal being responded to from the pending proposals map, regardless of whether the promise was resumed or timed out.

If the response is valid, i.e. the yielded promise was successfully resumed, the proposal and the result are added to the map of finalized proposals, and a response is returned to the caller within the same transaction that the proposal was submitted in.

If the response is invalid, i.e. the yielded promise timed out, the function returns a promise to call fail_on_timeout, which panics and produces a failed receipt in a separate block to provide a clear error to the user (the return_external_response receipt is still successful).

tip

Visit the yield and resume section of the docs for a deeper look into this pattern.


View Functions

The contract exposes view functions to retrieve the manifesto text, pending proposals, and finalized proposals.


Next steps

Now that you understand the DAO agent contract implementation, continue to the agent page to learn about the verifiable agent that queries the contract for pending requests and casts a vote using an LLM.