Skip to main content

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',
},
},
};