import axios from 'axios';
import { createHash } from 'crypto';
import { v4 as uuidv4 } from 'uuid';
export class ZerodhaClient {
constructor(apiKey, apiSecret, redirectUri) {
this.apiKey = apiKey;
this.apiSecret = apiSecret;
this.redirectUri = redirectUri;
this.baseURL = 'https://api.kite.trade';
this.accessToken = null;
this.refreshToken = null;
}
// Generate login URL for OAuth
getLoginURL() {
const state = uuidv4();
const params = new URLSearchParams({
api_key: this.apiKey,
api_secret: this.apiSecret,
redirect_uri: this.redirectUri,
state: state
});
return `${this.baseURL}/connect/login?${params.toString()}`;
}
// Exchange request token for access token
async getAccessToken(requestToken) {
try {
const checksum = createHash('sha256')
.update(`${this.apiKey}${requestToken}${this.apiSecret}`)
.digest('hex');
const response = await axios.post(`${this.baseURL}/session/token`, {
api_key: this.apiKey,
request_token: requestToken,
checksum: checksum
});
this.accessToken = response.data.data.access_token;
this.refreshToken = response.data.data.refresh_token;
return {
accessToken: this.accessToken,
refreshToken: this.refreshToken
};
} catch (error) {
throw new Error(`Failed to get access token: ${error.message}`);
}
}
// Set access token (for when you already have one)
setAccessToken(accessToken) {
this.accessToken = accessToken;
}
// Get user profile
async getUserProfile() {
try {
const response = await axios.get(`${this.baseURL}/user/profile`, {
headers: {
'Authorization': `token ${this.apiKey}:${this.accessToken}`
}
});
return response.data.data;
} catch (error) {
throw new Error(`Failed to get user profile: ${error.message}`);
}
}
// Get holdings
async getHoldings() {
try {
const response = await axios.get(`${this.baseURL}/portfolio/holdings`, {
headers: {
'Authorization': `token ${this.apiKey}:${this.accessToken}`
}
});
return response.data.data;
} catch (error) {
throw new Error(`Failed to get holdings: ${error.message}`);
}
}
// Get positions
async getPositions() {
try {
const response = await axios.get(`${this.baseURL}/portfolio/positions`, {
headers: {
'Authorization': `token ${this.apiKey}:${this.accessToken}`
}
});
return response.data.data;
} catch (error) {
throw new Error(`Failed to get positions: ${error.message}`);
}
}
// Get instruments
async getInstruments(exchange = 'NSE') {
try {
const response = await axios.get(`${this.baseURL}/instruments/${exchange}`, {
headers: {
'Authorization': `token ${this.apiKey}:${this.accessToken}`
}
});
return response.data.data;
} catch (error) {
throw new Error(`Failed to get instruments: ${error.message}`);
}
}
// Get quote for symbols
async getQuote(symbols) {
try {
const symbolsParam = Array.isArray(symbols) ? symbols.join(',') : symbols;
const response = await axios.get(`${this.baseURL}/quote/ltp?i=${symbolsParam}`, {
headers: {
'Authorization': `token ${this.apiKey}:${this.accessToken}`
}
});
return response.data.data;
} catch (error) {
throw new Error(`Failed to get quote: ${error.message}`);
}
}
// Place order
async placeOrder(orderParams) {
try {
const response = await axios.post(`${this.baseURL}/orders/regular`, orderParams, {
headers: {
'Authorization': `token ${this.apiKey}:${this.accessToken}`,
'Content-Type': 'application/x-www-form-urlencoded'
}
});
return response.data.data;
} catch (error) {
throw new Error(`Failed to place order: ${error.message}`);
}
}
// Get order history
async getOrderHistory() {
try {
const response = await axios.get(`${this.baseURL}/orders`, {
headers: {
'Authorization': `token ${this.apiKey}:${this.accessToken}`
}
});
return response.data.data;
} catch (error) {
throw new Error(`Failed to get order history: ${error.message}`);
}
}
// Cancel order
async cancelOrder(orderId) {
try {
const response = await axios.delete(`${this.baseURL}/orders/regular/${orderId}`, {
headers: {
'Authorization': `token ${this.apiKey}:${this.accessToken}`
}
});
return response.data.data;
} catch (error) {
throw new Error(`Failed to cancel order: ${error.message}`);
}
}
// Get margins
async getMargins() {
try {
const response = await axios.get(`${this.baseURL}/user/margins`, {
headers: {
'Authorization': `token ${this.apiKey}:${this.accessToken}`
}
});
return response.data.data;
} catch (error) {
throw new Error(`Failed to get margins: ${error.message}`);
}
}
// Check if access token is valid
async validateToken() {
try {
await this.getUserProfile();
return true;
} catch (error) {
return false;
}
}
}