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.
Files changed (46) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +33 -0
  3. data/CODE_REVIEW_ISSUES.md +2 -2
  4. data/GUIDE.md +63 -21
  5. data/README.md +16 -5
  6. data/README1.md +4 -4
  7. data/REVIEW_SUMMARY.md +2 -2
  8. data/docs/API_VERIFICATION.md +67 -0
  9. data/docs/AUTHENTICATION.md +54 -2
  10. data/docs/TESTING_GUIDE.md +40 -42
  11. data/docs/live_order_updates.md +2 -2
  12. data/docs/rails_integration.md +7 -7
  13. data/docs/standalone_ruby_websocket_integration.md +24 -24
  14. data/docs/technical_analysis.md +1 -1
  15. data/docs/websocket_integration.md +4 -4
  16. data/examples/comprehensive_websocket_examples.rb +2 -2
  17. data/examples/instrument_finder_test.rb +2 -2
  18. data/examples/market_depth_example.rb +2 -2
  19. data/examples/market_feed_example.rb +2 -2
  20. data/examples/order_update_example.rb +2 -2
  21. data/examples/trading_fields_example.rb +2 -2
  22. data/lib/DhanHQ/auth/token_generator.rb +33 -0
  23. data/lib/DhanHQ/auth/token_manager.rb +88 -0
  24. data/lib/DhanHQ/auth/token_renewal.rb +25 -0
  25. data/lib/DhanHQ/auth.rb +91 -31
  26. data/lib/DhanHQ/client.rb +42 -2
  27. data/lib/DhanHQ/configuration.rb +2 -2
  28. data/lib/DhanHQ/contracts/alert_order_contract.rb +20 -0
  29. data/lib/DhanHQ/contracts/order_contract.rb +0 -23
  30. data/lib/DhanHQ/contracts/trade_by_order_id_contract.rb +12 -0
  31. data/lib/DhanHQ/contracts/trade_contract.rb +0 -65
  32. data/lib/DhanHQ/contracts/trade_history_contract.rb +52 -0
  33. data/lib/DhanHQ/core/auth_api.rb +21 -0
  34. data/lib/DhanHQ/core/base_resource.rb +5 -5
  35. data/lib/DhanHQ/helpers/request_helper.rb +1 -1
  36. data/lib/DhanHQ/models/alert_order.rb +83 -0
  37. data/lib/DhanHQ/models/token_response.rb +88 -0
  38. data/lib/DhanHQ/resources/alert_orders.rb +11 -0
  39. data/lib/DhanHQ/resources/edis.rb +5 -20
  40. data/lib/DhanHQ/resources/ip_setup.rb +23 -0
  41. data/lib/DhanHQ/resources/trader_control.rb +23 -0
  42. data/lib/DhanHQ/version.rb +1 -1
  43. data/lib/dhan_hq.rb +31 -77
  44. metadata +43 -4
  45. data/lib/DhanHQ/config.rb +0 -33
  46. data/lib/DhanHQ/models/edis.rb +0 -194
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: '09510cd95013213714d5ba4878e87dfa6bd5968cb90ea4e0a539ddb0d05bc2b0'
4
- data.tar.gz: 459c316d0f2c68ad8b0556518dfd70762e460e5c68ef063439e73f68ca33ba02
3
+ metadata.gz: b583e87aa598f3891e937f44859f9b451fc8f6bcec7a304cc0c33790fe9117ea
4
+ data.tar.gz: 1a090fa608fba141da311ba8aaf21d36e750f78e90497f728c5f96d580044c7e
5
5
  SHA512:
6
- metadata.gz: d54cf0cc58e82d1566f525303ea25fddd051b1c838152da39f12509435a16e4005f55417d5516c52f6222b4ba6286c421202d0a148f743be7ca4127b7ef03bae
7
- data.tar.gz: ea522dfeb54054d8162d00ead357efb4c444c3dd0c4c182c4d688a2bd9a484d44af7b2bab221476c93fded44511bfff7e8cf4b75d6e962fc8191486bee5440b1
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)
@@ -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("CLIENT_ID", nil)
15
+ DhanHQ.configure_with_env if ENV.fetch("DHAN_CLIENT_ID", nil)
16
16
  ```
17
- **Problem**: If `CLIENT_ID` exists but `ACCESS_TOKEN` doesn't, configuration will be partially initialized, leading to authentication failures later.
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
- | `CLIENT_ID` | Your Dhan trading client id. |
48
- | `ACCESS_TOKEN` | REST/WebSocket access token generated via Dhan APIs. |
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
- ### EDIS (Electronic Delivery Instruction Slip)
644
+ ### Alert Orders
645
645
 
646
646
  ```ruby
647
- # Generate a CDSL form for a single ISIN
648
- form = DhanHQ::Models::Edis.form(
649
- isin: "INE0ABCDE123",
650
- qty: 1,
651
- exchange: "NSE",
652
- segment: "EQ",
653
- bulk: false
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
- # Prepare a bulk file
657
- bulk_form = DhanHQ::Models::Edis.bulk_form(
658
- isin: %w[INE0ABCDE123 INE0XYZ89012],
659
- exchange: "NSE",
660
- segment: "EQ"
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
- # Manage T-PIN and status inquiries
664
- DhanHQ::Models::Edis.tpin # => {"status"=>"TPIN sent"}
665
- authorisations = DhanHQ::Models::Edis.inquire("ALL")
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
- All helpers accept snake_case keys; the client camelizes them before calling `/v2/edis/...`.
710
+ ### Kill Switch (model)
669
711
 
670
- ### Kill Switch
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
- | `CLIENT_ID` | Trading account client id issued by Dhan. |
70
- | `ACCESS_TOKEN` | API access token generated from the Dhan console. |
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
- **RenewToken (web-generated tokens):** For tokens generated from Dhan Web (24h validity), use `DhanHQ::Auth.renew_token(access_token, client_id)` to refresh; use the returned token in your provider or store. The gem does **not** implement API key/secret or Partner consent flows—implement those in your app and pass the token to the gem. See [docs/AUTHENTICATION.md](docs/AUTHENTICATION.md).
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
- * `CLIENT_ID` – trading account client id issued by Dhan.
49
- * `ACCESS_TOKEN` – API access token generated from the Dhan console.
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
- CLIENT_ID=your_client_id
68
- ACCESS_TOKEN=your_access_token
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 `CLIENT_ID` and `ACCESS_TOKEN` before proceeding
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`.
@@ -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["ACCESS_TOKEN"]
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["ACCESS_TOKEN"], config.client_id)
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.
@@ -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 "CLIENT_ID: #{ENV['CLIENT_ID']}"
76
- puts "ACCESS_TOKEN: #{ENV['ACCESS_TOKEN'] ? 'Set' : 'Not Set'}"
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
- #### Get EDIS Form
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
- # Get EDIS form
929
- edis_form = DhanHQ::Models::Edis.form
930
- puts "EDIS Form:"
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
- #### Get Bulk EDIS Form
935
+ #### Form (POST /edis/form)
936
936
 
937
937
  ```ruby
938
- # Get bulk EDIS form
939
- bulk_form = DhanHQ::Models::Edis.bulk_form
940
- puts "Bulk Form: #{bulk_form.size} entries"
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
- #### Generate TPIN
943
+ #### Bulk form (POST /edis/bulkform)
944
944
 
945
945
  ```ruby
946
- # Generate TPIN
947
- tpin = DhanHQ::Models::Edis.generate_tpin
948
- puts "TPIN: #{tpin[:tpin]}"
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 EDIS Status
951
+ #### Inquire (GET /edis/inquire/{isin})
952
952
 
953
953
  ```ruby
954
- # Inquire EDIS status by ISIN
955
- isin = "INE467B01029" # Example ISIN
956
- status = DhanHQ::Models::Edis.inquire(isin: isin)
957
- puts "EDIS Status: #{status[:status]}"
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
- #### Activate Kill Switch
963
+ #### Trader Control (resource-only)
963
964
 
964
965
  ```ruby
965
- # Activate kill switch
966
- result = DhanHQ::Models::KillSwitch.update(status: "ACTIVATE")
967
- if result
968
- puts "Kill switch activated"
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
- #### Deactivate Kill Switch
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(status: "DEACTIVATE")
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
@@ -194,8 +194,8 @@ client.start
194
194
  ### Environment Variables
195
195
  ```bash
196
196
  # Required
197
- CLIENT_ID=your_client_id
198
- ACCESS_TOKEN=your_access_token
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
@@ -29,8 +29,8 @@ boot successfully:
29
29
 
30
30
  | Variable | Description |
31
31
  | --- | --- |
32
- | `CLIENT_ID` | Dhan trading client id for the account you want to trade with. |
33
- | `ACCESS_TOKEN` | REST/WebSocket access token (regenerate via the Dhan console or APIs). |
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['CLIENT_ID'] ||= creds[:client_id]
65
- ENV['ACCESS_TOKEN'] ||= creds[:access_token]
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['CLIENT_ID'] ||= ENV.fetch('DHAN_CLIENT_ID', nil)
81
- ENV['ACCESS_TOKEN'] ||= ENV.fetch('DHAN_ACCESS_TOKEN', nil)
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 ACCESS_TOKEN)
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