06 - Claim Flow (Gewinn-Auszahlung)¶
Dokument: Gasless Claim-System Stand: 28.02.2026 Scope: Merkle-basiertes Claiming via Relayer Status: Audit-ready
1. Übersicht¶
Das Claim-System ermöglicht es Nutzern, Lotteriegewinne und Affiliate-Provisionen gasless abzurufen. Claims werden über Merkle-Tree-Proofs verifiziert und über einen Relayer on-chain ausgeführt.
Beteiligte Systeme¶
| System | Rolle |
|---|---|
| Frontend (Claim-Seite) | UI, Signierung, Status-Tracking |
| Backend (API Routes) | Payload-Generierung, Relayer-Steuerung |
| Relayer (Wallet) | On-Chain TX-Submission |
| ClaimRouter (Contract) | Claim-Ausführung, Merkle-Verifikation |
| PrizeVaultV3 (Contract) | Winner-Auszahlung |
| AffiliateVaultV2 (Contract) | Affiliate-Auszahlung |
2. Ablaufdiagramm¶
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
│ Payload │────▶│ Approval │────▶│ Signatur │────▶│ Execute │
│ abrufen │ │ (USDT) │ │ (EIP-712) │ │ (Relayer) │
│ /claim/load │ │ On-Chain │ │ Client │ │ /claim/exec │
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
3. Detaillierter Flow¶
Schritt 1: Payload abrufen¶
Endpoint: GET /api/claim/payload
Authentifizierung (EIP-191 Signatur):
Message: "Claim payload request\nAddress: 0x...\nChainId: 421614\nTimestamp: 1709..."
Headers:
x-claim-address: 0x...
x-claim-signature: 0x...
x-claim-timestamp: 1709...
Replay-Schutz: Timestamp ± 5 Minuten
Response:
{
"kycRequired": false,
"kycStatus": "verified",
"threshold": 10000000000,
"winner": {
"total": "5000000",
"claims": [{
"type": "winner",
"dayId": 100,
"player": "0x...",
"paidTo": "0x...",
"amount": "5000000",
"proof": ["0x...", "0x..."]
}],
"pending": false,
"remaining": 0
},
"affiliate": {
"total": "250000",
"claims": [...],
"pending": false,
"remaining": 0,
"locked": false,
"lockReason": null,
"requiredTips": 5,
"actualTips": 12
},
"claimRouterAddress": "0x..."
}
Backend-Logik: - Wallet-Signatur verifizieren (Replay-Schutz ± 5 Min) - KYC-Status vs. Schwellenwert prüfen - settlement_winner_payouts mit Merkle Proofs laden - settlement_affiliate_payouts mit Merkle Proofs laden - claim_reassignments prüfen - Affiliate-Restriktionen prüfen
Einschränkungen:
| Regel | Beschreibung |
|---|---|
| Max Claims/Batch | 20 Claims pro Request |
| Affiliate-Tag | Nur Sonntags claimbar |
| Affiliate-Aktivität | Mind. 5 bezahlte Tips in den letzten 6 Tagen |
| KYC-Schwellenwert | In bo_config konfiguriert |
Schritt 2: USDT Approval (falls nötig)¶
Prüfe Allowance des ClaimRouter Contracts
Falls unzureichend: USDT.approve(claimRouterAddress, MAX_UINT256)
Schritt 3: ClaimPermit signieren¶
EIP-712 Typ-Definition:
Domain: { name: "ChainbetsClaim", version: "1", chainId, verifyingContract }
Types: {
ClaimPermit: [
{ name: "claimer", type: "address" },
{ name: "totalWinnerAmount", type: "uint256" },
{ name: "totalAffiliateAmount", type: "uint256" },
{ name: "maxGasCostUsdt", type: "uint256" },
{ name: "deadline", type: "uint256" },
{ name: "nonce", type: "bytes32" },
{ name: "claimsHash", type: "bytes32" }
]
}
Permit-Generierung:
const nonce = generateNonce(); // 16 random Bytes
const deadline = calculateDeadline(5); // +5 Minuten
const claimsHash = computeClaimsHash(winnerClaims, affiliateClaims);
claimsHash-Berechnung:
Schritt 4: Ausführung via Relayer¶
Endpoint: POST /api/claim/execute
Request:
{
"permit": {
"claimer": "0x...",
"totalWinnerAmount": "5000000",
"totalAffiliateAmount": "250000",
"maxGasCostUsdt": "100000",
"deadline": 1709...,
"nonce": "0x...",
"claimsHash": "0x..."
},
"signature": "0x...",
"winnerClaims": [{
"dayId": 100,
"originalWallet": "0x...",
"paidTo": "0x...",
"amount": "5000000",
"proof": ["0x...", "0x..."]
}],
"affiliateClaims": [{
"dayId": 100,
"affiliate": "0x...",
"amount": "250000",
"proof": ["0x..."]
}]
}
Contract-Call:
Response:
{
"success": true,
"txHash": "0x...",
"blockNumber": 12345,
"gasUsed": "250000",
"gasCostUsdt": "75000",
"netPayout": "5175000",
"winnerClaimsExecuted": 1,
"affiliateClaimsExecuted": 1
}
Backend nach Ausführung: - settlement_winner_payouts: claimed=true - settlement_affiliate_payouts: claimed=true - claim_events: Event loggen
4. Optimistic Update¶
Endpoint: POST /api/claim/optimistic-update
Nach TX-Submission, vor Bestätigung:
{
"txHash": "0x...",
"payload": {
"winner": { "claims": [...] },
"affiliate": { "claims": [...] }
}
}
Setzt claimed_pending=true und pending_tx_hash in DB.
5. Claim-Typen¶
5.1 Winner Claims¶
| Feld | Beschreibung |
|---|---|
| dayId | Settlement-Tag |
| originalWallet | Ursprüngliche Spieler-Wallet |
| paidTo | Auszahlungs-Wallet (kann reassigned sein) |
| amount | Gewinnbetrag (USDT, 6 Decimals) |
| proof | Merkle-Proof-Array |
5.2 Affiliate Claims¶
| Feld | Beschreibung |
|---|---|
| dayId | Settlement-Tag |
| affiliate | Affiliate-Wallet |
| amount | Provisionsbetrag (USDT, 6 Decimals) |
| proof | Merkle-Proof-Array |
5.3 Affiliate-Einschränkungen¶
| Regel | Beschreibung |
|---|---|
| Auszahlungstag | Nur Sonntags |
| Mindestaktivität | 5 bezahlte Tips in den letzten 6 Tagen |
| Lock-Gründe | not_sunday, insufficient_activity |
6. Contract-Versionen¶
Datei: lib/contractVersions.ts
Claims können sich über verschiedene Contract-Versionen erstrecken (bei Migrationen):
function getContractsForDay(dayId): ContractVersion {
// Query: contract_versions WHERE from_day_id <= dayId <= to_day_id
// Fallback: .env Werte
}
function groupClaimsByVersion(dayIds): Map<ContractVersion, dayId[]>
7. Multicall-Batch-Verarbeitung¶
Datei: lib/buildClaimMulticall.ts
Für direkte Claims (ohne Relayer) wird Multicall3 verwendet:
Multicall3.aggregate3([
{ target: PrizeVaultV3, data: claimMerkle(dayId, wallet, paidTo, amount, proof) },
{ target: AffiliateVault, data: claimAffiliate(dayId, affiliate, to, amount, proof) },
...
])
Multicall3-Adresse: 0xcA11bde05977b3631167028862bE2a173976CA11
8. Debug-Endpoint¶
Endpoint: GET /api/claim/debug?address=0x...
Keine Signatur erforderlich. Gibt detaillierte Claim-Informationen zurück:
{
"contracts": { "prizeVault": "0x...", "claimRouter": "0x..." },
"trustedClaimers": { "prizeVault": "0x...", "match": { "prize": true } },
"winnerClaims": [{
"db": { "claimed": false, "merkleRoot": "0x..." },
"onChain": { "claimed": false, "merkleRoot": "0x...", "rootMatch": true },
"canClaim": true
}],
"summary": {
"totalWinnerInDb": 3,
"claimableWinner": 2,
"alreadyClaimedWinner": 1
}
}
9. Event-Logging¶
Datei: lib/claimEventLogger.ts
| Event-Typ | Beschreibung |
|---|---|
CLAIM_SIGNATURE_ERROR |
Signatur-Fehler |
CLAIM_TX_ERROR |
TX-Ausführungsfehler |
CLAIM_PENDING_SET |
Optimistic Update gesetzt |
CLAIM_TX_REVERTED |
TX reverted |
CLAIM_CONFIRMED |
Claim erfolgreich |
BACKFILL_TRIGGERED |
Backfill ausgelöst |
BACKFILL_CLAIM_CONFIRMED |
Backfill-Claim bestätigt |
DB-Tabelle: claim_events(event_type, scope, tx_hash, details)
Weiterführende Dokumente: - 05 - Buy Flow - 08 - Blockchain - 10 - Sicherheit