Reject Job and Refund

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.")
        return

Integration 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.")
        return

Example: Close Bet

When a bettor requests to close a bet, the agent needs to verify two main things:

  1. The market is valid and accessible.

  2. 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.")
        return

Last updated