main b3669a44a7c4 cached
12 files
31.6 KB
8.9k tokens
33 symbols
1 requests
Download .txt
Repository: 0xalberto/pumpfun-laserstream-sniper-rust
Branch: main
Commit: b3669a44a7c4
Files: 12
Total size: 31.6 KB

Directory structure:
gitextract_681ttebc/

├── .gitignore
├── README.md
├── Update.md
├── package.json
├── src/
│   ├── config/
│   │   ├── client.ts
│   │   ├── index.ts
│   │   └── loadEnv.ts
│   ├── constant/
│   │   └── index.ts
│   ├── index.ts
│   ├── strategy/
│   │   └── arbitrage.ts
│   └── types/
│       └── index.ts
└── tsconfig.json

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
node_modules/*
.env
*-lock.json
log.txt
ui/dist
dist
/node_modules
frontend/node_modules
frontend/dist

================================================
FILE: README.md
================================================
# Polymarket Arbitrage Bot

Polymarket **arbitrage bot** for 15-minute Up/Down markets. Automates the **dump-and-hedge** strategy with configurable thresholds, stop-loss hedging, and optional simulation mode. Full credential management, CLOB order execution, and market discovery via Gamma API.

[![Node.js](https://img.shields.io/badge/Node.js-16+-green.svg)](https://nodejs.org/)
[![TypeScript](https://img.shields.io/badge/TypeScript-5+-blue.svg)](https://www.typescriptlang.org/)
[![License](https://img.shields.io/badge/License-Apache%202.0-yellow.svg)](LICENSE)


![Portfolio](img.png)


## 🎯 Overview

This **Polymarket arbitrage bot** runs a **dump-and-hedge** strategy on Polymarket’s 15m Up/Down markets (e.g. BTC, ETH, SOL, XRP) by:

- **Market discovery** – Finds the current 15m market per asset via Gamma API slug
- **Price monitoring** – Polls CLOB orderbooks for Up/Down bid/ask and time remaining
- **Dump detection** – In the first N minutes of each period, detects a sharp price drop on one side (Up or Down)
- **Leg 1** – Buys the dumped side at the dip
- **Hedge (Leg 2)** – Waits until combined cost (leg1 + opposite ask) is at or below target (e.g. ≤ 0.95), then buys the opposite side to lock in profit
- **Stop-loss hedge** – If the hedge condition isn’t met within a max wait time, hedges anyway to limit risk
- **Settlement** – On market close, redeems winning outcome tokens and tracks P&L
- **Simulation mode** – Run without placing real orders (default); switch to production when ready
- **Type-safe** – Full TypeScript with strict types and clear config via `.env`

Perfect for automating the dump-and-hedge arbitrage on Polymarket 15m markets with controllable risk and optional dry-run.

<!-- Add screenshots/demo images here -->
<!--
![Bot Dashboard](docs/images/dashboard.png)
![Trade Execution](docs/images/trades.png)
-->

## ✨ Key Features

### 🚀 Trading & Strategy
- **Dump-and-hedge** – Buy the dip on one outcome, then hedge when sum of prices ≤ target
- **Multi-market** – Supports multiple symbol/timeframe pairs (configurable via `ARBITRAGE_MARKETS`, e.g. `btc:5m,btc:15m`)
- **Automatic market discovery** – Resolves current 15m market by slug and period timestamp
- **Period rollover** – Detects new 15m periods and switches to the new market automatically
- **Stop-loss hedge** – Time-based fallback hedge if ideal hedge price isn’t reached

### 🛡️ Risk & Safety
- **Simulation by default** – No real orders until you set `PRODUCTION=true` or use `npm run prod`
- **Configurable sizing** – Shares per leg, sum target, move threshold, and watch window
- **Stop-loss parameters** – Max wait before forced hedge and stop-loss percentage
- **Position tracking** – Per-period and total P&L; redemption of winning tokens on close

### 🔧 Production-Ready
- **Env-based config** – All settings in `.env` (no config files to commit)
- **CLOB auth** – API key derivation from signer or optional explicit API key/secret/passphrase
- **Proxy wallet support** – Optional Polymarket proxy/profile address and signature type (EOA / Proxy / GnosisSafe)
- **History logging** – Append-only `history.toml` for audit and debugging
- **Graceful handling** – Continues monitoring on transient API errors; clear stderr logging

## 🚀 Quick Start

### Prerequisites

- **Node.js 16+** – [Download Node.js](https://nodejs.org/)
- **Polygon wallet** – With USDC for trading (production)
- **POL/MATIC** – For gas when redeeming winning tokens (production)

### Installation

```bash
# Clone the repository
git clone https://github.com/zkOSAI/polymarket-arbitrage-bot.git
cd polymarket-arbitrage-bot

# Install dependencies
npm install

# Build the project
npm run build
```

### Configuration

1. **Create environment file:**
```bash
cp .env.example .env
```

2. **Edit `.env` with your settings:**
```env
# Wallet + auth (required in live mode)
WALLET_PRIVATE_KEY=0x...              # Alias: PRIVATE_KEY
PROXY_WALLET_ADDRESS=0x...            # Required when SIGNATURE_TYPE=1 or 2 (Alias: FUNDER_ADDRESS)
SIGNATURE_TYPE=1                      # 0=EOA, 1=Proxy/Magic, 2=GnosisSafe

# APIs / chain
GAMMA_API_URL=https://gamma-api.polymarket.com
CLOB_API_URL=https://clob.polymarket.com # Alias: CLOB_HOST
CHAIN_ID=137
POLYGON_RPC_URL=https://polygon-rpc.com   # optional override for USDC preflight check
AMOY_RPC_URL=https://rpc-amoy.polygon.technology # optional, used when CHAIN_ID != 137

# Markets
ARBITRAGE_MARKETS=btc:5m,btc:15m

# Mode
SIMULATION_MODE=true                   # true=paper mode, false=live mode (Alias: PRODUCTION with inverse meaning)

# Strategy tuning
ARBITRAGE_CHECK_INTERVAL_MS=5000
ARBITRAGE_ORDER_USD=20
ARBITRAGE_OBI_DEPTH_LEVELS=5
ARBITRAGE_TREND_THRESHOLD=0.05
```

3. **Run the bot:**
```bash

# Development – run TypeScript with ts-node
npm run dev
```

Logs go to stderr and are appended to `history.toml`.

## ⚙️ Configuration Guide

### Environment Variables

These are the environment variables currently used by the code.

| Variable | Required | Description | Default |
|----------|----------|-------------|---------|
| `WALLET_PRIVATE_KEY` | Live mode | Private key used to sign CLOB requests. Alias: `PRIVATE_KEY` | - |
| `PROXY_WALLET_ADDRESS` | Live mode when `SIGNATURE_TYPE` is `1` or `2` | Polymarket proxy/profile wallet. Alias: `FUNDER_ADDRESS` | - |
| `SIGNATURE_TYPE` | No | `0` EOA, `1` Proxy/Magic, `2` GnosisSafe | `1` |
| `GAMMA_API_URL` | No | Gamma API base URL (used for market discovery + proxy validation) | `https://gamma-api.polymarket.com` |
| `CLOB_API_URL` | No | CLOB API base URL. Alias: `CLOB_HOST` | `https://clob.polymarket.com` |
| `CHAIN_ID` | No | Network chain id (`137` Polygon mainnet) | `137` |
| `POLYGON_RPC_URL` | No | RPC endpoint for Polygon USDC balance preflight check | `https://polygon-rpc.com` |
| `AMOY_RPC_URL` | No | RPC endpoint for Amoy (used when `CHAIN_ID != 137`) | `https://rpc-amoy.polygon.technology` |
| `SIMULATION_MODE` | No | `true` = paper mode, `false` = live orders | `false` |
| `PRODUCTION` | No | Backward-compatible alias of mode with inverse semantics (`true` => live) | `false` |
| `ARBITRAGE_MARKETS` | No | Comma-separated `symbol:timeframe` list | `btc:5m,btc:15m` |
| `ARBITRAGE_CHECK_INTERVAL_MS` | No | Poll interval in milliseconds | `5000` |
| `ARBITRAGE_ORDER_USD` | No | Order size in USDC per entry/switch action | `20` |
| `ARBITRAGE_OBI_DEPTH_LEVELS` | No | Orderbook bid depth levels used for OBI trend calc | `5` |
| `ARBITRAGE_TREND_THRESHOLD` | No | OBI threshold above/below neutral trend | `0.05` |

### Preflight checks (live mode)

Before trading starts, the bot validates:
- `PRIVATE_KEY` ↔ `PROXY_WALLET_ADDRESS` binding via Gamma `public-profile`
- USDC balance of trading wallet/proxy is `>= ARBITRAGE_ORDER_USD`

## 📖 How It Works

### Dump-and-hedge flow

1. **Discovery** – For each target in `ARBITRAGE_MARKETS`, the bot finds the active Up/Down market via Gamma slug matching.
2. **Monitoring** – Every `ARBITRAGE_CHECK_INTERVAL_MS`, it fetches YES/NO orderbooks and computes OBI trend from bid depth.
3. **Entry** – If no position: buy YES on uptrend, buy NO on downtrend.
4. **Neutral exit** – If trend is neutral and a position exists, sell current position.
5. **Trend-follow hold** – Hold YES on uptrend and hold NO on downtrend.
6. **Trend-flip switch** – If trend flips against the held side, sell and rotate into the new trend side.
7. **Preflight safety** – In live mode, validates key/proxy binding and checks USDC balance is at least `ARBITRAGE_ORDER_USD`.

### Simulation vs production

- **Simulation** (`SIMULATION_MODE=true`): no orders sent to the CLOB; strategy logic and logging run as normal.
- **Production** (`SIMULATION_MODE=false`): real orders sent to the CLOB. Requires `WALLET_PRIVATE_KEY`; set `PROXY_WALLET_ADDRESS` and `SIGNATURE_TYPE` for proxy/GnosisSafe accounts.

## 📦 Available Scripts

| Command | Description |
|---------|-------------|
| `npm run dev` | Run with ts-node |

## 🐳 Docker (optional)

If you add a `Dockerfile` later:

```bash
docker build -t polymarket-arbitrage-bot .
docker run --env-file .env -d --name polymarket-arbitrage-bot polymarket-arbitrage-bot
docker logs -f polymarket-arbitrage-bot
```

## 🛠️ Troubleshooting

### Bot doesn’t find markets
- Confirm `ARBITRAGE_MARKETS` is valid `symbol:timeframe` pairs (example: `btc:5m,btc:15m`).
- Check network access to Gamma and CLOB APIs; try default `GAMMA_API_URL` and `CLOB_API_URL` first.

### Orders fail in production
- Ensure `WALLET_PRIVATE_KEY` is set and correct (hex, with or without `0x`).
- If using a proxy, set `PROXY_WALLET_ADDRESS` and `SIGNATURE_TYPE` (usually `2` for GnosisSafe).
- Verify USDC balance and that the market is still active and accepting orders.

### Redemption fails
- Ensure you have enough POL for gas on Polygon.
- Confirm the market is closed and resolved; the bot only redeems after resolution.

### Strategy not entering trades
- Lower `ARBITRAGE_TREND_THRESHOLD` if trend stays neutral too often.
- Increase `ARBITRAGE_OBI_DEPTH_LEVELS` to smooth noisy orderbook signals.

## 🔐 Security Best Practices

- **Never commit `.env`** – Keep it in `.gitignore` (already listed).
- **Use env vars for secrets** – Don’t hardcode `WALLET_PRIVATE_KEY` or API credentials.
- **Test in simulation first** – Run with `SIMULATION_MODE=true` before enabling live mode.
- **Limit wallet use** – Prefer a dedicated wallet with limited funds for the bot.
- **Rotate keys** – Replace credentials if they may have been exposed.

## 📚 Project structure

- `src/main.ts` – Entry point, config load, market discovery, and monitor/trader wiring.
- `src/config.ts` – Loads and validates `.env` into typed config.
- `src/api.ts` – Polymarket Gamma + CLOB API client (markets, orderbook, orders, redemption).
- `src/monitor.ts` – Fetches orderbook snapshots and drives the strategy callback.
- `src/dumpHedgeTrader.ts` – Dump detection, leg 1/2, stop-loss hedge, closure and P&L.
- `src/models.ts` – Shared types (Market, OrderBook, TokenPrice, etc.).
- `src/logger.ts` – History log and stderr output.
- `history.toml` – Append-only log (created at runtime; in `.gitignore`).

## 🤝 Contributing

Contributions are welcome. Please open an issue or pull request.

1. Fork the repository  
2. Create a feature branch (`git checkout -b feature/AmazingFeature`)  
3. Commit your changes (`git commit -m 'Add some AmazingFeature'`)  
4. Push to the branch (`git push origin feature/AmazingFeature`)  
5. Open a Pull Request  

## 📄 License

This project is licensed under the Apache License 2.0 – see the [LICENSE](LICENSE) file for details.

## ⚠️ Disclaimer

**IMPORTANT LEGAL DISCLAIMER:**

This software is provided “as-is” for educational and research purposes only. Trading on prediction markets involves substantial risk of loss.

- **No warranty** – The software is provided without any warranties.  
- **Use at your own risk** – You are solely responsible for any losses incurred.  
- **Not financial advice** – This is not investment or trading advice.  
- **Compliance** – Ensure compliance with local laws and regulations.  
- **Testing** – Always test with simulation and small amounts before production.

The authors and contributors are not responsible for any financial losses, damages, or legal issues arising from the use of this software.

## 📞 Support & Contact

- **Telegram**: [@soladity](https://t.me/soladity)
- **Issues**: [GitHub Issues](https://github.com/zkOSAI/polymarket-arbitrage-bot/issues)  
- **Discussions**: [GitHub Discussions](https://github.com/zkOSAI/polymarket-arbitrage-bot/discussions)  

## 🌟 Star history

If you find this project useful, please consider giving it a star ⭐

## 📈 Roadmap

- [ ] Optional WebSocket orderbook updates for lower latency  
- [ ] Backtesting / replay mode for strategy tuning  
- [ ] Optional Telegram/Discord notifications  
- [ ] More timeframe support (e.g. 1h)  
- [ ] PnL export and simple reporting  

---

**Keywords**: Polymarket bot, Polymarket arbitrage bot, dump and hedge, 15m Up Down, prediction markets bot, Polygon, trading automation, Polymarket CLOB


================================================
FILE: Update.md
================================================
# Version 1.0

- Multi Target Address
- Revert Trade
- Size Multiplier
- Poll Interval_sec
- Take Profit
- Stop Loss
- Trailing Stop
- Buy Amount Limit In Usd
- Entry Trade Sec
- Trade Sec From Resolve (Exit Time)

# Version 1.1

- Multi Target Address
- Fix type error
- Basic UI Implementation
- - User Activity
- - Holding Asset Track

# Version 1.1.1

- Fix Decimal Issue in Traded Share
- Add Dump Dashboard / Setting 

# Version 1.1.2

- Update FrontEnd UI structure
- Init Struct for Dashboard / Settings

================================================
FILE: package.json
================================================
{
  "name": "polymarket-arbitrage-bot",
  "version": "1.0.0",
  "main": "index.js",
  "scripts": {
    "log": "tsx src/index.ts > log.txt 2>&1",
    "build": "tsc",
    "dev": "tsx src/index.ts",
    "start": "node dist/index.js",
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "devDependencies": {
    "@types/dotenv": "^8.2.3",
    "@types/node": "^20.11.0",
    "concurrently": "^9.1.2",
    "ts-node": "^10.9.2",
    "tsx": "^4.7.0",
    "typescript": "^5.3.3"
  },
  "dependencies": {
    "@polymarket/builder-relayer-client": "^0.0.8",
    "@polymarket/clob-client": "^5.2.1",
    "@polymarket/order-utils": "^3.0.1",
    "@safe-global/protocol-kit": "^6.1.2",
    "@safe-global/types-kit": "^3.0.0",
    "@types/ws": "^8.18.1",
    "dotenv": "^16.4.5",
    "ethers": "^5.7.2",
    "puppeteer-core": "^24.37.3",
    "ts-big-lib": "latest",
    "ws": "^8.19.0",
    "zod": "^4.3.5"
  },
  "author": "",
  "license": "ISC",
  "description": ""
}


================================================
FILE: src/config/client.ts
================================================
import { Contract, providers, utils, Wallet } from "ethers";
import { ClobClient, Chain } from "@polymarket/clob-client";
import { SignatureType } from "@polymarket/order-utils";
import type { AppConfig } from "../types";

interface GammaPublicProfile {
  proxyWallet?: string;
}

const ERC20_ABI = ["function balanceOf(address owner) view returns (uint256)"];
const USDC_BY_CHAIN_ID: Record<number, string> = {
  137: "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174",
};

function getClientSetup(config: AppConfig): {
  clobHost: string;
  chain: Chain;
  wallet: Wallet;
  sigType: SignatureType;
  funder?: string;
} {
  const { clobHost, chainId, walletPrivateKey, proxyWalletAddress, signatureType } = config;
  const chain = chainId === 137 ? Chain.POLYGON : Chain.AMOY;
  const pk = walletPrivateKey.startsWith("0x") ? walletPrivateKey : "0x" + walletPrivateKey;
  const wallet = new Wallet(pk);
  const sigType = signatureType as SignatureType;
  const funder = proxyWalletAddress || undefined;
  return { clobHost, chain, wallet, sigType, funder };
}

export async function validateWalletBinding(config: AppConfig): Promise<void> {
  const { wallet, sigType, funder } = getClientSetup(config);
  if (sigType === 0) {
    return;
  }
  if (!funder) {
    throw new Error("PROXY_WALLET_ADDRESS is required when SIGNATURE_TYPE is 1 or 2.");
  }
  const eoaAddress = wallet.address.toLowerCase();
  const expectedProxy = funder.toLowerCase();
  const url = `${config.gammaApiUrl}/public-profile?address=${eoaAddress}`;
  const res = await fetch(url);
  if (!res.ok) {
    throw new Error(`Failed to validate wallet binding via Gamma public-profile (${res.status})`);
  }
  const profile = (await res.json()) as GammaPublicProfile;
  const actualProxy = (profile.proxyWallet ?? "").toLowerCase();
  if (!actualProxy) {
    throw new Error("Gamma public-profile has no proxyWallet for this PRIVATE_KEY address.");
  }
  if (actualProxy !== expectedProxy) {
    throw new Error(
      `Invalid wallet binding. PRIVATE_KEY resolves to ${eoaAddress} but Gamma proxyWallet is ${actualProxy}, expected ${expectedProxy}.`
    );
  }
}

export async function validateUsdcBalance(config: AppConfig): Promise<void> {
  const { wallet, sigType, funder } = getClientSetup(config);
  const walletToCheck = (sigType === 1 || sigType === 2 ? funder : wallet.address)?.toLowerCase();
  if (!walletToCheck) {
    throw new Error("Cannot validate USDC balance: missing wallet address to check.");
  }

  const usdcAddress = USDC_BY_CHAIN_ID[config.chainId];
  if (!usdcAddress) {
    throw new Error(`USDC validation is not configured for CHAIN_ID=${config.chainId}.`);
  }

  const rpcUrl =
    config.chainId === 137
      ? process.env.POLYGON_RPC_URL ?? "https://polygon-rpc.com"
      : process.env.AMOY_RPC_URL ?? "https://rpc-amoy.polygon.technology";
  const provider = new providers.JsonRpcProvider(rpcUrl);
  const usdc = new Contract(usdcAddress, ERC20_ABI, provider);
  const rawBalance = await usdc.balanceOf(walletToCheck);
  const balance = Number(utils.formatUnits(rawBalance, 6));
  if (Number.isNaN(balance)) {
    throw new Error("Cannot validate USDC balance: failed to parse balance.");
  }
  if (balance < config.arbitrage.orderUsd) {
    throw new Error(
      `Insufficient USDC balance on ${walletToCheck}. Balance=${balance.toFixed(2)} USDC, required >= ${config.arbitrage.orderUsd.toFixed(2)} USDC.`
    );
  }
}

export async function createClient(config: AppConfig): Promise<ClobClient> {
  const { clobHost, chain, wallet, sigType, funder } = getClientSetup(config);
  const tempClient = new ClobClient(clobHost, chain, wallet, undefined, sigType, funder);
  const creds = await tempClient.createOrDeriveApiKey();
  return new ClobClient(clobHost, chain, wallet, creds, sigType, funder);
}


================================================
FILE: src/config/index.ts
================================================
export * from "./loadEnv";
export * from "./client";

================================================
FILE: src/config/loadEnv.ts
================================================
import "dotenv/config";
import { Wallet } from "ethers";
import type { AppConfig } from "../types";
import { DEFAULT_CHAIN_ID, DEFAULT_HOST } from "../constant";

function parseMarkets(raw: string): Array<{ symbol: string; timeframe: string }> {
  return raw
    .split(",")
    .map((item) => item.trim())
    .filter(Boolean)
    .map((item) => {
      const [symbolRaw, timeframeRaw] = item.split(":");
      return {
        symbol: (symbolRaw ?? "").trim().toLowerCase(),
        timeframe: (timeframeRaw ?? "").trim().toLowerCase(),
      };
    })
    .filter((m) => m.symbol && m.timeframe);
}

export function loadConfig(): AppConfig {
  const walletPrivateKey = (process.env.WALLET_PRIVATE_KEY ?? process.env.PRIVATE_KEY ?? "").trim();
  const proxyWalletAddress = (process.env.PROXY_WALLET_ADDRESS ?? process.env.FUNDER_ADDRESS ?? "").trim();
  const signatureType = parseInt(process.env.SIGNATURE_TYPE ?? "1", 10);
  let walletAddress = "";
  if (walletPrivateKey) {
    const pk = walletPrivateKey.startsWith("0x") ? walletPrivateKey : "0x" + walletPrivateKey;
    try {
      walletAddress = new Wallet(pk).address;
    } catch {
      /* ignore */
    }
  }

  return {
    clobHost: (process.env.CLOB_API_URL ?? process.env.CLOB_HOST ?? DEFAULT_HOST).trim(),
    gammaApiUrl: (process.env.GAMMA_API_URL ?? "https://gamma-api.polymarket.com").trim(),
    chainId: parseInt(process.env.CHAIN_ID ?? String(DEFAULT_CHAIN_ID), 10),
    simulationMode: (process.env.SIMULATION_MODE ?? process.env.PRODUCTION ?? "false").toLowerCase() !== "true",
    walletPrivateKey,
    proxyWalletAddress,
    walletAddress: proxyWalletAddress || walletAddress,
    signatureType,
    arbitrage: {
      markets: parseMarkets(process.env.ARBITRAGE_MARKETS ?? "btc:5m,btc:15m"),
      checkIntervalMs: parseInt(process.env.ARBITRAGE_CHECK_INTERVAL_MS ?? "5000", 10),
      orderUsd: parseFloat(process.env.ARBITRAGE_ORDER_USD ?? "20"),
      obiDepthLevels: parseInt(process.env.ARBITRAGE_OBI_DEPTH_LEVELS ?? "5", 10),
      trendThreshold: parseFloat(process.env.ARBITRAGE_TREND_THRESHOLD ?? "0.05"),
    },
  };
}


================================================
FILE: src/constant/index.ts
================================================
export const DEFAULT_HOST = "https://clob.polymarket.com";

export const DEFAULT_CHAIN_ID = 137;

================================================
FILE: src/index.ts
================================================
import { loadConfig } from "./config";
import { createClient, validateUsdcBalance, validateWalletBinding } from "./config/client";
import { runObiArbitrage } from "./strategy/arbitrage";

async function run() {
  const config = loadConfig();
  if (!config.arbitrage.markets.length) {
    console.error("No markets. Set ARBITRAGE_MARKETS in .env (example: btc:5m,btc:15m)");
    process.exit(1);
  }
  if (!config.walletPrivateKey) {
    console.error("No wallet. Set WALLET_PRIVATE_KEY in .env");
    process.exit(1);
  }
  if (!config.proxyWalletAddress && config.signatureType !== 0) {
    console.error("Set PROXY_WALLET_ADDRESS in .env for proxy/Magic wallet");
    process.exit(1);
  }
  try {
    await validateWalletBinding(config);
      await validateUsdcBalance(config);
    console.log(`Wallet validation passed for SIGNATURE_TYPE=${config.signatureType}`);
  } catch (error) {
    console.error((error as Error)?.message ?? error);
    process.exit(1);
  }

  const client = config.simulationMode ? null : await createClient(config);
  await runObiArbitrage(client, config);
}

run().catch((e) => {
  console.error(e);
  process.exit(1);
});


================================================
FILE: src/strategy/arbitrage.ts
================================================
import { ClobClient, OrderType, Side } from "@polymarket/clob-client";
import type { AppConfig } from "../types";
import { Big } from "ts-big-lib";
type Trend = "UPTREND" | "DOWNTREND" | "NEUTRAL";
type PositionSide = "yes" | "no";

interface GammaMarket {
  slug?: string;
  active?: boolean;
  closed?: boolean;
  archived?: boolean;
  endDate?: string;
  outcomes?: string | string[];
  clobTokenIds?: string | string[];
}

interface MarketBinding {
  symbol: string;
  timeframe: string;
  slug: string;
  yesTokenId: string;
  noTokenId: string;
}

interface OrderLevel {
  price: string;
  size: string;
}

interface OrderBookResponse {
  bids?: OrderLevel[];
  asks?: OrderLevel[];
}

interface BotPosition {
  side: PositionSide;
  tokenId: string;
  size: number;
}

const localPositions = new Map<string, BotPosition>();

function keyOf(market: { symbol: string; timeframe: string }): string {
  return `${market.symbol}:${market.timeframe}`;
}

function parseArrayField(value: string | string[] | undefined): string[] {
  if (Array.isArray(value)) return value.map((v) => String(v));
  if (!value) return [];
  try {
    const parsed = JSON.parse(value);
    if (Array.isArray(parsed)) return parsed.map((v) => String(v));
  } catch {
    return [];
  }
  return [];
}

function parseOutcomeTokens(m: GammaMarket): { yesTokenId: string; noTokenId: string } | null {
  const outcomes = parseArrayField(m.outcomes).map((x) => x.toLowerCase());
  const tokenIds = parseArrayField(m.clobTokenIds);
  if (outcomes.length < 2 || tokenIds.length < 2) return null;
  const yesIdx = outcomes.indexOf("yes");
  const noIdx = outcomes.indexOf("no");
  if (yesIdx < 0 || noIdx < 0) return null;
  return { yesTokenId: tokenIds[yesIdx], noTokenId: tokenIds[noIdx] };
}

function computeBidDepth(book: OrderBookResponse, levels: number): number {
  const bids = book.bids ?? [];
  return bids.slice(0, levels).reduce((sum, level) => sum + Number(level.size ?? 0), 0);
}

function bestAsk(book: OrderBookResponse): number {
  return Number(book.asks?.[0]?.price ?? 0);
}

function bestBid(book: OrderBookResponse): number {
  return Number(book.bids?.[0]?.price ?? 0);
}

function computeTrend(yesBidDepth: number, noBidDepth: number, threshold: number): { trend: Trend; obi: number } {
  const denom = yesBidDepth + noBidDepth;
  if (denom <= 0) return { trend: "NEUTRAL", obi: 0 };
  const obi = (yesBidDepth - noBidDepth) / denom;
  if (obi > threshold) return { trend: "UPTREND", obi };
  if (obi < -threshold) return { trend: "DOWNTREND", obi };
  return { trend: "NEUTRAL", obi };
}

async function fetchJson<T>(url: string): Promise<T> {
  const res = await fetch(url);
  if (!res.ok) throw new Error(`${url} -> ${res.status}`);
  return (await res.json()) as T;
}

async function discoverMarket(
  config: AppConfig,
  symbol: string,
  timeframe: string
): Promise<MarketBinding | null> {
  const list = await fetchJson<GammaMarket[]>(`${config.gammaApiUrl}/markets?active=true&closed=false&limit=500`);
  const now = Date.now();
  const target = `${symbol}-updown-${timeframe}`;
  const candidate = list
    .filter((m) => {
      const slug = (m.slug ?? "").toLowerCase();
      if (!slug.includes(target)) return false;
      if (m.archived || m.closed || m.active === false) return false;
      const endMs = m.endDate ? new Date(m.endDate).getTime() : Number.MAX_SAFE_INTEGER;
      return endMs > now;
    })
    .sort((a, b) => {
      const ae = a.endDate ? new Date(a.endDate).getTime() : Number.MAX_SAFE_INTEGER;
      const be = b.endDate ? new Date(b.endDate).getTime() : Number.MAX_SAFE_INTEGER;
      return ae - be;
    })[0];

  if (!candidate?.slug) return null;
  const tokenIds = parseOutcomeTokens(candidate);
  if (!tokenIds) return null;
  return {
    symbol,
    timeframe,
    slug: candidate.slug,
    yesTokenId: tokenIds.yesTokenId,
    noTokenId: tokenIds.noTokenId,
  };
}

async function buyToken(
  client: ClobClient | null,
  tokenId: string,
  amountUsd: number,
  simulation: boolean
): Promise<void> {
  if (simulation) return;
  if (!client) throw new Error("Client missing");
  const tickSize = await client.getTickSize(tokenId);
  const negRisk = await client.getNegRisk(tokenId);
  await client.createAndPostMarketOrder(
    { tokenID: tokenId, amount: new Big(amountUsd).toString(), side: Side.BUY, orderType: OrderType.FOK },
    { tickSize, negRisk },
    OrderType.FOK
  );
}

async function sellToken(
  client: ClobClient | null,
  tokenId: string,
  sizeShares: number,
  simulation: boolean
): Promise<void> {
  if (simulation) return;
  if (!client) throw new Error("Client missing");
  const tickSize = await client.getTickSize(tokenId);
  const negRisk = await client.getNegRisk(tokenId);
  await client.createAndPostMarketOrder(
    { tokenID: tokenId, amount: sizeShares, side: Side.SELL, orderType: OrderType.FOK },
    { tickSize, negRisk },
    OrderType.FOK
  );
}

async function buyAndStorePosition(args: {
  client: ClobClient | null;
  stateKey: string;
  side: PositionSide;
  tokenId: string;
  askPrice: number;
  orderUsd: number;
  simulation: boolean;
}): Promise<void> {
  await buyToken(args.client, args.tokenId, args.orderUsd, args.simulation);
  localPositions.set(args.stateKey, {
    side: args.side,
    tokenId: args.tokenId,
    size: args.orderUsd / args.askPrice,
  });
}

export async function runObiArbitrage(client: ClobClient | null, config: AppConfig): Promise<void> {
  const markets = config.arbitrage.markets;
  if (!markets.length) {
    throw new Error("No arbitrage markets configured. Set ARBITRAGE_MARKETS in .env (example: btc:5m,btc:15m).");
  }
  console.log(`Arbitrage strategy started | ${config.simulationMode ? "SIM" : "LIVE"} | ${markets.map((m) => `${m.symbol}:${m.timeframe}`).join(", ")}`);

  async function tick() {
    for (const target of markets) {
      try {
        const binding = await discoverMarket(config, target.symbol, target.timeframe);
        if (!binding) {
          console.log(`[${keyOf(target)}] market not found`);
          continue;
        }
        const yesBook = await fetchJson<OrderBookResponse>(`${config.clobHost}/book?token_id=${binding.yesTokenId}`);
        const noBook = await fetchJson<OrderBookResponse>(`${config.clobHost}/book?token_id=${binding.noTokenId}`);

        const yesBidDepth = computeBidDepth(yesBook, config.arbitrage.obiDepthLevels);
        const noBidDepth = computeBidDepth(noBook, config.arbitrage.obiDepthLevels);
        const { trend, obi } = computeTrend(yesBidDepth, noBidDepth, config.arbitrage.trendThreshold);
        const yesAsk = bestAsk(yesBook);
        const noAsk = bestAsk(noBook);
        const yesBid = bestBid(yesBook);
        const noBid = bestBid(noBook);
        const stateKey = keyOf(target);
        const pos = localPositions.get(stateKey);

        const header = `[${stateKey}] ${binding.slug} | trend=${trend} obi=${obi.toFixed(4)} | yes(${yesBidDepth.toFixed(2)}) no(${noBidDepth.toFixed(2)})`;
        if (!pos) {
          if (trend === "UPTREND" && yesAsk > 0) {
            await buyAndStorePosition({
              client,
              stateKey,
              side: "yes",
              tokenId: binding.yesTokenId,
              askPrice: yesAsk,
              orderUsd: config.arbitrage.orderUsd,
              simulation: config.simulationMode,
            });
            console.log(`${header} | ACTION=BUY_YES`);
          } else if (trend === "DOWNTREND" && noAsk > 0) {
            await buyAndStorePosition({
              client,
              stateKey,
              side: "no",
              tokenId: binding.noTokenId,
              askPrice: noAsk,
              orderUsd: config.arbitrage.orderUsd,
              simulation: config.simulationMode,
            });
            console.log(`${header} | ACTION=BUY_NO`);
          } else {
            console.log(`${header} | ACTION=WAIT`);
          }
          continue;
        }

        if (trend === "NEUTRAL") {
          await sellToken(client, pos.tokenId, pos.size, config.simulationMode);
          localPositions.delete(stateKey);
          console.log(`${header} | ACTION=SELL_${pos.side.toUpperCase()}_ON_NEUTRAL`);
          continue;
        }

        if (trend === "UPTREND") {
          if (pos.side === "yes") {
            console.log(`${header} | ACTION=WAIT_HOLD_YES`);
          } else {
            await sellToken(client, pos.tokenId, pos.size, config.simulationMode);
            if (yesAsk > 0) {
              await buyAndStorePosition({
                client,
                stateKey,
                side: "yes",
                tokenId: binding.yesTokenId,
                askPrice: yesAsk,
                orderUsd: config.arbitrage.orderUsd,
                simulation: config.simulationMode,
              });
              console.log(`${header} | ACTION=SELL_NO_AND_BUY_YES_ON_UPTREND`);
            } else {
              localPositions.delete(stateKey);
              console.log(`${header} | ACTION=SELL_NO_ON_UPTREND`);
            }
          }
          continue;
        }

        if (trend === "DOWNTREND") {
          if (pos.side === "no") {
            console.log(`${header} | ACTION=WAIT_HOLD_NO`);
          } else {
            await sellToken(client, pos.tokenId, pos.size, config.simulationMode);
            if (noAsk > 0) {
              await buyAndStorePosition({
                client,
                stateKey,
                side: "no",
                tokenId: binding.noTokenId,
                askPrice: noAsk,
                orderUsd: config.arbitrage.orderUsd,
                simulation: config.simulationMode,
              });
              console.log(`${header} | ACTION=SELL_YES_AND_BUY_NO_ON_DOWNTREND`);
            } else {
              localPositions.delete(stateKey);
              console.log(`${header} | ACTION=SELL_YES_ON_DOWNTREND`);
            }
          }
          continue;
        }

        console.log(`${header} | ACTION=WAIT`);
        void yesBid;
        void noBid;
      } catch (e) {
        console.error(`[${keyOf(target)}]`, (e as Error)?.message ?? e);
      }
    }
  }

  await tick();
  setInterval(() => {
    tick().catch((e) => console.error("tick", (e as Error)?.message ?? e));
  }, Math.max(1000, config.arbitrage.checkIntervalMs));
}


================================================
FILE: src/types/index.ts
================================================
export interface AppConfig {
  clobHost: string;
  gammaApiUrl: string;
  chainId: number;
  simulationMode: boolean;
  walletPrivateKey: string;
  proxyWalletAddress: string;
  walletAddress: string;
  signatureType: number;
  arbitrage: {
    markets: Array<{ symbol: string; timeframe: string }>;
    checkIntervalMs: number;
    orderUsd: number;
    obiDepthLevels: number;
    trendThreshold: number;
  };
}


================================================
FILE: tsconfig.json
================================================
{
  "compilerOptions": {
    "outDir": "./dist",
    "rootDir": "./src",
    // Module settings
    "module": "CommonJS", // <-- Use CommonJS to support require()
    "moduleResolution": "node", // Node-style module resolution
    "target": "esnext",
    "lib": [
      "esnext",
      "dom"
    ],
    // Output
    "sourceMap": true,
    "declaration": true,
    "declarationMap": true,
    // Type checking
    "strict": false, // <-- disable strict mode
    "noUncheckedIndexedAccess": false,
    "exactOptionalPropertyTypes": false,
    // Other recommended options
    "jsx": "react-jsx",
    "isolatedModules": true,
    "skipLibCheck": true
  },
  "include": ["src"],
  "exclude": ["dist", "node_modules", "test", "ui"]
}
Download .txt
gitextract_681ttebc/

├── .gitignore
├── README.md
├── Update.md
├── package.json
├── src/
│   ├── config/
│   │   ├── client.ts
│   │   ├── index.ts
│   │   └── loadEnv.ts
│   ├── constant/
│   │   └── index.ts
│   ├── index.ts
│   ├── strategy/
│   │   └── arbitrage.ts
│   └── types/
│       └── index.ts
└── tsconfig.json
Download .txt
SYMBOL INDEX (33 symbols across 6 files)

FILE: src/config/client.ts
  type GammaPublicProfile (line 6) | interface GammaPublicProfile {
  constant ERC20_ABI (line 10) | const ERC20_ABI = ["function balanceOf(address owner) view returns (uint...
  constant USDC_BY_CHAIN_ID (line 11) | const USDC_BY_CHAIN_ID: Record<number, string> = {
  function getClientSetup (line 15) | function getClientSetup(config: AppConfig): {
  function validateWalletBinding (line 31) | async function validateWalletBinding(config: AppConfig): Promise<void> {
  function validateUsdcBalance (line 58) | async function validateUsdcBalance(config: AppConfig): Promise<void> {
  function createClient (line 88) | async function createClient(config: AppConfig): Promise<ClobClient> {

FILE: src/config/loadEnv.ts
  function parseMarkets (line 6) | function parseMarkets(raw: string): Array<{ symbol: string; timeframe: s...
  function loadConfig (line 21) | function loadConfig(): AppConfig {

FILE: src/constant/index.ts
  constant DEFAULT_HOST (line 1) | const DEFAULT_HOST = "https://clob.polymarket.com";
  constant DEFAULT_CHAIN_ID (line 3) | const DEFAULT_CHAIN_ID = 137;

FILE: src/index.ts
  function run (line 5) | async function run() {

FILE: src/strategy/arbitrage.ts
  type Trend (line 4) | type Trend = "UPTREND" | "DOWNTREND" | "NEUTRAL";
  type PositionSide (line 5) | type PositionSide = "yes" | "no";
  type GammaMarket (line 7) | interface GammaMarket {
  type MarketBinding (line 17) | interface MarketBinding {
  type OrderLevel (line 25) | interface OrderLevel {
  type OrderBookResponse (line 30) | interface OrderBookResponse {
  type BotPosition (line 35) | interface BotPosition {
  function keyOf (line 43) | function keyOf(market: { symbol: string; timeframe: string }): string {
  function parseArrayField (line 47) | function parseArrayField(value: string | string[] | undefined): string[] {
  function parseOutcomeTokens (line 59) | function parseOutcomeTokens(m: GammaMarket): { yesTokenId: string; noTok...
  function computeBidDepth (line 69) | function computeBidDepth(book: OrderBookResponse, levels: number): number {
  function bestAsk (line 74) | function bestAsk(book: OrderBookResponse): number {
  function bestBid (line 78) | function bestBid(book: OrderBookResponse): number {
  function computeTrend (line 82) | function computeTrend(yesBidDepth: number, noBidDepth: number, threshold...
  function fetchJson (line 91) | async function fetchJson<T>(url: string): Promise<T> {
  function discoverMarket (line 97) | async function discoverMarket(
  function buyToken (line 131) | async function buyToken(
  function sellToken (line 148) | async function sellToken(
  function buyAndStorePosition (line 165) | async function buyAndStorePosition(args: {
  function runObiArbitrage (line 182) | async function runObiArbitrage(client: ClobClient | null, config: AppCon...

FILE: src/types/index.ts
  type AppConfig (line 1) | interface AppConfig {
Condensed preview — 12 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (34K chars).
[
  {
    "path": ".gitignore",
    "chars": 102,
    "preview": "node_modules/*\n.env\n*-lock.json\nlog.txt\nui/dist\ndist\n/node_modules\nfrontend/node_modules\nfrontend/dist"
  },
  {
    "path": "README.md",
    "chars": 12117,
    "preview": "# Polymarket Arbitrage Bot\n\nPolymarket **arbitrage bot** for 15-minute Up/Down markets. Automates the **dump-and-hedge**"
  },
  {
    "path": "Update.md",
    "chars": 511,
    "preview": "# Version 1.0\n\n- Multi Target Address\n- Revert Trade\n- Size Multiplier\n- Poll Interval_sec\n- Take Profit\n- Stop Loss\n- T"
  },
  {
    "path": "package.json",
    "chars": 968,
    "preview": "{\n  \"name\": \"polymarket-arbitrage-bot\",\n  \"version\": \"1.0.0\",\n  \"main\": \"index.js\",\n  \"scripts\": {\n    \"log\": \"tsx src/i"
  },
  {
    "path": "src/config/client.ts",
    "chars": 3802,
    "preview": "import { Contract, providers, utils, Wallet } from \"ethers\";\nimport { ClobClient, Chain } from \"@polymarket/clob-client\""
  },
  {
    "path": "src/config/index.ts",
    "chars": 52,
    "preview": "export * from \"./loadEnv\";\nexport * from \"./client\";"
  },
  {
    "path": "src/config/loadEnv.ts",
    "chars": 2111,
    "preview": "import \"dotenv/config\";\nimport { Wallet } from \"ethers\";\nimport type { AppConfig } from \"../types\";\nimport { DEFAULT_CHA"
  },
  {
    "path": "src/constant/index.ts",
    "chars": 96,
    "preview": "export const DEFAULT_HOST = \"https://clob.polymarket.com\";\n\nexport const DEFAULT_CHAIN_ID = 137;"
  },
  {
    "path": "src/index.ts",
    "chars": 1154,
    "preview": "import { loadConfig } from \"./config\";\nimport { createClient, validateUsdcBalance, validateWalletBinding } from \"./confi"
  },
  {
    "path": "src/strategy/arbitrage.ts",
    "chars": 10323,
    "preview": "import { ClobClient, OrderType, Side } from \"@polymarket/clob-client\";\nimport type { AppConfig } from \"../types\";\nimport"
  },
  {
    "path": "src/types/index.ts",
    "chars": 414,
    "preview": "export interface AppConfig {\n  clobHost: string;\n  gammaApiUrl: string;\n  chainId: number;\n  simulationMode: boolean;\n  "
  },
  {
    "path": "tsconfig.json",
    "chars": 729,
    "preview": "{\n  \"compilerOptions\": {\n    \"outDir\": \"./dist\",\n    \"rootDir\": \"./src\",\n    // Module settings\n    \"module\": \"CommonJS\""
  }
]

About this extraction

This page contains the full source code of the 0xalberto/pumpfun-laserstream-sniper-rust GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 12 files (31.6 KB), approximately 8.9k tokens, and a symbol index with 33 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!