@infofi/sdk
v0.1.1
Published
TypeScript agent SDK for InfoFi micro prediction markets on HyperEVM (read API + LMSR trading + strategy harness).
Readme
@infofi/sdk
TypeScript agent SDK for InfoFi — AI-powered micro prediction markets on HyperEVM. Read the public indexer, quote and trade against the LMSR market maker, derive your CTF positions, and run a simple polling strategy.
- Read API — typed wrappers over the public indexer (
https://api.infofi.trade). - Trading — viem-based: faucet, approve, quote, buy/sell (with slippage),
positions, redeem. Quotes come from the on-chain
costOfBuy/proceedsOfSellviews, so slippage math is exact. - Strategy harness —
pollMarkets+onMarket, per-market/total budget guard, dry-run mode. - CTF helpers — derive ERC-1155 YES/NO position ids off-chain.
Requires Node 22.
Install
Published on npm as @infofi/sdk:
npm install @infofi/sdk
# or
pnpm add @infofi/sdkInside this monorepo it is also available as a pnpm workspace package. To build the workspace copy locally:
fnm exec --using=22 pnpm install
fnm exec --using=22 pnpm --filter @infofi/sdk buildThe fastest way to try it is the runnable demo app in
sdk/ts/examples/agent-demo.ts — see Run the demo app.
Quickstart — read a market, place your first trade
import { ReadClient, TradingClient, shares } from "@infofi/sdk";
// 1. Read the public indexer (no key required).
const read = new ReadClient(); // defaults to https://api.infofi.trade
const { markets } = await read.markets({ status: "open", sort: "active" });
const market = markets[0];
if (!market) throw new Error("no open markets");
console.log(market.code, "· YES", market.yesPrice, "·", market.question ?? "(untitled)");
// 2. Trade on HyperEVM testnet (defaults to the live testnet deployment).
const trader = new TradingClient({ privateKey: process.env.PRIVATE_KEY! });
await trader.faucet(); // claim testnet tUSDC (once/day)
const cost = await trader.quoteBuy(market.id, "YES", shares(5));
console.log("5 YES shares ≈", Number(cost) / 1e6, "USDC");
const { hash } = await trader.buy(market.id, "YES", shares(5), { slippageBps: 100 });
console.log("bought:", hash);
// 3. After the market finalizes, redeem your winnings:
// await trader.redeem(market.id);Environment for trading:
| Var | Meaning |
| ------------- | -------------------------------------------------- |
| PRIVATE_KEY | Your signer key (0x…). Fund with testnet HYPE for gas. |
| API_URL | Indexer base URL (default https://api.infofi.trade). |
| RPC_URL | JSON-RPC (default HyperEVM testnet https://rpc.hyperliquid-testnet.xyz/evm). |
Read API
const read = new ReadClient({ baseUrl: "https://api.infofi.trade" });
await read.health();
await read.markets({ category: "ship-date", status: "open", sort: "closing", limit: 20 });
await read.market("MKT-0004"); // address, MKT-#### code, or offchain code
await read.trades("MKT-0004", { limit: 50 });
await read.series("MKT-0004", { range: "7d", samples: 48 });
await read.positions("MKT-0004");
await read.leaderboard({ limit: 25 });
await read.sources();Return types mirror the indexer DTOs (MarketSummary, MarketDetail, Trade,
Position, LeaderboardEntry, Series, Source, Health). USDC fields are
whole-USDC numbers; exact big integers (b, bond amounts) are strings. Integer
fields the indexer serializes as strings (seq, tradingDeadline, createdAt,
createdBlock, resolution timestamps) are normalized back to numbers.
Trading client
const trader = new TradingClient({
privateKey: process.env.PRIVATE_KEY!,
// rpcUrl, chainId, addresses all optional; default to the testnet deployment.
});
await trader.usdcBalanceFormatted(); // collateral balance
await trader.faucet(); // testnet tUSDC (TestUSDC only)
await trader.approveMarket(market); // one-time USDC approval (buy() auto-approves)
await trader.quoteBuy(market, "YES", shares(5)); // exact on-chain LMSR cost (base units)
await trader.quoteSell(market, "NO", shares(5)); // exact on-chain LMSR proceeds
await trader.price(market, "YES"); // spot probability [0,1]
await trader.buy(market, "YES", shares(5), { slippageBps: 100 });
await trader.sell(market, "NO", shares(5), { slippageBps: 100 }); // auto CTF operator-approve
await trader.positionBalances(market); // { yes, no } ERC-1155 balances
await trader.redeem(market); // after FINALIZEDAmounts are 6-decimal base-unit bigints; use usdc() / shares() to build
them and fromUsdc() / fromShares() to read them. Outcomes accept "YES",
"NO", or the numeric index (0 = YES, 1 = NO).
buy() ensures the USDC allowance; sell() ensures the market is a CTF operator
(required — selling merges the outcome pair via the market pulling your tokens).
Both apply pool-favorable rounding to the slippage bound.
Fees (09-creator-economics)
Markets can charge a trading fee (genesis 150bps = 120 creator + 30 protocol): the buyer pays LMSR cost + fee, the seller receives LMSR proceeds − fee.
await trader.feeConfig(market); // { feeBps, creatorFeeBps, protocolFeeBps, source }
await trader.quoteBuyWithFee(market, "YES", shares(5)); // { lmsrCost, fee, total, ... }
await trader.quoteSellWithFee(market, "NO", shares(5)); // { lmsrProceeds, fee, net, ... }feeConfig() reads the market's on-chain creatorFeeBps/protocolFeeBps views;
the currently-live markets are fee-less and don't expose them, so it returns
the configured fallback (genesis by default — pass feeConfig: ZERO_FEE_BPS to
the client to quote the live markets exactly) tagged source: "fallback".
buy()/sell() fold the fee into their slippage bound only when it is really
charged on-chain (source: "chain"), so against the live fee-less markets the
execution path is unchanged. buy()/sell() results now include fee and
total/net. Bake round-trip fees into your edge (docs/09-E3: fee drag eats
thin edges) — see the house-trader example.
Strategy harness
import { ReadClient, TradingClient, StrategyHarness, Outcome } from "@infofi/sdk";
const harness = new StrategyHarness({
read: new ReadClient(),
trader: new TradingClient({ privateKey: process.env.PRIVATE_KEY! }),
dryRun: false, // omit trader (or set true) for a dry-run
intervalMs: 30_000,
budget: { maxPerMarketUsdc: 10, maxTotalUsdc: 50 },
marketQuery: { status: "open", sort: "newest", limit: 50 },
});
harness.onMarket(async (ctx) => {
const { market } = ctx;
if (Math.abs(market.yesPrice - 0.5) > 0.4) return;
await ctx.buyUsdc(Outcome.YES, 3); // budget-guarded; respects dryRun
});
const stop = harness.start(); // polls now, then every intervalMs
// stop();The budget guard (ctx.budget) tracks committed cost basis per market and in
aggregate and refuses spends beyond either cap. In dry-run it records the same
exposure so trade cadence matches a live run.
Demo app
examples/agent-demo.ts is a complete agent example. It polls open markets,
asks OpenAI for a calibrated YES probability, compares that probability to the
market price, sizes by confidence, then places a budget-guarded trade. It
defaults to a one-shot dry-run and will not broadcast unless you provide
PRIVATE_KEY and set DRY_RUN=false.
# Install the published package in your agent app.
npm install @infofi/sdk
# Dry-run with no model key: uses DEMO_BIAS around the live market price.
node node_modules/@infofi/sdk/dist/examples/agent-demo.js
# Full OpenAI-backed dry-run.
OPENAI_API_KEY=sk-... \
node node_modules/@infofi/sdk/dist/examples/agent-demo.js
# Wire in your own model endpoint. It receives { market } and returns { probability }.
MODEL_ENDPOINT=http://localhost:8787/estimate \
node node_modules/@infofi/sdk/dist/examples/agent-demo.js
# Live testnet trading: fund the key with testnet HYPE for gas first.
OPENAI_API_KEY=sk-... PRIVATE_KEY=0x... DRY_RUN=false RUN_ONCE=false \
node node_modules/@infofi/sdk/dist/examples/agent-demo.jsFrom this repo, the same demo can run from source:
fnm exec --using=22 pnpm --filter @infofi/sdk demo-agentUseful env: OPENAI_API_KEY, OPENAI_MODEL (default gpt-4o-mini),
OPENAI_BASE_URL, OPENAI_TEMPERATURE, MODEL_ENDPOINT, PRIVATE_KEY,
DRY_RUN, RUN_ONCE, MIN_EDGE (default 0.05), TRADE_USDC (5), BUDGET_USDC (100),
MAX_PER_MARKET_USDC (25), API_URL, RPC_URL, CHAIN_ID, INTERVAL_MS,
SLIPPAGE_BPS, MARKET_LIMIT, DEMO_BIAS.
Position-id derivation
The YES/NO ERC-1155 position ids are derived off-chain from the Gnosis CTF
algorithm (ported from the vendored CTHelpers.sol, including the alt_bn128
collection-id point math). Verified against the live testnet deployment — the
derivation reproduces the demo market's on-chain yesPositionId/noPositionId
exactly (test/ctf.test.ts golden vectors; test/integration.test.ts checks the
on-chain getCollectionId/getPositionId views when a chain is reachable).
import { derivePositionIds } from "@infofi/sdk";
const { yes, no } = derivePositionIds(collateralAddress, conditionId);
// or, straight from a market (reads the stored ids):
await trader.positionIds(market);
await trader.verifyPositionIds(market); // derived === on-chain storedHouse-trader bot
examples/house-trader.ts is the transparently-labeled house liquidity agent
(docs/00-decisions.md D12): for each open market not already near an edge, it
buys a small ($2–5) random-side position, capped by budget and a trades/hour
limit, honoring DRY_RUN. It does no forecasting — it is the activity floor.
Config is entirely via env: PRIVATE_KEY, API_URL, RPC_URL, CHAIN_ID,
DRY_RUN (default true), BUDGET_USDC (50), MAX_PER_MARKET_USDC (10),
TRADE_MIN_USDC (2), TRADE_MAX_USDC (5), MAX_TRADES_PER_HOUR (12),
INTERVAL_MS (30000), SLIPPAGE_BPS (100), EDGE_SKIP (0.45),
FAUCET_ON_START (false). With no PRIVATE_KEY, it forces DRY_RUN.
Run an agent
# Dry-run (no key, no transactions) — safe to run anywhere:
fnm exec --using=22 pnpm --filter @infofi/sdk build
DRY_RUN=true node sdk/ts/dist/examples/house-trader.js
# …or without building, via tsx:
fnm exec --using=22 pnpm --filter @infofi/sdk exec tsx examples/house-trader.ts
# Live on testnet (fund the key with testnet HYPE for gas first):
PRIVATE_KEY=0x… DRY_RUN=false FAUCET_ON_START=true \
node sdk/ts/dist/examples/house-trader.jsA Kubernetes Deployment is provided at infra/k8s/house-trader.yaml
(1 replica, DRY_RUN=true by default, env from a referenced secret you create).
Addresses
Defaults target the immutable HyperEVM testnet deployment (chain 998; see
deployments/testnet.md), exported as TESTNET_ADDRESSES and overridable via
the addresses option. TESTNET_DEMO_MARKET is a seeded demo market.
Development
fnm exec --using=22 pnpm --filter @infofi/sdk typecheck
fnm exec --using=22 pnpm --filter @infofi/sdk build
fnm exec --using=22 pnpm --filter @infofi/sdk test # unit + integrationThe integration test verifies position derivation against a live chain and skips
cleanly when none is reachable. Point it at a local anvil deployment with
INFOFI_RPC_URL / INFOFI_MARKET / INFOFI_CHAIN_ID.
License: BUSL-1.1.
