> ## Documentation Index
> Fetch the complete documentation index at: https://docs.counsel.markets/llms.txt
> Use this file to discover all available pages before exploring further.

# Building Bots on counsel: Automate Your Betting Strategy

> Bots are first-class on counsel. Fetch unsigned bet intents, sign with your own key, and submit, the SDK handles the full XRPL lifecycle in a few lines.

Bots are first-class citizens on counsel. A bot is an ordinary XRPL client: it reads live markets, fetches an unsigned bet `Payment`, signs it with its own key, and submits it to the ledger. counsel never holds a key. Every bet carries the counsel `SourceTag`, so a bot's on-chain volume counts toward leaderboard attribution the same way a manual bet does. Because counsel markets are parimutuel, there is no order book, bots add value by correcting mispricings, balancing pools, or arbitraging across venues. Each of these actions is a native tagged Payment, generating direct on-chain volume.

## What a bot can do

* **Value-betting**, estimate the true probability of each outcome and stake when the market's `implied_prob` diverges from your model by a meaningful margin. This pushes the line toward the fair price, benefiting all participants.
* **Pool-balancing**, parimutuel lines drift continuously until `bet_cutoff`. A bot can identify a lopsided pool near close and bet the underweighted side, capturing better odds while correcting the skew.
* **Cross-venue arbitrage**, take a counsel position and hedge it against the same event on another platform, locking in a spread when the implied probabilities diverge.

## Quickstart, install and configure

Install the SDK from the repo and configure the base URL:

```typescript theme={null}
import { Counsel } from "./counsel";

const counsel = new Counsel({ baseUrl: "https://api.counsel.markets" });
```

<Tip>
  Set `COUNSEL_URL` to `https://api.counsel.markets` and reference it with `process.env.COUNSEL_URL` to keep the base URL out of your source code.
</Tip>

## Read live markets

Fetch all public markets and inspect their current pool state and indicative odds:

```typescript theme={null}
const { markets } = await counsel.markets();
// Each market has: id, question, phase, status, outcomes[]
// Each outcome: index, label, implied_prob, payout_per_unit, pool_xrp
```

Filter to markets that are currently accepting bets before doing anything else:

```typescript theme={null}
const open = (markets as any[]).filter(
  (m) => m.status === "open" && m.phase === "open"
);
```

## Fetch a bet intent

Before committing, retrieve an unsigned transaction for the specific bet you want to place. This also returns the projected line impact of your stake:

```typescript theme={null}
const intent = await counsel.betIntent(marketId, walletAddress, outcomeIndex, amountXrp);
// intent.tx = unsigned XRPL Payment ready to sign
// intent.projected_implied_odds_after = { implied_prob, payout_per_unit }
```

`betIntent` maps to:

```
GET /api/v1/markets/:id/bet-intent
  ?account=WALLET_ADDRESS
  &outcome=OUTCOME_INDEX
  &amount=AMOUNT_XRP
```

The returned `tx` is a fully-formed XRPL `Payment` object. All that remains is to autofill sequence/fee fields, sign, and submit.

## Sign and submit

`placeBet` handles the full lifecycle: it calls `betIntent` internally, signs with the provided seed using `xrpl.js`, submits, and waits for ledger validation. It returns the confirmed transaction hash.

```typescript theme={null}
const hash = await counsel.placeBet(process.env.BOT_SEED!, marketId, outcomeIndex, amountXrp);
console.log("tx:", hash);
```

<Warning>
  Use testnet seeds only. Never commit seeds to source control. Load `BOT_SEED` exclusively from environment variables or a secrets manager.
</Warning>

Internally, `placeBet` connects to the XRPL WebSocket node specified in `CounselOptions.wss` (defaults to `wss://s.altnet.rippletest.net:51233` for testnet), autofills the transaction, signs it with `Wallet.fromSeed(seed)`, submits via `submitAndWait`, and asserts `tesSUCCESS` before returning. The WebSocket connection is disconnected after each call.

## Full value-betting example

The following bot fetches all open markets, runs each outcome through a probability model, and bets when it finds sufficient edge. It also checks the projected post-stake odds before committing to avoid betting after the line has already moved past the fair price:

```typescript theme={null}
import { Counsel } from "./counsel";

const counsel = new Counsel({ baseUrl: process.env.COUNSEL_URL! });
const SEED = process.env.BOT_SEED!; // testnet only

// Your model: the probability you believe each outcome has.
function myModel(question: string): number[] {
  // ...return [pYes, pNo]; here a naive 50/50
  return [0.5, 0.5];
}

const { markets } = await counsel.markets();
for (const m of markets as any[]) {
  if (m.status !== "open" || m.phase !== "open") continue;
  const fair = myModel(m.question);
  for (const o of m.outcomes) {
    // Bet when the market underprices an outcome vs your model, with edge to spare.
    if (fair[o.index] - o.implied_prob > 0.08) {
      const intent = await counsel.betIntent(m.id, walletAddress, o.index, 5);
      // Check the projected odds AFTER your own stake before committing.
      if (intent.projected_implied_odds_after.implied_prob < fair[o.index]) {
        await counsel.placeBet(SEED, m.id, o.index, 5);
      }
    }
  }
}
```

The inner check on `projected_implied_odds_after.implied_prob` is important: in a small pool, your own 5 XRP stake can move the line enough to eliminate the edge you detected from the stale `implied_prob` in the market list.

## Anti-slippage

Parimutuel odds are not fixed, every bet changes the pool, and therefore every subsequent payout. When you read `implied_prob` from `GET /markets`, that figure already reflects all bets submitted before your read. By the time you fetch the bet intent, further bets may have arrived.

Always use `projected_implied_odds_after` as your decision gate, not the market-list odds:

```typescript theme={null}
const intent = await counsel.betIntent(marketId, walletAddress, outcomeIndex, amountXrp);

if (intent.projected_implied_odds_after.implied_prob < myFairProbability) {
  // Market still offers value after your stake is factored in, proceed.
  await counsel.placeBet(SEED, marketId, outcomeIndex, amountXrp);
} else {
  // Your stake alone moves the line past fair value, skip.
}
```

For large stakes relative to pool size, consider breaking a single bet into smaller sequential bets and re-checking the intent each time.

## Rate limits

The public API requires no authentication. There are no enforced rate limits, but the API is a shared resource. Poll `GET /markets` at a reasonable cadence, **5-10 seconds** is appropriate for most strategies. High-frequency polling does not provide an advantage on parimutuel markets: odds change when bets arrive, not on a tick schedule, and final payouts are determined at close regardless of when you placed your bet.

## SDK method reference

| Method                                    | Endpoint                                  |
| ----------------------------------------- | ----------------------------------------- |
| `markets()`                               | `GET /api/v1/markets`                     |
| `market(id)`                              | `GET /api/v1/markets/:id`                 |
| `betIntent(id, account, outcome, amount)` | `GET /api/v1/markets/:id/bet-intent`      |
| `positions(address)`                      | `GET /api/v1/accounts/:address/positions` |
| `leaderboard()`                           | `GET /api/v1/leaderboard`                 |
