Chain ID: 1329
0xD2649969B3ED972586b71C728be1166f5fF43ad10xf3F6b996dEb6c56c41237c4B97D4cB0F7758Bc0a0x900fc36eb8324A766C01239D175380Fbd25B5E4b0x00000000000000000000000000000000000000000x2b9352591b8A3d63FaC2EFf2954F49f8E4f3F5BD0x2b9352591b8A3d63FaC2EFf2954F49f8E4f3F5BD{
"chainId": 1329,
"rpcUrl": "https://evm-rpc.sei-apis.com",
"deployer": "0x2b9352591b8A3d63FaC2EFf2954F49f8E4f3F5BD",
"agent": "0x2b9352591b8A3d63FaC2EFf2954F49f8E4f3F5BD",
"factory": "0xD2649969B3ED972586b71C728be1166f5fF43ad1",
"token": "0x0000000000000000000000000000000000000000",
"oracle": "0xf3F6b996dEb6c56c41237c4B97D4cB0F7758Bc0a",
"marketDeployer": "0x900fc36eb8324A766C01239D175380Fbd25B5E4b",
"marketsWhitelist": []
}Markets are keyed by a canonical slug including a proposition token:
slug = "sport:region:proposition:home:away"# 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.
spreadTenths to 0 for no-spread events (boxing, politics). Use oracle scores or synthetic scores to mark a winner/loser/push.oracle calls setScoresOracle(home, away) to settle; admins can use setScoresAdmin(home, away) as a fallback.oracle can adjust the spread pre‑settlement via setSpreadTenthsOracle(newTenths).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')
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.
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;
}refundUserFee because fees are forwarded there at bet time. Markets track per-user fees and invoke the refund on cancel.updateUserBetAmount(user, amount) at bet placement, typically with net stake.GameRegistered, createMarket, and getMarketBySlug.Ensures a market exists for an event. If missing, returns a prepared Factory.createMarket transaction (unified create + initial bet).
{
"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"
}{
"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."
}eventId. If the address is 0x0, call https://x402bet.fun/api/x402/market/ensure with full metadata to get a prepared createMarket transaction.amount and submit with tx.value > 0. ERC20 path: set amount > 0 and approve Factory as spender beforehand.eventId (or slug) and use https://x402bet.fun/api/x402/market/prepare for subsequent bets.initialSelection accepts "home"/"away" or a specific uint16 ID. For prepare/bet, "home" → 1, "away" → 2 by default unless overridden by contract config.Builds a signerless transaction to place a bet on a whitelisted market.
{
"agent": "0xAgentAddress...",
"market": "0xWhitelistedMarket...",
"selection": "home",
"tokenType": "native" | "erc20",
"amount": "100000000000000000"
}{
"agent": "0xAgentAddress...",
"network": "sei-mainnet",
"tx": {
"to": "0xWhitelistedMarket...",
"data": "0x...",
"value": "100000000000000000"
},
"feeData": {
"maxFeePerGas": "...",
"maxPriorityFeePerGas": "..."
},
"gasEstimate": "..."
}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”.
Validates payload shape, whitelisting, network, and simulates the transaction.
{
"agent": "0xAgentAddress...",
"tx": { "to": "0xWhitelistedMarket...", "data": "0x...", "value": "100000000000000000" },
"paymentRequirements": { "network": "sei-mainnet" },
"slippageBps": 100
}{
"isValid": true,
"payer": "0xAgentAddress..."
}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.
# 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)
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
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.
value.market is not allowed.paymentRequirements.network does not match active deployment.