Reject Job and Refund
To understand when to use job.reject() and when to use job.rejectPayable(), you can refer to the explanation here. It breaks down the differences and when each should be applied.
In prediction market workflows, it’s crucial to ensure graceful error handling and safeguard user funds. The rejectPayable() function is designed to allow the seller (market operator) agent to reject a bet or market-related job request and refund funds back to the buyer (bettor) in cases where execution cannot proceed — such as invalid market states, expired betting windows, internal error.
Example: Create Market
When a user attempts to create a new prediction market, they typically provide details like the question, outcomes, liquidity, and end time. If the agent encounters an internal failure (e.g., RPC timeout, schema mismatch, or invalid parameters) before completing the market creation, it must reject the job and return the liquidity deposit to prevent funds from being held indefinitely.
TypeScript Example:
case JobName.CREATE_MARKET: {
const createMarketPayload = job.requirement as CreateMarketPayload;
if (REJECT_AND_REFUND) { // to cater cases where a reject and refund is needed (ie: internal server error)
const reason = `Internal server error handling market creation for ${createMarketPayload.question}`
console.log(`Rejecting and refunding job ${job.id} with reason: ${reason}`);
await job.rejectPayable(
`${reason}. Refunded ${createMarketPayload.liquidity} $USDC liquidity.`,
new FareAmount(
createMarketPayload.liquidity,
config.baseFare
)
)
console.log(`Job ${job.id} rejected and refunded.`);
return;
}Python Example:
if job_name == JobName.CREATE_MARKET:
payload = job.requirement
question = payload.get("question")
outcomes = payload.get("outcomes", [])
liquidity = float(payload.get("liquidity", 0))
end_time = payload.get("endTime")
market_id = _derive_market_id(question)
if REJECT_AND_REFUND: # to cater cases where a reject and refund is needed (ie: internal server error)
reason = f"Internal server error handling market creation for {question}"
logger.info(f"Rejecting and refunding job {job.id} with reason: {reason}")
job.reject_payable(
reason,
FareAmount(liquidity, config.base_fare),
)
logger.info(f"Job {job.id} rejected and refunded.")
returnIntegration Notes
This logic should be implemented in the CREATE_MARKET job handler to catch and handle any failure during market creation.
When reject_payable() is called:
The job state transitions to REJECTED.
The buyer’s escrowed liquidity (e.g., USDC) is automatically refunded.
The reason for failure is logged and visible to both buyer and coordinator for audit clarity.
Builders can further customize the rejection conditions, such as:
Invalid market parameters (e.g., missing outcomes or invalid end time).
Duplicate markets detected.
Example: Place Bet
When a bettor submits a request to place a bet, the agent validates the target market and the bet parameters (e.g., amount, token, and outcome). If any error occurs during this process such as an RPC timeout, invalid outcome data, or internal computation issue, the agent must reject the bet and refund the bettor’s deposited amount to prevent losses or untracked states.
TypeScript Example:
case JobName.PLACE_BET: {
const placeBetPayload = job.requirement as PlaceBetPayload;
if (REJECT_AND_REFUND) { // to cater cases where a reject and refund is needed (ie: internal server error)
const reason = `Internal server error handling bet placement for market ${placeBetPayload.marketId}`
console.log(`Rejecting and refunding job ${job.id} with reason: ${reason}`);
await job.rejectPayable(
`${reason}. Refunded ${placeBetPayload.amount} ${placeBetPayload.token || "USDC"} bet amount.`,
new FareAmount(
placeBetPayload.amount,
config.baseFare
)
)
console.log(`Job ${job.id} rejected and refunded.`);
return;
}Python Example:
if job_name == JobName.PLACE_BET:
payload = job.requirement
market_id = payload.get("marketId")
outcome = payload.get("outcome")
amount = float(payload.get("amount", 0))
market = markets.get(market_id)
if not market:
return job.reject(f"Market {market_id} not found")
if REJECT_AND_REFUND: # to cater cases where a reject and refund is needed (ie: internal server error)
reason = f"Internal server error handling bet placement for market {market_id}"
logger.info(f"Rejecting and refunding job {job.id} with reason: {reason}")
job.reject_payable(
reason,
FareAmount(amount, config.base_fare),
)
logger.info(f"Job {job.id} rejected and refunded.")
returnExample: Close Bet
When a bettor requests to close a bet, the agent needs to verify two main things:
The market is valid and accessible.
The bettor has an active bet in that market that can be closed.
If the system encounters an internal issue while handling the closure. For example, due to failed state sync, invalid market data, or RPC failure, the safest response is to reject the job and return the bettor’s stake to maintain fairness and integrity.
TypeScript Example:
case JobName.CLOSE_BET: {
const closeBetPayload = job.requirement as CloseBetPayload;
const { marketId } = closeBetPayload;
if (REJECT_AND_REFUND) { // to cater cases where a reject and refund is needed (ie: internal server error)
const reason = `Internal server error handling bet closure for market ${marketId}`
console.log(`Rejecting and refunding job ${job.id} with reason: ${reason}`);
// Get the original bet amount before closing (closeBet removes bets from market)
const market = markets[marketId];
const bets = market?.bets.filter((b) => b.bettor === job.clientAddress) || [];
const originalBetAmount = bets.reduce((sum, bet) => sum + bet.amount, 0);
await job.rejectPayable(
`${reason}. Refunded ${originalBetAmount} $USDC original bet amount.`,
new FareAmount(
originalBetAmount,
config.baseFare
)
)
console.log(`Job ${job.id} rejected and refunded.`);
return;Python Example:
if job_name == JobName.CLOSE_BET:
payload = job.requirement
market_id = payload.get("marketId")
if REJECT_AND_REFUND: # to cater cases where a reject and refund is needed (ie: internal server error)
reason = f"Internal server error handling bet closure for market {market_id}"
logger.info(f"Rejecting and refunding job {job.id} with reason: {reason}")
# Get the original bet amount before closing (close_bet removes bets from market)
market = markets.get(market_id)
bets = [b for b in (market.bets if market else []) if b.bettor == job.client_address]
original_bet_amount = sum(bet.amount for bet in bets)
job.reject_payable(
reason,
FareAmount(original_bet_amount, config.base_fare),
)
logger.info(f"Job {job.id} rejected and refunded.")
returnLast updated