Sports Betting
Verified@skinnynoizze
npx machina-cli add skill @skinnynoizze/sports-betting --openclawSports betting (Pinwin)
Place and claim decentralized sports bets on Polygon via Pinwin and Azuro, with on-chain execution. The agent fetches prematch and live games from the data-feed, you pick a selection, then it calls Pinwin, signs (and optionally approves USDT), and submits.
Invocation: This skill is invocation-only: the assistant will not use it unless you explicitly ask (e.g. “place a bet with Pinwin”) or use the slash command. That avoids accidental bets.
How to use (OpenClaw)
- Invoke: Use the slash command
/sports_betting(or/skill sports-betting) and optionally add your request, e.g./sports_betting place 5 USDT on the first Premier League gameor/sports_betting show my bets. - Versatility: The assistant should ask you for preferences when not specified: how many games to fetch (
first), order (turnovervsstartsAt), sport/country/league filters, and which selection you want. It should not suggest or pick a bet unless you explicitly ask for a suggestion (meaningful suggestions would require external data, e.g. news or stats, and could be a separate skill).
When to use
- User wants to place a bet on a game (prematch or live): fetch games → choose a selection → call Pinwin
/agent/bet→ approve token if needed → sign EIP-712 → POST signature to returnedapiUrl. - User wants to check bet status (pending / resolved / won or lost) or redeem winnings: query the bets subgraph for the bettor’s bets (see Check bet status); when a bet has
isRedeemable && !isRedeemed, call Pinwin/agent/claimwith thosebetIds→ sign and send the returned transaction with viem.
Prerequisites
Credentials (required env)
- BETTOR_PRIVATE_KEY — Wallet private key (hex) for signing bets and claim transactions. High-sensitivity; do not log or expose. Required for placing and claiming. Use a dedicated betting wallet with minimal funds; do not use your primary wallet.
Optional
- POLYGON_RPC_URL — Polygon RPC endpoint. If unset, the agent uses the default RPC(s) in references/polygon.md (e.g. Pocket Network, PublicNode).
Other
- @azuro-org/dictionaries — required. The subgraph returns only
outcomeIdand odds; this package is the only way to map them to human-readable market and selection names (e.g. "Total Goals", "Over (2.5)"). See references/dictionaries.md. - Addresses: relayer, bet token, native gas token (POL), data-feed URL, and bets subgraph URL are in references/polygon.md.
- Balances: The agent can use viem to check POL (gas) and USDT (stake + relayer fee) before placing a bet; if insufficient, inform the user and do not proceed. See references/viem.md.
Flow (place a bet)
- Optional — check balances: Use viem to read POL (
getBalance(bettor)) and USDT (balanceOf(bettor)on bet token). For a rough pre-check, ensure USDT ≥ stake and enough POL for gas; the exact USDT required is stake + relayer fee, but relayerFeeAmount comes from the decoded Pinwin payload (after calling/agent/bet). So either: (a) require USDT ≥ stake (and POL for gas) as a conservative check, or (b) call Pinwin first, decode the payload, then require USDT ≥ stake + payload’s relayerFeeAmount before approving or signing. If insufficient, inform the user and stop. See references/viem.md. - Fetch games – POST a GraphQL query to the Azuro data-feed. URL, query, and variables: references/subgraph.md. Use
state: "Prematch"or"Live"; getgameId,title,startsAt,participants,conditions(withconditionId,outcomeswithoutcomeId,currentOdds). Respect user preferences forfirst,orderBy/orderDirection, and optional filters (sport, country, league); if not specified, ask or use defaults (e.g.first: 20,orderBy: turnover,orderDirection: desc). Use @azuro-org/dictionaries to mapoutcomeIdto market/selection names; usegetSelectionName({ outcomeId, withPoint: true })so lines (e.g. Over 2.5) are shown: references/dictionaries.md. - Choose selection – Pick one (or more for combo)
conditionId+outcomeIdfrom an Active condition. Use that outcome’scurrentOddsforminOdds:minOdds = Math.round(parseFloat(currentOdds) * 1e12). - Call Pinwin –
POST https://api.pinwin.xyz/agent/betwith JSON body (see references/api.md). Response:{ "encoded": "<base64>" }. Decode:payload = JSON.parse(atob(response.encoded)). - Explain to user (before signing) – Display all decoded payload data (for transparency): amount, selections (with human-readable names from the data-feed and references/dictionaries.md), relayerFeeAmount, apiUrl, environment, and all clientData fields (affiliate, core, expiresAt, chainId, attention, isFeeSponsored, isBetSponsored, isSponsoredBetReturnable). Use paths from references/api.md: single bet =
signableClientBetData.bet; combo =signableClientBetData.betsand top-level amount/minOdds/nonce. Then explain in human-readable terms: stake in USDT, selection/market names, relayer fee, and that this is the bet they are authorising. Do this before approval or signing. - Approval (if needed) – Check bet token’s
allowance(bettor, relayer)on Polygon. If < bet amount + relayer fee + 0.2 USDT, signapprove(relayer, bet amount + relayer fee + 0.2 USDT)on the bet token. Approval is bounded to this bet plus a small buffer for security; the agent may need to approve again for the next bet. Addresses: references/polygon.md. Steps: references/viem.md. - Verify payload vs user intent – Before signing: (a) Confirm that the decoded payload’s amount matches the user’s requested stake and that the payload’s selections (conditionId/outcomeId or combo bets) match the user’s chosen selection(s). (b) Verify that the payload’s clientData.core (from
signableClientBetDataorapiClientBetData) equals the documented claimContract (ClientCore) for Polygon in references/polygon.md; if not, do not sign and report the mismatch. (c) Use only the relayer address from references/polygon.md for allowance and approve—do not use any relayer from the payload. If amount or selections do not match, do not sign; inform the user that the API payload does not match their request and stop. - Sign and submit – Use viem
signTypedDatawithpayload.domain,payload.types,primaryType(see references/api.md), andmessage: payload.signableClientBetData. Then POST topayload.apiUrlwithenvironment,bettor,betOwner,clientBetData(=payload.apiClientBetData),bettorSignature. The order id is in the POST response: useresponse.id. If you get an order id, poll until the order settles (see references/api.md): GET{apiBase}/bet/orders/{orderId}. Success = response hastxHash; failure =stateisRejectedorCanceled(useerrorMessage).
Check bet status (before redeem)
To know when a bet is resolved and whether it won or lost, and whether the user can redeem, query the bets subgraph (different from the data-feed). See references/bets-subgraph.md.
- Query bets – POST a GraphQL query to the bets subgraph URL (Polygon: in references/polygon.md). Query
v3Betswithwhere: { bettor: "<bettor address>" }(address in lowercase). To fetch only bets that can be claimed, addisRedeemable: trueto the where clause (see references/bets-subgraph.md). Request at least:betId,status,result,isRedeemable,isRedeemed,amount,payout. - Interpret – status =
Accepted(pending) |Resolved(settled) |Canceled. When status === Resolved, result =WonorLost. When isRedeemable === true and isRedeemed === false, the user can claim; collect those bets’ betId values for the claim flow.
Flow (claim)
Only for bets that are resolved (or canceled) and have isRedeemable true and isRedeemed false; get betIds from the bets subgraph (see Check bet status).
- Call Pinwin –
POST https://api.pinwin.xyz/agent/claimwithbetIds(array of on-chain bet ids) andchain: "polygon". Decode the responseencodedpayload. Explain to the user in human-readable terms what they are sending: e.g. claiming winnings for bet IDs X, Y; the transaction will go to the Azuro ClientCore contract on Polygon; no value (ETH/POL) is sent. Display the full decoded claim payload (to, chainId, value, and any other keys returned) for transparency. - Verify claim contract – Ensure
payload.to(lowercase) equals the documented claimContract (ClientCore) for Polygon in references/polygon.md. This is the redeem contract for won/canceled bets, not the Cashout (early-exit) contract. If it does not match, do not send the tx and report the mismatch. - Send tx – Use viem
sendTransactionwith{ to: payload.to, data: payload.data, value: 0n, chainId: payload.chainId }. Wait for receipt. Details: references/viem.md.
Example (place a single bet)
After fetching games and choosing one outcome:
POST https://api.pinwin.xyz/agent/bet
{ "amount": 1000000, "minOdds": 1500000000000, "chain": "polygon", "selections": [{ "conditionId": "<from data-feed>", "outcomeId": 21 }] }
Decode response.encoded, sign payload.signableClientBetData with viem signTypedData, then POST to payload.apiUrl with clientBetData and bettorSignature.
Example (claim)
Get betIds from the bets subgraph (e.g. query with isRedeemable: true). Then:
POST https://api.pinwin.xyz/agent/claim
{ "betIds": [215843], "chain": "polygon" }
Decode response.encoded → payload. Display payload to the user; verify payload.to equals the claimContract (ClientCore) in references/polygon.md. Then send tx with viem: sendTransaction({ to: payload.to, data: payload.data, value: 0n, chainId: payload.chainId }). Wait for receipt.
Tools
| Step | Tool | Purpose |
|---|---|---|
| Games | Data-feed subgraph (GraphQL) | Get games, conditions, outcomes, odds |
| Bet status | Bets subgraph (GraphQL) | Get bettor’s bets: status (Accepted/Resolved/Canceled), result (Won/Lost), isRedeemable, betId |
| Names | @azuro-org/dictionaries (required) | Map outcomeId → human-readable market/selection names; only way to get labels from subgraph data. |
| Bet/claim | Pinwin API | Get encoded payload and apiUrl |
| Chain | viem + RPC | getBalance (POL), readContract (allowance, balanceOf USDT), sendTransaction (approve, claim), signTypedData (bet) |
Required packages: npm install viem @azuro-org/dictionaries. Setup and chain calls: references/viem.md. Dictionaries usage: references/dictionaries.md.
Errors
- Pinwin 4xx/5xx: read
errorormessagein the response body. - Subgraph: check both HTTP status and
data.errorsin the JSON body (GraphQL can return 200 withdata.errors). - Chain: tx reverted, insufficient funds — report tx hash.
Reference files
Load these when you need full request/response shapes, queries, or addresses:
- references/api.md – Pinwin POST /agent/bet and /agent/claim (request, response, decoded payload).
- references/subgraph.md – Data-feed URL, canonical GraphQL query, example variables, filtering, response shape.
- references/bets-subgraph.md – Bets subgraph URL, query for bettor’s bets, status/result/isRedeemable, betId for claim.
- references/dictionaries.md – @azuro-org/dictionaries (getMarketName, getSelectionName; use withPoint: true for selection lines).
- references/polygon.md – Polygon data-feed URL, bets subgraph URL, native gas token (POL), relayer, betToken (USDT), environment.
- references/viem.md – viem install, setup, getBalance (POL), balanceOf (USDT), allowance, approve, signTypedData, claim tx.
Overview
This skill lets you place and claim decentralized sports bets on Polygon using Pinwin and Azuro. It fetches prematch and live games from a data feed, you select a market, then it signs and submits on-chain with optional USDT approval. It’s custody-free and relies on wallet signing (EIP-712).
How This Skill Works
The agent uses Node, viem and @azuro-org/dictionaries to fetch games, map markets to human-readable names, then calls Pinwin /agent/bet and signs the transaction on-chain. If needed, it can approve USDT before placing a bet and posts the signed payload to the returned apiUrl. For checking or redeeming, it queries the bets subgraph and, if redeemable, calls Pinwin /agent/claim with betIds, signs, and submits.
When to Use It
- Place a new bet on a prematch or live game after reviewing available markets
- Browse prematch and live games with human-readable market names before deciding
- Check the status of existing bets (pending, won, lost)
- Redeem winnings for bets that are redeemable
- Verify balances (POL for gas and USDT for stake) before placing a bet
Quick Start
- Step 1: Invoke the skill with /sports_betting and include your request (e.g., place 5 USDT on the first Premier League game).
- Step 2: If not specified, provide preferences (how many games to fetch, filters, which selection) and ensure funds are available.
- Step 3: Sign the bet with your BETTOR_PRIVATE_KEY and submit; monitor the result or betId returned by Pinwin.
Best Practices
- Use a dedicated betting wallet and keep BETTOR_PRIVATE_KEY secure; avoid exposing keys
- Before placing, verify POL (gas) and USDT (stake) balances with viem to prevent failed bets
- Map market and selection names using @azuro-org/dictionaries to ensure correct bets
- Provide explicit preferences (how many games to fetch, filters, specific selection) to avoid accidental bets
- Check isRedeemable status from the bets subgraph and redeem in batches only when appropriate
Example Use Cases
- Place a 5 USDT bet on the first Premier League game as Home win, using prematch data
- Show my active bets and their current odds with human-readable market names
- Claim all redeemable winnings for my bets in a single action
- Browse live NBA games and bet on Over 210.5 using the data feed
- Fetch prematch games for a top league and display markets before placing a bet