Stop Gas Draining: How to Set Up Sybil-Resistant Gas Sponsorship
Comprehensive guide to preventing abuse and gas draining in your sponsorship campaigns. Learn to implement rate limits, wallet whitelists, Gitcoin Passport verification, and smart contract allowlists.
Stop Gas Draining: How to Set Up Sybil-Resistant Gas Sponsorship
You've created a gas sponsorship campaign. Congratulations! Now comes the hard part: preventing bad actors from draining your entire budget in minutes.
This guide covers battle-tested strategies to protect your gas sponsorship from Sybil attacks, bots, and abuse.
The Gas Draining Problem
Without protection, your sponsorship campaign is vulnerable to:
- Sybil Attacks: One person creates hundreds of wallets
- Bot Farming: Automated scripts repeatedly claiming sponsored gas
- Wash Trading: Users cycling funds through sponsored transactions
- Contract Exploitation: Calling expensive functions to drain gas
Real Example: A project launched a "free mint" campaign. Within 2 hours, one attacker with 500 wallets drained $15,000 in sponsored gas by minting and immediately selling NFTs.
Defense Layers
Effective protection requires multiple layers:
Layer 1: Rate Limiting (Per Wallet)
↓
Layer 2: Identity Verification (Gitcoin Passport, etc.)
↓
Layer 3: Contract Allowlisting (Only approved contracts)
↓
Layer 4: Transaction Analysis (Pattern detection)
↓
Layer 5: Budget Controls (Hard caps, alerts)
Layer 1: Rate Limiting
Per-Wallet Limits
The simplest defense. Set maximum sponsored transactions per wallet:
// GasX Campaign Configuration
{
"rateLimits": {
"perWallet": {
"maxTransactionsPerHour": 10,
"maxTransactionsPerDay": 50,
"maxGasPerDay": "0.01", // In ETH equivalent
"cooldownMinutes": 5 // Time between transactions
}
}
}
Why It Works
- Legitimate users rarely need more than 10-20 transactions/day
- Forces attackers to create many more wallets (costly)
- Easy to implement, low false positive rate
GasX Implementation
In your campaign settings:
- Go to Campaigns → Edit Campaign
- Navigate to Security tab
- Set Transaction Limits:
- Transactions per hour: 10
- Transactions per day: 50
- Gas per day: 0.01 ETH
Layer 2: Identity Verification
Rate limits aren't enough when attackers can create unlimited wallets. Add identity verification.
Option A: Gitcoin Passport Score
Gitcoin Passport provides a "humanity score" based on verified credentials:
// Check Gitcoin Passport score before sponsoring
interface PassportCheck {
address: string;
score: number;
threshold: number;
}
async function checkPassportScore(address: string): Promise<boolean> {
const response = await fetch(
`https://api.scorer.gitcoin.co/registry/score/${SCORER_ID}/${address}`,
{
headers: {
'X-API-Key': GITCOIN_API_KEY,
},
}
);
const { score } = await response.json();
// Require minimum score of 15 (adjustable)
return score >= 15;
}
Score Guidelines:
| Score | Trust Level | Recommendation |
|---|---|---|
| < 10 | Low | Block or heavily limit |
| 10-20 | Medium | Standard limits |
| 20-30 | High | Relaxed limits |
| > 30 | Very High | Minimal restrictions |
Option B: On-Chain Reputation
Check if wallets have meaningful on-chain history:
async function checkOnChainReputation(address: string): Promise<ReputationScore> {
const [balance, txCount, tokenHoldings, nftHoldings] = await Promise.all([
provider.getBalance(address),
provider.getTransactionCount(address),
getERC20Holdings(address),
getNFTHoldings(address),
]);
// Score based on activity
let score = 0;
if (balance > parseEther('0.01')) score += 10;
if (txCount > 10) score += 10;
if (txCount > 100) score += 20;
if (tokenHoldings.length > 0) score += 10;
if (nftHoldings.length > 0) score += 10;
// Check account age (proxy: first transaction date)
const firstTx = await getFirstTransaction(address);
const ageInDays = (Date.now() - firstTx.timestamp) / (1000 * 60 * 60 * 24);
if (ageInDays > 30) score += 10;
if (ageInDays > 180) score += 20;
if (ageInDays > 365) score += 30;
return { score, details: { balance, txCount, ageInDays } };
}
Option C: Social Verification
Require connection to social accounts:
- Twitter/X verification
- GitHub account linking
- Discord server membership
- Email verification
GasX supports integrating with:
- Privy for social logins
- Dynamic for wallet + social
- Worldcoin for proof of personhood
Layer 3: Contract Allowlisting
Never sponsor transactions to arbitrary contracts. Maintain a strict allowlist:
// Campaign contract allowlist
{
"allowedContracts": [
{
"address": "0x1234...",
"name": "MyNFT Contract",
"allowedFunctions": ["mint", "transfer"],
"maxGasPerCall": 200000
},
{
"address": "0x5678...",
"name": "Governance",
"allowedFunctions": ["vote", "delegate"],
"maxGasPerCall": 150000
}
],
"blockUnknownContracts": true
}
Function-Level Controls
Don't just allowlist contracts—allowlist specific functions:
function validateTransaction(tx: Transaction): ValidationResult {
const contract = allowedContracts.find(
(c) => c.address.toLowerCase() === tx.to.toLowerCase()
);
if (!contract) {
return { valid: false, reason: 'Contract not in allowlist' };
}
// Decode function selector
const selector = tx.data.slice(0, 10);
const functionName = decodeFunctionSelector(selector, contract.abi);
if (!contract.allowedFunctions.includes(functionName)) {
return { valid: false, reason: `Function ${functionName} not allowed` };
}
// Check gas limit
if (tx.gasLimit > contract.maxGasPerCall) {
return { valid: false, reason: 'Gas limit exceeds maximum' };
}
return { valid: true };
}
Layer 4: Transaction Analysis
Detect suspicious patterns in real-time:
Pattern Detection Rules
const suspiciousPatterns = [
{
name: 'rapid-fire',
check: (wallet: WalletHistory) =>
wallet.recentTxCount(5 * 60 * 1000) > 5, // 5+ tx in 5 minutes
action: 'flag',
},
{
name: 'new-wallet-burst',
check: (wallet: WalletHistory) =>
wallet.ageInHours < 24 && wallet.sponsoredTxCount > 10,
action: 'block',
},
{
name: 'gas-maximizer',
check: (tx: Transaction, avgGas: number) =>
tx.gasUsed > avgGas * 3, // 3x average gas usage
action: 'flag',
},
{
name: 'funding-source-analysis',
check: (wallet: WalletHistory) =>
wallet.fundingSource.isKnownAttacker,
action: 'block',
},
];
Clustering Detection
Identify wallets controlled by the same entity:
async function detectWalletClusters(
wallets: string[]
): Promise<ClusterResult[]> {
const clusters: Map<string, string[]> = new Map();
for (const wallet of wallets) {
// Check common funding sources
const fundingTxs = await getFundingTransactions(wallet);
for (const tx of fundingTxs) {
const source = tx.from;
const existing = clusters.get(source) || [];
existing.push(wallet);
clusters.set(source, existing);
}
}
// Flag clusters with many wallets
return Array.from(clusters.entries())
.filter(([, wallets]) => wallets.length > 5)
.map(([source, wallets]) => ({
fundingSource: source,
wallets,
riskLevel: wallets.length > 20 ? 'critical' : 'high',
}));
}
Layer 5: Budget Controls
Your last line of defense: hard budget limits and alerts.
Budget Configuration
{
"budget": {
"total": 1000, // Total campaign budget (USD)
"dailyLimit": 100, // Max spend per day
"hourlyLimit": 20, // Max spend per hour
"perWalletLimit": 5, // Max per wallet lifetime
"alerts": {
"warningThreshold": 0.7, // Alert at 70% spent
"criticalThreshold": 0.9, // Critical alert at 90%
"channels": ["email", "slack", "webhook"]
},
"autoActions": {
"onDailyLimitReached": "pause_until_reset",
"onTotalExhausted": "pause_campaign",
"onSuspiciousActivity": "require_manual_approval"
}
}
}
Real-Time Monitoring
GasX provides real-time dashboards showing:
- Spending rate (gas/hour)
- Unique wallets vs. transactions
- Geographic distribution (via IP)
- Contract function distribution
- Anomaly alerts
Complete Security Configuration
Here's a production-ready GasX campaign configuration:
{
"name": "Secure NFT Mint Campaign",
"network": "arbitrum",
"security": {
"rateLimits": {
"perWallet": {
"maxTransactionsPerHour": 5,
"maxTransactionsPerDay": 20,
"maxGasPerDay": "0.005",
"cooldownMinutes": 10
}
},
"verification": {
"gitcoinPassport": {
"enabled": true,
"minimumScore": 15,
"refreshIntervalHours": 24
},
"onChainReputation": {
"enabled": true,
"minimumAge": 7,
"minimumTransactions": 5
}
},
"contractAllowlist": [
{
"address": "0x...",
"functions": ["mint"],
"maxGas": 200000
}
],
"patternDetection": {
"enabled": true,
"rules": ["rapid-fire", "new-wallet-burst", "clustering"]
}
},
"budget": {
"total": 500,
"dailyLimit": 50,
"alerts": {
"email": "team@example.com",
"slack": "https://hooks.slack.com/..."
}
}
}
Incident Response Playbook
When you detect an attack:
1. Immediate Actions (< 5 minutes)
- Pause the campaign
- Block identified attacker wallets
- Capture transaction logs
2. Analysis (< 1 hour)
- Identify attack vector
- Trace wallet clusters
- Estimate damage
3. Recovery (< 24 hours)
- Update security rules
- Implement additional verification
- Resume with tighter limits
4. Post-Mortem
- Document the attack
- Update allowlists
- Share learnings (anonymized)
Balancing Security vs. UX
More security = more friction. Find the right balance:
| Campaign Type | Security Level | Rationale |
|---|---|---|
| Public Airdrop | Maximum | High abuse risk |
| Event/Hackathon | Medium | Controlled audience |
| Private Beta | Low | Trusted users |
| DAO Governance | Medium-High | Important but familiar users |
Progressive Trust
Start strict, relax for returning users:
function getSecurityLevel(wallet: WalletData): SecurityLevel {
if (wallet.isFirstTimeUser) return 'strict';
if (wallet.successfulTx < 5) return 'medium';
if (wallet.successfulTx >= 5 && wallet.flagCount === 0) return 'relaxed';
if (wallet.flagCount > 0) return 'strict';
return 'medium';
}
Conclusion
Gas sponsorship is powerful but requires robust security. By implementing multiple layers of defense:
- Rate limits stop casual abuse
- Identity verification stops Sybil attacks
- Contract allowlists prevent exploitation
- Pattern detection catches sophisticated attackers
- Budget controls limit maximum damage
GasX builds all of these protections into our platform. Create a campaign and configure security settings in minutes—no code required.
Further Reading
Ready to eliminate gas friction?
Create your first gas sponsorship campaign in under 5 minutes. No coding required.
Create Campaign