Comprehensive guide to automated market validation and permissionless launch system.
The permissionless launch system enables community-driven market creation without requiring admin approval. All markets are validated automatically using a robust set of rules, ensuring quality while maintaining decentralization.
- Automated Validation: 4 validation rules enforce market quality
- Rate Limiting: Prevents spam with 3 markets per wallet per 24 hours
- Instant Publishing: Valid markets go live immediately
- Specific Error Codes: Actionable feedback for each validation failure
- Redis-Backed: Distributed rate limiting with Redis
Rule: Market questions must be at least 50 characters long.
Rationale: Ensures markets have sufficient context for users to make informed decisions.
Example Valid:
Will Bitcoin reach $100,000 by the end of 2026?
(52 characters)
Example Invalid:
Will BTC hit $100k?
(19 characters)
Error Response:
{
"error": {
"code": "DESCRIPTION_TOO_SHORT",
"message": "Market question must be at least 50 characters long",
"statusCode": 400,
"details": {
"currentLength": 19,
"requiredLength": 50
}
}
}Rule: Markets must have between 2 and 5 outcomes.
Rationale:
- Binary markets (2 outcomes) are the most common
- Multi-choice markets (3-5 outcomes) provide flexibility
- More than 5 outcomes become difficult to manage and bet on
Example Valid (Binary):
{
"outcomes": ["Yes", "No"]
}Example Valid (Multi-choice):
{
"outcomes": ["Bitcoin", "Ethereum", "Cardano", "Solana", "Other"]
}Example Invalid:
{
"outcomes": ["Only One"]
}Error Response:
{
"error": {
"code": "INVALID_OUTCOME_COUNT",
"message": "Market must have between 2 and 5 outcomes",
"statusCode": 400,
"details": {
"currentCount": 1,
"requiredRange": "2-5"
}
}
}Rule: End date must be:
- In the future (after current time)
- Within 1 year from now
- Valid ISO 8601 format
Rationale:
- Past dates don't make sense for prediction markets
- 1 year maximum prevents extremely long-term markets that may never resolve
- Standard date format ensures consistency
Example Valid:
{
"endDate": "2026-12-31T23:59:59Z"
}Example Invalid (Past):
{
"endDate": "2023-01-01T00:00:00Z"
}Example Invalid (Too Far):
{
"endDate": "2030-01-01T00:00:00Z"
}Error Response:
{
"error": {
"code": "INVALID_END_DATE",
"message": "End date must be in the future and within 1 year",
"statusCode": 400,
"details": {
"providedDate": "2023-01-01T00:00:00Z",
"minimumDate": "2026-03-25T10:00:00Z",
"maximumDate": "2027-03-25T10:00:00Z"
}
}
}Rule: Market questions must be unique (case-insensitive, whitespace-trimmed).
Rationale: Prevents fragmentation of liquidity and user confusion.
Duplicate Detection:
- Case-insensitive comparison
- Leading/trailing whitespace ignored
- Exact match after normalization
Example: These are considered duplicates:
"Will Bitcoin reach $100,000 by end of 2026?"
"WILL BITCOIN REACH $100,000 BY END OF 2026?"
" Will Bitcoin reach $100,000 by end of 2026? "
Error Response:
{
"error": {
"code": "DUPLICATE_MARKET",
"message": "A market with this question already exists",
"statusCode": 409,
"details": {
"existingMarketId": 123,
"existingQuestion": "Will Bitcoin reach $100,000 by end of 2026?"
}
}
}- Limit: 3 markets per wallet per 24 hours
- Window: 86400 seconds (24 hours)
- Storage: Redis with automatic TTL
- Key Format:
rate_limit:create:{walletAddress}
- First Market: Counter set to 1, TTL set to 24 hours
- Second Market: Counter incremented to 2
- Third Market: Counter incremented to 3 (at limit)
- Fourth Market: Rejected with 429 status
All responses include rate limit information:
X-RateLimit-Limit: 3
X-RateLimit-Remaining: 1
X-RateLimit-Reset: 1711368000000When rate limit is exceeded:
HTTP/1.1 429 Too Many Requests
Retry-After: 75600
X-RateLimit-Limit: 3
X-RateLimit-Remaining: 0
X-RateLimit-Reset: 1711368000000{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Maximum 3 markets per wallet per 24 hours",
"statusCode": 429,
"details": {
"limit": 3,
"windowSeconds": 86400,
"retryAfterSeconds": 75600,
"resetAt": "2026-03-26T10:00:00Z"
}
}
}Endpoint: POST /api/markets
Headers:
Content-Type: application/jsonRequest Body:
{
"question": "Will Bitcoin reach $100,000 by the end of 2026?",
"endDate": "2026-12-31T23:59:59Z",
"outcomes": ["Yes", "No"],
"walletAddress": "GTEST1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ",
"contractAddress": "CCONTRACT1234567890ABCDEFGHIJKLMNOPQRSTUVW"
}Required Fields:
question(string, min 50 chars)endDate(ISO 8601 string)outcomes(array of 2-5 strings)walletAddress(Stellar wallet address)
Optional Fields:
contractAddress(Soroban contract address)
Status: 201 Created
{
"market": {
"id": 456,
"question": "Will Bitcoin reach $100,000 by the end of 2026?",
"end_date": "2026-12-31T23:59:59Z",
"outcomes": ["Yes", "No"],
"resolved": false,
"winning_outcome": null,
"total_pool": "0",
"status": "ACTIVE",
"contract_address": "CCONTRACT1234567890ABCDEFGHIJKLMNOPQRSTUVW",
"created_at": "2026-03-25T10:00:00Z"
},
"message": "Market created successfully and published immediately"
}All validation errors follow this format:
{
"error": {
"code": "ERROR_CODE",
"message": "Human-readable error message",
"statusCode": 400,
"details": {
// Additional context specific to the error
}
}
}Possible Error Codes:
DESCRIPTION_TOO_SHORT(400)INVALID_OUTCOME_COUNT(400)INVALID_END_DATE(400)DUPLICATE_MARKET(409)RATE_LIMIT_EXCEEDED(429)MISSING_WALLET_ADDRESS(400)MISSING_REQUIRED_FIELDS(400)DATABASE_ERROR(500)
Request:
curl -X POST http://localhost:4000/api/markets \
-H "Content-Type: application/json" \
-d '{
"question": "Will the global average temperature increase by more than 1.5Β°C by 2030?",
"endDate": "2030-12-31T23:59:59Z",
"outcomes": ["Yes", "No"],
"walletAddress": "GTEST1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
}'Response: 201 Created
Request:
curl -X POST http://localhost:4000/api/markets \
-H "Content-Type: application/json" \
-d '{
"question": "Which cryptocurrency will have the highest market cap by end of 2026?",
"endDate": "2026-12-31T23:59:59Z",
"outcomes": ["Bitcoin", "Ethereum", "Cardano", "Solana", "Other"],
"walletAddress": "GTEST1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
}'Response: 201 Created
Request:
curl -X POST http://localhost:4000/api/markets \
-H "Content-Type: application/json" \
-d '{
"question": "Will BTC hit $100k?",
"endDate": "2026-12-31T23:59:59Z",
"outcomes": ["Yes", "No"],
"walletAddress": "GTEST1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
}'Response: 400 Bad Request
{
"error": {
"code": "DESCRIPTION_TOO_SHORT",
"message": "Market question must be at least 50 characters long",
"statusCode": 400,
"details": {
"currentLength": 19,
"requiredLength": 50
}
}
}Request (4th market in 24 hours):
curl -X POST http://localhost:4000/api/markets \
-H "Content-Type: application/json" \
-d '{
"question": "Will this fourth market be accepted by the system today?",
"endDate": "2026-12-31T23:59:59Z",
"outcomes": ["Yes", "No"],
"walletAddress": "GTEST1234567890ABCDEFGHIJKLMNOPQRSTUVWXYZ"
}'Response: 429 Too Many Requests
{
"error": {
"code": "RATE_LIMIT_EXCEEDED",
"message": "Rate limit exceeded. Maximum 3 markets per wallet per 24 hours",
"statusCode": 429,
"details": {
"limit": 3,
"windowSeconds": 86400,
"retryAfterSeconds": 75600,
"resetAt": "2026-03-26T10:00:00Z"
}
}
}- Redis Server: Required for rate limiting
- PostgreSQL: Required for market storage
- Node.js 16+: Required for backend
- Install Dependencies:
cd backend
npm install- Start Redis:
docker compose up -d redis- Configure Environment:
cp .env.example .envEdit .env:
REDIS_HOST=localhost
REDIS_PORT=6379
REDIS_PASSWORD=
DATABASE_URL=postgresql://user:password@localhost:5432/stella_polymarket- Start Backend:
npm startRun Unit Tests:
npm testRun Specific Test Suite:
npm test marketValidation.test.js
npm test rateLimiting.test.jsCheck Coverage:
npm test -- --coverageTarget: >90% coverage
POST /api/markets
β
validateMarketCreation
ββ Check description length
ββ Check outcome count
ββ Check end date validity
ββ Check for duplicates
β
rateLimitMarketCreation
ββ Check wallet address
ββ Increment Redis counter
ββ Check if limit exceeded
ββ Set rate limit headers
β
Market Creation Handler
ββ Insert into database
ββ Log creation
ββ Return 201 Created
rate_limit:create:{walletAddress}
β
Value: Integer (creation count)
TTL: 86400 seconds (24 hours)
CREATE TABLE markets (
id SERIAL PRIMARY KEY,
question TEXT NOT NULL,
end_date TIMESTAMPTZ NOT NULL,
outcomes TEXT[] NOT NULL,
resolved BOOLEAN DEFAULT FALSE,
winning_outcome INT,
total_pool NUMERIC DEFAULT 0,
status TEXT DEFAULT 'ACTIVE',
contract_address TEXT,
created_at TIMESTAMPTZ DEFAULT NOW()
);All validation events are logged with structured logging:
// Validation passed
logger.debug({
question,
outcomes_count: outcomes.length
}, 'Market validation passed');
// Validation failed
logger.warn({
question_length: question?.length || 0,
validation: 'DESCRIPTION_TOO_SHORT'
}, 'Market validation failed: description too short');
// Rate limit exceeded
logger.warn({
wallet_address: walletAddress,
current_count: currentCount,
max_creations: maxCreations,
ttl_seconds: ttl,
validation: 'RATE_LIMIT_EXCEEDED'
}, 'Market creation rate limit exceeded');
// Market created
logger.info({
market_id: result.rows[0].id,
question,
wallet_address: walletAddress,
permissionless: true
}, "Market created via permissionless launch");- Validation Failures by Type: Track which validation rules fail most often
- Rate Limit Hits: Monitor how often users hit the rate limit
- Market Creation Rate: Track markets created per hour/day
- Duplicate Attempts: Monitor duplicate market attempts
- Redis Performance: Track Redis response times
Symptom: Rate limiting not working, all requests allowed
Solution:
- Check Redis is running:
docker ps | grep redis - Test connection:
redis-cli ping - Check environment variables:
REDIS_HOST,REDIS_PORT - Review logs for Redis connection errors
Fallback: If Redis is unavailable, rate limiting is bypassed to prevent blocking all market creation.
Symptom: Duplicate markets being created
Solution:
- Check database query is case-insensitive
- Verify TRIM() is applied to questions
- Check for database connection issues
- Review logs for duplicate check errors
Symptom: Users still rate limited after 24 hours
Solution:
- Check Redis TTL:
redis-cli TTL rate_limit:create:{wallet} - Verify TTL is set on first creation
- Check system clock is correct
- Manually delete key if needed:
redis-cli DEL rate_limit:create:{wallet}
Currently, wallet addresses are accepted as-is. Consider adding:
- Stellar address format validation
- Signature verification to prove ownership
- Blacklist for known malicious addresses
- Rate limits are per wallet address
- Consider IP-based rate limiting as additional layer
- Monitor for patterns of abuse (many wallets from same IP)
- Current implementation prevents exact duplicates
- Consider fuzzy matching for similar questions
- Monitor for slight variations of same market
- Configurable Rate Limits: Allow admins to adjust limits per user tier
- Market Categories: Add category validation
- Automated Quality Scoring: ML-based quality assessment
- Community Moderation: Allow users to flag low-quality markets
- Reputation System: Higher limits for trusted users
- Market Templates: Pre-approved templates for common market types
For issues or questions:
- Check logs:
backend/logs/ - Review test cases:
backend/src/tests/ - Open GitHub issue with error details
Same as main project license.