Overview
The DecentralizedOracle contract enables trustless resolution of prediction markets through a consensus mechanism. Multiple oracle nodes independently verify the actual text posted by public figures and reach consensus on the correct market outcome.
Contract Address : 0x7EF22e27D44E3f4Cc2f133BB4ab2065D180be3C1 (BASE Sepolia)
Current Status : The contract is deployed but resolution is currently centralized (single EOA). The X API pay-per-use model (launched Feb 2026) now makes multi-oracle verification economically viable. Decentralized resolution is planned for production.
Key Features
Consensus-Based Requires 3+ validators to reach 66% consensus threshold
On-Chain Validation Levenshtein distance calculated on-chain for transparency
Proof Verification IPFS screenshot hashes provide cryptographic proof
Auto-Resolution Markets auto-resolve when all submissions reach consensus
Architecture
DecentralizedOracle.sol:24-50
contract DecentralizedOracle is ReentrancyGuard {
using Strings for uint256 ;
struct OracleData {
string actualText;
string screenshotIPFS;
bytes32 textHash;
uint256 levenshteinDistance;
address [] validators;
mapping ( address => bool ) hasValidated;
bool consensusReached;
}
struct MarketOracleData {
mapping ( bytes32 => OracleData) submissions; // submissionId => OracleData
bytes32 [] submissionIds;
uint256 totalValidations;
bool isResolved;
}
IEnhancedPredictionMarket public predictionMarket;
INodeRegistry public nodeRegistry;
mapping ( bytes32 => MarketOracleData) public marketOracles;
uint256 public constant MIN_VALIDATORS = 3 ;
uint256 public constant CONSENSUS_THRESHOLD = 66 ; // 66%
}
Constants
Constant Value Description MIN_VALIDATORS3 Minimum number of validators required for consensus CONSENSUS_THRESHOLD66 Percentage agreement required (66%)
Oracle Workflow
Core Functions
submitOracleData
Oracle nodes submit validated data for a specific submission after market expiry.
The submission ID being validated
The actual text posted by the public figure
IPFS hash of the screenshot proof
The predicted text from this submission (for distance calculation)
DecentralizedOracle.sol:86-151
function submitOracleData (
bytes32 marketId ,
bytes32 submissionId ,
string calldata actualText ,
string calldata screenshotIPFS ,
string calldata predictedText
) external nonReentrant {
require (nodeRegistry. isActiveNode ( msg.sender ), "Not an active oracle node" );
// Verify market exists and is expired but not resolved
(,, uint256 endTime, bool resolved,,) = predictionMarket. getMarket (marketId);
require (endTime > 0 , "Market does not exist" );
require ( block .timestamp > endTime, "Market not yet expired" );
require ( ! resolved, "Market already resolved" );
MarketOracleData storage marketData = marketOracles[marketId];
OracleData storage oracleData = marketData.submissions[submissionId];
// Ensure oracle hasn't already validated this submission
require ( ! oracleData.hasValidated[ msg.sender ], "Oracle already validated this submission" );
// Calculate Levenshtein distance on-chain
uint256 distance = calculateLevenshteinDistance (actualText, predictedText);
// First submission for this submissionId
if (oracleData.validators.length == 0 ) {
oracleData.actualText = actualText;
oracleData.screenshotIPFS = screenshotIPFS;
oracleData.textHash = keccak256 ( abi . encodePacked (actualText));
oracleData.levenshteinDistance = distance;
marketData.submissionIds. push (submissionId);
} else {
// Verify consistency with previous submissions
require (
keccak256 ( abi . encodePacked (actualText)) == oracleData.textHash,
"Text hash mismatch with previous submissions"
);
require (
keccak256 ( abi . encodePacked (screenshotIPFS)) == keccak256 ( abi . encodePacked (oracleData.screenshotIPFS)),
"Screenshot IPFS hash mismatch"
);
}
// Record validation
oracleData.validators. push ( msg.sender );
oracleData.hasValidated[ msg.sender ] = true ;
marketData.totalValidations ++ ;
emit OracleDataSubmitted (
marketId,
submissionId,
msg.sender ,
actualText,
screenshotIPFS,
distance
);
// Check for consensus
if (oracleData.validators.length >= MIN_VALIDATORS && ! oracleData.consensusReached) {
oracleData.consensusReached = true ;
emit ConsensusReached (marketId, submissionId, oracleData.validators.length);
// Try to auto-resolve if all submissions have consensus
_tryAutoResolve (marketId);
}
}
Requirements :
Caller must be an active oracle node (verified by NodeRegistry)
Market must be expired but not yet resolved
Oracle hasn’t already validated this submission
If not first validator, actual text and IPFS hash must match previous submissions
Events :
OracleDataSubmitted(bytes32 indexed marketId, bytes32 indexed submissionId, address indexed oracle, string actualText, string screenshotIPFS, uint256 levenshteinDistance)
ConsensusReached(bytes32 indexed marketId, bytes32 indexed submissionId, uint256 validatorCount)
calculateLevenshteinDistance
Calculate the edit distance between predicted and actual text on-chain.
First string (predicted text)
Second string (actual text)
DecentralizedOracle.sol:157-202
function calculateLevenshteinDistance (
string memory a ,
string memory b
) public pure returns ( uint256 ) {
bytes memory bytesA = bytes (a);
bytes memory bytesB = bytes (b);
uint256 lenA = bytesA.length;
uint256 lenB = bytesB.length;
// Handle empty strings
if (lenA == 0 ) return lenB;
if (lenB == 0 ) return lenA;
// For gas efficiency, limit string length
require (lenA <= 280 && lenB <= 280 , "Strings too long for on-chain calculation" );
// Create a 2D array for dynamic programming
uint256 [][] memory dp = new uint256 [][](lenA + 1 );
for ( uint256 i = 0 ; i <= lenA; i ++ ) {
dp[i] = new uint256 []( lenB + 1 );
}
// Initialize base cases
for ( uint256 i = 0 ; i <= lenA; i ++ ) {
dp[i][ 0 ] = i;
}
for ( uint256 j = 0 ; j <= lenB; j ++ ) {
dp[ 0 ][j] = j;
}
// Fill the dp table
for ( uint256 i = 1 ; i <= lenA; i ++ ) {
for ( uint256 j = 1 ; j <= lenB; j ++ ) {
uint256 cost = bytesA[i - 1 ] == bytesB[j - 1 ] ? 0 : 1 ;
uint256 deletion = dp[i - 1 ][j] + 1 ;
uint256 insertion = dp[i][j - 1 ] + 1 ;
uint256 substitution = dp[i - 1 ][j - 1 ] + cost;
dp[i][j] = _min ( _min (deletion, insertion), substitution);
}
}
return dp[lenA][lenB];
}
Returns : The edit distance (number of insertions, deletions, substitutions needed to transform string a into string b)
Gas Cost : O(m*n) where m and n are string lengths. Strings limited to 280 characters for gas efficiency.
Auto-Resolution
When all submissions reach consensus, the market auto-resolves to the submission with the lowest Levenshtein distance.
DecentralizedOracle.sol:207-241
function _tryAutoResolve ( bytes32 marketId ) private {
MarketOracleData storage marketData = marketOracles[marketId];
if (marketData.isResolved) return ;
// Check if we have enough consensus on submissions
uint256 lowestDistance = type ( uint256 ).max;
bytes32 winningSubmissionId;
bool canResolve = true ;
for ( uint256 i = 0 ; i < marketData.submissionIds.length; i ++ ) {
bytes32 submissionId = marketData.submissionIds[i];
OracleData storage oracleData = marketData.submissions[submissionId];
// All submissions need consensus
if ( ! oracleData.consensusReached) {
canResolve = false ;
break ;
}
// Track submission with lowest Levenshtein distance
if (oracleData.levenshteinDistance < lowestDistance) {
lowestDistance = oracleData.levenshteinDistance;
winningSubmissionId = submissionId;
}
}
// Auto-resolve if all submissions have consensus
if (canResolve && marketData.submissionIds.length > 0 ) {
marketData.isResolved = true ;
predictionMarket. resolveMarket (marketId, winningSubmissionId);
emit MarketAutoResolved (marketId, winningSubmissionId, lowestDistance);
}
}
Logic :
Verify all submissions have reached consensus (3+ validators each)
Find submission with lowest Levenshtein distance
Resolve market to that submission
Emit MarketAutoResolved event
View Functions
getOracleData
Get oracle validation data for a specific submission.
DecentralizedOracle.sol:246-264
function getOracleData (
bytes32 marketId ,
bytes32 submissionId
) external view returns (
string memory actualText ,
string memory screenshotIPFS ,
uint256 levenshteinDistance ,
uint256 validatorCount ,
bool consensusReached
) {
OracleData storage data = marketOracles[marketId].submissions[submissionId];
return (
data.actualText,
data.screenshotIPFS,
data.levenshteinDistance,
data.validators.length,
data.consensusReached
);
}
hasOracleValidated
Check if a specific oracle has already validated a submission.
DecentralizedOracle.sol:269-275
function hasOracleValidated (
bytes32 marketId ,
bytes32 submissionId ,
address oracle
) external view returns ( bool ) {
return marketOracles[marketId].submissions[submissionId].hasValidated[oracle];
}
Events
event OracleDataSubmitted (
bytes32 indexed marketId ,
bytes32 indexed submissionId ,
address indexed oracle ,
string actualText ,
string screenshotIPFS ,
uint256 levenshteinDistance
);
event ConsensusReached (
bytes32 indexed marketId ,
bytes32 indexed submissionId ,
uint256 validatorCount
);
event MarketAutoResolved (
bytes32 indexed marketId ,
bytes32 indexed winningSubmissionId ,
uint256 lowestDistance
);
Oracle Node Requirements
To participate as an oracle node:
Register as Node Operator via NodeRegistry contract
Maintain Active Status through regular heartbeat calls
Access X API for post verification (pay-per-use model)
IPFS Storage for screenshot proof hosting
Gas Reserves for submitting validation transactions
X API Integration
With X’s new pay-per-use API (launched Feb 2026), oracle nodes can:
// Example X API verification workflow
const { TwitterApi } = require ( 'twitter-api-v2' );
async function verifyPost ( handle , postId , expectedText ) {
const client = new TwitterApi ({
appKey: process . env . X_API_KEY ,
appSecret: process . env . X_API_SECRET ,
});
// Fetch the tweet by ID (pay-per-use)
const tweet = await client . v2 . singleTweet ( postId , {
'tweet.fields' : [ 'created_at' , 'author_id' , 'text' ]
});
// Verify authorship matches expected handle
const author = await client . v2 . user ( tweet . data . author_id );
if ( author . data . username !== handle . replace ( '@' , '' )) {
throw new Error ( 'Author mismatch' );
}
// Take screenshot and upload to IPFS
const screenshotBuffer = await captureScreenshot ( postId );
const ipfsHash = await uploadToIPFS ( screenshotBuffer );
// Submit oracle data on-chain
const tx = await oracleContract . submitOracleData (
marketId ,
submissionId ,
tweet . data . text ,
ipfsHash ,
predictedText
);
await tx . wait ();
console . log ( 'Oracle data submitted' );
}
Oracle Rewards
Oracles receive 28.6% of platform fees (2% of market volume) distributed via the PayoutManager .
Reward Distribution :
If oracle contributions are tracked: Weighted by contribution
If no contributions tracked: Equal distribution among participating oracles
Example calculation:
Market volume: 10 ETH
Platform fee: 0.7 ETH (7%)
Oracle share: 0.14 ETH (28.6% of 0.7 ETH = 2% of volume)
3 oracles participated: 0.047 ETH each
Usage Example
const oracle = await ethers . getContractAt (
"DecentralizedOracle" ,
"0x7EF22e27D44E3f4Cc2f133BB4ab2065D180be3C1"
);
// Oracle node submits validation data
const actualText = "Starship flight 2 is GO for March" ;
const ipfsHash = "QmX8H3j9K..." ; // Screenshot proof on IPFS
const predictedText = "Starship flight 2 confirmed for March" ;
const tx = await oracle . submitOracleData (
marketId ,
submissionId ,
actualText ,
ipfsHash ,
predictedText
);
await tx . wait ();
console . log ( "Oracle data submitted" );
// Check oracle data
const data = await oracle . getOracleData ( marketId , submissionId );
console . log ( "Actual text:" , data . actualText );
console . log ( "Validators:" , data . validatorCount . toString ());
console . log ( "Consensus reached:" , data . consensusReached );
console . log ( "Levenshtein distance:" , data . levenshteinDistance . toString ());
Security Considerations
Node Verification Only registered active nodes can submit oracle data
Hash Consistency All oracles must submit matching text and IPFS hashes
Double-Vote Prevention Each oracle can only validate each submission once
Consensus Threshold 66% agreement required before considering data valid
Future Improvements
Commit-Reveal Scheme
Prevent oracle collusion by requiring commits before reveals
Economic Incentives
Slash dishonest oracles, reward accurate validators
Reputation System
Track oracle accuracy over time, weight votes by reputation
Dispute Resolution
Allow challenges to oracle consensus with stake
Next Steps
Contracts Overview View all deployed smart contracts
PredictionMarketV2 See how markets integrate with oracles