DhanHQ 2.2.2 → 2.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/CHANGELOG.md +33 -0
- data/CODE_REVIEW_ISSUES.md +2 -2
- data/GUIDE.md +63 -21
- data/README.md +16 -5
- data/README1.md +4 -4
- data/REVIEW_SUMMARY.md +2 -2
- data/docs/API_VERIFICATION.md +67 -0
- data/docs/AUTHENTICATION.md +54 -2
- data/docs/TESTING_GUIDE.md +40 -42
- data/docs/live_order_updates.md +2 -2
- data/docs/rails_integration.md +7 -7
- data/docs/standalone_ruby_websocket_integration.md +24 -24
- data/docs/technical_analysis.md +1 -1
- data/docs/websocket_integration.md +4 -4
- data/examples/comprehensive_websocket_examples.rb +2 -2
- data/examples/instrument_finder_test.rb +2 -2
- data/examples/market_depth_example.rb +2 -2
- data/examples/market_feed_example.rb +2 -2
- data/examples/order_update_example.rb +2 -2
- data/examples/trading_fields_example.rb +2 -2
- data/lib/DhanHQ/auth/token_generator.rb +33 -0
- data/lib/DhanHQ/auth/token_manager.rb +88 -0
- data/lib/DhanHQ/auth/token_renewal.rb +25 -0
- data/lib/DhanHQ/auth.rb +91 -31
- data/lib/DhanHQ/client.rb +42 -2
- data/lib/DhanHQ/configuration.rb +2 -2
- data/lib/DhanHQ/contracts/alert_order_contract.rb +20 -0
- data/lib/DhanHQ/contracts/order_contract.rb +0 -23
- data/lib/DhanHQ/contracts/trade_by_order_id_contract.rb +12 -0
- data/lib/DhanHQ/contracts/trade_contract.rb +0 -65
- data/lib/DhanHQ/contracts/trade_history_contract.rb +52 -0
- data/lib/DhanHQ/core/auth_api.rb +21 -0
- data/lib/DhanHQ/core/base_resource.rb +5 -5
- data/lib/DhanHQ/helpers/request_helper.rb +1 -1
- data/lib/DhanHQ/models/alert_order.rb +83 -0
- data/lib/DhanHQ/models/token_response.rb +88 -0
- data/lib/DhanHQ/resources/alert_orders.rb +11 -0
- data/lib/DhanHQ/resources/edis.rb +5 -20
- data/lib/DhanHQ/resources/ip_setup.rb +23 -0
- data/lib/DhanHQ/resources/trader_control.rb +23 -0
- data/lib/DhanHQ/version.rb +1 -1
- data/lib/dhan_hq.rb +31 -77
- metadata +43 -4
- data/lib/DhanHQ/config.rb +0 -33
- data/lib/DhanHQ/models/edis.rb +0 -194
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: b583e87aa598f3891e937f44859f9b451fc8f6bcec7a304cc0c33790fe9117ea
|
|
4
|
+
data.tar.gz: 1a090fa608fba141da311ba8aaf21d36e750f78e90497f728c5f96d580044c7e
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: '0966f2a2aa331b675ecc1675510f6ad3dca6b7a987c8dde15dde2efe506ac659157b6e0979d0c23f480d2835fa603bfd37530f33275d422226abb2419e46bfd0'
|
|
7
|
+
data.tar.gz: 74ea10d0744fb01324afd4a1768fa6969b07f4d1fcb9da94d7a6caf01fd01c2863d6a3746d288628d10e497bd55c68687e0b54e237bd91736ad4bb8c1decc9f8
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,38 @@
|
|
|
1
1
|
## [Unreleased]
|
|
2
2
|
|
|
3
|
+
---
|
|
4
|
+
|
|
5
|
+
## [2.4.0] - 2026-02-18
|
|
6
|
+
|
|
7
|
+
### Added
|
|
8
|
+
- Support for Individual TOTP-based access token generation
|
|
9
|
+
- `DhanHQ::Auth.generate_access_token`
|
|
10
|
+
- `DhanHQ::Auth.generate_totp`
|
|
11
|
+
|
|
12
|
+
### Updated
|
|
13
|
+
- RenewToken now uses POST (aligned with Dhan API behavior)
|
|
14
|
+
- Improved error handling for authentication APIs
|
|
15
|
+
|
|
16
|
+
### Notes
|
|
17
|
+
- TOTP tokens can now be programmatically regenerated without browser login
|
|
18
|
+
- RenewToken still applies only to web-generated tokens
|
|
19
|
+
|
|
20
|
+
---
|
|
21
|
+
|
|
22
|
+
## [2.3.0] - 2026-02-04
|
|
23
|
+
|
|
24
|
+
### Added
|
|
25
|
+
- **Alert Orders**: `DhanHQ::Resources::AlertOrders` (BaseResource) and `DhanHQ::Models::AlertOrder` with full CRUD. Endpoints: GET/POST `/alerts/orders`, GET/PUT/DELETE `/alerts/orders/{id}` (per API docs). Validation via `DhanHQ::Contracts::AlertOrderContract`.
|
|
26
|
+
- **IP Setup**: `DhanHQ::Resources::IPSetup` (resource-only). Methods: `current` (GET `/ip/getIP`), `set(ip:)` (POST `/ip/setIP`), `update(ip:)` (PUT `/ip/modifyIP`) per API docs.
|
|
27
|
+
- **Trader Control (Kill Switch)**: `DhanHQ::Resources::TraderControl` (resource-only). Methods: `status` (GET `/trader-control`), `enable` (POST action ENABLE), `disable` (POST action DISABLE). `DhanHQ::Resources::KillSwitch` and `DhanHQ::Models::KillSwitch` remain for backward compatibility.
|
|
28
|
+
- **docs/API_VERIFICATION.md**: Documents alignment with [dhanhq.co/docs/v2](https://dhanhq.co/docs/v2/) and [api.dhan.co/v2](https://api.dhan.co/v2/#/) for EDIS, Alert Orders, IP Setup.
|
|
29
|
+
|
|
30
|
+
### Changed
|
|
31
|
+
- **EDIS**: Resource-only, aligned with [dhanhq.co/docs/v2/edis](https://dhanhq.co/docs/v2/edis/). Use `DhanHQ::Resources::Edis`: `form(params)` (POST `/edis/form`; isin, qty, exchange, segment, bulk), `bulk_form(params)` (POST `/edis/bulkform`), `tpin` (GET `/edis/tpin`), `inquire(isin)` (GET `/edis/inquire/{isin}`).
|
|
32
|
+
- **BaseResource**: Fixed path building: `all`/`find`/`create`/`update`/`delete` now pass relative endpoints (`""`, `"/#{id}"`) so the base path is not doubled.
|
|
33
|
+
|
|
34
|
+
---
|
|
35
|
+
|
|
3
36
|
## [2.2.2] - 2026-01-31
|
|
4
37
|
|
|
5
38
|
### Contracts (date validation)
|
data/CODE_REVIEW_ISSUES.md
CHANGED
|
@@ -12,9 +12,9 @@
|
|
|
12
12
|
**Location**: `lib/DhanHQ/client.rb:40`
|
|
13
13
|
**Issue**: The client initializes configuration conditionally, which can lead to runtime errors:
|
|
14
14
|
```ruby
|
|
15
|
-
DhanHQ.configure_with_env if ENV.fetch("
|
|
15
|
+
DhanHQ.configure_with_env if ENV.fetch("DHAN_CLIENT_ID", nil)
|
|
16
16
|
```
|
|
17
|
-
**Problem**: If `
|
|
17
|
+
**Problem**: If `DHAN_CLIENT_ID` exists but `DHAN_ACCESS_TOKEN` doesn't, configuration will be partially initialized, leading to authentication failures later.
|
|
18
18
|
**Recommendation**: Validate both required credentials before proceeding or fail fast with a clear error message.
|
|
19
19
|
|
|
20
20
|
### 2. **Race Condition in Rate Limiter**
|
data/GUIDE.md
CHANGED
|
@@ -44,8 +44,8 @@ DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Lo
|
|
|
44
44
|
|
|
45
45
|
| Variable | Description |
|
|
46
46
|
| -------------- | ---------------------------------------------------- |
|
|
47
|
-
| `
|
|
48
|
-
| `
|
|
47
|
+
| `DHAN_CLIENT_ID` | Your Dhan trading client id. |
|
|
48
|
+
| `DHAN_ACCESS_TOKEN` | REST/WebSocket access token generated via Dhan APIs. |
|
|
49
49
|
|
|
50
50
|
Provide them via `.env`, Rails credentials, or your secret manager of choice
|
|
51
51
|
before the initializer runs.
|
|
@@ -641,33 +641,75 @@ profile.active_segment # => "Equity, Derivative, Currency, Commodity"
|
|
|
641
641
|
|
|
642
642
|
If the credentials are invalid the helper raises `DhanHQ::InvalidAuthenticationError`.
|
|
643
643
|
|
|
644
|
-
###
|
|
644
|
+
### Alert Orders
|
|
645
645
|
|
|
646
646
|
```ruby
|
|
647
|
-
#
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
|
|
647
|
+
# Model (CRUD)
|
|
648
|
+
DhanHQ::Models::AlertOrder.all
|
|
649
|
+
alert = DhanHQ::Models::AlertOrder.find("alert-id")
|
|
650
|
+
alert = DhanHQ::Models::AlertOrder.create(
|
|
651
|
+
exchange_segment: "NSE_EQ",
|
|
652
|
+
security_id: "11536",
|
|
653
|
+
condition: "GTE",
|
|
654
|
+
trigger_price: 100.0,
|
|
655
|
+
transaction_type: "BUY",
|
|
656
|
+
quantity: 10
|
|
654
657
|
)
|
|
658
|
+
alert.save
|
|
659
|
+
alert.destroy
|
|
655
660
|
|
|
656
|
-
#
|
|
657
|
-
|
|
658
|
-
|
|
659
|
-
|
|
660
|
-
|
|
661
|
-
|
|
661
|
+
# Resource only
|
|
662
|
+
DhanHQ::Resources::AlertOrders.new.all
|
|
663
|
+
```
|
|
664
|
+
|
|
665
|
+
### EDIS (Electronic Delivery Instruction Slip)
|
|
666
|
+
|
|
667
|
+
EDIS is resource-only (no model). Use `DhanHQ::Resources::Edis` per [dhanhq.co/docs/v2/edis](https://dhanhq.co/docs/v2/edis/):
|
|
668
|
+
|
|
669
|
+
```ruby
|
|
670
|
+
edis = DhanHQ::Resources::Edis.new
|
|
671
|
+
|
|
672
|
+
# Generate T-PIN (GET /edis/tpin)
|
|
673
|
+
edis.tpin
|
|
674
|
+
|
|
675
|
+
# Retrieve form & enter T-PIN (POST /edis/form): isin, qty, exchange, segment, bulk
|
|
676
|
+
edis.form(isin: "INE733E01010", qty: 1, exchange: "NSE", segment: "EQ", bulk: false)
|
|
662
677
|
|
|
663
|
-
#
|
|
664
|
-
|
|
665
|
-
|
|
678
|
+
# Bulk form (POST /edis/bulkform)
|
|
679
|
+
edis.bulk_form(exchange: "NSE", segment: "EQ", bulk: true)
|
|
680
|
+
|
|
681
|
+
# Inquire status (GET /edis/inquire/{isin})
|
|
682
|
+
edis.inquire("INE002A01018")
|
|
683
|
+
edis.inquire("ALL")
|
|
684
|
+
```
|
|
685
|
+
|
|
686
|
+
Params use snake_case; the client camelizes them before calling `/edis/...`.
|
|
687
|
+
|
|
688
|
+
### IP Setup
|
|
689
|
+
|
|
690
|
+
Resource-only (account configuration) per API docs: GET /ip/getIP, POST /ip/setIP, PUT /ip/modifyIP.
|
|
691
|
+
|
|
692
|
+
```ruby
|
|
693
|
+
ip = DhanHQ::Resources::IPSetup.new
|
|
694
|
+
ip.current # GET /ip/getIP
|
|
695
|
+
ip.set(ip: "103.21.58.121")
|
|
696
|
+
ip.update(ip: "103.21.58.121")
|
|
697
|
+
```
|
|
698
|
+
|
|
699
|
+
### Trader Control (Kill Switch)
|
|
700
|
+
|
|
701
|
+
Resource-only control toggle:
|
|
702
|
+
|
|
703
|
+
```ruby
|
|
704
|
+
tc = DhanHQ::Resources::TraderControl.new
|
|
705
|
+
tc.status # GET /trader-control
|
|
706
|
+
tc.disable # Kill switch ON — trading blocked
|
|
707
|
+
tc.enable # Trading resumed
|
|
666
708
|
```
|
|
667
709
|
|
|
668
|
-
|
|
710
|
+
### Kill Switch (model)
|
|
669
711
|
|
|
670
|
-
|
|
712
|
+
Existing model API for backward compatibility:
|
|
671
713
|
|
|
672
714
|
```ruby
|
|
673
715
|
activate_payload = DhanHQ::Models::KillSwitch.activate
|
data/README.md
CHANGED
|
@@ -4,7 +4,7 @@ A clean Ruby client for **Dhan API v2** with ORM-like models (Orders, Positions,
|
|
|
4
4
|
|
|
5
5
|
* ActiveRecord-style models: `find`, `all`, `where`, `save`, `update`, `cancel`
|
|
6
6
|
* Validations & errors exposed via ActiveModel-like interfaces
|
|
7
|
-
* REST coverage: Orders, Super Orders, Forever Orders, Trades, Positions, Holdings, Funds/Margin, HistoricalData, OptionChain, MarketFeed, ExpiredOptionsData
|
|
7
|
+
* REST coverage: Orders, Alert Orders, Super Orders, Forever Orders, Trades, Positions, Holdings, Funds/Margin, HistoricalData, OptionChain, MarketFeed, ExpiredOptionsData, EDIS, IP Setup, Trader Control (Kill Switch)
|
|
8
8
|
* **WebSocket**: Orders, Market Feed, Market Depth - subscribe/unsubscribe dynamically, auto-reconnect with backoff, 429 cool-off, idempotent subs, header+payload binary parsing, normalized ticks
|
|
9
9
|
|
|
10
10
|
## ⚠️ BREAKING CHANGE NOTICE
|
|
@@ -53,7 +53,7 @@ gem install DhanHQ
|
|
|
53
53
|
|
|
54
54
|
## Configuration
|
|
55
55
|
|
|
56
|
-
### From ENV / .env
|
|
56
|
+
### From ENV / .env (static token)
|
|
57
57
|
|
|
58
58
|
```ruby
|
|
59
59
|
require 'dhan_hq'
|
|
@@ -66,8 +66,8 @@ DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Lo
|
|
|
66
66
|
|
|
67
67
|
| Variable | Purpose |
|
|
68
68
|
| -------------- | ------------------------------------------------- |
|
|
69
|
-
| `
|
|
70
|
-
| `
|
|
69
|
+
| `DHAN_CLIENT_ID` | Trading account client id issued by Dhan. |
|
|
70
|
+
| `DHAN_ACCESS_TOKEN` | API access token generated from the Dhan console. |
|
|
71
71
|
|
|
72
72
|
`configure_with_env` raises if either value is missing. Load them via `dotenv`,
|
|
73
73
|
Rails credentials, or any other mechanism that populates `ENV` before
|
|
@@ -114,7 +114,18 @@ end
|
|
|
114
114
|
|
|
115
115
|
If the API returns **401** or error code **807** (token expired) and `access_token_provider` is set, the client retries the request **once** with a fresh token from the provider. Otherwise it raises `DhanHQ::InvalidAuthenticationError` or `DhanHQ::TokenExpiredError`. Missing or nil token from config raises `DhanHQ::AuthenticationError`.
|
|
116
116
|
|
|
117
|
-
|
|
117
|
+
### Authentication approaches (overview)
|
|
118
|
+
|
|
119
|
+
The gem intentionally **does not own** Dhan’s consent flows (web, API key/secret, Partner). You obtain a token using Dhan’s docs, then choose one of these approaches:
|
|
120
|
+
|
|
121
|
+
- **Static token (ENV / config)**: Set `DHAN_CLIENT_ID` and `DHAN_ACCESS_TOKEN` via ENV and call `DhanHQ.configure_with_env`, or assign `config.client_id` / `config.access_token` manually. Best when you’re comfortable rotating tokens out-of-band (cron, runbook).
|
|
122
|
+
- **Dynamic token provider**: Use `config.access_token_provider` (and optional `config.on_token_expired`) so the gem asks your app for the token on every request and retries **once** on 401 with a fresh token. Ideal for OAuth-style flows or central token stores.
|
|
123
|
+
- **Token endpoint bootstrap**: Call `DhanHQ.configure_from_token_endpoint(base_url:, bearer_token:)` to fetch `{ access_token, client_id, base_url? }` from your own HTTP endpoint and populate configuration in one step.
|
|
124
|
+
- **TOTP-based token generation (individuals)**: Use `DhanHQ::Auth.generate_totp(secret)` + `DhanHQ::Auth.generate_access_token(dhan_client_id:, pin:, totp:)` when you want fully automated, TOTP-backed tokens for a single account. The low-level API returns the raw response hash from Dhan.
|
|
125
|
+
- **Client helpers + auto management**: Use `client.generate_access_token(dhan_client_id:, pin:, totp: nil, totp_secret: nil)` and `client.enable_auto_token_management!(dhan_client_id:, pin:, totp_secret:)` when you want `TokenResponse` objects, config auto-updates, and background token lifecycle (generate + renew) wrapped around each request.
|
|
126
|
+
- **RenewToken (web-generated tokens)**: For 24h tokens generated from **Dhan Web**, call `DhanHQ::Auth.renew_token(access_token:, client_id:)` or `client.renew_access_token` / `TokenManager#refresh!` to renew. This only works for active web tokens; for TOTP-generated tokens prefer regenerating.
|
|
127
|
+
|
|
128
|
+
For detailed behavior, timelines, and error types, see **[docs/AUTHENTICATION.md](docs/AUTHENTICATION.md)**.
|
|
118
129
|
|
|
119
130
|
### Logging
|
|
120
131
|
|
data/README1.md
CHANGED
|
@@ -45,8 +45,8 @@ DhanHQ.logger.level = (ENV["DHAN_LOG_LEVEL"] || "INFO").upcase.then { |level| Lo
|
|
|
45
45
|
|
|
46
46
|
**Minimum environment variables**
|
|
47
47
|
|
|
48
|
-
* `
|
|
49
|
-
* `
|
|
48
|
+
* `DHAN_CLIENT_ID` – trading account client id issued by Dhan.
|
|
49
|
+
* `DHAN_ACCESS_TOKEN` – API access token generated from the Dhan console.
|
|
50
50
|
|
|
51
51
|
If either key is missing `configure_with_env` raises an error. Ensure your
|
|
52
52
|
application loads them into `ENV` before requiring the gem.
|
|
@@ -64,8 +64,8 @@ Create a `.env` file in your project root to supply the minimum values (and any
|
|
|
64
64
|
optional overrides you need):
|
|
65
65
|
|
|
66
66
|
```dotenv
|
|
67
|
-
|
|
68
|
-
|
|
67
|
+
DHAN_CLIENT_ID=your_client_id
|
|
68
|
+
DHAN_ACCESS_TOKEN=your_access_token
|
|
69
69
|
```
|
|
70
70
|
|
|
71
71
|
The gem requires `dotenv/load`, so these variables are loaded automatically when you require `DhanHQ`.
|
data/REVIEW_SUMMARY.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# DhanHQ Client Gem - Review Summary
|
|
2
2
|
|
|
3
|
-
**API Documentation**: https://api.dhan.co/v2/#/
|
|
3
|
+
**API Documentation**: https://api.dhan.co/v2/#/
|
|
4
4
|
**Review Date**: 2025-01-27
|
|
5
5
|
|
|
6
6
|
## 🚨 Top 10 Critical Issues
|
|
@@ -15,7 +15,7 @@
|
|
|
15
15
|
- **File**: `lib/DhanHQ/client.rb:40`
|
|
16
16
|
- **Issue**: Partial configuration can lead to runtime authentication failures
|
|
17
17
|
- **Impact**: Silent failures, difficult debugging
|
|
18
|
-
- **Fix**: Validate both `
|
|
18
|
+
- **Fix**: Validate both `DHAN_CLIENT_ID` and `DHAN_ACCESS_TOKEN` before proceeding
|
|
19
19
|
|
|
20
20
|
### 3. **Memory Leak in Order Tracker** (HIGH)
|
|
21
21
|
- **File**: `lib/DhanHQ/ws/orders/client.rb:18`
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
# API Verification (Dhan v2)
|
|
2
|
+
|
|
3
|
+
This document records how the gem’s implementation aligns with the official Dhan API v2 docs.
|
|
4
|
+
|
|
5
|
+
**Sources:**
|
|
6
|
+
|
|
7
|
+
- [dhanhq.co/docs/v2](https://dhanhq.co/docs/v2/) – main docs
|
|
8
|
+
- [dhanhq.co/docs/v2/edis](https://dhanhq.co/docs/v2/edis/) – EDIS
|
|
9
|
+
- [api.dhan.co/v2](https://api.dhan.co/v2/#/) – Developer Kit (when available)
|
|
10
|
+
- In-repo: `CODE_REVIEW_ISSUES.md` (Alert Orders, IP Setup paths)
|
|
11
|
+
|
|
12
|
+
---
|
|
13
|
+
|
|
14
|
+
## EDIS
|
|
15
|
+
|
|
16
|
+
**Doc:** [dhanhq.co/docs/v2/edis](https://dhanhq.co/docs/v2/edis/)
|
|
17
|
+
|
|
18
|
+
| Doc endpoint | Method | Gem method | Path / behaviour |
|
|
19
|
+
|---------------------------|--------|--------------|-------------------|
|
|
20
|
+
| Generate T-PIN | GET | `#tpin` | `/edis/tpin` |
|
|
21
|
+
| Retrieve form & enter T-PIN | POST | `#form(params)` | `/edis/form`; body: `isin`, `qty`, `exchange`, `segment`, `bulk` |
|
|
22
|
+
| Bulk form | POST | `#bulk_form(params)` | `/edis/bulkform` (aligned with TODO-1 / legacy) |
|
|
23
|
+
| Inquire status | GET | `#inquire(isin)` | `/edis/inquire/{isin}` |
|
|
24
|
+
|
|
25
|
+
**Request (form):** `isin`, `qty`, `exchange` (NSE/BSE), `segment` (EQ), `bulk` (boolean).
|
|
26
|
+
|
|
27
|
+
---
|
|
28
|
+
|
|
29
|
+
## Alert Orders
|
|
30
|
+
|
|
31
|
+
**Doc ref:** `CODE_REVIEW_ISSUES.md` (§31) – Alert Orders endpoints.
|
|
32
|
+
|
|
33
|
+
| Doc path | Gem resource | Path used |
|
|
34
|
+
|---------------------------|---------------------------|------------------|
|
|
35
|
+
| `/alerts/orders` | `Resources::AlertOrders` | `HTTP_PATH = "/alerts/orders"` |
|
|
36
|
+
| GET/POST `/alerts/orders` | `#all`, `#create` | BaseResource |
|
|
37
|
+
| GET/PUT/DELETE `/alerts/orders/{trigger-id}` | `#find`, `#update`, `#delete` | `/{id}` |
|
|
38
|
+
|
|
39
|
+
Model: `Models::AlertOrder`; ID attribute `alert_id` (response field name may be `alertId` or `triggerId` depending on API; adjust if production returns `triggerId`).
|
|
40
|
+
|
|
41
|
+
---
|
|
42
|
+
|
|
43
|
+
## IP Setup
|
|
44
|
+
|
|
45
|
+
**Doc ref:** `CODE_REVIEW_ISSUES.md` (§32) – IP Setup endpoints.
|
|
46
|
+
|
|
47
|
+
| Doc path | Gem method | Path used |
|
|
48
|
+
|-----------------|----------------|---------------|
|
|
49
|
+
| GET /ip/getIP | `#current` | `get("/getIP")` |
|
|
50
|
+
| POST /ip/setIP | `#set(ip:)` | `post("/setIP", params: { ip: ip })` |
|
|
51
|
+
| PUT /ip/modifyIP| `#update(ip:)` | `put("/modifyIP", params: { ip: ip })` |
|
|
52
|
+
|
|
53
|
+
---
|
|
54
|
+
|
|
55
|
+
## Trader Control / Kill Switch
|
|
56
|
+
|
|
57
|
+
- **Existing:** `Resources::KillSwitch`, `Models::KillSwitch` – path `/v2/killswitch`.
|
|
58
|
+
- **Added:** `Resources::TraderControl` – path `/trader-control`; `#status`, `#enable`, `#disable`.
|
|
59
|
+
Not found on the public docs pages checked; kept for compatibility with design that referenced trader-control.
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Base URL and paths
|
|
64
|
+
|
|
65
|
+
- Default base URL: `https://api.dhan.co/v2`.
|
|
66
|
+
- Resource `HTTP_PATH` values are relative to that base (e.g. `/edis`, `/alerts/orders`, `/ip`).
|
|
67
|
+
- Order API resources use `API_TYPE = :order_api`.
|
data/docs/AUTHENTICATION.md
CHANGED
|
@@ -23,7 +23,7 @@ Set `access_token` once; it is sent on every request:
|
|
|
23
23
|
```ruby
|
|
24
24
|
DhanHQ.configure do |config|
|
|
25
25
|
config.client_id = ENV["DHAN_CLIENT_ID"]
|
|
26
|
-
config.access_token = ENV["
|
|
26
|
+
config.access_token = ENV["DHAN_ACCESS_TOKEN"]
|
|
27
27
|
end
|
|
28
28
|
```
|
|
29
29
|
|
|
@@ -68,7 +68,7 @@ Example: refresh in provider and cache the result until near expiry:
|
|
|
68
68
|
config.access_token_provider = lambda do
|
|
69
69
|
stored = YourTokenStore.current
|
|
70
70
|
if stored.nil? || stored.expired_soon?
|
|
71
|
-
response = DhanHQ::Auth.renew_token(stored&.access_token || ENV["
|
|
71
|
+
response = DhanHQ::Auth.renew_token(stored&.access_token || ENV["DHAN_ACCESS_TOKEN"], config.client_id)
|
|
72
72
|
YourTokenStore.update!(response["accessToken"], response["expiryTime"])
|
|
73
73
|
stored = YourTokenStore.current
|
|
74
74
|
end
|
|
@@ -108,3 +108,55 @@ Rescue `AuthenticationError` for local config/token resolution failures; rescue
|
|
|
108
108
|
- [rails_integration.md](rails_integration.md) — Rails initializer with optional `access_token_provider` and RenewToken
|
|
109
109
|
- [TESTING_GUIDE.md](TESTING_GUIDE.md) — Config examples and RenewToken
|
|
110
110
|
- [CHANGELOG.md](../CHANGELOG.md) — 2.2.0 and 2.2.1 auth and token changes
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
# SUPPORTED AUTHENTICATION AND TOKEN GENERATION APPROACHES:
|
|
114
|
+
|
|
115
|
+
We now support five distinct authentication approaches in this gem.
|
|
116
|
+
1️⃣ Static token (manual, simplest)
|
|
117
|
+
What: You paste a token you got from Dhan (web, OAuth, partner, whatever) into config.
|
|
118
|
+
How:
|
|
119
|
+
DhanHQ.configure do |config| config.client_id = ENV["DHAN_CLIENT_ID"] config.access_token = ENV["DHAN_ACCESS_TOKEN"]end
|
|
120
|
+
When: You’re OK rotating tokens manually (e.g. cron job, ops runbook).
|
|
121
|
+
2️⃣ Dynamic token via access_token_provider
|
|
122
|
+
What: Gem asks you for a token on every request (proc/lambda).
|
|
123
|
+
How:
|
|
124
|
+
DhanHQ.configure do |config| config.client_id = ENV["DHAN_CLIENT_ID"] config.access_token_provider = -> { MyTokenStore.fetch_current_token } config.on_token_expired = ->(error) { MyTokenStore.refresh!(error) }end
|
|
125
|
+
Behavior:
|
|
126
|
+
On 401 (auth failure), client calls on_token_expired, then retries once using a fresh token from access_token_provider.
|
|
127
|
+
3️⃣ Fetch-from-token-endpoint (configure_from_token_endpoint)
|
|
128
|
+
What: Gem calls your HTTP endpoint once to get access_token + client_id.
|
|
129
|
+
How:
|
|
130
|
+
DhanHQ.configure_from_token_endpoint( base_url: "https://myapp.com", bearer_token: ENV["DHAN_TOKEN_ENDPOINT_BEARER"])# expects JSON: { access_token: "...", client_id: "...", base_url: "..." (optional) }
|
|
131
|
+
When: Multi-tenant or central credential service, you don’t want tokens in ENV directly.
|
|
132
|
+
4️⃣ TOTP-based token generation (new DhanHQ::Auth flow)
|
|
133
|
+
Module API (low-level)
|
|
134
|
+
What: Direct call to Dhan’s generateAccessToken endpoint using TOTP.
|
|
135
|
+
totp = DhanHQ::Auth.generate_totp(ENV["DHAN_TOTP_SECRET"])response = DhanHQ::Auth.generate_access_token( dhan_client_id: ENV["DHAN_CLIENT_ID"], pin: ENV["DHAN_PIN"], totp: totp)token = response["accessToken"]expiry = response["expiryTime"]
|
|
136
|
+
Client API (high-level, returns TokenResponse)
|
|
137
|
+
client = DhanHQ::Client.new(api_type: :order_api)token = client.generate_access_token( dhan_client_id: ENV["DHAN_CLIENT_ID"], pin: ENV["DHAN_PIN"], totp_secret: ENV["DHAN_TOTP_SECRET"] # or `totp:` if you computed it)# auto-applies token + client_id to `DhanHQ.configuration`
|
|
138
|
+
When: Fully automated individual setup (no manual web token generation).
|
|
139
|
+
5️⃣ Auto token lifecycle management (TokenManager)
|
|
140
|
+
What: Gem handles generate + renew + retry around every API call.
|
|
141
|
+
How:
|
|
142
|
+
client = DhanHQ::Client.new(api_type: :order_api)client.enable_auto_token_management!( dhan_client_id: ENV["DHAN_CLIENT_ID"], pin: ENV["DHAN_PIN"], totp_secret: ENV["DHAN_TOTP_SECRET"])# From now on, `client.request` auto-ensures a valid token.
|
|
143
|
+
Behavior:
|
|
144
|
+
On first use: calls TOTP TokenGenerator → applies token.
|
|
145
|
+
Before each request: ensure_valid_token!:
|
|
146
|
+
If no token → generate.
|
|
147
|
+
If needs_refresh? → TokenRenewal (POST /v2/RenewToken) with current token + dhanClientId.
|
|
148
|
+
If renewal fails with auth error → falls back to full generate.
|
|
149
|
+
6️⃣ Web-token renewal only (RenewToken)
|
|
150
|
+
Module API:
|
|
151
|
+
response = DhanHQ::Auth.renew_token( access_token: current_token, client_id: ENV["DHAN_CLIENT_ID"])
|
|
152
|
+
Client / manager (high-level):
|
|
153
|
+
client.renew_access_token (returns TokenResponse, updates config).
|
|
154
|
+
TokenManager#refresh! internally uses Auth::TokenRenewal.
|
|
155
|
+
When: You’re using web-generated 24h tokens and want to extend them without switching to TOTP.
|
|
156
|
+
TL;DR
|
|
157
|
+
Manual: Static token (access_token)
|
|
158
|
+
Dynamic: access_token_provider (+ optional on_token_expired)
|
|
159
|
+
Central service: configure_from_token_endpoint
|
|
160
|
+
Fully automated: TOTP generate (Auth / Client#generate_access_token)
|
|
161
|
+
Production-grade automation: enable_auto_token_management! (generate + renew)
|
|
162
|
+
If you tell me your exact deployment style (single user box, multi-user SaaS, on-prem, etc.), I can tell you which one you should actually use and what to delete as overkill.
|
data/docs/TESTING_GUIDE.md
CHANGED
|
@@ -72,8 +72,8 @@ puts "Token provider: #{DhanHQ.configuration.access_token_provider ? 'Set' : 'No
|
|
|
72
72
|
|
|
73
73
|
```ruby
|
|
74
74
|
# Check current environment variables
|
|
75
|
-
puts "
|
|
76
|
-
puts "
|
|
75
|
+
puts "DHAN_CLIENT_ID: #{ENV['DHAN_CLIENT_ID']}"
|
|
76
|
+
puts "DHAN_ACCESS_TOKEN: #{ENV['DHAN_ACCESS_TOKEN'] ? 'Set' : 'Not Set'}"
|
|
77
77
|
puts "DHAN_LOG_LEVEL: #{ENV['DHAN_LOG_LEVEL'] || 'INFO'}"
|
|
78
78
|
puts "DHAN_CONNECT_TIMEOUT: #{ENV['DHAN_CONNECT_TIMEOUT'] || '10'}"
|
|
79
79
|
puts "DHAN_READ_TIMEOUT: #{ENV['DHAN_READ_TIMEOUT'] || '30'}"
|
|
@@ -521,7 +521,7 @@ if position
|
|
|
521
521
|
to_product_type: "MARGIN",
|
|
522
522
|
quantity: position.net_qty.abs
|
|
523
523
|
)
|
|
524
|
-
|
|
524
|
+
|
|
525
525
|
if result
|
|
526
526
|
puts "Position converted successfully"
|
|
527
527
|
else
|
|
@@ -784,22 +784,22 @@ if instrument
|
|
|
784
784
|
# Get LTP
|
|
785
785
|
ltp_data = instrument.ltp
|
|
786
786
|
puts "LTP: ₹#{ltp_data[:last_price]}"
|
|
787
|
-
|
|
787
|
+
|
|
788
788
|
# Get OHLC
|
|
789
789
|
ohlc_data = instrument.ohlc
|
|
790
790
|
puts "OHLC: #{ohlc_data[:ohlc]}"
|
|
791
|
-
|
|
791
|
+
|
|
792
792
|
# Get Quote
|
|
793
793
|
quote_data = instrument.quote
|
|
794
794
|
puts "Quote: #{quote_data[:ltp]}"
|
|
795
|
-
|
|
795
|
+
|
|
796
796
|
# Get Daily Historical Data
|
|
797
797
|
daily_data = instrument.daily(
|
|
798
798
|
from_date: Date.today - 7,
|
|
799
799
|
to_date: Date.today
|
|
800
800
|
)
|
|
801
801
|
puts "Daily candles: #{daily_data.size}"
|
|
802
|
-
|
|
802
|
+
|
|
803
803
|
# Get Intraday Historical Data
|
|
804
804
|
intraday_data = instrument.intraday(
|
|
805
805
|
from_date: Date.today,
|
|
@@ -807,7 +807,7 @@ if instrument
|
|
|
807
807
|
interval: 5
|
|
808
808
|
)
|
|
809
809
|
puts "Intraday candles: #{intraday_data.size}"
|
|
810
|
-
|
|
810
|
+
|
|
811
811
|
# Get Expiry List (for F&O instruments)
|
|
812
812
|
if instrument.instrument == "FUTSTK" || instrument.instrument == "OPTSTK"
|
|
813
813
|
expiry_list = instrument.expiry_list
|
|
@@ -922,65 +922,63 @@ end
|
|
|
922
922
|
|
|
923
923
|
### EDIS
|
|
924
924
|
|
|
925
|
-
|
|
925
|
+
EDIS is resource-only per [dhanhq.co/docs/v2/edis](https://dhanhq.co/docs/v2/edis/). Use `DhanHQ::Resources::Edis`.
|
|
926
|
+
|
|
927
|
+
#### Get TPIN (GET /edis/tpin)
|
|
926
928
|
|
|
927
929
|
```ruby
|
|
928
|
-
|
|
929
|
-
|
|
930
|
-
puts "
|
|
931
|
-
puts " ISIN: #{edis_form[:isin]}"
|
|
932
|
-
puts " Quantity: #{edis_form[:quantity]}"
|
|
930
|
+
edis = DhanHQ::Resources::Edis.new
|
|
931
|
+
tpin_response = edis.tpin
|
|
932
|
+
puts "TPIN: #{tpin_response}"
|
|
933
933
|
```
|
|
934
934
|
|
|
935
|
-
####
|
|
935
|
+
#### Form (POST /edis/form)
|
|
936
936
|
|
|
937
937
|
```ruby
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
puts "
|
|
938
|
+
edis = DhanHQ::Resources::Edis.new
|
|
939
|
+
response = edis.form(isin: "INE467B01029", qty: 10, exchange: "NSE", segment: "EQ", bulk: false)
|
|
940
|
+
puts "EDIS form: #{response.is_a?(Hash) ? 'OK' : response.class}"
|
|
941
941
|
```
|
|
942
942
|
|
|
943
|
-
####
|
|
943
|
+
#### Bulk form (POST /edis/bulkform)
|
|
944
944
|
|
|
945
945
|
```ruby
|
|
946
|
-
|
|
947
|
-
|
|
948
|
-
puts "
|
|
946
|
+
edis = DhanHQ::Resources::Edis.new
|
|
947
|
+
response = edis.bulk_form(exchange: "NSE", segment: "EQ", bulk: true)
|
|
948
|
+
puts "EDIS bulk form: #{response.is_a?(Hash) ? 'OK' : response.class}"
|
|
949
949
|
```
|
|
950
950
|
|
|
951
|
-
#### Inquire
|
|
951
|
+
#### Inquire (GET /edis/inquire/{isin})
|
|
952
952
|
|
|
953
953
|
```ruby
|
|
954
|
-
|
|
955
|
-
|
|
956
|
-
|
|
957
|
-
|
|
954
|
+
edis = DhanHQ::Resources::Edis.new
|
|
955
|
+
status = edis.inquire("INE467B01029")
|
|
956
|
+
puts "EDIS status: #{status}"
|
|
957
|
+
# Or pass "ALL" for all holdings
|
|
958
|
+
all_status = edis.inquire("ALL")
|
|
958
959
|
```
|
|
959
960
|
|
|
960
961
|
### Kill Switch
|
|
961
962
|
|
|
962
|
-
####
|
|
963
|
+
#### Trader Control (resource-only)
|
|
963
964
|
|
|
964
965
|
```ruby
|
|
965
|
-
|
|
966
|
-
|
|
967
|
-
|
|
968
|
-
|
|
969
|
-
else
|
|
970
|
-
puts "Failed to activate kill switch"
|
|
971
|
-
end
|
|
966
|
+
tc = DhanHQ::Resources::TraderControl.new
|
|
967
|
+
tc.disable # Kill switch ON
|
|
968
|
+
tc.enable # Trading resumed
|
|
969
|
+
tc.status
|
|
972
970
|
```
|
|
973
971
|
|
|
974
|
-
####
|
|
972
|
+
#### Kill Switch (model, backward compatible)
|
|
975
973
|
|
|
976
974
|
```ruby
|
|
975
|
+
# Activate kill switch
|
|
976
|
+
result = DhanHQ::Models::KillSwitch.update("ACTIVATE")
|
|
977
|
+
puts result[:kill_switch_status] if result.is_a?(Hash)
|
|
978
|
+
|
|
977
979
|
# Deactivate kill switch
|
|
978
|
-
result = DhanHQ::Models::KillSwitch.update(
|
|
979
|
-
if result
|
|
980
|
-
puts "Kill switch deactivated"
|
|
981
|
-
else
|
|
982
|
-
puts "Failed to deactivate kill switch"
|
|
983
|
-
end
|
|
980
|
+
result = DhanHQ::Models::KillSwitch.update("DEACTIVATE")
|
|
981
|
+
puts result[:kill_switch_status] if result.is_a?(Hash)
|
|
984
982
|
```
|
|
985
983
|
|
|
986
984
|
### Expired Options Data
|
data/docs/live_order_updates.md
CHANGED
|
@@ -194,8 +194,8 @@ client.start
|
|
|
194
194
|
### Environment Variables
|
|
195
195
|
```bash
|
|
196
196
|
# Required
|
|
197
|
-
|
|
198
|
-
|
|
197
|
+
DHAN_CLIENT_ID=your_client_id
|
|
198
|
+
DHAN_ACCESS_TOKEN=your_access_token
|
|
199
199
|
|
|
200
200
|
# Optional WebSocket settings
|
|
201
201
|
DHAN_WS_ORDER_URL=wss://api-order-update.dhan.co
|
data/docs/rails_integration.md
CHANGED
|
@@ -29,8 +29,8 @@ boot successfully:
|
|
|
29
29
|
|
|
30
30
|
| Variable | Description |
|
|
31
31
|
| --- | --- |
|
|
32
|
-
| `
|
|
33
|
-
| `
|
|
32
|
+
| `DHAN_CLIENT_ID` | Dhan trading client id for the account you want to trade with. |
|
|
33
|
+
| `DHAN_ACCESS_TOKEN` | REST/WebSocket access token (regenerate via the Dhan console or APIs). |
|
|
34
34
|
|
|
35
35
|
```bash
|
|
36
36
|
bin/rails credentials:edit
|
|
@@ -61,8 +61,8 @@ environment variables (Rails credentials can be copied into ENV on boot):
|
|
|
61
61
|
require 'dhan_hq'
|
|
62
62
|
|
|
63
63
|
if (creds = Rails.application.credentials.dig(:dhanhq))
|
|
64
|
-
ENV['
|
|
65
|
-
ENV['
|
|
64
|
+
ENV['DHAN_CLIENT_ID'] ||= creds[:client_id]
|
|
65
|
+
ENV['DHAN_ACCESS_TOKEN'] ||= creds[:access_token]
|
|
66
66
|
ENV['DHAN_LOG_LEVEL'] ||= creds[:log_level]&.upcase
|
|
67
67
|
ENV['DHAN_BASE_URL'] ||= creds[:base_url]
|
|
68
68
|
ENV['DHAN_WS_ORDER_URL'] ||= creds[:ws_order_url]
|
|
@@ -77,8 +77,8 @@ if (creds = Rails.application.credentials.dig(:dhanhq))
|
|
|
77
77
|
end
|
|
78
78
|
|
|
79
79
|
# fall back to traditional ENV variables when credentials are not defined
|
|
80
|
-
ENV['
|
|
81
|
-
ENV['
|
|
80
|
+
ENV['DHAN_CLIENT_ID'] ||= ENV.fetch('DHAN_CLIENT_ID', nil)
|
|
81
|
+
ENV['DHAN_ACCESS_TOKEN'] ||= ENV.fetch('DHAN_ACCESS_TOKEN', nil)
|
|
82
82
|
|
|
83
83
|
DhanHQ.configure_with_env
|
|
84
84
|
|
|
@@ -108,7 +108,7 @@ initializer calls `DhanHQ.configure_with_env`.
|
|
|
108
108
|
For token rotation without restarting the app (e.g. token stored in DB or refreshed via OAuth), use `access_token_provider` so the token is resolved at request time:
|
|
109
109
|
|
|
110
110
|
```ruby
|
|
111
|
-
# config/initializers/dhanhq.rb (alternative to static
|
|
111
|
+
# config/initializers/dhanhq.rb (alternative to static DHAN_ACCESS_TOKEN)
|
|
112
112
|
DhanHQ.configure do |config|
|
|
113
113
|
config.client_id = ENV["DHAN_CLIENT_ID"] || Rails.application.credentials.dig(:dhanhq, :client_id)
|
|
114
114
|
config.access_token_provider = lambda do
|