DhanHQ 2.3.0 → 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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 26a3e58a35f27dcc1902c2ec1c6849f0e03f80b5123c6ac310588c8e7a0d3a58
4
- data.tar.gz: 516cc99f9e3a61f8ec261788babc4309f1b6acc97b1c0ef98f5dbe22b1499217
3
+ metadata.gz: b583e87aa598f3891e937f44859f9b451fc8f6bcec7a304cc0c33790fe9117ea
4
+ data.tar.gz: 1a090fa608fba141da311ba8aaf21d36e750f78e90497f728c5f96d580044c7e
5
5
  SHA512:
6
- metadata.gz: ce920dd5a3804a4b7ddc41867f742ea187fe38255c1468c4a0f1528641dc144752571591de74812241b2d985c40381604a235032250eb4580d6e3b4ad2ffc0bc
7
- data.tar.gz: 418f01c9b4bebc8bda9e8d20567dc6d157b60fad7e00eda2522106fd9abfdea607142179e50f2c79c7cfa45a246941be252263354737a8279cb8ab7152d0b9b3
6
+ metadata.gz: '0966f2a2aa331b675ecc1675510f6ad3dca6b7a987c8dde15dde2efe506ac659157b6e0979d0c23f480d2835fa603bfd37530f33275d422226abb2419e46bfd0'
7
+ data.tar.gz: 74ea10d0744fb01324afd4a1768fa6969b07f4d1fcb9da94d7a6caf01fd01c2863d6a3746d288628d10e497bd55c68687e0b54e237bd91736ad4bb8c1decc9f8
data/CHANGELOG.md CHANGED
@@ -2,6 +2,23 @@
2
2
 
3
3
  ---
4
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
+
5
22
  ## [2.3.0] - 2026-02-04
6
23
 
7
24
  ### Added
@@ -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.
data/README.md CHANGED
@@ -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`
@@ -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
@@ -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
@@ -31,8 +31,8 @@ require 'dhan_hq'
31
31
 
32
32
  # Configure DhanHQ
33
33
  DhanHQ.configure do |config|
34
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
35
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
34
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
35
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
36
36
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
37
37
  end
38
38
 
@@ -59,8 +59,8 @@ market_client.stop
59
59
 
60
60
  ```bash
61
61
  # Set environment variables
62
- export CLIENT_ID="your_client_id"
63
- export ACCESS_TOKEN="your_access_token"
62
+ export DHAN_CLIENT_ID="your_client_id"
63
+ export DHAN_ACCESS_TOKEN="your_access_token"
64
64
 
65
65
  # Run the script
66
66
  ruby market_feed_script.rb
@@ -74,8 +74,8 @@ For dynamic token at request time use `config.access_token_provider`. For web-ge
74
74
 
75
75
  ```bash
76
76
  # Required
77
- export CLIENT_ID="your_client_id"
78
- export ACCESS_TOKEN="your_access_token"
77
+ export DHAN_CLIENT_ID="your_client_id"
78
+ export DHAN_ACCESS_TOKEN="your_access_token"
79
79
 
80
80
  # Optional
81
81
  export DHAN_WS_USER_TYPE="SELF" # or "PARTNER"
@@ -132,8 +132,8 @@ require 'json'
132
132
 
133
133
  # Configure DhanHQ
134
134
  DhanHQ.configure do |config|
135
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
136
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
135
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
136
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
137
137
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
138
138
  end
139
139
 
@@ -264,8 +264,8 @@ require 'json'
264
264
 
265
265
  # Configure DhanHQ
266
266
  DhanHQ.configure do |config|
267
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
268
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
267
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
268
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
269
269
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
270
270
  end
271
271
 
@@ -394,8 +394,8 @@ require 'json'
394
394
 
395
395
  # Configure DhanHQ
396
396
  DhanHQ.configure do |config|
397
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
398
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
397
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
398
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
399
399
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
400
400
  end
401
401
 
@@ -542,8 +542,8 @@ WorkingDirectory=/opt/dhanhq-market-feed
542
542
  ExecStart=/usr/bin/ruby market_feed_daemon.rb
543
543
  Restart=always
544
544
  RestartSec=10
545
- Environment=CLIENT_ID=your_client_id
546
- Environment=ACCESS_TOKEN=your_access_token
545
+ Environment=DHAN_CLIENT_ID=your_client_id
546
+ Environment=DHAN_ACCESS_TOKEN=your_access_token
547
547
  Environment=DHAN_WS_USER_TYPE=SELF
548
548
 
549
549
  [Install]
@@ -562,8 +562,8 @@ require 'fileutils'
562
562
 
563
563
  # Configure DhanHQ
564
564
  DhanHQ.configure do |config|
565
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
566
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
565
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
566
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
567
567
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
568
568
  end
569
569
 
@@ -779,8 +779,8 @@ require 'json'
779
779
 
780
780
  # Configure DhanHQ
781
781
  DhanHQ.configure do |config|
782
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
783
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
782
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
783
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
784
784
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
785
785
  end
786
786
 
@@ -1061,8 +1061,8 @@ require 'json'
1061
1061
 
1062
1062
  # Configure DhanHQ
1063
1063
  DhanHQ.configure do |config|
1064
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
1065
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
1064
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
1065
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
1066
1066
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
1067
1067
  end
1068
1068
 
@@ -1264,8 +1264,8 @@ end
1264
1264
 
1265
1265
  # Configure DhanHQ
1266
1266
  DhanHQ.configure do |config|
1267
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
1268
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
1267
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
1268
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
1269
1269
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
1270
1270
  config.logger = logger
1271
1271
  end
@@ -1383,8 +1383,8 @@ require 'json'
1383
1383
 
1384
1384
  # Configure DhanHQ
1385
1385
  DhanHQ.configure do |config|
1386
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
1387
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
1386
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
1387
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
1388
1388
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
1389
1389
  end
1390
1390
 
@@ -12,7 +12,7 @@ This guide explains how to use the technical analysis modules bundled with this
12
12
 
13
13
  ## Prerequisites
14
14
 
15
- - Environment variables set: `CLIENT_ID`, `ACCESS_TOKEN`
15
+ - Environment variables set: `DHAN_CLIENT_ID`, `DHAN_ACCESS_TOKEN`
16
16
  - Optional indicator gems:
17
17
  - `gem install ruby-technical-analysis technical-analysis`
18
18
 
@@ -28,8 +28,8 @@ require 'dhan_hq'
28
28
 
29
29
  # Configure DhanHQ
30
30
  DhanHQ.configure do |config|
31
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
32
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
31
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
32
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
33
33
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
34
34
  end
35
35
  # For dynamic token: use config.access_token_provider. For web-generated tokens, refresh with DhanHQ::Auth.renew_token. API key/Partner flows: implement in your app. See docs/AUTHENTICATION.md.
@@ -787,8 +787,8 @@ puts "Depth subscriptions: #{depth_client.subscriptions}"
787
787
  ```ruby
788
788
  # ✅ Good: Environment variables
789
789
  DhanHQ.configure do |config|
790
- config.client_id = ENV["CLIENT_ID"]
791
- config.access_token = ENV["ACCESS_TOKEN"]
790
+ config.client_id = ENV["DHAN_CLIENT_ID"]
791
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"]
792
792
  end
793
793
 
794
794
  # ❌ Bad: Hardcoded credentials
@@ -12,8 +12,8 @@ require "dhan_hq"
12
12
 
13
13
  # Configure DhanHQ
14
14
  DhanHQ.configure do |config|
15
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
16
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
15
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
16
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
17
17
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
18
18
  end
19
19
 
@@ -8,8 +8,8 @@ require "dhan_hq"
8
8
 
9
9
  # Configure DhanHQ
10
10
  DhanHQ.configure do |config|
11
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
12
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
11
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
12
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
13
13
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
14
14
  end
15
15
 
@@ -10,8 +10,8 @@ require "dhan_hq"
10
10
 
11
11
  # Configure DhanHQ
12
12
  DhanHQ.configure do |config|
13
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
14
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
13
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
14
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
15
15
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
16
16
  end
17
17
 
@@ -10,8 +10,8 @@ require "dhan_hq"
10
10
 
11
11
  # Configure DhanHQ
12
12
  DhanHQ.configure do |config|
13
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
14
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
13
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
14
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
15
15
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
16
16
  end
17
17
 
@@ -10,8 +10,8 @@ require "dhan_hq"
10
10
 
11
11
  # Configure DhanHQ
12
12
  DhanHQ.configure do |config|
13
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
14
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
13
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
14
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
15
15
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
16
16
  end
17
17
 
@@ -9,8 +9,8 @@ require "dhan_hq"
9
9
 
10
10
  # Configure DhanHQ
11
11
  DhanHQ.configure do |config|
12
- config.client_id = ENV["CLIENT_ID"] || "your_client_id"
13
- config.access_token = ENV["ACCESS_TOKEN"] || "your_access_token"
12
+ config.client_id = ENV["DHAN_CLIENT_ID"] || "your_client_id"
13
+ config.access_token = ENV["DHAN_ACCESS_TOKEN"] || "your_access_token"
14
14
  config.ws_user_type = ENV["DHAN_WS_USER_TYPE"] || "SELF"
15
15
  end
16
16
 
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DhanHQ
4
+ module Auth
5
+ # Backward-compatible wrapper for token generation.
6
+ # Delegates to module-level Auth.generate_access_token.
7
+ class TokenGenerator
8
+ def generate(dhan_client_id:, pin:, totp: nil, totp_secret: nil)
9
+ otp = resolve_totp(totp, totp_secret)
10
+
11
+ response = Auth.generate_access_token(
12
+ dhan_client_id: dhan_client_id,
13
+ pin: pin,
14
+ totp: otp
15
+ )
16
+
17
+ Models::TokenResponse.new(response)
18
+ end
19
+
20
+ private
21
+
22
+ def resolve_totp(totp, secret)
23
+ totp = totp.to_s.strip
24
+ return totp unless totp.empty?
25
+
26
+ secret = secret.to_s.strip
27
+ raise ArgumentError, "Provide `totp` or `totp_secret` (e.g. ENV['DHAN_TOTP_SECRET'])" if secret.empty?
28
+
29
+ Auth.generate_totp(secret)
30
+ end
31
+ end
32
+ end
33
+ end