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:Copy
// 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:Copy
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] || [])];
}
}
Copy
### 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:Copy
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:Copy
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);
}
Copy
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;
}
Copy
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);
}
Copy
private calculateDurationScore(duration: number): number {
// 25 points per hour, maximum 50 points
const hoursAttended = duration / 3600;
return Math.min(hoursAttended * 25, 50);
}
Copy
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:Copy
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:Copy
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:Copy
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:Copy
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:Copy
// 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:Copy
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:Copy
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
- Custom Schema Design - Event-specific schemas
- NFT Integration - Attendance NFTs and POAPs
- Analytics Guide - Event data analysis
- Privacy Options - Private event attestations