import axios from 'axios';
import { dhanConfig } from './config.js';
import {
AuthStep1Response,
AuthStep3Response,
DhanAuthToken,
AuthState,
FundLimit,
PlaceOrderRequest,
OrderResponse,
OrderBook,
TradeBook,
ModifyOrderRequest,
PlaceSuperOrderRequest,
SuperOrderBook,
SuperOrderLeg,
} from './types.js';
const AUTH_BASE_URL = 'https://auth.dhan.co';
// Helper to get standard API headers with access token
const getApiHeaders = () => {
const accessToken = getAccessToken();
if (!accessToken) {
throw new Error('No valid access token. Please authenticate first.');
}
return {
'Content-Type': 'application/json',
'access-token': accessToken,
'User-Agent': 'DhanMCPClient/1.0',
};
};
// Helper to log to stderr (doesn't interfere with stdio protocol)
const log = (message: string) => {
console.error(`[DhanAuth] ${message}`);
};
// In-memory storage for authentication state (in production, use a database)
let authState: AuthState = {};
/**
* Step 1: Generate Consent
* Validates the App ID and secret and initiates a login session
*/
export async function generateConsent(): Promise<{
consentAppId: string;
loginUrl: string;
message: string;
}> {
try {
log('Generating consent...');
const response = await axios.post<AuthStep1Response>(
`${AUTH_BASE_URL}/app/generate-consent?client_id=${dhanConfig.clientId}`,
{},
{
headers: {
app_id: dhanConfig.apiKey,
app_secret: dhanConfig.apiSecret,
},
}
);
if (response.data.status !== 'success') {
throw new Error(`Failed to generate consent: ${response.data.status}`);
}
authState.consentAppId = response.data.consentAppId;
authState.consentAppStatus = response.data.consentAppStatus;
const loginUrl = `${AUTH_BASE_URL}/login/consentApp-login?consentAppId=${response.data.consentAppId}`;
log('✓ Consent generated successfully');
log(`Consent App ID: ${response.data.consentAppId}`);
return {
consentAppId: response.data.consentAppId,
loginUrl,
message: `Please open this URL in your browser to authenticate:\n${loginUrl}\n\nAfter login, you will be redirected and the tokenId will be passed as a query parameter.`,
};
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${error.response?.data}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed: ${errorMessage}`);
throw new Error(`Step 1 Failed: ${errorMessage}`);
}
}
/**
* Step 2: Browser-Based Login
* This is handled by the user opening the login URL in their browser
* The redirect will provide the tokenId which must be passed to Step 3
*/
export function getStep2Instructions(
consentAppId: string
): { loginUrl: string; instruction: string } {
const loginUrl = `${AUTH_BASE_URL}/login/consentApp-login?consentAppId=${consentAppId}`;
return {
loginUrl,
instruction: `1. Open this URL in your browser: ${loginUrl}
2. Log in with your Dhan credentials
3. Complete 2FA verification (OTP/PIN/Password)
4. You will be redirected to: ${dhanConfig.redirectUrl}?tokenId=<TOKEN_ID>
5. Copy the tokenId from the URL and use it in the 'consumeConsent' tool`,
};
}
/**
* Step 3: Consume Consent
* Uses the tokenId from Step 2 to generate the final access token
*/
export async function consumeConsent(
tokenId: string
): Promise<DhanAuthToken> {
try {
log('Consuming consent with tokenId...');
const response = await axios.get<AuthStep3Response>(
`${AUTH_BASE_URL}/app/consumeApp-consent?tokenId=${tokenId}`,
{
headers: {
app_id: dhanConfig.apiKey,
app_secret: dhanConfig.apiSecret,
},
}
);
const authToken: DhanAuthToken = {
accessToken: response.data.accessToken,
dhanClientId: response.data.dhanClientId,
dhanClientName: response.data.dhanClientName,
dhanClientUcc: response.data.dhanClientUcc,
expiryTime: response.data.expiryTime,
givenPowerOfAttorney: response.data.givenPowerOfAttorney,
generatedAt: new Date().toISOString(),
};
authState.authToken = authToken;
authState.tokenId = tokenId;
log('✓ Access token generated successfully');
log(`Client Name: ${authToken.dhanClientName}`);
log(`Client ID: ${authToken.dhanClientId}`);
log(`Token Expiry: ${authToken.expiryTime}`);
return authToken;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${error.response?.data}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed: ${errorMessage}`);
throw new Error(`Step 3 Failed: ${errorMessage}`);
}
}
/**
* Get current authentication state
*/
export function getAuthState(): AuthState {
return authState;
}
/**
* Get the current valid access token if available
*/
export function getAccessToken(): string | null {
if (!authState.authToken) {
return null;
}
const expiryTime = new Date(authState.authToken.expiryTime);
if (new Date() > expiryTime) {
log('Access token has expired');
return null;
}
return authState.authToken.accessToken;
}
/**
* Reset authentication state
*/
export function resetAuthState(): void {
authState = {};
log('State reset');
}
/**
* Check if token is valid and not expired
*/
export function isTokenValid(): boolean {
if (!authState.authToken) {
return false;
}
const expiryTime = new Date(authState.authToken.expiryTime);
return new Date() < expiryTime;
}
/**
* Get account fund limit (balance, margins, etc.)
*/
export async function getFundLimit(): Promise<FundLimit> {
try {
const accessToken = getAccessToken();
if (!accessToken) {
throw new Error('No valid access token. Please authenticate first.');
}
log('Fetching fund limit...');
const response = await axios.get<FundLimit>(
'https://api.dhan.co/v2/fundlimit',
{
headers: {
'Content-Type': 'application/json',
'access-token': accessToken,
},
}
);
log(`✓ Fund limit retrieved for client ${response.data.dhanClientId}`);
log(`Available Balance: ${response.data.availabelBalance}`);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to get fund limit: ${errorMessage}`);
throw new Error(`Failed to get fund limit: ${errorMessage}`);
}
}
// ============ ORDER MANAGEMENT APIS ============
/**
* Place a new order
*/
export async function placeOrder(
request: PlaceOrderRequest
): Promise<OrderResponse> {
try {
log(`Placing order: ${request.transactionType} ${request.quantity} shares`);
const response = await axios.post<OrderResponse>(
'https://api.dhan.co/v2/orders',
request,
{
headers: getApiHeaders(),
}
);
log(`✓ Order placed successfully. Order ID: ${response.data.orderId}`);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to place order: ${errorMessage}`);
throw new Error(`Failed to place order: ${errorMessage}`);
}
}
/**
* Modify a pending order
*/
export async function modifyOrder(
orderId: string,
request: ModifyOrderRequest
): Promise<OrderResponse> {
try {
log(`Modifying order: ${orderId}`);
const response = await axios.put<OrderResponse>(
`https://api.dhan.co/v2/orders/${orderId}`,
request,
{
headers: getApiHeaders(),
}
);
log(`✓ Order modified successfully. Order ID: ${response.data.orderId}`);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to modify order: ${errorMessage}`);
throw new Error(`Failed to modify order: ${errorMessage}`);
}
}
/**
* Cancel a pending order
*/
export async function cancelOrder(orderId: string): Promise<OrderResponse> {
try {
log(`Cancelling order: ${orderId}`);
const response = await axios.delete<OrderResponse>(
`https://api.dhan.co/v2/orders/${orderId}`,
{
headers: getApiHeaders(),
}
);
log(`✓ Order cancelled successfully. Order ID: ${response.data.orderId}`);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to cancel order: ${errorMessage}`);
throw new Error(`Failed to cancel order: ${errorMessage}`);
}
}
/**
* Get all orders (Order Book)
*/
export async function getOrderBook(): Promise<OrderBook[]> {
try {
log('Fetching order book...');
const response = await axios.get<OrderBook[]>(
'https://api.dhan.co/v2/orders',
{
headers: getApiHeaders(),
}
);
log(`✓ Order book retrieved. Total orders: ${response.data.length}`);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to get order book: ${errorMessage}`);
throw new Error(`Failed to get order book: ${errorMessage}`);
}
}
/**
* Get a specific order by Order ID
*/
export async function getOrderByID(orderId: string): Promise<OrderBook> {
try {
log(`Fetching order: ${orderId}`);
const response = await axios.get<OrderBook>(
`https://api.dhan.co/v2/orders/${orderId}`,
{
headers: getApiHeaders(),
}
);
log(`✓ Order retrieved. Status: ${response.data.orderStatus}`);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to get order: ${errorMessage}`);
throw new Error(`Failed to get order: ${errorMessage}`);
}
}
/**
* Get order by correlation ID
*/
export async function getOrderByCorrelationID(
correlationId: string
): Promise<OrderBook> {
try {
log(`Fetching order by correlation ID: ${correlationId}`);
const response = await axios.get<OrderBook>(
`https://api.dhan.co/v2/orders/external/${correlationId}`,
{
headers: getApiHeaders(),
}
);
log(`✓ Order retrieved. Order ID: ${response.data.orderId}`);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to get order by correlation ID: ${errorMessage}`);
throw new Error(
`Failed to get order by correlation ID: ${errorMessage}`
);
}
}
/**
* Get all trades (Trade Book)
*/
export async function getTradeBook(): Promise<TradeBook[]> {
try {
log('Fetching trade book...');
const response = await axios.get<TradeBook[]>(
'https://api.dhan.co/v2/trades',
{
headers: getApiHeaders(),
}
);
log(`✓ Trade book retrieved. Total trades: ${response.data.length}`);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to get trade book: ${errorMessage}`);
throw new Error(`Failed to get trade book: ${errorMessage}`);
}
}
/**
* Get trades for a specific order
*/
export async function getOrderTrades(orderId: string): Promise<TradeBook[]> {
try {
log(`Fetching trades for order: ${orderId}`);
const response = await axios.get<TradeBook[]>(
`https://api.dhan.co/v2/trades/${orderId}`,
{
headers: getApiHeaders(),
}
);
log(`✓ Trades retrieved. Total trades: ${response.data.length}`);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to get order trades: ${errorMessage}`);
throw new Error(`Failed to get order trades: ${errorMessage}`);
}
}
// ============ SUPER ORDER MANAGEMENT APIS ============
/**
* Place a super order (Entry, Target, Stop Loss)
*/
export async function placeSuperOrder(
request: PlaceSuperOrderRequest
): Promise<OrderResponse> {
try {
log(
`Placing super order: ${request.transactionType} ${request.quantity} shares with target ${request.targetPrice} and SL ${request.stopLossPrice}`
);
const response = await axios.post<OrderResponse>(
'https://api.dhan.co/v2/super/orders',
request,
{
headers: getApiHeaders(),
}
);
log(
`✓ Super order placed successfully. Order ID: ${response.data.orderId}`
);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to place super order: ${errorMessage}`);
throw new Error(`Failed to place super order: ${errorMessage}`);
}
}
/**
* Modify a super order
*/
export async function modifySuperOrder(
orderId: string,
legName: string,
request: Record<string, unknown>
): Promise<OrderResponse> {
try {
log(`Modifying super order: ${orderId}, leg: ${legName}`);
const payload = {
...request,
legName,
};
const response = await axios.put<OrderResponse>(
`https://api.dhan.co/v2/super/orders/${orderId}`,
payload,
{
headers: getApiHeaders(),
}
);
log(
`✓ Super order modified successfully. Order ID: ${response.data.orderId}`
);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to modify super order: ${errorMessage}`);
throw new Error(`Failed to modify super order: ${errorMessage}`);
}
}
/**
* Cancel a super order leg
*/
export async function cancelSuperOrderLeg(
orderId: string,
orderLeg: string
): Promise<OrderResponse> {
try {
log(
`Cancelling super order leg: ${orderId}, leg: ${orderLeg}`
);
const response = await axios.delete<OrderResponse>(
`https://api.dhan.co/v2/super/orders/${orderId}/${orderLeg}`,
{
headers: getApiHeaders(),
}
);
log(
`✓ Super order leg cancelled successfully. Order ID: ${response.data.orderId}`
);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to cancel super order leg: ${errorMessage}`);
throw new Error(`Failed to cancel super order leg: ${errorMessage}`);
}
}
/**
* Get all super orders
*/
export async function getSuperOrderBook(): Promise<SuperOrderBook[]> {
try {
log('Fetching super order book...');
const response = await axios.get<SuperOrderBook[]>(
'https://api.dhan.co/v2/super/orders',
{
headers: getApiHeaders(),
}
);
log(`✓ Super order book retrieved. Total super orders: ${response.data.length}`);
return response.data;
} catch (error) {
const errorMessage =
error instanceof axios.AxiosError
? `API Error: ${error.response?.status} - ${JSON.stringify(error.response?.data)}`
: error instanceof Error
? error.message
: 'Unknown error';
log(`✗ Failed to get super order book: ${errorMessage}`);
throw new Error(`Failed to get super order book: ${errorMessage}`);
}
}