This guide documents the recipient validation, blocking, and error handling system in TipStream.
TipStream implements comprehensive recipient validation to ensure safe and valid tip transactions. The system includes format validation, blocking mechanisms, rate limiting, and detailed error reporting.
-
Validation Module (
frontend/src/lib/recipient-validation.js)- Format validation for Stacks addresses
- Contract principal detection
- Self-tip prevention
-
Block Check Hook (
frontend/src/hooks/useBlockCheck.js)- Real-time blocking status checks
- Integration with smart contract
- Status caching for performance
-
Error Management (
frontend/src/lib/recipient-errors.js)- Centralized error definitions
- Severity classification
- User-friendly error messages
-
Rate Limiting (
frontend/src/lib/recipient-rate-limiter.js)- Per-recipient rate limiting
- Time-based quotas
- Automatic reset
Valid Stacks addresses must:
- Start with
SP,ST, orSM - Be exactly 41 characters long
- Contain only alphanumeric characters after prefix
- Match pattern:
/^S[PTM][0-9A-Z]{38,39}$/i
import { isValidStacksAddress } from './lib/recipient-validation';
const isValid = isValidStacksAddress('SP31PKQVQZVZCK3FM3NH67CGD6G1FMR17VQVS2W5T');
// Returns: trueUsers cannot send tips to themselves:
import { validateRecipient } from './lib/recipient-validation';
const result = validateRecipient(
'SP31PKQVQZVZCK3FM3NH67CGD6G1FMR17VQVS2W5T', // recipient
'SP31PKQVQZVZCK3FM3NH67CGD6G1FMR17VQVS2W5T' // sender
);
// Returns: { valid: false, error: 'SELF_TIP' }Contract principals (addresses with .contract-name) are rejected:
const result = validateRecipient(
'SP31PKQVQZVZCK3FM3NH67CGD6G1FMR17VQVS2W5T.tipstream'
);
// Returns: { valid: false, error: 'CONTRACT_PRINCIPAL' }Recipients can block specific senders via smart contract:
import { useBlockCheck } from './hooks/useBlockCheck';
function SendTip({ recipient }) {
const { blocked, checking } = useBlockCheck(recipient);
if (blocked) {
return <div>This recipient has blocked you</div>;
}
// Render tip form
}- error: Critical issues that prevent transaction
- warning: Non-critical issues that should be addressed
| Code | Severity | Description |
|---|---|---|
INVALID_FORMAT |
warning | Address format is invalid |
SELF_TIP |
warning | Attempt to tip self |
CONTRACT_PRINCIPAL |
error | Contract address not allowed |
RECIPIENT_BLOCKED |
error | Recipient has blocked sender |
RATE_LIMIT_EXCEEDED |
warning | Too many requests |
import {
getRecipientErrorMessage,
getRecipientErrorSeverity,
formatRecipientError
} from './lib/recipient-errors';
const error = formatRecipientError(
'SP31PKQVQZVZCK3FM3NH67CGD6G1FMR17VQVS2W5T',
'SELF_TIP'
);
console.log(error);
// {
// recipient: 'SP31PKQVQZVZCK3FM3NH67CGD6G1FMR17VQVS2W5T',
// errorCode: 'SELF_TIP',
// message: 'You cannot send a tip to yourself',
// severity: 'warning',
// timestamp: '2026-04-08T13:15:00.000Z'
// }import { RateLimiter } from './lib/recipient-rate-limiter';
const limiter = new RateLimiter(
5, // maxRequests: 5 requests
60000 // windowMs: per 60 seconds
);// Check if request is allowed
if (!limiter.isAllowed()) {
const waitTime = limiter.getWaitTime();
console.log(`Rate limit exceeded. Try again in ${waitTime}ms`);
return;
}
// Record the request
limiter.recordRequest();
// Make API call
await checkBlockStatus(recipient);isAllowed()- Check if request is within rate limitrecordRequest()- Record a new requestgetRemaining()- Get remaining quotagetWaitTime()- Get time until quota resetsreset()- Manually reset the limiter
import {
trackBlockedRecipientDetected,
trackContractPrincipalDetected,
trackBlockCheckCompleted
} from './lib/recipient-block-tracking';
// Track when a blocked recipient is detected
trackBlockedRecipientDetected(recipient);
// Track contract principal detection
trackContractPrincipalDetected(recipient);
// Track successful block check
trackBlockCheckCompleted(recipient, isBlocked);blocked_recipient_detected- Recipient has blocked sendercontract_principal_detected- Invalid contract addressblocked_submission_attempted- User tried to submit while blockedblock_check_completed- Block status check finishedblock_check_failed- Block status check failedrecipient_changed- User changed recipient field
import { useState, useEffect } from 'react';
import { validateRecipient } from './lib/recipient-validation';
import { useBlockCheck } from './hooks/useBlockCheck';
import { formatRecipientError } from './lib/recipient-errors';
function SendTip({ sender }) {
const [recipient, setRecipient] = useState('');
const [validationError, setValidationError] = useState(null);
const { blocked, checking } = useBlockCheck(recipient);
useEffect(() => {
if (!recipient) {
setValidationError(null);
return;
}
const result = validateRecipient(recipient, sender);
if (!result.valid) {
setValidationError(
formatRecipientError(recipient, result.error)
);
} else {
setValidationError(null);
}
}, [recipient, sender]);
const canSubmit = !validationError && !blocked && !checking;
return (
<div>
<input
value={recipient}
onChange={(e) => setRecipient(e.target.value)}
placeholder="SP2..."
/>
{validationError && (
<div className={validationError.severity === 'error' ? 'text-red-500' : 'text-yellow-500'}>
{validationError.message}
</div>
)}
{blocked && (
<div className="text-red-500">
This recipient has blocked you
</div>
)}
<button disabled={!canSubmit}>
Send Tip
</button>
</div>
);
}All validation components have comprehensive test coverage:
frontend/src/test/recipient-validation.test.js- Format validation testsfrontend/src/test/recipient-errors.test.js- Error handling testsfrontend/src/test/recipient-rate-limiter.test.js- Rate limiting testsfrontend/src/test/recipient-block-tracking.test.js- Telemetry tests
cd frontend
npm test -- recipient-validation
npm test -- recipient-errors
npm test -- recipient-rate-limiter
npm test -- recipient-block-tracking-
Always validate before submission
- Run validation as user types
- Show immediate feedback
- Prevent invalid submissions
-
Handle errors gracefully
- Display user-friendly messages
- Use appropriate severity levels
- Log errors for debugging
-
Respect rate limits
- Cache block check results
- Debounce validation calls
- Show wait time to users
-
Track important events
- Monitor blocked attempts
- Analyze validation failures
- Identify UX improvements
-
Test thoroughly
- Cover all error cases
- Test edge conditions
- Verify rate limiting
-
Input Sanitization
- Always validate address format
- Trim whitespace
- Reject malformed input
-
Rate Limiting
- Prevent abuse of block checking
- Limit validation requests
- Protect backend resources
-
Privacy
- Truncate addresses in logs
- Don't expose full recipient in telemetry
- Respect user blocking preferences
Issue: Block check always returns false
- Verify wallet is connected
- Check network configuration
- Confirm contract deployment
Issue: Rate limit errors
- Reduce validation frequency
- Implement debouncing
- Increase rate limit window
Issue: False positive validations
- Verify address format regex
- Check for whitespace
- Review validation logic
See individual module documentation: