Request for Comment: Leveraging ComposeDB to Enable Interoperable DAO Reputation

Hello everyone!

With ComposeDB on Ceramic mainnet now for four months, we have been successfully solving decentralized, verifiable, and composable data for dApps. Continuing our focus on supporting reputation use cases, we are exploring how to solve the needs of DAO communities and the coordination tools they use.

We have heard interesting feedback already about the overlap between reputation and DAOs - governance delegates are critical participants in the ecosystem.

In response to feedback asking us to demonstrate how ComposeDB might be used, we decided to outline an initial proposal on how we understand data needs in this context, along with a concept of how a solution on ComposeDB might be architected and implemented.

Request for Feedback Outline and Intention

The purpose of this post is to outline an initial standpoint on how these three layers could potentially be implemented in concrete terms by proposing:

  • A collection (composite) of data models corresponding to the identity signals outlined above
  • A pre-packaged, simple way to deploy the models
  • Context around our decision-making related to the two bullet points above

Data Categories Overview

DAO data in particular presents an exciting use case well suited for ComposeDB on Ceramic and we’re delighted to hear your thoughts on the usefulness of these concepts. We think about data ownership for this use case as breaking down broadly across four categories:

  • Self-defined DAO-agnostic identity

This identity category is agnostic of any specific DAO and controlled by the individual. These primitives are designed to span multiple use cases.

  • Delegate/Member/Contributor self-defined DAO-specific identity

This category is DAO-contextual and is also controlled by the member/delegate/contributor.

  • Group-defined identity and attestations

Identity signals that fall into this bucket can both reference group-defined identity (such as descriptive metadata about the organization), as well as attestations related to individuals, and are controlled by the DAOs themselves.

  • Third-party reputation signals

This reputation category points to specific individuals, thus acting as trust signals, and represent the outcome of an underlying attribution framework. This grouping would be controlled and authored by third-party organizations.

ComposeDB Reference Documentation

For those new to ComposeDB and how creating data models work, here are several reference points you will find helpful as you read this post:

Introduction to Data Modeling

Schema Definition Overview

Defining Data Relations in ComposeDB

Supported Directives

Supported Scalars

Note: the schema definitions you’ll see in the following code blocks were slightly reformatted for the purposes of this post. Please reference the exact schema definitions in the GitHub repository within the “Getting Started” section toward the end.

Finally, as you read through this post and think about data interoperability between models, consider benefits such as those outlined below, and how these benefits extend to both data producers and data consumers:

Self-Defined, DAO-Agnostic Data Models

ModeSetting, Links, Skills, and GeneralProfile

# User is the controller of this model
type ModeSetting @createModel(
    accountRelation: SINGLE
    description: "A mode settings primitive that allows users to toggle between light, dark, and system settings"
  ) {
    author: DID! @documentAccount
    mode: Mode!

enum Mode {

# User is the controller of this model
type Links @createModel(
    accountRelation: SINGLE
    description: "A universal links primitive designed to span various use-cases"
  ) {
    author: DID! @documentAccount
    twitter: String @string(maxLength:200)
    github: String @string(maxLength:200)
    telegram: String @string(maxLength:200)
    discord: String @string(maxLength:200)
    medium: String @string(maxLength:200)
    website: String @string(maxLength:200)
    other: [PlatformUser] @list(maxLength: 100)

type PlatformUser {
  platformName: String! @string(maxLength:200)
  userName: String! @string(maxLength:200)

# User is the controller of this model
type Skills @createModel(
    accountRelation: SINGLE
    description: "Standardized skills model for DAO contributors"
  author: DID! @documentAccount
  peopleAndGovernance: Boolean
  developmentEngineering: Boolean
  growthAndMarketing: Boolean
  daoTeams: Boolean
  communityManagement: Boolean
  discord: Boolean
  socialMedia: Boolean
  budgetManagement: Boolean
  compensation: Boolean
  grants: Boolean
  web3: Boolean
  frontEnd: Boolean
  backEnd: Boolean
  fullStack: Boolean
  UX: Boolean
  UI: Boolean
  productDesign: Boolean
  devOps: Boolean
  projectManagement: Boolean
  security: Boolean
  memes: Boolean
  art: Boolean
  NFTs: Boolean
  graphics: Boolean
  branding: Boolean
  threeD: Boolean
  video: Boolean
  communications: Boolean
  translation: Boolean
  docs: Boolean
  writing: Boolean
  podcasting: Boolean
  strategy: Boolean
  treaturyManagement: Boolean
  contractAudits: Boolean
  multisigs: Boolean
  dataAnalysis: Boolean
  risk: Boolean
  tokenomics: Boolean
  contributorExperience: Boolean
  other: String @string(maxLength:200)

# User is the controller of this model
type GeneralProfile @createModel(accountRelation: SINGLE, description: "A basic general profile") {
  author: DID! @documentAccount
  firstName: String @string(maxLength:200)
  lastName: String @string(maxLength:200)
  personalBio: String @string(maxLength:10000)

The first four models in our initial library are examples of identity and preference that live a layer beneath DAO-specific identity data buckets. A few notes:

  • The array of PlatformUser values in the other field within the “Links” model is meant as a way of future-proofing the data model by allowing users to add additional platforms and usernames.
  • The SYSTEM setting within the Mode enum (used in the mode field within the “ModeSetting” model) is meant as a way for the user to designate the default app setting.
  • Applications that subscribe to these models would query to see if a data instance exists for a given user. If yes, that application could pull that data into the application experience.


Group-Defined, DAO-Specific Data Models

DAOProfile, Member, and Contributor

# Individual DAO is the controller of this model
type DAOProfile @createModel(
    accountRelation: LIST
    description: "A simple DAO identifier model"
  ) {
  author: DID! @documentAccount
  daoContractAddress: String! @string(minLength:42, maxLength:42)
  version: CommitID! @documentVersion
  name: String! @string(minLength: 3, maxLength: 150)
  description: String @string(maxLength:100000)
  members: [Member] @relationFrom(model: "Member", property: "DaoProfile")
  contributors: [Contributor] @relationFrom(model: "Contributor", property: "DaoProfile")


# Individual DAO is the controller of this model
type Member @createModel(
    accountRelation: LIST
    description: "A simple DAO-authored member attestation model"
  ) {
  author: DID! @documentAccount
  version: CommitID! @documentVersion
  memberID: DID! @accountReference
  active: Boolean!
  DaoProfile: StreamID! @documentReference(model: "DAOProfile")
  daoProfile: DAOProfile! @relationDocument(property: "DaoProfile")
  role: String @string(maxLength: 150)


# Individual DAO is the controller of this model
type Contributor @createModel(
    accountRelation: LIST
    description: "A simple DAO-authored contributor attestation model"
  ) {
  author: DID! @documentAccount
  version: CommitID! @documentVersion
  memberID: DID! @accountReference
  active: Boolean!
  DaoProfile: StreamID! @documentReference(model: "DAOProfile")
  daoProfile: DAOProfile! @relationDocument(property: "DaoProfile")
  role: String @string(maxLength: 150)
  team: String @string(maxLength: 150)

The DAOProfile, Member, and Contributor models fall into the group-controlled data category we outlined above and are therefore designed to be authored on behalf of the DAO as a whole. A few notes:

  • You’ll notice that the fields for DAOProfile are fairly sparse, which is by design. Please take note of the fields we marked as required (daoContractAddress and name).
  • The memberID field serves as both the Ceramic and on-chain identifier given the convention: did:pkh:eip155:1:<on-chain-address>.
  • The field named active is meant to indicate whether the individual is or isn’t currently a member or contributor.

Self-Defined, DAO-Specific Data Models

DelegateOfProfile, ContributorProfile, and MemberProfile

# Delegate is the controller of this model
type DelegateOfProfile @createModel(
    accountRelation: LIST
    description: "DAO-specific delegate profile"
  ) {
  author: DID! @documentAccount
  version: CommitID! @documentVersion
  DaoProfile: StreamID! @documentReference(model: "DAOProfile")
  daoProfile: DAOProfile! @relationDocument(property: "DaoProfile")
  intentStatement: String! @string(minLength: 25, maxLength: 100000)
  daoRelevantQuals: String @string(minLength: 25, maxLength: 100000)


# Contributor is the controller of this model
type ContributorProfile @createModel(
    accountRelation: LIST
    description: "DAO-specific individual-authored contributor profile"
  ) {
  author: DID! @documentAccount
  version: CommitID! @documentVersion
  DaoProfile: StreamID! @documentReference(model: "DAOProfile")
  daoProfile: DAOProfile! @relationDocument(property: "DaoProfile")
  contributorBio: String! @string(minLength: 25, maxLength: 100000)
  optionalAddition: String @string(minLength: 25, maxLength: 100000)


# Member is the controller of this model
type MemberProfile @createModel(
    accountRelation: LIST
    description: "DAO-specific member profile"
  ) {
  author: DID! @documentAccount
  version: CommitID! @documentVersion
  DaoProfile: StreamID! @documentReference(model: "DAOProfile")
  daoProfile: DAOProfile! @relationDocument(property: "DaoProfile")
  memberBio: String! @string(minLength: 25, maxLength: 100000)
  optionalAddition: String @string(minLength: 25, maxLength: 100000)

The DelegateOfProfile is controlled by the delegate, and one delegate can have multiple instances of this document to represent each of their relational identities within specific DAO contexts (aligning with the second data category we defined). One assumption we want to validate is that we believe the only critical self-defining field in this context should be a delegate’s intentStatement as it relates to a specific DAO, with daoRelevantQuals as an optional field where the controller might provide additional context.

ContributorProfile and MemberProfile are similar to the DelegateOfProfile - controlled by the individual contributor who can have multiple instances that uniquely relate to a DAO.

Third-Party Trust and Reputation Signals


# Coordinape is the controller of this model
type DelegateCircleDistribution @createModel(
    accountRelation: LIST
    description: "A time-series data model to track, index, and analyze inferred delegate contributor value within their circles"
  ) {
  delegateID: DID! @accountReference
  version: CommitID! @documentVersion
  circleIdentifier: String! @string(minLength: 3, maxLength: 100)
  DaoProfile: StreamID! @documentReference(model: "DAOProfile")
  daoProfile: DAOProfile! @relationDocument(property: "DaoProfile")
  epochEndDate: DateTime!
  getTokensReceived: Int! @int(min: 0) 
  totalTokensAvailable: Int! @int(min: 0)

Aligning with the fourth data category we outlined is DelegateDistribution. As we mentioned above, the eventual plan for building out rich, multifaceted delegate data is for many models to be created corresponding to each data category. When it comes to the third-party trust signal category, our thinking is that each model would represent a certain trust attribution framework that DAOs may or may not use.

We will include a brief description related to Coordinape’s framework below:

Coordinape provides a framework and platform for DAOs to determine how to allocate a budget to compensate active delegates within defined time periods called Epochs, and are an ideal example of this concept. We talked with the Coordinape team about how DAOs that use them as a budget coordination tool might represent third-party reputation data. (Note: This is one of a wide variety of concepts that could be used to extend this composite. We welcome feedback with models for your own projects!)

To briefly summarize, DAOs that use this framework allocate a certain number of GIVE tokens (representing a portion of the budget set for member compensation) to active participants (referred to as a Circle). It is then the responsibility of each member of the Circle to distribute to other members of the Circle (based on the perceived value they’ve provided over the course of that Epoch from the allocator’s perspective). For more information read the Coordinape docs.

Given that the GIVE tokens live natively on Coordinape’s server, it would make sense that Coordinape would be the controller of each model instance, authoring new model instances at the end of each Epoch period. As such, a new instance of this model for any given delegate of a specific DAO would be created every Epoch. This allows developers to aggregate and average out data for a delegate over a given period of time, or just the most recent instance. If a delegate is involved across multiple DAOs that use Coordinape in their budgeting logic, developers would also be able to aggregate meta-level delegate insights related to involvement.

Current System Limitations

Before we jump in, it’s important to outline two constraints and their potential implications on how teams would integrate the models into their stack.

Group-Controlled Data Models: The Ceramic Network is designed to support data streams that are individually owned. However, both the group-defined identity and 3rd-party reputation data buckets will require group control of a single DID, which comes with a set of potential risks and vulnerabilities (the possibility of late publishing attacks, for example), and a variety of supplementary technologies and platforms to help facilitate group-authored mutations. For the purpose of this RFC, we are hoping that this implementation ambiguity will spark meaningful conversation around the different ways to securely achieve group authorship, along with a clear understanding of their trade-offs and risk vectors.

Querying/Filtering by Field: Developers cannot currently filter, order, or query by individual field within a given model instance (a feature that comes standard when working with traditional Web2 databases). Note that there is an RFC and active work underway to add this feature within the next several months.

Getting Started

In an attempt to make it as easy as possible to get up and running with this composite library, we decided to go with a cloud deployment methodology. Here are the steps involved:

  1. Begin following the steps in the Running in the Cloud guide on ComposeDB
  2. Once you arrive at the section that starts with “Clone the simpledeploy repository and enter the created directory” use the following code instead:
git clone
cd delegate-simpledeploy
  1. Continue following the guide until you arrive at the sentence starting with "You can now follow the existing guides” - at this point, use the following steps instead:
    1. Run npm install within the root directory to download the required dependencies
    2. Run npm run dev after your dependencies have been installed (make sure your terminal is running the correct version of node)
  2. You can now perform your GraphQL queries using the following endpoint: http://localhost:5005/graphql

You can also see how we’ve constructed a sample mutation query for you within the client folder, and how this can be tied so a simple frontend interface. If you’d like to interact with the frontend portion, use the following steps:

  1. In a new terminal, run cd client to enter the client folder
  2. Run npm install within the client directory to download the required dependencies
  3. Run npm run dev after your dependencies have been installed to experiment with a pre-built mutation query using your new endpoint
  4. Navigate to http://localhost:3000/ to sign in with your Ethereum wallet and perform mutations on your ModeSetting data model. You may have to wait until your new node has finished syncing before accessing data from other users

Please note that this initial composite library uses the Ceramic testnet.

Feedback Guidelines

Any constructive feedback is more than welcome! If possible, pointing us to supporting evidence or documentation that backs up your reasoning for altering the data models would be very helpful.

If providing feedback related to the packaging, deployment, or node access architecture assumptions, we’d love any details you’re willing to provide around the specific needs of your technical stack or configuration to make these deployment packages as frictionless as possible in the future.

We are asking our community for feedback on the following:

  1. What might we be missing within the data models/composite? What should we consider integrating to optimize for this use case while keeping the models modular and composable to accelerate usage?
  2. Do the constraints make sense for the fields we’ve defined in our data models?
  3. Does the packaging make sense for your stack? Does the format make it as easy for you as possible to integrate into your ecosystem? How can we improve?
  4. Are we making reasonable assumptions related to the burden of data authorship?

Feel free to share your thoughts on relevant models specific to your projects. We’re excited to continue our efforts to help teams build on and work with composable data and would love to engage with other teams with relevant use cases.

Finally, we are asking that our community respond directly (via the forum in which this RFC is posted) as a comment. We are hoping that this will facilitate meaningful, shared learning across community members and engineering teams.


This is great! We have built a similar sdk for links aggregator use case:



  3. Would

  4. Not sure I understand about the Group-Controlled data party, is it expected to be controlledd by the admin did or is this something will be supported by a protocol change? Maybe the entire group-controlled data deserve its own RFC

  5. It seems in this case the difference between individual controlled and group-controlled is the purpose the data servce:

  • for information: controlled by individual eg. user preference, links in bio
  • for credentials, controlled by issuing organization, eg. DAO
  1. Anyway to make Skills, Links extensibles? Instead of hard code all skills or links, is it possible to append new ones from a growing set of options
  2. “DAOProfile” total nitpick: DAOInfo is more straightforward as Profile may lead people to assume its a profile of a DAO member.
  3. Can user create DelegateOfProfile Contributor/Member Profile without being a member?, will that be an issue?
1 Like

Hi @mzk :grinning:! Could I ask the status of this ?

Hello @0xEE3CA4dd4CeB341691 ! Apologies for the late follow, and thank you for responding to the post. Very cool to see the s3 link aggregator SDK and viewer! As a side note - I think the vision for Underglaze is especially exciting - pairing together highly composable data models with corresponding React components has the potential to accelerate development in a meaningful way for many teams.

Regarding your question on point # 4 - I don’t think that group-controlled ceramic account capabilities will be supported by a protocol change in the near future. Still, I do see an expressed need across a number of different use cases for multiple humans to be able to represent themselves as one “team” or entity (DAOs are one example I use in this post, Desci projects want to group-author models representing organizations, entities creating attestations that are meant to represent more than one person…the list goes on). There are naive approaches to this problem (for example, if a group of people all had access to the same seed phrase that was used to instantiate a DID used for authorship using Ed25519Provider), but there are clear security and UX problems to this approach. It would be great to see the community come up with creative ways to solve this.

And yes - you pretty much summarized the delineation between the different data buckets and how those correspond to group vs. individual authorship in #5. However, there are similarities between the two as well - there’s a need for expressed individual identity and group-defined identity, for example.

As for making links extensible - that’s what I was picturing when I wrote in the “other” field (within Links) that ends up being an array of type PlatformUser (which is meant to be a flexible model to express identity in any platform). Would you mind sharing what you had in mind/how I can improve this thinking?

I agree with you on #4 - the wording could definitely lead to misinterpretation.

And yes - I had designed the DelegateOfProfile, Contributor, and Member models such that someone can create all three, only one, or any combination of two. Prior to creating this RFC, one of the major pieces of feedback I received (while the post was in draft mode) was that many people who contribute work to DAOs in many cases are not members, and so we chose to decouple the models to allow for that flexibility.

Finally - just a quick note that we’ve been speaking to multiple teams who are interested in incremental adoption of this playbook, starting with a simplified version of a Delegate Statement model. I’ll share in this thread once we have more news on that front, including a summary and a new model definition.

1 Like

I don’t love the naming convention of the capital vs lowercase first letter being the distinction between the reference link to another document and the field that actually pulls the underlying data from that link. It seems not that clear and easy to confuse the two.

Some possible alternatives:

  • daoProfileLink or daoProfileReference (for the @documentReference) and daoProfile (for the @relationDocument)
  • daoProfile (for the @documentReference) and daoProfileDocument (for the @relationDocument)

I feel like we should set some sort of best practice here that can be used across the entire ComposeDB ecosystem for the naming convention between these two types of fields in data models. I’d be curious for @paul’s opinion on this as well.

1 Like

Yes I agree it would be best to avoid confusion between the two, in examples I’ve used (relatedDocument)ID for the stream ID and (relatedDocument) for the relation but (relatedDocument)Link and (relatedDocument)Reference seem like good options as well.