Skip to main content
Prove participation and eligibility in DAO governance processes using AttestProtocol. This guide covers membership verification, reputation-based voting, and contribution tracking.

Use Case Overview

DAO voting attestations enable sophisticated governance models beyond simple token-weighted voting. Track contributions, verify membership, and implement reputation-based voting power. Ideal For:
  • DAOs requiring Sybil-resistant voting
  • Contribution-based governance models
  • Multi-tier membership systems
  • Cross-DAO reputation portability

Implementation

Schema Selection

Choose or create schemas for your governance needs:
// Basic DAO membership
const membershipSchema = 'dao-member-v1';
// Definition: isMember:bool,joinDate:uint64,role:string,votingPower:uint32

// Contribution tracking
const contributionSchema = 'dao-contribution-v1';
// Definition: totalContributions:uint32,codeCommits:uint16,proposalsSubmitted:uint16,lastActive:uint64

// Reputation scoring
const reputationSchema = 'dao-reputation-v1';
// Definition: score:uint16,participationRate:uint8,proposalSuccess:uint8,peerEndorsements:uint16

DAO Integration

Implement attestation-based governance:
import { StellarAttestSDK, SolanaAttestSDK } from '@attestprotocol/sdk';

class AttestationDAO {
  private sdk: StellarAttestSDK | SolanaAttestSDK;
  private daoAddress: string;
  
  constructor(sdk: StellarAttestSDK | SolanaAttestSDK, daoAddress: string) {
    this.sdk = sdk;
    this.daoAddress = daoAddress;
  }
Membership Management
  async grantMembership(memberAddress: string, role: string = 'member') {
    // Verify membership proposal was approved through governance
    const proposal = await this.getProposal(memberAddress);
    
    if (!proposal.passed) {
      throw new Error('Membership proposal not passed');
    }
    
    // Create membership attestation with role and base voting power
    const membershipValue = [
      `isMember:true`,
      `joinDate:${Date.now()}`,
      `role:${role}`,
      `votingPower:1`
    ].join(',');
    
    const attestation = await this.sdk.attest({
      schemaUID: 'dao-member-v1',
      subject: memberAddress,
      value: membershipValue,
      reference: `membership-${proposal.id}`
    });
    
    return attestation;
  }
Voting Power Calculation
  async calculateVotingPower(voterAddress: string): Promise<number> {
    // Start with base membership check
    const basePower = await this.getBaseMembershipPower(voterAddress);
    if (basePower === 0) return 0; // Not a member
    
    // Add contribution-based power
    const contributionPower = await this.getContributionPower(voterAddress);
    
    // Apply reputation multiplier
    const totalPower = basePower + contributionPower;
    const finalPower = await this.applyReputationMultiplier(voterAddress, totalPower);
    
    // Cap maximum voting power to prevent concentration
    return Math.min(finalPower, 100);
  }
Base Membership Power
  private async getBaseMembershipPower(voterAddress: string): Promise<number> {
    const membership = await this.sdk.fetchAttestation({
      schemaUID: 'dao-member-v1',
      subject: voterAddress
    });
    
    if (!membership.data || membership.data.revoked) {
      return 0; // Not a member
    }
    
    const memberData = this.parseAttestationData(membership.data.value);
    return memberData.votingPower || 1;
  }
Contribution-Based Power
  private async getContributionPower(voterAddress: string): Promise<number> {
    const contributions = await this.sdk.fetchAttestation({
      schemaUID: 'dao-contribution-v1',
      subject: voterAddress
    });
    
    if (!contributions.data || contributions.data.revoked) {
      return 0;
    }
    
    const contribData = this.parseAttestationData(contributions.data.value);
    let contributionPower = 0;
    
    // Weight formula: 1 point per 10 general contributions
    contributionPower += Math.floor(contribData.totalContributions / 10);
    
    // Bonus points for different contribution types
    contributionPower += contribData.codeCommits * 2;      // 2 points per code commit
    contributionPower += contribData.proposalsSubmitted * 3; // 3 points per proposal
    
    return contributionPower;
  }
Reputation Multiplier
  private async applyReputationMultiplier(voterAddress: string, basePower: number): Promise<number> {
    const reputation = await this.sdk.fetchAttestation({
      schemaUID: 'dao-reputation-v1',
      subject: voterAddress
    });
    
    if (!reputation.data || reputation.data.revoked) {
      return basePower; // No multiplier, return base power
    }
    
    const repData = this.parseAttestationData(reputation.data.value);
    
    // Reputation multiplier: 1.0x to 2.0x based on score (max 200)
    const multiplier = 1 + (repData.score / 200);
    
    return Math.floor(basePower * multiplier);
  }
Vote Casting
  async castVote(
    voterAddress: string,
    proposalId: string,
    vote: 'for' | 'against' | 'abstain'
  ) {
    // Calculate voter's current voting power
    const votingPower = await this.calculateVotingPower(voterAddress);
    
    if (votingPower === 0) {
      throw new Error('No voting power - membership or contributions required');
    }
    
    // Record the weighted vote
    await this.recordVote({
      proposalId,
      voter: voterAddress,
      vote,
      power: votingPower,
      timestamp: Date.now()
    });
    
    // Update member's participation metrics
    await this.updateParticipation(voterAddress);
    
    return {
      proposalId,
      vote,
      votingPower,
      txHash: 'vote_tx_hash'
    };
  }
Participation Tracking
  private async updateParticipation(memberAddress: string) {
    // Get current reputation or initialize defaults
    const current = await this.sdk.fetchAttestation({
      schemaUID: 'dao-reputation-v1',
      subject: memberAddress
    });
    
    let reputation = {
      score: 50,              // Base score
      participationRate: 0,   
      proposalSuccess: 0,
      peerEndorsements: 0
    };
    
    if (current.data && !current.data.revoked) {
      reputation = this.parseAttestationData(current.data.value);
    }
    
    // Reward participation with score increases
    reputation.participationRate = Math.min(reputation.participationRate + 1, 100);
    reputation.score = Math.min(reputation.score + 1, 200);
    
    // Update reputation attestation
    const reputationValue = [
      `score:${reputation.score}`,
      `participationRate:${reputation.participationRate}`,
      `proposalSuccess:${reputation.proposalSuccess}`,
      `peerEndorsements:${reputation.peerEndorsements}`
    ].join(',');
    
    await this.sdk.attest({
      schemaUID: 'dao-reputation-v1',
      subject: memberAddress,
      value: reputationValue,
      reference: `reputation-update-${Date.now()}`
    });
  }
}

Contribution Tracking

Track and reward member contributions:
class ContributionTracker {
  
  async recordContribution(
    contributor: string,
    type: 'code' | 'proposal' | 'community' | 'content',
    details: any
  ) {
    // Get existing contribution history
    const currentContributions = await this.getCurrentContributions(contributor);
    
    // Update contribution counts based on type
    const updatedContributions = this.updateContributionCounts(currentContributions, type);
    
    // Create new attestation with updated counts
    const attestation = await this.issueUpdatedAttestation(contributor, updatedContributions, type);
    
    // Check if milestone rewards should be issued
    await this.checkContributionRewards(contributor, updatedContributions);
    
    return attestation;
  }
Get Current Contributions
  private async getCurrentContributions(contributor: string) {
    const existing = await this.sdk.fetchAttestation({
      schemaUID: 'dao-contribution-v1',
      subject: contributor
    });
    
    // Default values for new contributors
    let contributions = {
      totalContributions: 0,
      codeCommits: 0,
      proposalsSubmitted: 0,
      lastActive: 0
    };
    
    // Parse existing data if available
    if (existing.data && !existing.data.revoked) {
      contributions = this.parseAttestationData(existing.data.value);
    }
    
    return contributions;
  }
Update Contribution Counts
  private updateContributionCounts(contributions: any, type: string) {
    // Increment total contributions counter
    contributions.totalContributions += 1;
    contributions.lastActive = Date.now();
    
    // Update specific contribution type counters
    switch(type) {
      case 'code':
        contributions.codeCommits += 1;
        break;
      case 'proposal':
        contributions.proposalsSubmitted += 1;
        break;
      case 'community':
        // Community contributions count toward total only
        break;
      case 'content':
        // Content contributions count toward total only  
        break;
    }
    
    return contributions;
  }
Issue Updated Attestation
  private async issueUpdatedAttestation(contributor: string, contributions: any, type: string) {
    // Create structured attestation value
    const contributionValue = [
      `totalContributions:${contributions.totalContributions}`,
      `codeCommits:${contributions.codeCommits}`,
      `proposalsSubmitted:${contributions.proposalsSubmitted}`,
      `lastActive:${contributions.lastActive}`
    ].join(',');
    
    const attestation = await this.sdk.attest({
      schemaUID: 'dao-contribution-v1',
      subject: contributor,
      value: contributionValue,
      reference: `contribution-${type}-${Date.now()}`
    });
    
    return attestation;
  }
Milestone Rewards
  private async checkContributionRewards(contributor: string, contributions: any) {
    // Define contribution milestones for rewards
    const milestones = [10, 50, 100, 500, 1000];
    
    // Check if current total hits a milestone
    if (milestones.includes(contributions.totalContributions)) {
      console.log(`Contributor ${contributor} reached ${contributions.totalContributions} contributions`);
      await this.issueReward(contributor, `milestone-${contributions.totalContributions}`);
    }
  }
}

Cross-DAO Reputation

Enable portable reputation across DAOs:
class CrossDAOReputation {
  
  async aggregateReputation(memberAddress: string): Promise<{
    totalScore: number;
    daos: Array<{
      dao: string;
      score: number;
      role: string;
    }>;
  }> {
    // Get reputation data from all participating DAOs
    const daoReputations = await this.fetchAllDAOReputations(memberAddress);
    
    // Filter out null results and calculate totals
    const validReputations = daoReputations.filter(r => r !== null);
    const totalScore = validReputations.reduce((sum, r) => sum + r.score, 0);
    
    return {
      totalScore,
      daos: validReputations
    };
  }
Fetch DAO Reputations
  private async fetchAllDAOReputations(memberAddress: string) {
    // Define participating DAOs and their reputation schemas
    const participatingDAOs = [
      { name: 'TechDAO', schema: 'techdao-reputation-v1' },
      { name: 'DeFiDAO', schema: 'defidao-reputation-v1' },
      { name: 'ArtDAO', schema: 'artdao-reputation-v1' }
    ];
    
    // Fetch reputation attestations from all DAOs in parallel
    const reputations = await Promise.all(
      participatingDAOs.map(async (dao) => {
        return await this.fetchSingleDAOReputation(memberAddress, dao);
      })
    );
    
    return reputations;
  }
Single DAO Reputation
  private async fetchSingleDAOReputation(memberAddress: string, dao: any) {
    try {
      const attestation = await this.sdk.fetchAttestation({
        schemaUID: dao.schema,
        subject: memberAddress
      });
      
      // Return parsed data if attestation exists and is valid
      if (attestation.data && !attestation.data.revoked) {
        const data = this.parseAttestationData(attestation.data.value);
        return {
          dao: dao.name,
          score: data.score || 0,
          role: data.role || 'member'
        };
      }
      
      return null; // No valid reputation in this DAO
    } catch (error) {
      console.warn(`Failed to fetch reputation from ${dao.name}:`, error);
      return null;
    }
  }
Issue Portable Reputation
  async issuePortableReputation(memberAddress: string) {
    // Aggregate reputation across all DAOs
    const aggregated = await this.aggregateReputation(memberAddress);
    
    if (aggregated.daos.length === 0) {
      throw new Error('No DAO reputation found for this member');
    }
    
    // Find highest scoring DAO for "topDAO" field
    const topDAO = aggregated.daos.reduce((prev, current) => 
      (current.score > prev.score) ? current : prev
    );
    
    // Create cross-DAO reputation attestation
    const reputationValue = [
      `totalScore:${aggregated.totalScore}`,
      `daoCount:${aggregated.daos.length}`,
      `topDAO:${topDAO.dao}`
    ].join(',');
    
    const attestation = await this.sdk.attest({
      schemaUID: 'cross-dao-reputation-v1',
      subject: memberAddress,
      value: reputationValue,
      reference: `cross-dao-${Date.now()}`
    });
    
    return attestation;
  }
}

Advanced Patterns

Quadratic Voting

Implement quadratic voting with attestations:
async function calculateQuadraticVotes(
  voterAddress: string,
  votesToCast: number
): Promise<{ cost: number; allowed: boolean }> {
  // Get voter's reputation score
  const reputation = await sdk.fetchAttestation({
    schemaUID: 'dao-reputation-v1',
    subject: voterAddress
  });
  
  if (!reputation.data || reputation.data.revoked) {
    return { cost: 0, allowed: false };
  }
  
  const repData = parseAttestationData(reputation.data.value);
  const maxVotes = Math.floor(Math.sqrt(repData.score)); // Square root of reputation
  
  if (votesToCast > maxVotes) {
    return { cost: 0, allowed: false };
  }
  
  // Quadratic cost: n² tokens for n votes
  const cost = votesToCast * votesToCast;
  
  return { cost, allowed: true };
}

Delegation with Attestations

Enable vote delegation:
// Delegation schema
const delegationSchema = {
  name: 'vote-delegation-v1',
  definition: 'delegator:address,delegate:address,startTime:uint64,endTime:uint64,scope:string'
};

async function delegateVotingPower(
  delegator: string,
  delegate: string,
  duration: number = 30 * 24 * 60 * 60 * 1000 // 30 days
) {
  // Verify delegator has voting power
  const delegatorPower = await calculateVotingPower(delegator);
  
  if (delegatorPower === 0) {
    throw new Error('No voting power to delegate');
  }
  
  // Issue delegation attestation
  const attestation = await sdk.attest({
    schemaUID: 'vote-delegation-v1',
    subject: delegate,
    value: `delegator:${delegator},delegate:${delegate},startTime:${Date.now()},endTime:${Date.now() + duration},scope:all`,
    reference: `delegation-${delegator}-to-${delegate}`
  });
  
  return attestation;
}

// Check delegated voting power
async function getTotalVotingPower(voterAddress: string): Promise<number> {
  let totalPower = await calculateVotingPower(voterAddress);
  
  // Add delegated power
  const delegations = await sdk.fetchAttestation({
    schemaUID: 'vote-delegation-v1',
    subject: voterAddress
  });
  
  if (delegations.data && !delegations.data.revoked) {
    const delegationData = parseAttestationData(delegations.data.value);
    
    // Check if delegation is still valid
    if (Date.now() <= delegationData.endTime) {
      const delegatorPower = await calculateVotingPower(delegationData.delegator);
      totalPower += delegatorPower;
    }
  }
  
  return totalPower;
}

Sybil Resistance

Implement proof of humanity requirements:
async function verifySybilResistance(memberAddress: string): Promise<{
  isHuman: boolean;
  verificationMethods: string[];
}> {
  // Check multiple attestation types for Sybil resistance
  const checks = await Promise.all([
    // Proof of Humanity
    sdk.fetchAttestation({
      schemaUID: 'proof-of-humanity-v1',
      subject: memberAddress
    }),
    
    // Social verification
    sdk.fetchAttestation({
      schemaUID: 'social-verification-v1',
      subject: memberAddress
    }),
    
    // Biometric verification
    sdk.fetchAttestation({
      schemaUID: 'biometric-verification-v1',
      subject: memberAddress
    })
  ]);
  
  const verificationMethods = [];
  let verificationScore = 0;
  
  checks.forEach((check, index) => {
    if (check.data && !check.data.revoked) {
      const data = parseAttestationData(check.data.value);
      if (data.verified || data.isHuman) {
        verificationScore += 1;
        verificationMethods.push(['humanity', 'social', 'biometric'][index]);
      }
    }
  });
  
  return {
    isHuman: verificationScore >= 2, // Require at least 2 verification methods
    verificationMethods
  };
}

Integration Examples

Snapshot Integration

Integrate with Snapshot for off-chain voting:
class SnapshotAttestationStrategy {
  async getVotingPower(
    space: string,
    network: string,
    provider: any,
    addresses: string[],
    options: any,
    snapshot: number
  ): Promise<Record<string, number>> {
    const scores: Record<string, number> = {};
    
    // Fetch attestations for all addresses
    const attestationPromises = addresses.map(async (address) => {
      const power = await this.calculateVotingPower(address);
      scores[address] = power;
    });
    
    await Promise.all(attestationPromises);
    
    return scores;
  }
  
  private async calculateVotingPower(address: string): Promise<number> {
    // Implementation matches DAO voting power calculation
    const dao = new AttestationDAO(sdk, 'dao_address');
    return await dao.calculateVotingPower(address);
  }
}

On-Chain Governor

Implement on-chain governance with attestations:
// Stellar/Soroban Governor Contract
use soroban_sdk::{contractimpl, Address, Env};

pub struct Governor;

#[contractimpl]
impl Governor {
    pub fn propose(
        env: Env,
        proposer: Address,
        description: String,
        actions: Vec<Action>
    ) -> Result<u64, Error> {
        // Verify proposer has required reputation
        let reputation = Self::get_reputation(&env, &proposer)?;
        
        if reputation < MIN_PROPOSAL_REPUTATION {
            return Err(Error::InsufficientReputation);
        }
        
        // Create proposal
        let proposal_id = Self::create_proposal(&env, proposer, description, actions)?;
        
        Ok(proposal_id)
    }
    
    pub fn cast_vote(
        env: Env,
        voter: Address,
        proposal_id: u64,
        support: u8
    ) -> Result<(), Error> {
        // Get voting power from attestations
        let voting_power = Self::get_voting_power(&env, &voter)?;
        
        if voting_power == 0 {
            return Err(Error::NoVotingPower);
        }
        
        // Record weighted vote
        Self::record_vote(&env, proposal_id, voter, support, voting_power)?;
        
        Ok(())
    }
}

Testing Implementation

describe('DAO Voting Integration', () => {
  let dao: AttestationDAO;
  let contributionTracker: ContributionTracker;
  let member: string;
  
  beforeEach(async () => {
    dao = new AttestationDAO(sdk, 'test_dao');
    contributionTracker = new ContributionTracker(sdk);
    member = 'test_member_address';
  });
  
  it('should grant membership and calculate voting power', async () => {
    // Grant membership
    await dao.grantMembership(member, 'contributor');
    
    // Add contributions
    await contributionTracker.recordContribution(member, 'code', {
      commits: 5,
      linesAdded: 500
    });
    
    // Calculate voting power
    const power = await dao.calculateVotingPower(member);
    
    expect(power).to.be.greaterThan(1); // More than base membership
  });
  
  it('should handle vote delegation', async () => {
    const delegator = 'delegator_address';
    const delegate = 'delegate_address';
    
    // Setup delegator with voting power
    await dao.grantMembership(delegator, 'member');
    
    // Delegate voting power
    await delegateVotingPower(delegator, delegate);
    
    // Check delegate's total power
    const delegatePower = await getTotalVotingPower(delegate);
    
    expect(delegatePower).to.include.delegated.power;
  });
});

Best Practices

Voting Power Caps

Prevent concentration of power:
const VOTING_POWER_LIMITS = {
  individual: 100,        // Max 100 votes per individual
  percentageOfTotal: 0.05 // Max 5% of total voting power
};

async function applyVotingCaps(
  voterAddress: string,
  calculatedPower: number,
  totalDAOPower: number
): Promise<number> {
  // Apply individual cap
  let cappedPower = Math.min(calculatedPower, VOTING_POWER_LIMITS.individual);
  
  // Apply percentage cap
  const maxByPercentage = Math.floor(totalDAOPower * VOTING_POWER_LIMITS.percentageOfTotal);
  cappedPower = Math.min(cappedPower, maxByPercentage);
  
  return cappedPower;
}

Participation Incentives

Reward active participation:
async function calculateParticipationRewards(memberAddress: string) {
  const participation = await sdk.fetchAttestation({
    schemaUID: 'dao-participation-v1',
    subject: memberAddress
  });
  
  if (participation.data) {
    const data = parseAttestationData(participation.data.value);
    
    // Reward tiers based on participation rate
    if (data.participationRate >= 90) {
      return { tier: 'platinum', multiplier: 2.0 };
    } else if (data.participationRate >= 70) {
      return { tier: 'gold', multiplier: 1.5 };
    } else if (data.participationRate >= 50) {
      return { tier: 'silver', multiplier: 1.2 };
    }
  }
  
  return { tier: 'bronze', multiplier: 1.0 };
}

Next Steps