| name | venue-integration |
| description | Guide for integrating a new trading venue into an executor when TradeCore doesn't support it yet. Use when the build-executor skill needs a venue that isn't in tradecore-venues, when the user says 'add support for X venue', 'integrate X exchange', 'connect to X protocol', or when you discover the executor needs a venue that has no existing @recallnet/* package. This skill helps you figure out what KIND of venue it is, architect the right abstraction, build it as a promotable skunk- package, and wire it into the executor with proper branded types and human-assisted credential setup. |
Venue Integration
You need to integrate a venue that TradeCore doesn't support yet. This is one of the most consequential decisions in an executor build — get the abstraction wrong and everything downstream is wrong too.
First: confirm there's no existing support
- Load
/tradecore-packages. Search for any package that already wraps this venue.
- Load
/tradecore-venues. Check if this venue has guidance, even partial.
- Search npm.pkg.github.com for
@recallnet/* packages mentioning the venue name.
If there IS partial support (e.g., a package exists but missing features you need), file a TradeCore Goal issue via /issue-filing describing the gap. Then decide: wait for TradeCore, or build a local skunk- wrapper. Usually build local and file the issue simultaneously.
If there is NO support, continue with this skill.
Step 1: Classify the venue
Not every venue is an orderbook. The classification determines every architectural decision downstream.
| Venue kind | Examples | Key abstraction | Data shape |
|---|
| CLOB (central limit order book) | Kalshi, Polymarket, Hyperliquid, Binance | Orders, fills, book levels | Orderbook snapshots, trade events |
| Onchain lending | Morpho Blue, Aave, Compound | Supply/withdraw/borrow/repay | Market state, positions, health factors |
| Onchain DEX | Uniswap, Curve | Swaps, liquidity positions | Pool state, tick data, reserves |
| Intent-based | Morpho Midnight, CoW Protocol | Intents, settlement | Intent state, match results |
| Vault/earn | Morpho Vaults, Yearn | Deposit/withdraw, share price | Vault state, APY, allocations |
| Prediction market | Polymarket, Kalshi, Limitless | Yes/No contracts, binary settlement | Market lifecycle, settlement events |
| Perpetual futures | Hyperliquid, dYdX | Positions, funding, liquidation | Funding rates, mark prices, positions |
The classification determines:
- Whether you need an order book abstraction or a state-snapshot abstraction
- Whether "execution" means submitting an order or signing a transaction
- Whether "settlement" means market expiry or transaction confirmation
- What branded types you need
If the venue doesn't fit any category: describe it in terms of what actions the executor can take (deposit, withdraw, swap, bid, etc.) and what state it reads (prices, positions, liquidity, etc.). Build from those primitives.
Step 2: Read the venue documentation
Before writing any code:
- Read the official API docs. Identify: REST vs GraphQL vs WebSocket vs RPC/contract calls. What auth is required? What are the rate limits? Is there an SLA?
- Read the official SDK/client library if one exists in TypeScript. Prefer using official packages over hand-rolling API calls.
- Identify the data model. What are the canonical IDs? (market IDs, token addresses, chain IDs, etc.) What state transitions exist?
- Identify the execution model. How do you submit an action? How do you confirm it completed? What can go wrong between submission and confirmation?
Write this down as a venue analysis document before proceeding. Example format:
## Venue Analysis: [Name]
**Kind:** [CLOB / onchain lending / DEX / intent / vault / prediction / perps]
**API surface:** [REST / GraphQL / WebSocket / RPC / SDK]
**Auth:** [API key / wallet signing / both / none]
**Official TS package:** [package name or "none"]
### Canonical IDs
- Market: [type and format]
- Position: [type and format]
- Transaction/Order: [type and format]
### Core actions
- [action 1]: [what it does, how confirmed]
- [action 2]: [what it does, how confirmed]
### State model
- [state 1]: [what it represents, how queried]
- [state 2]: [what it represents, how queried]
### Risks
- [risk 1]: [what can go wrong, how to handle]
Step 3: Design branded types
Every value on the trading path gets a branded type. Not string, not number — a named type that prevents mixing up a market ID with a position ID, or a USD amount with a contract count.
Load /package-authoring — this will become a @recallnet/skunk-{venue}-types package (or part of a larger venue package).
Example for a CLOB venue:
type MarketId = string & { readonly __brand: "MarketId" };
type OrderId = string & { readonly __brand: "OrderId" };
type ContractCount = number & { readonly __brand: "ContractCount" };
type UsdAmount = number & { readonly __brand: "UsdAmount" };
type Price = number & { readonly __brand: "Price" };
function createMarketId(raw: string): MarketId { return raw as MarketId; }
Example for an onchain lending venue:
type MorphoMarketId = string & { readonly __brand: "MorphoMarketId" };
type EvmAddress = string & { readonly __brand: "EvmAddress" };
type TokenAmount = bigint & { readonly __brand: "TokenAmount" };
type ShareAmount = bigint & { readonly __brand: "ShareAmount" };
type HealthFactor = number & { readonly __brand: "HealthFactor" };
type Apy = number & { readonly __brand: "Apy" };
type Lltv = number & { readonly __brand: "Lltv" };
Why this matters: Without branded types, an agent will inevitably pass a MarketId where a PositionId is expected, or mix USD amounts with token amounts. Branded types make these bugs compile-time errors instead of runtime losses.
Step 4: Design the client surface
Split the client by concern. Never build one god-client. The pattern:
| Client | Purpose | Data source |
|---|
| DataClient | Discovery, market/vault state, positions, historical data | API / GraphQL / subgraph |
| ExecutionClient | Submit actions, confirm outcomes | API / RPC / contract calls |
| RiskClient | Pre-trade validation, health checks, exposure limits | Computed from state |
For onchain venues, add:
| Client | Purpose | Data source |
|---|
| OnchainClient | Direct contract reads for confirmation/reconciliation | RPC / Viem |
The key rule: DataClient is for convenience and discovery. ExecutionClient is for submitting actions. OnchainClient (if applicable) is for confirming truth. Never use the convenience API as the source of truth for execution-critical paths — it may have no SLA.
Official packages first
If the venue publishes an official TypeScript SDK:
- Install it as a dependency of your venue package
- Wrap it with branded types — the SDK's raw types become internal, your branded types are the public surface
- Add retry logic, timeout handling, and error classification on top
If no official SDK exists:
- Use the venue's REST/GraphQL/WS API directly
- Define Zod schemas for every API response at the boundary
- Parse through schemas on every response — no
as casts on external data
Step 5: Design the event/state model
Do not force a venue into an existing data shape. This is the most common and most expensive mistake.
- A CLOB venue produces: orderbook snapshots, trade fills, order lifecycle events
- An onchain lending venue produces: market state snapshots, position state snapshots, transaction lifecycle events
- A DEX produces: pool state snapshots, swap events, liquidity events
The normalized event stream should use the venue's natural language:
| Venue kind | Event types | NOT this |
|---|
| CLOB | orderbook_snapshot, trade_fill, order_lifecycle | fine as-is |
| Onchain lending | market_state, position_state, tx_lifecycle | NOT orderbook_snapshot, NOT trade_fill |
| DEX | pool_state, swap_event, liquidity_event | NOT orderbook_snapshot |
| Intent-based | intent_submitted, intent_matched, intent_settled | NOT order_fill |
If you catch yourself creating fake orderbook snapshots from non-orderbook data, STOP. That's synthetic data (see AGENTS.md "No Synthetic Data" rule). Build the right abstraction instead.
Step 6: Build the package
Load /package-authoring. Create @recallnet/skunk-{venue} (or @recallnet/skunk-{venue}-{component} if large enough to split).
The package should contain:
- Branded types
- Client implementations (data, execution, risk)
- Zod schemas for API responses
- Error classification (retriable vs permanent vs fatal)
- Tests at 90%+ branch coverage
Package structure:
packages/skunk-{venue}/
├── src/
│ ├── index.ts # Public API
│ ├── types.ts # Branded types
│ ├── data-client.ts # Discovery and state
│ ├── execution-client.ts # Action submission
│ ├── risk-client.ts # Pre-trade validation
│ ├── schemas.ts # Zod schemas for API responses
│ ├── errors.ts # Error classification
│ └── *.test.ts # Tests beside source
├── package.json
├── tsconfig.json
├── tsup.config.ts
└── eslint.config.mjs
Step 7: Credential setup (human engagement)
Venue integration almost always requires API keys, wallet keys, or signing credentials. The agent cannot and should not handle secrets.
PAUSE and tell the operator:
I need credentials for {venue}. Please:
1. Create/obtain API credentials from {venue docs URL}
2. Add them to your local .env or .envrc.local:
{VENUE}_API_KEY=...
{VENUE}_API_SECRET=... (if needed)
{VENUE}_WALLET_KEY=... (if onchain)
3. Tell me when ready — I'll validate the credentials at startup.
After the operator provides credentials:
- Add the env vars to the executor's Zod env schema
- Add a startup credential validation (lightweight authenticated call)
- Fail hard on auth errors — do not retry or fall back
Never:
- Hardcode credentials
- Commit
.env files
- Generate or suggest wallet private keys
- Store secrets in code, config, or comments
Step 8: Wire into the executor
The venue package is a dependency of the executor, not part of it:
{
"dependencies": {
"@recallnet/skunk-{venue}": "workspace:*"
}
}
In the executor:
- Collector imports
DataClient for market discovery and state snapshots
- Scorer replays collected state through the strategy model
- Runtime uses
ExecutionClient for live actions, RiskClient for pre-trade validation
- Reconciliation uses
OnchainClient (if applicable) for truth confirmation
What this skill does NOT do
- Does not help with venues already supported by TradeCore — use
/tradecore-venues instead
- Does not make trading decisions — it provides the venue interface, the strategy uses it
- Does not handle venue-specific strategy logic — that belongs in the executor, not the venue package