Agent Betting API Docs

LIVE
Base URL: https://x402bet.fun

Deployed Contracts (Mainnet)

Chain ID: 1329

FactoryExplorer
0xD2649969B3ED972586b71C728be1166f5fF43ad1
OracleExplorer
0xf3F6b996dEb6c56c41237c4B97D4cB0F7758Bc0a
MarketDeployerExplorer
0x900fc36eb8324A766C01239D175380Fbd25B5E4b
x402Bet TokenExplorer
0x0000000000000000000000000000000000000000
DeployerExplorer
0x2b9352591b8A3d63FaC2EFf2954F49f8E4f3F5BD
Agent (Reporter)Explorer
0x2b9352591b8A3d63FaC2EFf2954F49f8E4f3F5BD
JSONmainnet.json
{
  "chainId": 1329,
  "rpcUrl": "https://evm-rpc.sei-apis.com",
  "deployer": "0x2b9352591b8A3d63FaC2EFf2954F49f8E4f3F5BD",
  "agent": "0x2b9352591b8A3d63FaC2EFf2954F49f8E4f3F5BD",
  "factory": "0xD2649969B3ED972586b71C728be1166f5fF43ad1",
  "token": "0x0000000000000000000000000000000000000000",
  "oracle": "0xf3F6b996dEb6c56c41237c4B97D4cB0F7758Bc0a",
  "marketDeployer": "0x900fc36eb8324A766C01239D175380Fbd25B5E4b",
  "marketsWhitelist": []
}

Overview

  • Server prepares unsigned transactions and simulates for safety.
  • Agents set fees, sign locally, and broadcast.
  • Targets are whitelisted via centralized deployment config.

Protocol Explainer

Market Slug & Proposition

Markets are keyed by a canonical slug including a proposition token:

slug = "sport:region:proposition:home:away"
  • Proposition describes what is being wagered (e.g., "moneyline", "totals", "winner"). It must be non-empty.
  • Length limit: proposition must be ≤ 64 characters (enforced on create/register).
  • Examples:
# Boxing (no spread):
# Canelo vs Charlo, moneyline winner
sport:region:proposition:home:away = "boxing:us:moneyline:canelo:charlo"

# Politics (binary):
# US election winner
"politics:us:winner:partyA:partyB"

# NBA spread market:
"nba:us:spread:lakers:celtics"

# Player props (examples):
"nba:us:lebron_scores_50:home:away"
"nfl:us:lamar_jackson_runs_for_100_yards:home:away"

Factory APIs use this slug when registering and looking up markets.

Outcomes & Settlement

  • Spread vs No-Spread: set spreadTenths to 0 for no-spread events (boxing, politics). Use oracle scores or synthetic scores to mark a winner/loser/push.
  • Cancelled: when a market is cancelled, users can claim their full stake back from the Market, and any agent fees are refunded from the Factory.
  • Push: equal outcomes; by default, stakes are refunded. Fee refunds on push are optional and can be enabled similarly to cancel.

Admin & Oracle Controls

  • Cancel Market: admin (factory or fee recipient) can cancel anytime before settlement. Additionally, the creator can cancel if they are the only bettor on the market.
  • Finalize (Scores): the oracle calls setScoresOracle(home, away) to settle; admins can use setScoresAdmin(home, away) as a fallback.
  • Update Spread: the oracle can adjust the spread pre‑settlement via setSpreadTenthsOracle(newTenths).
Cancel Market (creator or admin)Solidity
import { ethers } from 'ethers'

const rpc = 'https://evm-rpc.sei-apis.com'
const pk = process.env.USER_PK! // creator or admin
const wallet = new ethers.Wallet(pk, new ethers.JsonRpcProvider(rpc))

const marketAddr = '0xMarket...'
const market = new ethers.Contract(marketAddr, [ 'function cancelMarket()' ], wallet)

// If caller is admin: always allowed before settlement
// If caller is creator: allowed when they are the sole bettor
await (await market.cancelMarket()).wait()
console.log('market cancelled')
Finalize Scores & Update SpreadSolidity
import { ethers } from 'ethers'

const rpc = 'https://evm-rpc.sei-apis.com'
const oraclePk = process.env.ORACLE_PK!
const oracle = new ethers.Wallet(oraclePk, new ethers.JsonRpcProvider(rpc))
const marketAddr = '0xMarket...'

const market = new ethers.Contract(marketAddr, [
  'function setScoresOracle(uint16 home, uint16 away)',
  'function setSpreadTenthsOracle(int16 newTenths)'
], oracle)

// Finalize with scores (oracle)
await (await market.setScoresOracle(101, 99)).wait()
console.log('settled with scores')

// Adjust spread pre-settlement (oracle)
await (await market.setSpreadTenthsOracle(25)).wait() // 2.5 points

After cancellation or push, users call claim() to refund stakes. Agent fees are refunded by Factory per tracked totals.

Fee Refunds & Factory Hooks

Two lightweight hooks connect Market and Factory for tracking and refunds:

interface IFactoryLite {
  // Called on bet placement to track referral totals
  function updateUserBetAmount(address user, uint256 amount) external;
  
  // Called on cancel claim to return accumulated fees to the user
  function refundUserFee(address user, address token, uint256 amount) external;
}
  • Stake refunds are executed by the Market contract because stakes are held there.
  • Fee refunds are executed by the Factory via refundUserFee because fees are forwarded there at bet time. Markets track per-user fees and invoke the refund on cancel.
  • Referral tracking uses updateUserBetAmount(user, amount) at bet placement, typically with net stake.
  • Factory registries incorporate the proposition token in GameRegistered, createMarket, and getMarketBySlug.

POST https://x402bet.fun/api/x402/market/ensure

Ensures a market exists for an event. If missing, returns a prepared Factory.createMarket transaction (unified create + initial bet).

Request JSONExample
{
  "agent": "0xAgentAddress...",
  "eventId": 123456,
  "homeSelectionId": 1,
  "awaySelectionId": 2,
  "spreadTenths": 0,
  "spreadAppliesToHome": false,
  "sport": "boxing",
  "region": "us",
  "proposition": "moneyline",
  "homeVsAway": "canelo:charlo",
  "initialSelection": "home",
  "tokenType": "native",
  "amount": "100000000000000000"
}
Response JSONReturned
{
  "exists": false,
  "factory": "0xFactory...",
  "network": "sei-mainnet",
  "tx": { "to": "0xFactory...", "data": "0x...", "value": "100000000000000000" },
  "feeData": { "maxFeePerGas": "...", "maxPriorityFeePerGas": "..." },
  "gasEstimate": "...",
  "notes": "Ensure Factory is approved for ERC20 single-tx create+bet."
}
Agent Flow: Market Missingensure → prepare → verify → sign & send
  • Lookup by eventId. If the address is 0x0, call https://x402bet.fun/api/x402/market/ensure with full metadata to get a prepared createMarket transaction.
  • Native path: set amount and submit with tx.value > 0. ERC20 path: set amount > 0 and approve Factory as spender beforehand.
  • After creation succeeds, re‑query the market address by eventId (or slug) and use https://x402bet.fun/api/x402/market/prepare for subsequent bets.
  • Selection mapping: initialSelection accepts "home"/"away" or a specific uint16 ID. For prepare/bet, "home" → 1, "away" → 2 by default unless overridden by contract config.

POST https://x402bet.fun/api/x402/market/prepare

Builds a signerless transaction to place a bet on a whitelisted market.

Request JSONExample
{
  "agent": "0xAgentAddress...",
  "market": "0xWhitelistedMarket...",
  "selection": "home",
  "tokenType": "native" | "erc20",
  "amount": "100000000000000000"
}
Response JSONReturned
{
  "agent": "0xAgentAddress...",
  "network": "sei-mainnet",
  "tx": {
    "to": "0xWhitelistedMarket...",
    "data": "0x...",
    "value": "100000000000000000"
  },
  "feeData": {
    "maxFeePerGas": "...",
    "maxPriorityFeePerGas": "..."
  },
  "gasEstimate": "..."
}

Create + Bet (Unified)single function: native via msg.value, ERC20 via amount
import { ethers } from 'ethers'

const rpc = 'https://evm-rpc.sei-apis.com'
const adminPk = process.env.ADMIN_PK!
const admin = new ethers.Wallet(adminPk, new ethers.JsonRpcProvider(rpc))

const factoryAddr = '0xFactory...'
const factory = new ethers.Contract(factoryAddr, [
  'function createMarket(uint64 eventId, uint16 homeSelectionId, uint16 awaySelectionId, int16 spreadTenths, bool spreadAppliesToHome, string sport, string region, string proposition, string homeVsAway, uint16 initialSelectionId, uint256 amount) payable returns (address)'
], admin)

const sport = 'nba'
const region = 'us'
const proposition = 'spread'
const homeVsAway = 'bucks:lakers'

const eventId = 123456n
const homeSelectionId = 1
const awaySelectionId = 2
const spreadTenths = 50 // 5.0 points
const spreadAppliesToHome = true
const initialSelectionId = homeSelectionId // seed on 'home'

// Single transaction (native): create market and seed initial wager
const tx = await factory.createMarket(
  eventId,
  homeSelectionId,
  awaySelectionId,
  spreadTenths,
  spreadAppliesToHome,
  sport,
  region,
  proposition,
  homeVsAway,
  initialSelectionId,
  0n,
  { value: ethers.parseEther('0.1') } // msg.value > 0 selects Native
)
const rcpt = await tx.wait()
console.log('created and seeded in one tx', rcpt.hash)

The unified factory method deploys the market and immediately places a bet. Native uses msg.value; ERC20 uses the explicit amount parameter. At least one must be non-zero.

Note: For ERC20 single‑tx create+bet, you must first approve(factory, amount). If not approved, the call reverts with “approve factory as spender first”.

POST https://x402bet.fun/api/x402/verify

Validates payload shape, whitelisting, network, and simulates the transaction.

Request JSON
{
  "agent": "0xAgentAddress...",
  "tx": { "to": "0xWhitelistedMarket...", "data": "0x...", "value": "100000000000000000" },
  "paymentRequirements": { "network": "sei-mainnet" },
  "slippageBps": 100
}
Response JSON
{
  "isValid": true,
  "payer": "0xAgentAddress..."
}
Odds & Payout Previewnet stake + bonus

Payout preview expects the net stake (fees already debited) and includes any current bonus pool measured as extra contract balance over recorded stakes.

  • getPayoutPreview(selectionId, netStake) returns the expected payout including stake, using the net amount added to the pool plus any measured bonus.
import { ethers } from 'ethers'

const market = new ethers.Contract(marketAddress, MARKET_ABI, provider)
const selectionId = 1 // home

// Compute net stake from user-entered gross
const grossAmount = ethers.parseUnits('10', 18)
const feeBps = await market.feeBps()
const fee = (grossAmount * BigInt(feeBps)) / 10000n
const netStake = grossAmount - fee

const preview = await market.getPayoutPreview(selectionId, netStake)
console.log('Preview payout (wei):', preview.toString())

The preview includes current measured bonus (balance − total recorded pools). At settlement, bonus is locked and distributed pro‑rata to winners.

Examples

Native SEI Betprepare + verify + sign & send
# Prepare unsigned native bet
curl -s -X POST https://x402bet.fun/api/x402/market/prepare   -H 'Content-Type: application/json'   -d '{
    "agent": "0xAgentAddress...",
    "market": "0xWhitelistedMarket...",
    "selection": "home",
    "tokenType": "native",
    "amount": "100000000000000000"
  }' | jq . > prepare.json

# Verify preflight
jq -n --argfile p prepare.json '{
  agent: $p.agent,
  tx: $p.tx,
  paymentRequirements: { network: $p.network }
}' | curl -s -X POST https://x402bet.fun/api/x402/verify   -H 'Content-Type: application/json'   -d @- | jq .
import { ethers } from 'ethers'
import fs from 'fs'

const rpc = 'https://evm-rpc.sei-apis.com'
const pk = process.env.AGENT_PK!
const wallet = new ethers.Wallet(pk, new ethers.JsonRpcProvider(rpc))

const prepare = JSON.parse(fs.readFileSync('prepare.json','utf8'))
const tx: any = {
  to: prepare.tx.to,
  data: prepare.tx.data,
  value: prepare.tx.value ? ethers.toBigInt(prepare.tx.value) : undefined,
}

const fee = await wallet.provider.getFeeData()
tx.maxFeePerGas = fee.maxFeePerGas
tx.maxPriorityFeePerGas = fee.maxPriorityFeePerGas
tx.gasLimit = await wallet.provider.estimateGas({ from: await wallet.getAddress(), ...tx })

const sent = await wallet.sendTransaction(tx)
const rcpt = await sent.wait()
console.log('status', rcpt?.status, 'hash', sent.hash)
ERC20 x402Bet Betapprove + prepare
import { ethers } from 'ethers'

const rpc = 'https://evm-rpc.sei-apis.com'
const pk = process.env.AGENT_PK!
const provider = new ethers.JsonRpcProvider(rpc)
const wallet = new ethers.Wallet(pk, provider)

const token = '0xX402BetToken...'
const market = '0xWhitelistedMarket...'
const amount = ethers.parseUnits('2.5', 18)

const erc20 = new ethers.Contract(token, [
  'function approve(address spender, uint256 amount) returns (bool)'
], wallet)

await (await erc20.approve(market, amount)).wait()
console.log('approved')

curl -s -X POST https://x402bet.fun/api/x402/market/prepare   -H 'Content-Type: application/json'   -d '{
    "agent": "0xAgentAddress...",
    "market": "0xWhitelistedMarket...",
    "selection": "away",
    "tokenType": "erc20",
    "amount": "2500000000000000000"
  }' | jq . > prepare_erc20.json
Create If Missing + Betadmin creates market if slug not found; then place bet
import { ethers } from 'ethers'

// Admin signer (createMarket is onlyAdmin)
const rpc = 'https://evm-rpc.sei-apis.com'
const adminPk = process.env.ADMIN_PK!
const admin = new ethers.Wallet(adminPk, new ethers.JsonRpcProvider(rpc))

// Factory and Market ABIs (minimal)
const factoryAddr = '0xFactory...'
const factory = new ethers.Contract(factoryAddr, [
  'function getMarketBySlug(string sport, string region, string proposition, string homeVsAway) view returns (address)',
  'function createMarket(uint64 eventId, uint16 homeSelectionId, uint16 awaySelectionId, int16 spreadTenths, bool spreadAppliesToHome, string sport, string region, string proposition, string homeVsAway, uint16 initialSelectionId, uint256 amount) payable returns (address)'
], admin)

const sport = 'nba'
const region = 'us'
const proposition = 'spread'
const homeVsAway = 'bucks:lakers'

let marketAddr = await factory.getMarketBySlug(sport, region, proposition, homeVsAway)
if (marketAddr === ethers.ZeroAddress) {
  const eventId = 123456n
  const homeSelectionId = 1
  const awaySelectionId = 2
  const spreadTenths = 50 // 5.0 points
  const spreadAppliesToHome = true
  const initialSelectionId = homeSelectionId
  // Native create+bet example: msg.value > 0, amount = 0
  const tx = await factory.createMarket(
    eventId,
    homeSelectionId,
    awaySelectionId,
    spreadTenths,
    spreadAppliesToHome,
    sport,
    region,
    proposition,
    homeVsAway,
    initialSelectionId,
    0n,
    { value: ethers.parseEther('0.1') }
  )
  const rcpt = await tx.wait()
  // Read returned address from event or call result
  const created = await factory.getMarketBySlug(sport, region, proposition, homeVsAway)
  marketAddr = created
}

// Place a bet on the newly found/created market
const market = new ethers.Contract(marketAddr, [
  'function placeBetNative(address bettor, uint16 selectionId) payable',
  'function placeBetERC20(address bettor, uint16 selectionId, uint256 amount)'
], admin)

// Native bet example (home): send 0.1 SEI, attribute to admin
await (await market.placeBetNative(admin.address, 1, { value: ethers.parseEther('0.1') })).wait()

// ERC20 bet example (away): approve then place, attribute to admin
const tokenAddr = '0xX402BetToken...'
const amount = ethers.parseUnits('2.5', 18)
const erc20 = new ethers.Contract(tokenAddr, [
  'function approve(address spender, uint256 amount) returns (bool)'
], admin)
await (await erc20.approve(marketAddr, amount)).wait()
await (await market.placeBetERC20(admin.address, 2, amount)).wait()
// Unified create+bet (ERC20) alternative: approve Factory, then call with amount
// await (await erc20.approve(factoryAddr, amount)).wait()
// await (await factory.createMarket(
//   eventId,
//   homeSelectionId,
//   awaySelectionId,
//   spreadTenths,
//   spreadAppliesToHome,
//   sport,
//   region,
//   proposition,
//   homeVsAway,
//   2, // initialSelectionId
//   amount, // amount > 0 selects ERC20 path
//   { value: 0n }
// )).wait()

If the slug is not registered, the admin call creates it and places an initial bet immediately. Use tx value for native; use an explicit amount for ERC20. At least one must be non-zero. For ERC20 single‑tx create+bet, approve the Factory as spender. The call reverts with “approve factory as spender first” if not approved.

Payload Notes

  • Selection: outcome identifier expected by the Market contract (e.g., "home", "away").
  • TokenType: native uses SEI; erc20 uses x402Bet token.
  • Amount: wei string; for native bets, used as tx value.

Common Errors

  • Target not whitelisted: provided market is not allowed.
  • Network mismatch: paymentRequirements.network does not match active deployment.
  • Simulation failed: upstream call would revert; adjust inputs or selection.

Operational Recommendations

  • Apply per-agent API keys and rate limits.
  • Log verification outcomes and revert reasons for analysis.
  • Monitor RPC health and latency; retry on transient failures.