Submitting a prediction is the core interaction in Proteus. You predict the exact text a public figure will post, stake ETH on your prediction, and compete against other submissions using Levenshtein distance scoring.
Overview
Each submission requires:
Market ID : Which market you’re betting on
Predicted Text : Your exact prediction (up to 280 characters)
ETH Stake : Minimum 0.001 ETH
The submission with the lowest Levenshtein distance to the actual text wins the entire pool (minus 7% platform fee).
Prerequisites
Connect Wallet
Ensure your wallet is connected with BASE Sepolia ETH.
MetaMask: Click “Connect Wallet” and sign the message
Coinbase Embedded Wallet: Enter email and verify OTP
Find an Open Market
Browse active markets where:
block.timestamp < endTime - 1 hour (betting cutoff not passed)
Market is not yet resolved
Prepare Your Prediction
Craft your predicted text carefully:
Maximum 280 characters (tweet length)
Every character matters — scoring uses exact edit distance
Consider the actor’s typical style, vocabulary, and topics
Submitting a Prediction
Enter Predicted Text
Type your prediction in the submission form. Character limits:
Minimum : 1 character
Maximum : 280 characters
Empty predictions are rejected on-chain
Set Your Stake
Choose how much ETH to stake: const MIN_BET = 0.001 // ETH
Your stake enters the prize pool. Winner takes all (minus 7% fee).
Submit Transaction
Click Submit Prediction and confirm the transaction. The createSubmission function is called: function createSubmission (
uint256 _marketId ,
string calldata _predictedText
) external payable whenNotPaused nonReentrant returns ( uint256 )
This is a payable function — your ETH stake is sent with the transaction.
Contract Interaction
Using Web3.js (Frontend)
Here’s how submissions are created in the frontend:
class BettingContract {
async createSubmission ( marketId , predictedText , stakeEth ) {
const stakeWei = Web3 . utils . toWei ( stakeEth . toString (), 'ether' );
// Validate
if ( stakeWei < Web3 . utils . toWei ( '0.001' , 'ether' )) {
throw new Error ( 'Minimum stake is 0.001 ETH' );
}
if ( predictedText . length > 280 ) {
throw new Error ( 'Prediction exceeds 280 characters' );
}
// Submit with ETH
const tx = await this . contract . methods
. createSubmission ( marketId , predictedText )
. send ({
from: this . account ,
value: stakeWei ,
gas: 300000
});
// Extract submission ID from event
const submissionId = tx . events . SubmissionCreated . returnValues . submissionId ;
console . log ( `Submission # ${ submissionId } created!` );
return { submissionId , transactionHash: tx . transactionHash };
}
}
Using Python (Backend)
from web3 import Web3
w3 = Web3(Web3.HTTPProvider( 'https://sepolia.base.org' ))
# Contract setup
contract_address = '0x5174Da96BCA87c78591038DEe9DB1811288c9286'
contract = w3.eth.contract( address = contract_address, abi = contract_abi)
# Create submission
market_id = 0
predicted_text = "Starship flight 2 is GO for March. Humanity becomes multiplanetary or we die trying."
stake_eth = 0.1
tx_hash = contract.functions.createSubmission(
market_id,
predicted_text
).transact({
'from' : submitter_address,
'value' : w3.to_wei(stake_eth, 'ether' ),
'gas' : 300000
})
receipt = w3.eth.wait_for_transaction_receipt(tx_hash)
print ( f 'Submission created: { receipt.transactionHash.hex() } ' )
On-Chain Validation
The contract enforces these rules:
if (market.endTime == 0 ) revert MarketNotFound ();
if (market.resolved) revert MarketAlreadyResolved ();
if ( block .timestamp >= market.endTime) revert MarketEnded ();
if ( block .timestamp >= market.endTime - BETTING_CUTOFF) revert BettingCutoffPassed ();
if ( msg .value < MIN_BET) revert InsufficientBet ();
if ( bytes (_predictedText).length == 0 ) revert EmptyPrediction ();
if ( bytes (_predictedText).length > MAX_TEXT_LENGTH) revert PredictionTooLong ();
Constants
uint256 public constant MIN_BET = 0.001 ether ;
uint256 public constant BETTING_CUTOFF = 1 hours ;
uint256 public constant MAX_TEXT_LENGTH = 280 ;
Submission Struct
Your submission is stored on-chain:
struct Submission {
uint256 marketId; // Which market
address submitter; // Your address
string predictedText; // Your prediction
uint256 amount; // ETH staked
bool claimed; // Has payout been claimed
}
Events Emitted
event SubmissionCreated (
uint256 indexed submissionId ,
uint256 indexed marketId ,
address submitter ,
string predictedText ,
uint256 amount
);
Listen for this event to track your submission:
contract . events . SubmissionCreated ({
filter: { marketId: 0 },
fromBlock: 'latest'
})
. on ( 'data' , ( event ) => {
console . log ( 'New submission:' , event . returnValues );
});
Strategy Tips
Study the Actor's Pattern
Review recent posts to understand:
Vocabulary : Specific words they frequently use
Sentence structure : Short vs. long, formal vs. casual
Topics : What they’re currently focused on
Time of day : When they typically post
Example: Elon Musk often uses phrases like “Humanity becomes multiplanetary” and posts about SpaceX launches.
Frontier AI models can generate predictions: const prompt = `You are @elonmusk. Write your next tweet about SpaceX in exactly his style and vocabulary. Keep it under 280 characters.` ;
const prediction = await claude . generate ( prompt );
In the thesis example , Claude achieved distance 1 (single character difference).
Consider Insider Information
Some actors don’t post frequently. You can predict silence: const prediction = "__NULL__" ;
If the actor doesn’t post during the market window, oracle resolves with __NULL__, giving you distance 0 (exact match).
Multi-Submission Strategy
You can submit multiple predictions to the same market:
const predictions = [
"Starship flight 2 is GO for March." ,
"Starship flight 2 confirmed for March." ,
"Starship launch 2 scheduled for March."
];
for ( const text of predictions ) {
await bettingContract . createSubmission ( marketId , text , 0.01 );
}
Each submission is independent. You compete against yourself and others. Only the winning submission claims the pool.
Viewing Your Submissions
Query On-Chain
const userSubmissions = await contract . methods
. getUserSubmissions ( userAddress )
. call ();
console . log ( 'Your submission IDs:' , userSubmissions );
// Get details for each
for ( const subId of userSubmissions ) {
const details = await contract . methods
. getSubmissionDetails ( subId )
. call ();
console . log ( `Submission # ${ subId } :` , {
marketId: details . marketId ,
predictedText: details . predictedText ,
amount: Web3 . utils . fromWei ( details . amount , 'ether' ),
claimed: details . claimed
});
}
Via API
curl https://proteus-production-6213.up.railway.app/api/chain/market/0
Response includes all submissions:
{
"market" : {
"id" : 0 ,
"submissions" : [
{
"id" : 0 ,
"creator" : "0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb" ,
"predicted_text" : "Starship flight 2 is GO for March..." ,
"stake" : "100000000000000000"
}
]
}
}
Example: Complete Submission Flow
// 1. Connect wallet
await window . proteusWallet . connect ();
// 2. Initialize contract
const bettingContract = new BettingContract ();
await bettingContract . init ();
// 3. Check market is open
const market = await bettingContract . contract . methods
. markets ( marketId )
. call ();
const now = Math . floor ( Date . now () / 1000 );
if ( now >= market . endTime - 3600 ) {
throw new Error ( 'Betting cutoff passed' );
}
// 4. Craft prediction
const prediction = "Starship flight 2 is GO for March. Humanity becomes multiplanetary or we die trying." ;
if ( prediction . length > 280 ) {
throw new Error ( 'Prediction too long' );
}
// 5. Submit with stake
const stake = 0.1 ; // ETH
const result = await bettingContract . createSubmission (
marketId ,
prediction ,
stake
);
console . log ( `Submission # ${ result . submissionId } created!` );
console . log ( `Transaction: https://sepolia.basescan.org/tx/ ${ result . transactionHash } ` );
Gas Costs
Submission transactions require:
Gas limit : ~300,000
Gas price : Variable (check current BASE gas price)
Total cost : Your stake + gas fees
const gasPrice = await web3 . eth . getGasPrice ();
const gasCost = 300000 * gasPrice ;
const totalCost = stake + gasCost ;
console . log ( `Total cost: ${ Web3 . utils . fromWei ( totalCost , 'ether' ) } ETH` );
Troubleshooting
Transaction Reverted
Insufficient Gas
Wrong Network
Common errors:
InsufficientBet: Stake < 0.001 ETH
BettingCutoffPassed: Market closes in < 1 hour
MarketEnded: Market already closed
PredictionTooLong: Text > 280 characters
EmptyPrediction: Text is empty string
Check the revert reason in BaseScan transaction details. If transaction fails with out-of-gas: // Increase gas limit
. send ({
from: account ,
value: stakeWei ,
gas: 350000 // Increased from 300000
});
Ensure you’re on BASE Sepolia (Chain ID: 84532): const chainId = await web3 . eth . getChainId ();
if ( chainId !== 84532 ) {
await window . ethereum . request ({
method: 'wallet_switchEthereumChain' ,
params: [{ chainId: '0x14a34' }] // 84532 in hex
});
}
Next Steps
After submitting:
Wait for market to close
Oracle resolves with actual text
Check if you won (lowest Levenshtein distance)
Claim payout if you’re the winner
Claiming Payouts Learn how winners are determined and how to claim your prize