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), and denote the smallest possible multiple of a contract (0.01 contract)
as one ccontract (centicontract). User-facing USDC balances are specified as fixed-point strings (e.g. "1.2625" for USDC). Contract quantities
in the API and SDK are specified as integer ccontracts strings (e.g. "1050" for 10.50 contracts at precision 2).
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' ,
ccontracts: '1000' , // 10.00 contracts * 100
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' ,
ccontracts: '1000' , // 10.00 contracts * 100
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' ;
ccontracts : string ;
priceUusdc : string ;
onEvent ?: ( event : BisonEvent ) => void ;
onError ?: ( error : Error ) => void ;
}): Promise < { disconnect : () => void ; txHash : `0x ${ string } ` | null } >
Parameters
Viem wallet client for signing messages
Viem public client for reading contract state
Chain identifier ("base" or "bsc"). Vault address is automatically resolved from the /info endpoint.
Kalshi market ticker (e.g., "KXFEDDECISION-26JAN-T425")
Position side: "yes" or "no"
Number of ccontracts as an integer string (e.g., "1050" for 10.50 contracts at precision 2)
Tip: use contractsToCcontracts("10.50", 2).toString() from the SDK to convert a fixed-point contracts string.
Limit price in µUSDC as an integer string
Optional callback for order events (placed, filled, etc.)
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' ,
ccontracts: '1000' , // 10.00 contracts * 100
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' ;
ccontracts : string ;
priceUusdc : string ;
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' ,
ccontracts: '500' , // 5.00 contracts * 100
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
Viem wallet client for signing messages
Chain identifier ("base" or "bsc"). Vault address is automatically resolved from the /info endpoint.
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
Optional filtering and pagination parameters
Filter by order status: "pending", "filled", "cancelled", or "closed" (filled OR cancelled)
Filter by action: "buy" or "sell"
Filter by side: "yes" or "no"
Orders created after this timestamp (milliseconds)
Orders created before this timestamp (milliseconds)
Sort by field: "createdAt" (default) or "updatedAt"
Sort direction: "desc" (default) or "asc"
Max orders per page (default: 50, max: 200)
Pagination cursor from previous response
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 => {
const requestedContracts = Number ( order . requestedCcontracts ) / 100 ;
const price = Number ( order . priceUusdc ) / 1_000_000 ;
console . log ( ` ${ order . action } ${ requestedContracts } ${ order . side } @ $ ${ price } ` );
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 } ` );
const contracts = Number ( position . ccontracts ) / 100 ;
console . log ( ` ${ position . side . toUpperCase () } : ${ contracts } 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' ,
ccontracts: '1000' , // 10.00 contracts * 100
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