Skip to main content

Overview

The SDK provides high-level methods for trading on Kalshi markets:
  • executeBuyFlow / executeSellFlow - Place buy/sell orders with automatic signature generation
  • executeCancelOrderFlow - Cancel existing orders
  • executeMintFlow / executeBurnFlow - Create/destroy position tokens
  • getUserOrders / getUserPositions - Query user’s orders and positions
To avoid ambiguity, we denote the smallest possible multiple of USDC (0.000001 USDC) as one uusdc, which stands for µUSDC (micro-USDC).

Developer Accounts

If you have a developer account, you can set it at the client level to automatically apply it to all orders:
const client = createBisonClient({ 
  baseUrl: 'https://api.bison.markets',
  devAccountId: 'your-dev-account-id'
});

// All orders automatically include your dev account ID
await client.executeBuyFlow({
  walletClient,
  publicClient,
  userAddress: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  chain: 'base',
  marketId: 'KXFEDDECISION-26JAN-T425',
  side: 'yes',
  number: 10,
  priceUusdc: 650000,
});
You can also override the dev account for specific orders when using the lower-level placeOrder method:
await client.placeOrder({
  chain: 'base',
  marketId: 'KXFEDDECISION-26JAN-T425',
  number: 10,
  priceUusdc: 650000,
  action: 'buy',
  side: 'yes',
  userAddress: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  signature: '0x...',
  expiry: 1234567890,
  devAccountId: 'different-dev-account' // Overrides client default
});
See the Developer Accounts guide for more information on how to get a dev account and earn from trading fees.

Placing Orders

Place limit orders on Kalshi markets. The SDK handles EIP-712 signature generation automatically.

executeBuyFlow

async executeBuyFlow(params: {
  walletClient: WalletClient;
  publicClient: PublicClient;
  userAddress: `0x${string}`;
  chain: string;
  marketId: string;
  side: 'yes' | 'no';
  number: number;
  priceUusdc: number;
  onEvent?: (event: BisonEvent) => void;
  onError?: (error: Error) => void;
}): Promise<{ disconnect: () => void; txHash: `0x${string}` | null }>

Parameters

walletClient
WalletClient
required
Viem wallet client for signing messages
publicClient
PublicClient
required
Viem public client for reading contract state
userAddress
string
required
User’s Ethereum address
chain
string
required
Chain identifier ("base" or "bsc"). Vault address is automatically resolved from the /info endpoint.
marketId
string
required
Kalshi market ticker (e.g., "KXFEDDECISION-26JAN-T425")
side
string
required
Position side: "yes" or "no"
number
number
required
Number of contracts to buy
priceUusdc
number
required
Limit price in µUSDC
onEvent
function
Optional callback for order events (placed, filled, etc.)
onError
function
Optional callback for WebSocket errors

Returns

Object containing:
  • disconnect - Function to stop listening for events
  • txHash - Transaction hash (currently always null for off-chain orders)

Example

import { createBisonClient } from '@bison-markets/sdk-ts';
import { createWalletClient, createPublicClient, http, custom } from 'viem';
import { base } from 'viem/chains';

const client = createBisonClient({ 
  baseUrl: 'https://api.bison.markets' 
});

const walletClient = createWalletClient({
  chain: base,
  transport: custom(window.ethereum!),
});

const publicClient = createPublicClient({
  chain: base,
  transport: http(),
});

// Buy 10 YES contracts at 65 cents each
const result = await client.executeBuyFlow({
  walletClient,
  publicClient,
  userAddress: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  chain: 'base',
  marketId: 'KXFEDDECISION-26JAN-T425',
  side: 'yes',
  number: 10,
  priceUusdc: 650000, // $0.65 * 10^6
  onEvent: (event) => {
    if (event.type === 'order_placed') {
      console.log('Order placed successfully:', event.orderId);
    } else if (event.type === 'order_filled') {
      console.log('Order filled:', event.orderId);
    }
  },
  onError: (error) => {
    console.error('WebSocket error:', error);
  },
});

// Stop listening for events when done
// result.disconnect();

executeSellFlow

Works identically to executeBuyFlow but places a sell order.
async executeSellFlow(params: {
  walletClient: WalletClient;
  publicClient: PublicClient;
  userAddress: `0x${string}`;
  chain: string;
  marketId: string;
  side: 'yes' | 'no';
  number: number;
  priceUusdc: number;
  onEvent?: (event: BisonEvent) => void;
  onError?: (error: Error) => void;
}): Promise<{ disconnect: () => void; txHash: `0x${string}` | null }>

Example

// Sell 5 NO contracts at 40 cents each
const result = await client.executeSellFlow({
  walletClient,
  publicClient,
  userAddress: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  chain: 'base',
  marketId: 'KXFEDDECISION-26JAN-T425',
  side: 'no',
  number: 5,
  priceUusdc: 400000, // $0.40 * 10^6
});

Cancelling Orders

Cancel an existing open order.

executeCancelOrderFlow

async executeCancelOrderFlow(params: {
  walletClient: WalletClient;
  userAddress: `0x${string}`;
  chain: string;
  orderId: string;
}): Promise<void>

Parameters

walletClient
WalletClient
required
Viem wallet client for signing messages
userAddress
string
required
User’s Ethereum address
chain
string
required
Chain identifier ("base" or "bsc"). Vault address is automatically resolved from the /info endpoint.
orderId
string
required
ID of the order to cancel (from order event or getUserOrders)

Example

await client.executeCancelOrderFlow({
  walletClient,
  userAddress: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  chain: 'base',
  orderId: 'abc123-order-id',
});

console.log('Order cancellation requested');

Querying Orders & Positions

getUserOrders

Get orders for a user with optional filtering and pagination.
async getUserOrders(
  userId: string,
  params?: GetUserOrdersParams
): Promise<GetUserOrdersResponse>

Parameters

userId
string
required
User’s Ethereum address
params
object
Optional filtering and pagination parameters

Response

The response includes an orders array and a pagination object:
{
  orders: Order[];
  pagination: {
    total: number;      // Total matching orders
    hasMore: boolean;   // More pages available
    nextCursor?: string; // Cursor for next page
  };
}

Examples

Get all pending orders:
const { orders } = await client.getUserOrders(
  '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  { status: 'pending' }
);

console.log('Open orders:');
orders.forEach(order => {
  console.log(`${order.action} ${order.requestedQuantity} ${order.side} @ $${order.priceUusdc / 1_000_000}`);
  console.log(`  Market: ${order.marketId}`);
  console.log(`  Order ID: ${order.kalshiOrderId}`);
});
Get order history with pagination:
// First page of completed orders
const result = await client.getUserOrders(
  '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  { 
    status: 'closed',
    sortBy: 'updatedAt',
    sortOrder: 'desc',
    limit: 10 
  }
);

console.log(`Showing ${result.orders.length} of ${result.pagination.total} orders`);

// Fetch next page if available
if (result.pagination.hasMore) {
  const nextPage = await client.getUserOrders(
    '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    { 
      status: 'closed',
      sortBy: 'updatedAt',
      sortOrder: 'desc',
      limit: 10,
      cursor: result.pagination.nextCursor
    }
  );
}
Filter by market:
const { orders } = await client.getUserOrders(
  '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  { marketId: 'KXFEDDECISION-26JAN-T425' }
);
Filter by date range:
const oneWeekAgo = Date.now() - 7 * 24 * 60 * 60 * 1000;

const { orders } = await client.getUserOrders(
  '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  { 
    status: 'filled',
    createdAfter: oneWeekAgo 
  }
);

getUserPositions

Get all positions for a user.
async getUserPositions(userId: string): Promise<GetUserPositionsResponse>

Example

const { positions } = await client.getUserPositions(
  '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266'
);

console.log('Current positions:');
positions.forEach(position => {
  console.log(`Market: ${position.marketId}`);
  console.log(`  ${position.side.toUpperCase()}: ${position.quantity} contracts`);
});

Position Token Utilities

Get token addresses and balances for position tokens.

getPositionTokenAddress

async getPositionTokenAddress(params: {
  publicClient: PublicClient;
  chain: 'base' | 'bsc';
  marketId: string;
  side: 'yes' | 'no';
}): Promise<`0x${string}` | null>
Returns the ERC20 token address for a position, or null if not yet created. The vault address is automatically resolved from the /info endpoint.

getTokenBalance

async getTokenBalance(params: {
  publicClient: PublicClient;
  tokenAddress: `0x${string}`;
  userAddress: `0x${string}`;
}): Promise<bigint>
Returns the token balance for a user.

addTokenToWallet

async addTokenToWallet(params: {
  tokenAddress: `0x${string}`;
  marketId: string;
  side: 'yes' | 'no';
}): Promise<boolean>
Prompts the user to add a position token to their wallet (MetaMask, etc.).

Example

// Get token address
const tokenAddress = await client.getPositionTokenAddress({
  publicClient,
  chain: 'base',
  marketId: 'KXFEDDECISION-26JAN-T425',
  side: 'yes',
});

if (tokenAddress) {
  // Get user's balance
  const balance = await client.getTokenBalance({
    publicClient,
    tokenAddress,
    userAddress: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
  });
  
  console.log('Token balance:', balance.toString());
  
  // Add to wallet
  const added = await client.addTokenToWallet({
    tokenAddress,
    marketId: 'KXFEDDECISION-26JAN-T425',
    side: 'yes',
  });
  
  if (added) {
    console.log('Token added to wallet');
  }
}

Error Handling

try {
  await client.executeBuyFlow({
    walletClient,
    publicClient,
    userAddress: '0xf39Fd6e51aad88F6F4ce6aB8827279cffFb92266',
    chain: 'base',
    marketId: 'KXFEDDECISION-26JAN-T425',
    side: 'yes',
    number: 10,
    priceUusdc: 650000,
  });
} catch (error) {
  if (error instanceof Error) {
    if (error.message.includes('insufficient balance')) {
      console.error('Not enough USDC deposited');
    } else if (error.message.includes('User rejected')) {
      console.error('User cancelled signature request');
    } else {
      console.error('Failed to place order:', error.message);
    }
  }
}

Important Notes

  • All order prices are in µUSDC (1 USD = 1,000,000 µUSDC, so $0.65 = 650000 µUSDC)
  • Order signatures expire after 10 minutes
  • Minting withdraws positions from the exchange to your wallet as ERC-20 tokens
  • Burning deposits ERC-20 tokens from your wallet back to the exchange as tradeable positions
  • The SDK automatically handles EIP-712 signature generation
  • WebSocket connection is automatically established for buy/sell flows
  • Always call disconnect() to clean up WebSocket connections
  • Developer account IDs set at the client level are automatically applied to all orders