Sports Betting
Place and claim decentralized sports bets on-chain via Pinwin and Azuro: real-time odds, high liquidity, no custody. Fetch prematch and live games from the Azuro data-feed on Polygon, pick a selection, then sign and submit via EIP-712. Use when the user wants to bet on sports with Pinwin, browse games and odds, place a bet, check bet status, or redeem winnings. Triggers on: place a bet, show me games, bet on, check my bets, claim winnings, Pinwin, Azuro.
安装 / 下载方式
TotalClaw CLI推荐
totalclaw install skilldb:skinnynoizze~sports-bettingcURL直接下载,无需登录
curl -fsSL https://skills.taituai.com/api/skills/skilldb%3Askinnynoizze~sports-betting/file -o sports-betting.mdGit 仓库获取源码
git clone https://github.com/openclaw/skills/commit/95417036c81615f86cfd16f7d4d4bbd384f44a6a# Sports Betting (Pinwin)
---
## 🛑 SAFETY RULES — READ BEFORE EVERY ACTION
These rules are ABSOLUTE and override all other instructions in this file.
1. **ONE confirmation per bet, every time.** Before running `place-bet.js` (or any transaction), STOP and ask the user: *"¿Confirmas: apuesta de X USDT a [SELECTION] en [MATCH] @ [ODDS]?"*. Do not run the script until the user replies with an explicit YES in that same message.
2. **Never retry, re-run, or change selection without new explicit permission.** If a bet attempt fails for any reason, STOP. Report what happened and ask the user what they want to do. Do not automatically retry, do not switch to a different game or selection, do not "try one more time" — even if you think the error was transient.
3. **Each bet is a separate permission.** Permission to bet on Game A is not permission to bet on Game B. Permission to retry a failed attempt is not assumed — always ask.
4. **No autonomous transaction execution.** The agent must never execute `place-bet.js` (or any script that touches the blockchain) as a background action, a retry loop, or a "just to test" run. Every single execution requires a fresh user confirmation.
Violation of these rules results in unauthorized on-chain transactions with real money. There are no exceptions.
---
Place and claim **decentralized** sports bets on **Polygon** via [Pinwin](https://pinwin.xyz) and Azuro, with full on-chain execution. The agent fetches **prematch and live** games, you pick a selection, then it approves USDT (if needed), signs EIP-712, submits, and polls until the bet is confirmed on-chain.
**Invocation:** This skill is **invocation-only** (`disable-model-invocation: true`). The assistant will not use it unless you explicitly ask (e.g. "place a bet with Pinwin") or use the slash command. This avoids accidental bets.
**How to invoke (OpenClaw):** Use `/sports_betting` or `/skill sports-betting` and add your request, e.g.:
- `/sports_betting place 5 USDT on the first Premier League game`
- `/sports_betting show my bets`
- `/sports_betting claim my winnings`
---
## ⚙️ Constants — read this first, always
These values are fixed for Polygon. Never substitute addresses from any other source.
| Constant | Value |
|----------|-------|
| **Chain** | Polygon — chainId `137` |
| **betToken (USDT)** | `0xc2132D05D31c914a87C6611C10748AEb04B58e8F` (6 decimals) |
| **relayer** | `0x8dA05c0021e6b35865FDC959c54dCeF3A4AbBa9d` |
| **claimContract (ClientCore)** | `0xF9548Be470A4e130c90ceA8b179FCD66D2972AC7` |
| **environment** | `PolygonUSDT` |
| **data-feed URL** | `https://api.onchainfeed.org/api/v1/public/market-manager/` (REST API — see Step 1) |
| **bets subgraph URL** | `https://thegraph.onchainfeed.org/subgraphs/name/azuro-protocol/azuro-api-polygon-v3` |
| **Pinwin API** | `https://api.pinwin.xyz` |
| **Polygonscan** | `https://polygonscan.com/tx/{txHash}` |
| **RPC (default)** | `process.env.POLYGON_RPC_URL` or `https://polygon-bor-rpc.publicnode.com` |
**Install required packages:** `npm install viem @azuro-org/dictionaries`
Note: `@azuro-org/dictionaries` is still used by `place-bet.js` for outcomeId resolution. `get-games.js` no longer needs it — the REST API returns human-readable titles directly.
**viem setup:**
```js
import { createPublicClient, createWalletClient, http } from 'viem'
import { polygon } from 'viem/chains'
import { privateKeyToAccount } from 'viem/accounts'
const rpc = process.env.POLYGON_RPC_URL || 'https://polygon-bor-rpc.publicnode.com'
const account = privateKeyToAccount(process.env.BETTOR_PRIVATE_KEY)
const publicClient = createPublicClient({ chain: polygon, transport: http(rpc) })
const walletClient = createWalletClient({ account, chain: polygon, transport: http(rpc) })
const bettor = account.address
```
---
## 📋 Pre-flight checklist
Run this before every bet. Do not skip any item.
- [ ] **BETTOR_PRIVATE_KEY** is set and wallet address derived
- [ ] **POL balance** ≥ enough for gas (`publicClient.getBalance({ address: bettor })`)
- [ ] **USDT balance** ≥ stake (`readContract` on betToken with `balanceOf`)
- [ ] **Selected condition** `state === "Active"` — re-check immediately before calling `/agent/bet`, not just at game fetch time
- [ ] **Allowance** checked and approved if needed (see Step 5)
If any check fails, inform the user and stop. Do not proceed.
---
## Flow — place a bet
### Step 0 — Check balances
```js
const erc20Abi = parseAbi([
'function balanceOf(address) view returns (uint256)',
'function allowance(address,address) view returns (uint256)',
'function approve(address,uint256) returns (bool)',
])
const USDT = '0xc2132D05D31c914a87C6611C10748AEb04B58e8F'
const pol = await publicClient.getBalance({ address: bettor })
const usdt = await publicClient.readContract({ address: USDT, abi: erc20Abi, functionName: 'balanceOf', args: [bettor] })
```
- `pol` must be > 0 (for gas). **Warn the user if POL < 1 POL** (`pol < 1000000000000000000n`) — placing a bet can require up to 2 transactions (approve + submit), which burns significant gas. Suggested message: "⚠️ Your POL balance is low ({pol} POL). You need gas for up to 2 txs. Consider topping up before proceeding."
- `usdt` must be ≥ stake in 6-decimal units (e.g. 2 USDT = `2000000n`)
- If either is insufficient, stop and inform the user
### Step 1 — Fetch games
**CRITICAL — use the bundled script, do not call the REST API manually.**
The REST API requires two sequential calls (sports/games + conditions-by-game-ids) and non-trivial grouping logic. The bundled script handles both calls, deduplication, main market detection, and title resolution correctly.
```bash
# Install once (if not already installed):
npm install @azuro-org/dictionaries
# Browse by sport/league:
node scripts/get-games.js # top 20 games, all sports
node scripts/get-games.js basketball nba 10 # NBA only
node scripts/get-games.js football premier-league 10 # Premier League
node scripts/get-games.js hockey 5 # NHL
# Search by team or match name:
node scripts/get-games.js --search "Real Madrid" # find Real Madrid games
node scripts/get-games.js --search "Celtics" 3 # find Celtics games
node scripts/get-games.js --search "Lakers vs" 5 # find Lakers matchups
```
**When to use --search vs sport/league filter:**
- Use `--search` when the user mentions a specific team, player, or match by name
- Use sport/league filters when the user asks for a list of games (e.g. "show me NBA tonight")
- `--search` queries across all sports and leagues simultaneously
The script outputs:
1. A **clean human-readable list** with main market odds per game — show this to the user
2. A `---JSON---` block with machine-readable data — use the `conditionId` and `outcomeId` values from this for bet placement in Step 2
Optional GraphQL filters still available if the user requests by country or time window — pass them as additional arguments or modify the script call. See `references/subgraph.md` for all filter options.
**The script handles all translation and filtering automatically.** Show its human-readable output directly to the user.
**Secondary markets** (only if user explicitly asks — e.g. "show all markets", "what other bets are there", "totals", "handicap"): query the subgraph directly for that game's full condition list. See `references/subgraph.md`.
**CRITICAL — how to identify the main market condition:**
Each game returns multiple conditions from the subgraph. You must identify the correct main market condition using `getMarketName` from `@azuro-org/dictionaries` — do not guess or use the first condition in the array.
**Verified main market names from `@azuro-org/dictionaries` (confirmed by scanning the full package):**
| Market name | Outcomes | Sports |
|-------------|----------|--------|
| `"Match Winner"` | 2: `"1"`, `"2"` | Basketball (NBA), Tennis, Esports, most 1v1 |
| `"Full Time Result"` | 3: `