Skip to main content
Issue verifiable proof of presence for events, conferences, and hackathons. This guide covers attendance verification patterns for community events, loyalty programs, and exclusive access management.

Use Case Overview

Event attendance attestations create permanent, verifiable records of participation that unlock benefits across platforms. Transform one-time events into lasting digital assets. Ideal For:
  • Conference and meetup organizers
  • Hackathon participation tracking
  • Loyalty and rewards programs
  • Exclusive community access
  • Achievement and milestone recognition

Implementation

Schema Configuration

Define event-specific attestation schemas:
// Basic event attendance
const attendanceSchema = 'event-attendance-v1';
// Definition: eventId:string,eventName:string,timestamp:uint64,role:string

// Extended event participation
const extendedSchema = 'event-participation-v1';
// Definition: eventId:string,eventType:string,role:string,sessions:string,achievements:string,timestamp:uint64

// Hackathon participation
const hackathonSchema = 'hackathon-participant-v1';
// Definition: eventId:string,teamId:string,projectName:string,placement:uint8,prizes:string,timestamp:uint64

Event Organizer System

Implement check-in and attestation issuance:
import { StellarAttestSDK, SolanaAttestSDK } from '@attestprotocol/sdk';

class EventAttestationSystem {
  private sdk: StellarAttestSDK | SolanaAttestSDK;
  private eventConfig: EventConfig;
  
  constructor(sdk: StellarAttestSDK | SolanaAttestSDK, eventConfig: EventConfig) {
    this.sdk = sdk;
    this.eventConfig = eventConfig;
  }
  
  constructor(sdk: AttestSDKBase, eventConfig: EventConfig) {
    this.sdk = sdk;
    this.eventConfig = eventConfig;
  }
  
  async checkInAttendee(attendeeAddress: string, checkInData: {
    ticketId?: string;
    role?: 'attendee' | 'speaker' | 'sponsor' | 'organizer' | 'volunteer';
    method?: 'qr' | 'nfc' | 'manual';
  }) {
    // Verify ticket or registration
    const isValid = await this.verifyRegistration(attendeeAddress, checkInData.ticketId);
    
    if (!isValid) {
      throw new Error('Invalid registration');
    }
    
    // Prevent duplicate check-ins
    const existing = await this.sdk.fetchAttestation({
      schemaUID: 'event-attendance-v1',
      subject: attendeeAddress
    });
    
    if (existing.data && !existing.data.revoked) {
      const data = this.parseAttestationData(existing.data.value);
      if (data.eventId === this.eventConfig.eventId) {
        throw new Error('Already checked in');
      }
    }
    
    // Issue attendance attestation
    const attestation = await this.sdk.attest({
      schemaUID: 'event-attendance-v1',
      subject: attendeeAddress,
      value: `eventId:${this.eventConfig.eventId},eventName:${this.eventConfig.name.replace(/\s/g, '_')},timestamp:${Date.now()},role:${checkInData.role || 'attendee'}`,
      reference: `checkin-${this.eventConfig.eventId}-${attendeeAddress}`
    });
    
    // Trigger post-check-in actions
    await this.onSuccessfulCheckIn(attendeeAddress, attestation.data);
    
    return {
      success: true,
      attestation: attestation.data,
      benefits: this.getAttendeeBenefits(checkInData.role)
    };
  }
  
  async batchCheckIn(attendees: Array<{
    address: string;
    role?: string;
    ticketId?: string;
  }>) {
    const results = await Promise.allSettled(
      attendees.map(attendee => 
        this.checkInAttendee(attendee.address, {
          ticketId: attendee.ticketId,
          role: attendee.role as any
        })
      )
    );
    
    const successful = results.filter(r => r.status === 'fulfilled').length;
    const failed = results.filter(r => r.status === 'rejected');
    
    return {
      total: attendees.length,
      successful,
      failed: failed.map((r, i) => ({
        address: attendees[i].address,
        error: (r as any).reason.message
      }))
    };
  }
  
  private async onSuccessfulCheckIn(attendeeAddress: string, attestation: any) {
    // Send welcome notification
    await this.sendWelcomeNotification(attendeeAddress);
    
    // Grant immediate benefits
    await this.grantEventBenefits(attendeeAddress);
    
    // Update event metrics
    await this.updateEventMetrics();
  }
  
  private getAttendeeBenefits(role?: string) {
    const baseBenefits = [
      'Event NFT',
      'Discord role',
      'Future event discounts'
    ];
    
    // Role-specific additional benefits
    const roleBenefits = {
      speaker: ['Speaker badge NFT', 'VIP lounge access', 'Recording access'],
      sponsor: ['Sponsor badge NFT', 'Lead scanner access', 'Booth analytics'],
      organizer: ['Full event access', 'Admin dashboard', 'Attendee data'],
      volunteer: ['Volunteer badge NFT', 'Meal vouchers', 'Certificate']
    };
    
    return [...baseBenefits, ...(roleBenefits[role] || [])];
  }
}

### Session Tracking

Track detailed participation within events:

```typescript
class SessionTracker {
  async recordSessionAttendance(
    attendeeAddress: string,
    sessionData: {
      sessionId: string;
      sessionName: string;
      startTime: number;
      duration: number;
      type: 'workshop' | 'talk' | 'panel' | 'networking';
    }
  ) {
    // Verify base event attendance
    const eventAttendance = await this.sdk.fetchAttestation({
      schemaUID: 'event-attendance-v1',
      subject: attendeeAddress
    });
    
    if (!eventAttendance.data || eventAttendance.data.revoked) {
      throw new Error('Must check in to main event first');
    }
    
    // Get or create extended participation record
    const existing = await this.sdk.fetchAttestation({
      schemaUID: 'event-participation-v1',
      subject: attendeeAddress
    });
    
    let sessions = [];
    if (existing.data && !existing.data.revoked) {
      const data = this.parseAttestationData(existing.data.value);
      sessions = data.sessions ? data.sessions.split('|') : [];
    }
    
    // Add new session
    sessions.push(sessionData.sessionId);
    
    // Update participation attestation
    const attestation = await this.sdk.attest({
      schemaUID: 'event-participation-v1',
      subject: attendeeAddress,
      value: `eventId:${this.eventId},eventType:conference,role:attendee,sessions:${sessions.join('|')},achievements:none,timestamp:${Date.now()}`,
      reference: `session-${sessionData.sessionId}-${attendeeAddress}`
    });
    
    // Check for achievement unlocks
    await this.checkAchievements(attendeeAddress, sessions);
    
    return attestation;
  }
  
  private async checkAchievements(attendeeAddress: string, sessions: string[]) {
    const achievements = [];
    
    // Workshop warrior - attended 3+ workshops
    const workshops = sessions.filter(s => s.includes('workshop')).length;
    if (workshops >= 3) {
      achievements.push('workshop-warrior');
    }
    
    // Early bird - attended opening keynote
    if (sessions.includes('opening-keynote')) {
      achievements.push('early-bird');
    }
    
    // Networking ninja - attended all networking sessions
    const networkingSessions = sessions.filter(s => s.includes('networking'));
    if (networkingSessions.length >= 3) {
      achievements.push('networking-ninja');
    }
    
    if (achievements.length > 0) {
      await this.issueAchievements(attendeeAddress, achievements);
    }
  }
}

Post-Event Benefits

Enable lasting benefits from attendance:
class PostEventBenefits {
  async verifyAndGrantAccess(
    userAddress: string,
    requiredEvents: string[],
    benefit: string
  ): Promise<{
    eligible: boolean;
    attestations: any[];
    accessGranted?: boolean;
  }> {
    // Check attendance at required events
    const attendanceChecks = await Promise.all(
      requiredEvents.map(async (eventId) => {
        const attestation = await this.sdk.fetchAttestation({
          schemaUID: 'event-attendance-v1',
          subject: userAddress
        });
        
        if (attestation.data && !attestation.data.revoked) {
          const data = this.parseAttestationData(attestation.data.value);
          return data.eventId === eventId ? attestation.data : null;
        }
        
        return null;
      })
    );
    
    const validAttestations = attendanceChecks.filter(a => a !== null);
    const eligible = validAttestations.length === requiredEvents.length;
    
    if (eligible) {
      // Grant the benefit
      const accessGranted = await this.grantBenefit(userAddress, benefit);
      
      return {
        eligible: true,
        attestations: validAttestations,
        accessGranted
      };
    }
    
    return {
      eligible: false,
      attestations: validAttestations
    };
  }
  
  async createLoyaltyTiers(userAddress: string): Promise<{
    tier: string;
    eventsAttended: number;
    benefits: string[];
  }> {
    // Fetch all event attestations for user
    const allEvents = await this.fetchUserEventHistory(userAddress);
    const eventCount = allEvents.length;
    
    let tier = 'bronze';
    let benefits = ['Newsletter access', '5% merch discount'];
    
    if (eventCount >= 10) {
      tier = 'platinum';
      benefits = [
        'VIP check-in',
        'Free merch',
        '50% ticket discount',
        'Speaker dinner invites',
        'Early access registration'
      ];
    } else if (eventCount >= 5) {
      tier = 'gold';
      benefits = [
        'Priority check-in',
        '25% ticket discount',
        'Exclusive workshops',
        'Networking app premium'
      ];
    } else if (eventCount >= 3) {
      tier = 'silver';
      benefits = [
        'Fast-track check-in',
        '15% ticket discount',
        'Workshop recordings',
        'Community Discord role'
      ];
    }
    
    // Issue loyalty tier attestation
    await this.sdk.attest({
      schemaUID: 'event-loyalty-v1',
      subject: userAddress,
      value: `tier:${tier},eventsAttended:${eventCount},lastUpdated:${Date.now()}`,
      reference: `loyalty-tier-${userAddress}`
    });
    
    return {
      tier,
      eventsAttended: eventCount,
      benefits
    };
  }
}

Advanced Patterns

Virtual Event Integration

Handle online and hybrid events:
class VirtualEventAttestations {
  
  async trackVirtualAttendance(
    attendeeAddress: string,
    sessionData: {
      platform: 'zoom' | 'youtube' | 'custom';
      sessionId: string;
      joinTime: number;
      duration: number;
      engagement: {
        chatMessages?: number;
        questionsAsked?: number;
        pollsAnswered?: number;
      };
    }
  ) {
    // Calculate participant engagement score
    const engagementScore = this.calculateEngagement(
      sessionData.duration,
      sessionData.engagement
    );
    
    // Create virtual attendance attestation
    return await this.issueVirtualAttestation(attendeeAddress, sessionData, engagementScore);
  }
Issue Virtual Attestation
  private async issueVirtualAttestation(
    attendeeAddress: string,
    sessionData: any,
    engagementScore: number
  ) {
    // Create structured attestation value
    const attestationValue = [
      `eventId:${this.eventId}`,
      `platform:${sessionData.platform}`,
      `duration:${sessionData.duration}`,
      `engagementScore:${engagementScore}`,
      `timestamp:${Date.now()}`
    ].join(',');
    
    const attestation = await this.sdk.attest({
      schemaUID: 'virtual-attendance-v1',
      subject: attendeeAddress,
      value: attestationValue,
      reference: `virtual-${sessionData.sessionId}-${attendeeAddress}`
    });
    
    return attestation;
  }
Engagement Score Calculation
  private calculateEngagement(duration: number, engagement: any): number {
    // Base score from attendance duration
    const durationScore = this.calculateDurationScore(duration);
    
    // Bonus points for interactions
    const interactionScore = this.calculateInteractionScore(engagement);
    
    // Total score capped at 100
    return Math.min(durationScore + interactionScore, 100);
  }
Duration Score Calculation
  private calculateDurationScore(duration: number): number {
    // 25 points per hour, maximum 50 points
    const hoursAttended = duration / 3600;
    return Math.min(hoursAttended * 25, 50);
  }
Interaction Score Calculation
  private calculateInteractionScore(engagement: any): number {
    let score = 0;
    
    // Chat participation (max 20 points)
    score += Math.min(engagement.chatMessages || 0, 20);
    
    // Questions asked (10 points each)
    score += (engagement.questionsAsked || 0) * 10;
    
    // Poll participation (5 points each)
    score += (engagement.pollsAnswered || 0) * 5;
    
    return score;
  }
}

Hackathon Participation

Track hackathon achievements:
class HackathonAttestations {
  async recordHackathonParticipation(
    teamData: {
      teamId: string;
      members: string[];
      projectName: string;
      githubRepo?: string;
      demoUrl?: string;
    },
    results?: {
      placement?: number;
      prizes?: string[];
      judgesScore?: number;
    }
  ) {
    // Issue attestations for all team members
    const attestations = await Promise.all(
      teamData.members.map(member =>
        this.sdk.attest({
          schemaUID: 'hackathon-participant-v1',
          subject: member,
          value: `eventId:${this.eventId},teamId:${teamData.teamId},projectName:${teamData.projectName.replace(/\s/g, '_')},placement:${results?.placement || 0},prizes:${results?.prizes?.join('|') || 'none'},timestamp:${Date.now()}`,
          reference: `hackathon-${this.eventId}-${teamData.teamId}`
        })
      )
    );
    
    // Issue special attestations for winners
    if (results?.placement && results.placement <= 3) {
      await this.issueWinnerBadges(teamData.members, results.placement);
    }
    
    return attestations;
  }
  
  async verifyHackathonWinner(
    userAddress: string,
    eventId: string
  ): Promise<{
    isWinner: boolean;
    placement?: number;
    prizes?: string[];
  }> {
    const attestation = await this.sdk.fetchAttestation({
      schemaUID: 'hackathon-participant-v1',
      subject: userAddress
    });
    
    if (attestation.data && !attestation.data.revoked) {
      const data = this.parseAttestationData(attestation.data.value);
      
      if (data.eventId === eventId && data.placement > 0) {
        return {
          isWinner: true,
          placement: data.placement,
          prizes: data.prizes !== 'none' ? data.prizes.split('|') : []
        };
      }
    }
    
    return { isWinner: false };
  }
}

NFT Integration

Mint NFTs based on attendance:
class AttendanceNFTs {
  async mintAttendanceNFT(
    attendeeAddress: string,
    eventId: string
  ) {
    // Verify attendance attestation
    const attendance = await this.sdk.fetchAttestation({
      schemaUID: 'event-attendance-v1',
      subject: attendeeAddress
    });
    
    if (!attendance.data || attendance.data.revoked) {
      throw new Error('No valid attendance attestation');
    }
    
    const data = this.parseAttestationData(attendance.data.value);
    
    if (data.eventId !== eventId) {
      throw new Error('Attendance not verified for this event');
    }
    
    // Mint NFT with attestation reference
    const nftMetadata = {
      name: `${data.eventName} Attendance`,
      description: `Proof of attendance at ${data.eventName}`,
      image: `ipfs://event-poap-${eventId}`,
      attributes: [
        { trait_type: 'Event', value: data.eventName },
        { trait_type: 'Date', value: new Date(data.timestamp).toISOString() },
        { trait_type: 'Role', value: data.role },
        { trait_type: 'Attestation', value: attendance.data.attestationUID }
      ]
    };
    
    // Mint NFT (implementation depends on blockchain)
    const nftTx = await this.mintNFT(attendeeAddress, nftMetadata);
    
    return {
      nftId: nftTx.tokenId,
      attestationId: attendance.data.attestationUID,
      metadata: nftMetadata
    };
  }
}

Integration Examples

QR Code Check-In

Implement mobile check-in system:
class QRCheckInSystem {
  generateCheckInQR(attendeeData: {
    address: string;
    ticketId: string;
    eventId: string;
  }): string {
    // Create signed payload
    const payload = {
      ...attendeeData,
      timestamp: Date.now(),
      nonce: crypto.randomBytes(16).toString('hex')
    };
    
    const signature = this.signPayload(payload);
    
    // Encode as QR data
    const qrData = {
      p: payload,
      s: signature
    };
    
    return JSON.stringify(qrData);
  }
  
  async processQRCheckIn(qrData: string): Promise<any> {
    try {
      const { p: payload, s: signature } = JSON.parse(qrData);
      
      // Verify signature
      if (!this.verifySignature(payload, signature)) {
        throw new Error('Invalid QR code');
      }
      
      // Check timestamp (prevent replay attacks)
      const age = Date.now() - payload.timestamp;
      if (age > 5 * 60 * 1000) { // 5 minute expiry
        throw new Error('QR code expired');
      }
      
      // Process check-in
      return await this.eventSystem.checkInAttendee(payload.address, {
        ticketId: payload.ticketId,
        method: 'qr'
      });
    } catch (error) {
      throw new Error(`Check-in failed: ${error.message}`);
    }
  }
}

Analytics Dashboard

Track event metrics:
class EventAnalytics {
  async getEventMetrics(eventId: string): Promise<{
    totalAttendees: number;
    roleBreakdown: Record<string, number>;
    checkInTimes: number[];
    engagementScore: number;
  }> {
    // Query all attestations for event
    const attestations = await this.queryEventAttestations(eventId);
    
    const metrics = {
      totalAttendees: attestations.length,
      roleBreakdown: {},
      checkInTimes: [],
      engagementScore: 0
    };
    
    attestations.forEach(attestation => {
      const data = this.parseAttestationData(attestation.value);
      
      // Role breakdown
      metrics.roleBreakdown[data.role] = (metrics.roleBreakdown[data.role] || 0) + 1;
      
      // Check-in times
      metrics.checkInTimes.push(data.timestamp);
    });
    
    // Calculate engagement
    const sessionAttestations = await this.querySessionAttestations(eventId);
    metrics.engagementScore = this.calculateEventEngagement(
      attestations.length,
      sessionAttestations.length
    );
    
    return metrics;
  }
  
  async exportAttendeeList(eventId: string): Promise<any[]> {
    const attestations = await this.queryEventAttestations(eventId);
    
    return attestations.map(attestation => ({
      address: attestation.subject,
      role: attestation.data.role,
      checkInTime: new Date(attestation.data.timestamp),
      attestationId: attestation.attestationUID
    }));
  }
}

Best Practices

Privacy Considerations

Balance transparency with privacy:
// Public attestation - minimal data
const publicAttestation = {
  eventId: 'eth-denver-2024',
  timestamp: Date.now(),
  role: 'attendee'
};

// Private attestation - hash sensitive data
const privateAttestation = {
  eventId: 'private-event-001',
  attendeeHash: crypto.createHash('sha256').update(attendeeEmail).digest('hex'),
  timestamp: Date.now()
};

Batch Operations

Optimize for large events:
async function batchCheckIn(attendees: string[]): Promise<any> {
  const BATCH_SIZE = 50;
  const results = [];
  
  for (let i = 0; i < attendees.length; i += BATCH_SIZE) {
    const batch = attendees.slice(i, i + BATCH_SIZE);
    
    const batchResults = await Promise.allSettled(
      batch.map(attendee => checkInAttendee(attendee))
    );
    
    results.push(...batchResults);
    
    // Rate limiting
    if (i + BATCH_SIZE < attendees.length) {
      await new Promise(resolve => setTimeout(resolve, 1000));
    }
  }
  
  return results;
}

Fraud Prevention

Implement security measures:
class FraudPrevention {
  async detectDuplicateCheckIns(eventId: string): Promise<string[]> {
    const attestations = await this.queryEventAttestations(eventId);
    const addressCounts = {};
    
    attestations.forEach(att => {
      addressCounts[att.subject] = (addressCounts[att.subject] || 0) + 1;
    });
    
    return Object.entries(addressCounts)
      .filter(([_, count]) => count > 1)
      .map(([address]) => address);
  }
  
  async validateCheckInLocation(
    attendeeAddress: string,
    location: { lat: number; lng: number }
  ): Promise<boolean> {
    const eventLocation = this.eventConfig.location;
    const maxDistance = 0.5; // 500m radius
    
    const distance = this.calculateDistance(
      location,
      eventLocation
    );
    
    return distance <= maxDistance;
  }
}

Next Steps