User Referral Mutations
Overview​
The User Referral API provides GraphQL mutations for performing actions related to referral commissions. Currently, the primary mutation allows users to claim their accumulated referral earnings.
claimEarningsFromReferrals Mutation​
Claim available referral commissions and transfer them to the user's account balance.
Mutation Definition​
mutation ClaimEarningsFromReferrals {
claimEarningsFromReferrals {
username
avatarUrl
directReferralsCount
totalIndirectReferralsCount
referredThisWeek
totalReferralStakes
totalEarning
totalClaimed
readyToClaim
}
}
Response Type​
Returns UserReferralInfoModel
with updated commission information:
type UserReferralInfoModel {
username: String! // User's display name
avatarUrl: String // Profile picture URL (optional)
directReferralsCount: Int! // Number of direct referrals
totalIndirectReferralsCount: Int! // Total indirect referrals
referredThisWeek: Int! // New referrals this week
totalReferralStakes: String! // Total betting volume from referrals
totalEarning: String! // Total commission earned
totalClaimed: String! // Updated total claimed amount
readyToClaim: String! // Updated available balance (should be $0.00)
}
Example Request​
curl -X POST https://api.blockbet.com/graphql \
-H "Content-Type: application/json" \
-H "Cookie: accessToken=your_jwt_token" \
-d '{
"query": "mutation { claimEarningsFromReferrals { totalClaimed readyToClaim } }"
}'
Example Response (Successful Claim)​
{
"data": {
"claimEarningsFromReferrals": {
"username": "player123",
"avatarUrl": "https://cdn.blockbet.com/avatars/player123.jpg",
"directReferralsCount": 15,
"totalIndirectReferralsCount": 42,
"referredThisWeek": 3,
"totalReferralStakes": "$125,430.50",
"totalEarning": "$2,847.65",
"totalClaimed": "$2,847.65",
"readyToClaim": "$0.00"
}
}
}
Claim Process Flow​
1. Pre-Claim Validation​
The system performs several validation checks before processing the claim:
User Status Validation​
if (userReferralStats.status !== UserStatus.ACTIVE) {
throw new AppError(ErrorMessage.USER_BLOCKED);
}
Available Balance Check​
if (userReferralStats?.readyToClaimValue === 0) {
throw new AppError(ErrorMessage.NO_EARNINGS_TO_CLAIM);
}
Currency Rate Validation​
if (!usdcRateInSystemCurrency || usdcRateInSystemCurrency === 0) {
throw new AppError(`USDC_${ErrorMessage.TOKEN_RATE_NOT_FOUND}`);
}
2. Transaction Processing​
The claim process uses atomic database transactions to ensure consistency:
await this.mainTransactionService.transaction(async (session) => {
// Step 1: Create commission claim record
commissionClaim = await this.userCommissionClaimsService.create({
obj: {
user: userId,
amount: claimableAmount,
amountUsdc: claimableUsdcAmount,
claimedAt: new Date(),
},
session,
});
// Step 2: Update user's claimed commission totals
await this.usersService.updateOneRoot({
filter: { _id: userId },
obj: {
$inc: {
'referralCommissions.totalCommissionClaimed': claimableAmount,
'referralCommissions.totalCommissionClaimedUsdcValue':
claimableUsdcAmount,
},
},
session,
});
// Step 3: Transfer credit via BadHombre API
const transferResponse = await this.badhombreApiProvider.transferCredit({
playerId: user.badhombreId,
amount: claimableUsdcAmount,
externalTransactionId: uuid(),
currencyCode: 'USDC',
type: 'RAF_CLAIM',
});
});
3. External API Integration​
The system integrates with the BadHombre API for credit transfers:
Transfer Parameters​
- playerId: User's BadHombre player ID
- amount: Claim amount in USDC
- externalTransactionId: Unique transaction identifier
- currencyCode: Always 'USDC' for referral claims
- type: 'RAF_CLAIM' for referral claim transactions
Error Handling​
Validation Errors​
No Earnings to Claim​
{
"errors": [
{
"message": "No earnings available to claim",
"extensions": {
"code": "NO_EARNINGS_TO_CLAIM"
}
}
]
}
User Account Blocked​
{
"errors": [
{
"message": "User account is blocked",
"extensions": {
"code": "USER_BLOCKED"
}
}
]
}
Currency Rate Not Available​
{
"errors": [
{
"message": "USDC token rate not found",
"extensions": {
"code": "USDC_TOKEN_RATE_NOT_FOUND"
}
}
]
}
Transaction Errors​
Failed Credit Transfer​
{
"errors": [
{
"message": "Failed to transfer credit to user account",
"extensions": {
"code": "FAILED_TO_TRANSFER_CREDIT"
}
}
]
}
Database Transaction Failure​
{
"errors": [
{
"message": "Transaction failed during commission claim",
"extensions": {
"code": "TRANSACTION_FAILED"
}
}
]
}
Rate Limiting​
The claim mutation has strict rate limiting to prevent abuse:
- Rate Limit: 5 claims per hour per user
- Cooldown: 12 minutes between successful claims
- Daily Limit: Maximum 10 claims per day
Rate Limit Response​
{
"errors": [
{
"message": "Rate limit exceeded for commission claims",
"extensions": {
"code": "RATE_LIMIT_EXCEEDED",
"retryAfter": 720,
"dailyLimitRemaining": 3
}
}
]
}
Currency Conversion​
System Currency to USDC​
The system converts internal currency amounts to USDC for payouts:
const systemCurrencyInUsdcValue = new BigNumber(1).dividedBy(
usdcRateInSystemCurrency,
);
const claimableUsdcAmount = new BigNumber(claimableAmount)
.multipliedBy(systemCurrencyInUsdcValue)
.toNumber();
Conversion Example​
- System Currency Amount: 1000 (internal units)
- USDC Rate: 0.001 (1 system unit = 0.001 USDC)
- USDC Amount: 1000 × 0.001 = 1.0 USDC
Audit Trail​
Commission Claim Record​
Each successful claim creates a detailed audit record:
interface UserCommissionClaim {
user: ObjectId; // User who claimed
amount: number; // Amount in system currency
amountUsdc: number; // Amount in USDC
claimedAt: Date; // Claim timestamp
transactionId?: string; // BadHombre transaction ID
status: 'PENDING' | 'COMPLETED' | 'FAILED';
}
Transaction Tracking​
The system tracks:
- Internal Transaction: Database updates and claim records
- External Transaction: BadHombre API transfer
- Audit Logs: Complete claim process logging
Best Practices​
Client Implementation​
Pre-Claim Validation​
// Check available balance before attempting claim
const stats = await client.query({
query: REFERRAL_STATS_QUERY,
});
if (
parseFloat(
stats.data.referralStats.readyToClaim.replace('$', '').replace(',', ''),
) > 0
) {
// Proceed with claim
const result = await client.mutate({
mutation: CLAIM_EARNINGS_MUTATION,
});
}
Error Handling​
try {
const result = await client.mutate({
mutation: CLAIM_EARNINGS_MUTATION,
});
// Show success message
showSuccess(`Claimed ${result.data.claimEarningsFromReferrals.readyToClaim}`);
} catch (error) {
if (error.extensions?.code === 'NO_EARNINGS_TO_CLAIM') {
showWarning('No earnings available to claim');
} else if (error.extensions?.code === 'RATE_LIMIT_EXCEEDED') {
showError(
`Rate limit exceeded. Try again in ${error.extensions.retryAfter} seconds`,
);
} else {
showError('Claim failed. Please try again later.');
}
}
User Feedback​
// Show loading state during claim
setClaimLoading(true);
try {
const result = await claimEarnings();
// Update UI with new balances
updateReferralStats(result.data.claimEarningsFromReferrals);
// Show success notification
showNotification('Commission claimed successfully!', 'success');
} finally {
setClaimLoading(false);
}
Security Considerations​
Input Validation​
- No user input required for claim mutation
- All validation handled server-side
- User context automatically injected
Transaction Safety​
- Atomic database transactions prevent partial updates
- External API failures trigger complete rollback
- Comprehensive error logging for debugging
Rate Limiting​
- Implement client-side rate limiting awareness
- Show appropriate cooldown timers
- Handle rate limit errors gracefully
Testing​
Test Scenarios​
Successful Claim​
mutation TestSuccessfulClaim {
claimEarningsFromReferrals {
totalClaimed
readyToClaim
}
}
No Earnings Available​
# Should return NO_EARNINGS_TO_CLAIM error
mutation TestNoEarnings {
claimEarningsFromReferrals {
readyToClaim
}
}
Rate Limited User​
# Should return RATE_LIMIT_EXCEEDED error after multiple claims
mutation TestRateLimit {
claimEarningsFromReferrals {
totalClaimed
}
}
Mock Responses​
For testing purposes, you can mock the mutation response:
const mockClaimResponse = {
data: {
claimEarningsFromReferrals: {
username: 'testuser',
totalClaimed: '$100.00',
readyToClaim: '$0.00',
},
},
};