Overview

Implement quest systems that handle:

  • Quest validation and status checking
  • User eligibility verification through credential groups
  • Quest discovery and filtering
  • Loyalty points tracking

Basic Quest Operations

Get Quest Details

query GetQuest($id: ID!) {
  quest(id: $id) {
    id
    name
    type
    status
    description
    startTime
    endTime
    cap
    participantsCount
    loyaltyPoints
    space {
      id
      name
    }
    chain
  }
}

Response:

{
  "data": {
    "quest": {
      "id": "GChdWUjXX3",
      "name": "Introduction to Web3!",
      "type": "Drop",
      "status": "Active",
      "description": "Welcome to Module 1-Course 1 of Mission Web3!",
      "startTime": 1699200000,
      "endTime": 1699800000,
      "cap": 10000,
      "participantsCount": 234163,
      "loyaltyPoints": 100,
      "space": {
        "id": "40",
        "name": "BNB Chain"
      },
      "chain": "BSC"
    }
  }
}

List Space Quests

query GetSpaceQuests($spaceId: ID!, $first: Int!, $statuses: [QuestStatus!]) {
  quests(input: {
    spaceId: $spaceId
    first: $first
    statuses: $statuses
  }) {
    totalCount
    pageInfo {
      hasNextPage
      endCursor
    }
    list {
      id
      name
      type
      status
      participantsCount
      loyaltyPoints
      startTime
      endTime
    }
  }
}

Quest Status Reference

StatusDescriptionWhen Used
DraftQuest in developmentBefore publishing
ActiveQuest is live and accepting participantsDuring quest period
NotStartedQuest published but not startedBefore start time
ExpiredQuest has endedAfter end time
CapReachedParticipant limit reachedWhen cap is full
DeletedQuest removedRemoved quests

Quest Types

TypeDescription
DropNFT drop quest
MysteryBoxMystery box reward
AirdropToken airdrop
PointsLoyalty points reward
ExternalLinkExternal link verification
BountyBounty quest

User Eligibility Verification

Check User Eligibility

query CheckUserEligibility($questId: ID!, $address: String!) {
  quest(id: $questId) {
    id
    name
    status
    credentialGroups(address: $address) {
      id
      name
      conditionRelation
      conditions {
        expression
        eligible
      }
      rewards {
        expression
        eligible
        rewardType
        rewardCount
      }
    }
  }
}

Key Points:

  • When conditions.eligible is true, user meets requirements
  • When rewards.eligible is true, user can claim rewards
  • conditionRelation can be ALL (must meet all) or ANY (meet at least one)

Basic Implementation

Quest Validation

async function validateQuest(questId, accessToken) {
  const response = await fetch('https://graphigo-business.prd.galaxy.eco/query', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'access-token': accessToken
    },
    body: JSON.stringify({
      query: `query GetQuest($id: ID!) {
        quest(id: $id) {
          id
          name
          status
          startTime
          endTime
          participantsCount
          cap
        }
      }`,
      variables: { id: questId }
    })
  });
  
  const data = await response.json();
  const quest = data.data.quest;
  
  if (!quest) throw new Error('Quest not found');
  
  // Check quest status
  if (quest.status === 'Draft') throw new Error('Quest is in draft mode');
  if (quest.status === 'Deleted') throw new Error('Quest has been deleted');
  if (quest.status === 'Expired') throw new Error('Quest has expired');
  if (quest.status === 'CapReached') throw new Error('Quest capacity reached');
  
  // Check timing for active quests
  if (quest.status === 'Active' && quest.startTime && quest.endTime) {
    const now = Math.floor(Date.now() / 1000);
    if (now < quest.startTime) throw new Error('Quest has not started yet');
    if (now > quest.endTime) throw new Error('Quest has ended');
  }
  
  return quest;
}

Check User Eligibility

async function checkUserEligibility(questId, userAddress, accessToken) {
  const response = await fetch('https://graphigo-business.prd.galaxy.eco/query', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'access-token': accessToken
    },
    body: JSON.stringify({
      query: `query CheckUserEligibility($questId: ID!, $address: String!) {
        quest(id: $questId) {
          id
          name
          status
          credentialGroups(address: $address) {
            id
            name
            conditionRelation
            conditions {
              expression
              eligible
            }
            rewards {
              expression
              eligible
              rewardType
              rewardCount
            }
          }
        }
      }`,
      variables: { questId, address: userAddress }
    })
  });
  
  const data = await response.json();
  const quest = data.data.quest;
  
  if (!quest) throw new Error('Quest not found');
  
  const eligibilityResults = quest.credentialGroups.map(group => {
    // Check conditions based on relation (ALL or ANY)
    const conditionsMet = group.conditionRelation === 'ALL' 
      ? group.conditions.every(c => c.eligible)
      : group.conditions.some(c => c.eligible);
    
    const eligibleRewards = group.rewards.filter(r => r.eligible);
    
    return {
      groupId: group.id,
      groupName: group.name,
      conditionsMet,
      eligibleRewards
    };
  });
  
  const isEligible = eligibilityResults.some(r => r.conditionsMet && r.eligibleRewards.length > 0);
  
  return {
    questId,
    userAddress,
    isEligible,
    eligibilityDetails: eligibilityResults,
    questName: quest.name,
    questStatus: quest.status
  };
}

Get Space Quests

async function getSpaceQuests(spaceId, accessToken, options = {}) {
  const { 
    limit = 20, 
    cursor = null, 
    statuses = ['Active']
  } = options;
  
  const response = await fetch('https://graphigo-business.prd.galaxy.eco/query', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'access-token': accessToken
    },
    body: JSON.stringify({
      query: `query GetSpaceQuests($input: ListQuestInput!) {
        quests(input: $input) {
          totalCount
          pageInfo {
            hasNextPage
            endCursor
          }
          list {
            id
            name
            type
            status
            participantsCount
            loyaltyPoints
            startTime
            endTime
            cap
          }
        }
      }`,
      variables: {
        input: {
          spaceId,
          first: limit,
          after: cursor,
          statuses
        }
      }
    })
  });
  
  const data = await response.json();
  return data.data.quests;
}

Quest Status Helpers

Check Quest Phase

function getQuestPhase(quest) {
  switch (quest.status) {
    case 'NotStarted':
      return 'not_started';
    case 'Active':
      if (quest.startTime && quest.endTime) {
        const now = Math.floor(Date.now() / 1000);
        if (now < quest.startTime) return 'scheduled';
        if (now > quest.endTime) return 'ended_pending';
      }
      return 'active';
    case 'Expired':
      return 'expired';
    case 'CapReached':
      return 'full';
    case 'Draft':
      return 'draft';
    case 'Deleted':
      return 'deleted';
    default:
      return 'unknown';
  }
}

function isQuestParticipable(quest) {
  if (quest.status !== 'Active') return false;
  
  // Check capacity
  if (quest.cap > 0 && quest.participantsCount >= quest.cap) return false;
  
  // Check timing
  if (quest.startTime && quest.endTime) {
    const now = Math.floor(Date.now() / 1000);
    if (now < quest.startTime || now > quest.endTime) return false;
  }
  
  return true;
}

Usage Examples

Basic Quest Validation

// Validate quest before showing UI
const accessToken = process.env.GALXE_ACCESS_TOKEN;

try {
  const quest = await validateQuest('GChdWUjXX3', accessToken);
  console.log(`Quest "${quest.name}" is ${quest.status}`);
  console.log(`Participants: ${quest.participantsCount}`);
  
  const phase = getQuestPhase(quest);
  const canParticipate = isQuestParticipable(quest);
  
  console.log(`Phase: ${phase}`);
  console.log(`Can participate: ${canParticipate}`);
} catch (error) {
  console.error('Quest validation failed:', error.message);
}

User Eligibility Check

// Check if user can participate
const userAddress = '0x1234567890123456789012345678901234567890';
const eligibility = await checkUserEligibility('GChdWUjXX3', userAddress, accessToken);

if (eligibility.isEligible) {
  console.log('User is eligible for quest');
  console.log('Available rewards:', eligibility.eligibilityDetails
    .flatMap(group => group.eligibleRewards)
    .map(reward => reward.expression)
  );
} else {
  console.log('User is not eligible for this quest');
  console.log('Missing requirements:', eligibility.eligibilityDetails
    .filter(group => !group.conditionsMet)
    .map(group => group.groupName)
  );
}

Quest Discovery

// Get active quests for a space
const spaceQuests = await getSpaceQuests('40', accessToken, {
  statuses: ['Active'],
  limit: 10
});

console.log(`Found ${spaceQuests.totalCount} active quests`);
console.log('Quest list:');
spaceQuests.list.forEach(quest => {
  console.log(`- ${quest.name} (${quest.loyaltyPoints} points, ${quest.participantsCount} participants)`);
});

Complete Quest Check

async function createQuestDashboard(spaceId, userAddress, accessToken) {
  // Get active quests
  const quests = await getSpaceQuests(spaceId, accessToken, {
    statuses: ['Active'],
    limit: 10
  });
  
  // Check eligibility for top quests
  const eligibilityChecks = await Promise.all(
    quests.list.slice(0, 5).map(quest => 
      checkUserEligibility(quest.id, userAddress, accessToken)
        .then(result => ({ ...quest, eligibility: result }))
        .catch(error => ({ ...quest, eligibility: { error: error.message } }))
    )
  );
  
  const eligibleQuests = eligibilityChecks.filter(q => q.eligibility.isEligible);
  const totalPoints = eligibleQuests.reduce((sum, q) => sum + q.loyaltyPoints, 0);
  
  return {
    space: { id: spaceId },
    totalQuests: quests.totalCount,
    checkedQuests: eligibilityChecks.length,
    eligibleQuests: eligibleQuests.length,
    potentialPoints: totalPoints,
    quests: eligibilityChecks
  };
}

Best Practices

  1. Status Validation: Always check quest status before showing participation UI
  2. Error Handling: Handle cases where quests are not found or access is denied
  3. Rate Limiting: Add delays when processing multiple quests or users
  4. Caching: Cache quest details for 1-5 minutes to reduce API calls
  5. Capacity Monitoring: Check both status and participant count vs cap

Quest Lifecycle

StatusUser ActionsNext Status
DraftNone (not visible)NotStarted or Active
NotStartedView details, set remindersActive
ActiveParticipate, check eligibilityExpired or CapReached
CapReachedView results onlyExpired
ExpiredView results onlyN/A
DeletedNone (hidden)N/A

Common Integration Patterns

  • Quest Discovery: Filter by space, status, and type
  • Eligibility Pre-check: Verify user can participate before showing UI
  • Status Monitoring: Track quest lifecycle changes
  • Reward Tracking: Monitor available rewards and claim status
  • Points Calculation: Track potential loyalty points earnings

Next Steps