> ## Documentation Index
> Fetch the complete documentation index at: https://docs.galxe.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Common Integration Patterns

> Reusable code patterns and GraphQL queries for efficient Galxe integration

## Authentication & Setup

### Basic GraphQL Client

```javascript theme={null}
const executeQuery = async (query, variables = {}) => {
  const response = await fetch('https://graphigo-business.prd.galaxy.eco/query', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'access-token': process.env.GALXE_ACCESS_TOKEN
    },
    body: JSON.stringify({ query, variables })
  });

  const data = await response.json();
  if (data.errors) throw new Error(data.errors[0].message);
  return data.data;
};
```

### Error Handling with Retry

```javascript theme={null}
const retryQuery = async (query, variables, maxRetries = 3) => {
  for (let attempt = 1; attempt <= maxRetries; attempt++) {
    try {
      return await executeQuery(query, variables);
    } catch (error) {
      const isRetryable = error.message.includes('rate limit') || 
                         error.message.includes('timeout');
      
      if (isRetryable && attempt < maxRetries) {
        await new Promise(resolve => setTimeout(resolve, Math.pow(2, attempt) * 1000));
        continue;
      }
      throw error;
    }
  }
};
```

## Quest Patterns

### Quest Status Validation

```graphql theme={null}
query QuestStatus($id: ID!) {
  quest(id: $id) {
    status
    startTime
    endTime
  }
}
```

```javascript theme={null}
const isQuestActive = (quest) => {
  const now = Date.now() / 1000;
  return quest.status === 'Active' && 
         now >= quest.startTime && 
         now <= quest.endTime;
};

const getQuestPhase = (quest) => {
  const now = Date.now() / 1000;
  if (now < quest.startTime) return 'upcoming';
  if (now <= quest.endTime) return 'active';
  return 'ended';
};
```

### User Eligibility Check

```graphql theme={null}
query CheckEligibility($questId: ID!, $address: String!) {
  quest(id: $questId) {
    credentialGroups(address: $address) {
      conditions { eligible }
      rewards { eligible rewardType }
    }
  }
}
```

```javascript theme={null}
const checkEligibility = async (questId, address) => {
  const data = await executeQuery(`
    query CheckEligibility($questId: ID!, $address: String!) {
      quest(id: $questId) {
        credentialGroups(address: $address) {
          conditions { eligible }
          rewards { eligible }
        }
      }
    }
  `, { questId, address });

  return data.quest.credentialGroups.some(group => 
    group.conditions.some(c => c.eligible) && 
    group.rewards.some(r => r.eligible)
  );
};
```

## Pagination Patterns

### Standard Pagination Query

```graphql theme={null}
query PaginatedQuery($first: Int!, $after: String) {
  items(first: $first, after: $after) {
    pageInfo {
      hasNextPage
      endCursor
    }
    edges {
      node {
        # your fields here
      }
    }
  }
}
```

### Fetch All Pages

```javascript theme={null}
const fetchAllPages = async (query, variables = {}, extractPath) => {
  let allItems = [];
  let hasNextPage = true;
  let cursor = null;

  while (hasNextPage) {
    const data = await executeQuery(query, { 
      ...variables, 
      first: 100, 
      after: cursor 
    });
    
    const result = extractPath(data);
    allItems.push(...result.edges.map(edge => edge.node));
    
    hasNextPage = result.pageInfo.hasNextPage;
    cursor = result.pageInfo.endCursor;

    // Rate limiting
    await new Promise(resolve => setTimeout(resolve, 100));
  }

  return allItems;
};

// Usage example
const allQuests = await fetchAllPages(
  `query GetQuests($spaceId: ID!, $first: Int!, $after: String) {
    quests(input: {spaceId: $spaceId, first: $first, after: $after}) {
      pageInfo { hasNextPage endCursor }
      list { id name status }
    }
  }`,
  { spaceId: "40" },
  data => data.quests
);
```

## Leaderboard Patterns

### User Position Search

<Warning>
  **No Direct User Lookup**: The B2B API does not provide direct user position lookup. You must search through leaderboard pages to find a specific user.
</Warning>

```javascript theme={null}
const findUserPosition = async (spaceId, userAddress) => {
  let cursorAfter = null;
  let pageCount = 0;
  const maxPages = 20; // Reasonable search limit
  
  while (pageCount < maxPages) {
    const data = await executeQuery(`
      query SearchUser($spaceId: Int!, $cursorAfter: String) {
        space(id: $spaceId) {
          loyaltyPointsRanks(cursorAfter: $cursorAfter) {
            pageInfo { hasNextPage endCursor }
            list {
              rank
              points
              address { address username }
            }
          }
        }
      }
    `, { spaceId, cursorAfter });

    const userRank = data.space.loyaltyPointsRanks.list.find(item => 
      item.address.address.toLowerCase() === userAddress.toLowerCase()
    );
    
    if (userRank) {
      return { found: true, rank: userRank.rank, points: userRank.points };
    }
    
    if (!data.space.loyaltyPointsRanks.pageInfo.hasNextPage) break;
    cursorAfter = data.space.loyaltyPointsRanks.pageInfo.endCursor;
    pageCount++;
    
    await new Promise(resolve => setTimeout(resolve, 100)); // Rate limiting
  }
  
  return { found: false, rank: null, points: 0 };
};

const getLeaderboardWithUser = async (spaceId, userAddress) => {
  const [leaderboardData, userPosition] = await Promise.all([
    executeQuery(`
      query GetLeaderboard($spaceId: Int!) {
        space(id: $spaceId) {
          loyaltyPointsRanks(first: 10) {
            list {
              rank
              points
              address { username }
            }
          }
        }
      }
    `, { spaceId }),
    findUserPosition(spaceId, userAddress)
  ]);

  return {
    leaderboard: leaderboardData.space.loyaltyPointsRanks.list,
    userPosition
  };
};
```

## Credential Patterns

### Batch Eligibility Check

```javascript theme={null}
const checkBatchEligibility = async (credId, addresses) => {
  const promises = addresses.map(address => 
    executeQuery(`
      query CheckEligibility($credId: ID!, $address: String!) {
        credential(id: $credId) { eligible(address: $address) }
      }
    `, { credId, address })
      .then(data => ({ address, eligible: data.credential.eligible === 1 }))
      .catch(() => ({ address, eligible: false }))
  );

  return Promise.all(promises);
};
```

### Credential Validation

```javascript theme={null}
const validateCredential = async (credId) => {
  const data = await executeQuery(`
    query ValidateCredential($credId: ID!) {
      credential(id: $credId) {
        syncStatus
        itemCount
        lastUpdate
      }
    }
  `, { credId });

  const cred = data.credential;
  return {
    isValid: cred.syncStatus === 'SYNCED',
    hasHolders: cred.itemCount > 0,
    lastSynced: new Date(cred.lastUpdate * 1000)
  };
};
```

## Real-time Monitoring

### Simple Polling Monitor

```javascript theme={null}
const createMonitor = (queryFn, interval = 30000) => {
  let intervalId = null;
  
  const start = (callback) => {
    intervalId = setInterval(async () => {
      try {
        const data = await queryFn();
        callback({ type: 'update', data });
      } catch (error) {
        callback({ type: 'error', error: error.message });
      }
    }, interval);
  };

  const stop = () => {
    if (intervalId) {
      clearInterval(intervalId);
      intervalId = null;
    }
  };

  return { start, stop };
};

// Usage
const leaderboardMonitor = createMonitor(
  () => getLeaderboardWithUser(40, userAddress),
  60000 // 1 minute
);

leaderboardMonitor.start((update) => {
  if (update.type === 'update') {
    console.log('Leaderboard updated:', update.data);
  }
});
```

## Rate Limiting

### Simple Rate Limiter

```javascript theme={null}
class RateLimiter {
  constructor(requestsPerSecond = 10) {
    this.requests = [];
    this.maxRequests = requestsPerSecond;
  }

  async execute(fn) {
    const now = Date.now();
    this.requests = this.requests.filter(time => now - time < 1000);

    if (this.requests.length >= this.maxRequests) {
      const waitTime = 1000 - (now - this.requests[0]);
      await new Promise(resolve => setTimeout(resolve, waitTime));
    }

    this.requests.push(now);
    return fn();
  }
}

// Usage
const limiter = new RateLimiter(5); // 5 requests per second

const data = await limiter.execute(() => 
  executeQuery(query, variables)
);
```

## Data Transformation

### Standardize User Objects

```javascript theme={null}
const standardizeUser = (userObject) => {
  // Handle different user object formats
  if (userObject.address) {
    return {
      id: userObject.address.id || userObject.address.address,
      username: userObject.address.username,
      address: userObject.address.address
    };
  }
  
  return {
    id: userObject.id,
    username: userObject.username,
    address: userObject.address
  };
};
```

### Format Points and Rankings

```javascript theme={null}
const formatRanking = (rankingData) => ({
  rank: parseInt(rankingData.rank),
  points: parseInt(rankingData.points),
  user: standardizeUser(rankingData),
  rankMovement: rankingData.delta || 0
});

const formatLeaderboard = (leaderboardData) => ({
  totalCount: leaderboardData.totalCount,
  rankings: leaderboardData.edges.map(edge => formatRanking(edge.node)),
  hasNextPage: leaderboardData.pageInfo.hasNextPage
});
```

## Utility Functions

### Time Helpers

```javascript theme={null}
const timeHelpers = {
  toUnix: (date) => Math.floor(date.getTime() / 1000),
  fromUnix: (timestamp) => new Date(timestamp * 1000),
  isAfter: (timestamp) => Date.now() / 1000 > timestamp,
  formatDuration: (seconds) => {
    const days = Math.floor(seconds / 86400);
    const hours = Math.floor((seconds % 86400) / 3600);
    const minutes = Math.floor((seconds % 3600) / 60);
    return `${days}d ${hours}h ${minutes}m`;
  }
};
```

### Validation Helpers

```javascript theme={null}
const validators = {
  isValidAddress: (address) => /^0x[a-fA-F0-9]{40}$/.test(address),
  isValidQuestId: (id) => typeof id === 'string' && id.length > 0,
  isValidSpaceId: (id) => Number.isInteger(id) && id > 0
};
```

## Best Practices Summary

1. **Always validate inputs** before API calls
2. **Implement retry logic** for network failures
3. **Use pagination** for large datasets
4. **Cache frequently accessed data** (30-60 seconds)
5. **Handle rate limits** with exponential backoff
6. **Monitor for errors** and log appropriately
7. **Use environment variables** for sensitive data

## Quick Reference

### Essential Queries

* Quest status: `quest(id) { status startTime endTime }`
* User eligibility: `quest(id) { credentialGroups(address) { conditions { eligible } } }`
* Leaderboard: `space(id) { loyaltyPointsRanks(cursorAfter) { list { rank points address { username } } } }`
* User search: Must paginate through leaderboard to find specific users

### Common Response Patterns

* Paginated: `{ pageInfo { hasNextPage endCursor } edges { node { ... } } }`
* User objects: `{ address { username address } }` or `{ username address }`
* Error format: `{ errors [{ message extensions { code category } }] }`

## Next Steps

* **[Quest Guide](/galxe-integration/guides/quests)** - Complete quest implementation
* **[Leaderboard Guide](/galxe-integration/guides/leaderboards)** - Advanced ranking features
* **[API Reference](/galxe-integration/api-reference/quest)** - Detailed API documentation
