blue-js-sdk 2.0.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 (215) hide show
  1. package/CHANGELOG.md +446 -0
  2. package/LICENSE +21 -0
  3. package/README.md +75 -0
  4. package/ai-path/ADMIN-ELEVATION.md +116 -0
  5. package/ai-path/AI-MANIFESTO.md +185 -0
  6. package/ai-path/BREAKING.md +74 -0
  7. package/ai-path/CHECKLIST.md +619 -0
  8. package/ai-path/CONNECTION-STEPS.md +724 -0
  9. package/ai-path/DECISION-TREE.md +378 -0
  10. package/ai-path/DEPENDENCIES.md +459 -0
  11. package/ai-path/E2E-FLOW.md +1555 -0
  12. package/ai-path/FAILURES.md +403 -0
  13. package/ai-path/GUIDE.md +1217 -0
  14. package/ai-path/README.md +558 -0
  15. package/ai-path/SPLIT-TUNNEL.md +266 -0
  16. package/ai-path/cli.js +535 -0
  17. package/ai-path/connect.js +884 -0
  18. package/ai-path/discover.js +178 -0
  19. package/ai-path/environment.js +266 -0
  20. package/ai-path/errors.js +86 -0
  21. package/ai-path/examples/autonomous-agent.mjs +220 -0
  22. package/ai-path/examples/multi-region.mjs +174 -0
  23. package/ai-path/examples/one-shot.mjs +31 -0
  24. package/ai-path/index.js +60 -0
  25. package/ai-path/pricing.js +136 -0
  26. package/ai-path/recommend.js +413 -0
  27. package/ai-path/run-admin.vbs +25 -0
  28. package/ai-path/setup.js +291 -0
  29. package/ai-path/wallet.js +137 -0
  30. package/app-helpers.js +363 -0
  31. package/app-settings.js +95 -0
  32. package/app-types.js +267 -0
  33. package/audit.js +847 -0
  34. package/batch.js +293 -0
  35. package/bin/setup.js +376 -0
  36. package/chain/authz.js +109 -0
  37. package/chain/broadcast.js +472 -0
  38. package/chain/client.js +160 -0
  39. package/chain/fee-grants.js +305 -0
  40. package/chain/index.js +891 -0
  41. package/chain/lcd.js +313 -0
  42. package/chain/queries.js +547 -0
  43. package/chain/rpc.js +408 -0
  44. package/chain/wallet.js +141 -0
  45. package/cli/config.js +143 -0
  46. package/cli/index.js +463 -0
  47. package/cli/output.js +182 -0
  48. package/cli.js +491 -0
  49. package/client/index.js +251 -0
  50. package/client.js +271 -0
  51. package/config/index.js +255 -0
  52. package/connection/connect.js +849 -0
  53. package/connection/disconnect.js +180 -0
  54. package/connection/discovery.js +321 -0
  55. package/connection/index.js +76 -0
  56. package/connection/proxy.js +148 -0
  57. package/connection/resilience.js +428 -0
  58. package/connection/security.js +232 -0
  59. package/connection/state.js +369 -0
  60. package/connection/tunnel.js +691 -0
  61. package/consumer.js +132 -0
  62. package/cosmjs-setup.js +1884 -0
  63. package/defaults.js +366 -0
  64. package/disk-cache.js +107 -0
  65. package/dist/client.d.ts +108 -0
  66. package/dist/client.d.ts.map +1 -0
  67. package/dist/client.js +400 -0
  68. package/dist/client.js.map +1 -0
  69. package/dist/index.d.ts +8 -0
  70. package/dist/index.d.ts.map +1 -0
  71. package/dist/index.js +8 -0
  72. package/dist/index.js.map +1 -0
  73. package/errors/index.js +112 -0
  74. package/errors.js +218 -0
  75. package/examples/README.md +64 -0
  76. package/examples/connect-direct.mjs +106 -0
  77. package/examples/connect-plan.mjs +125 -0
  78. package/examples/error-handling.mjs +109 -0
  79. package/examples/query-nodes.mjs +94 -0
  80. package/examples/wallet-basics.mjs +61 -0
  81. package/generated/amino/amino.ts +9 -0
  82. package/generated/cosmos/base/v1beta1/coin.ts +365 -0
  83. package/generated/cosmos_proto/cosmos.ts +323 -0
  84. package/generated/gogoproto/gogo.ts +9 -0
  85. package/generated/google/protobuf/descriptor.ts +7601 -0
  86. package/generated/google/protobuf/duration.ts +208 -0
  87. package/generated/google/protobuf/timestamp.ts +238 -0
  88. package/generated/sentinel/lease/v1/events.ts +924 -0
  89. package/generated/sentinel/lease/v1/lease.ts +292 -0
  90. package/generated/sentinel/lease/v1/msg.ts +949 -0
  91. package/generated/sentinel/lease/v1/params.ts +164 -0
  92. package/generated/sentinel/node/v3/events.ts +881 -0
  93. package/generated/sentinel/node/v3/msg.ts +1002 -0
  94. package/generated/sentinel/node/v3/node.ts +263 -0
  95. package/generated/sentinel/node/v3/params.ts +183 -0
  96. package/generated/sentinel/plan/v3/events.ts +675 -0
  97. package/generated/sentinel/plan/v3/msg.ts +1191 -0
  98. package/generated/sentinel/plan/v3/plan.ts +283 -0
  99. package/generated/sentinel/provider/v2/events.ts +171 -0
  100. package/generated/sentinel/provider/v2/msg.ts +480 -0
  101. package/generated/sentinel/provider/v2/params.ts +131 -0
  102. package/generated/sentinel/provider/v2/provider.ts +246 -0
  103. package/generated/sentinel/session/v3/events.ts +480 -0
  104. package/generated/sentinel/session/v3/msg.ts +616 -0
  105. package/generated/sentinel/session/v3/params.ts +260 -0
  106. package/generated/sentinel/session/v3/proof.ts +180 -0
  107. package/generated/sentinel/session/v3/session.ts +384 -0
  108. package/generated/sentinel/subscription/v3/events.ts +1181 -0
  109. package/generated/sentinel/subscription/v3/msg.ts +1305 -0
  110. package/generated/sentinel/subscription/v3/params.ts +167 -0
  111. package/generated/sentinel/subscription/v3/subscription.ts +315 -0
  112. package/generated/sentinel/types/v1/bandwidth.ts +124 -0
  113. package/generated/sentinel/types/v1/price.ts +149 -0
  114. package/generated/sentinel/types/v1/renewal.ts +87 -0
  115. package/generated/sentinel/types/v1/status.ts +54 -0
  116. package/generated/typeRegistry.ts +27 -0
  117. package/index.js +486 -0
  118. package/node-connect.js +3015 -0
  119. package/operator.js +134 -0
  120. package/package.json +113 -0
  121. package/plan-operations.js +199 -0
  122. package/preflight.js +352 -0
  123. package/pricing/index.js +262 -0
  124. package/proto/amino/amino.proto +84 -0
  125. package/proto/cosmos/base/v1beta1/coin.proto +61 -0
  126. package/proto/cosmos_proto/cosmos.proto +112 -0
  127. package/proto/gogoproto/gogo.proto +145 -0
  128. package/proto/google/api/annotations.proto +31 -0
  129. package/proto/google/api/http.proto +370 -0
  130. package/proto/google/protobuf/any.proto +106 -0
  131. package/proto/google/protobuf/duration.proto +115 -0
  132. package/proto/google/protobuf/timestamp.proto +145 -0
  133. package/proto/sentinel/lease/v1/events.proto +52 -0
  134. package/proto/sentinel/lease/v1/genesis.proto +15 -0
  135. package/proto/sentinel/lease/v1/lease.proto +25 -0
  136. package/proto/sentinel/lease/v1/msg.proto +62 -0
  137. package/proto/sentinel/lease/v1/params.proto +17 -0
  138. package/proto/sentinel/node/v3/events.proto +50 -0
  139. package/proto/sentinel/node/v3/genesis.proto +15 -0
  140. package/proto/sentinel/node/v3/msg.proto +63 -0
  141. package/proto/sentinel/node/v3/node.proto +27 -0
  142. package/proto/sentinel/node/v3/params.proto +21 -0
  143. package/proto/sentinel/node/v3/querier.proto +63 -0
  144. package/proto/sentinel/plan/v3/events.proto +41 -0
  145. package/proto/sentinel/plan/v3/genesis.proto +21 -0
  146. package/proto/sentinel/plan/v3/msg.proto +83 -0
  147. package/proto/sentinel/plan/v3/plan.proto +32 -0
  148. package/proto/sentinel/plan/v3/querier.proto +53 -0
  149. package/proto/sentinel/provider/v2/events.proto +16 -0
  150. package/proto/sentinel/provider/v2/genesis.proto +15 -0
  151. package/proto/sentinel/provider/v2/msg.proto +35 -0
  152. package/proto/sentinel/provider/v2/params.proto +17 -0
  153. package/proto/sentinel/provider/v2/provider.proto +24 -0
  154. package/proto/sentinel/provider/v3/genesis.proto +15 -0
  155. package/proto/sentinel/provider/v3/params.proto +13 -0
  156. package/proto/sentinel/session/v3/events.proto +30 -0
  157. package/proto/sentinel/session/v3/genesis.proto +15 -0
  158. package/proto/sentinel/session/v3/msg.proto +50 -0
  159. package/proto/sentinel/session/v3/params.proto +25 -0
  160. package/proto/sentinel/session/v3/proof.proto +25 -0
  161. package/proto/sentinel/session/v3/querier.proto +100 -0
  162. package/proto/sentinel/session/v3/session.proto +50 -0
  163. package/proto/sentinel/subscription/v2/allocation.proto +21 -0
  164. package/proto/sentinel/subscription/v2/payout.proto +22 -0
  165. package/proto/sentinel/subscription/v3/events.proto +65 -0
  166. package/proto/sentinel/subscription/v3/genesis.proto +17 -0
  167. package/proto/sentinel/subscription/v3/msg.proto +83 -0
  168. package/proto/sentinel/subscription/v3/params.proto +21 -0
  169. package/proto/sentinel/subscription/v3/subscription.proto +33 -0
  170. package/proto/sentinel/types/v1/bandwidth.proto +19 -0
  171. package/proto/sentinel/types/v1/price.proto +21 -0
  172. package/proto/sentinel/types/v1/renewal.proto +21 -0
  173. package/proto/sentinel/types/v1/status.proto +16 -0
  174. package/protocol/encoding.js +341 -0
  175. package/protocol/events.js +361 -0
  176. package/protocol/handshake.js +297 -0
  177. package/protocol/index.js +15 -0
  178. package/protocol/messages.js +346 -0
  179. package/protocol/plans.js +199 -0
  180. package/protocol/v2ray.js +268 -0
  181. package/protocol/v3.js +723 -0
  182. package/protocol/wireguard.js +125 -0
  183. package/security/index.js +132 -0
  184. package/session-manager.js +329 -0
  185. package/session-tracker.js +80 -0
  186. package/setup.js +376 -0
  187. package/speedtest/index.js +528 -0
  188. package/speedtest.js +567 -0
  189. package/src/client.ts +502 -0
  190. package/src/index.ts +20 -0
  191. package/state/index.js +347 -0
  192. package/state.js +516 -0
  193. package/test-all-chain-ops.js +493 -0
  194. package/test-all-logic.js +199 -0
  195. package/test-all-msg-types.js +292 -0
  196. package/test-every-connection.js +208 -0
  197. package/test-feegrant-connect.js +98 -0
  198. package/test-logic.js +148 -0
  199. package/test-mainnet.js +176 -0
  200. package/test-plan-lifecycle.js +335 -0
  201. package/tls-trust.js +132 -0
  202. package/tsconfig.build.json +20 -0
  203. package/tsconfig.json +34 -0
  204. package/types/chain.d.ts +746 -0
  205. package/types/connection.d.ts +425 -0
  206. package/types/errors.d.ts +174 -0
  207. package/types/index.d.ts +1380 -0
  208. package/types/nodes.d.ts +187 -0
  209. package/types/pricing.d.ts +156 -0
  210. package/types/protocol.d.ts +332 -0
  211. package/types/session.d.ts +236 -0
  212. package/types/settings.d.ts +192 -0
  213. package/v3protocol.js +1053 -0
  214. package/wallet/index.js +153 -0
  215. package/wireguard.js +307 -0
package/defaults.js ADDED
@@ -0,0 +1,366 @@
1
+ /**
2
+ * Sentinel dVPN SDK — Hardcoded Defaults & Recommended Values
3
+ *
4
+ * SINGLE SOURCE OF TRUTH for all values that may go stale.
5
+ * When the RPC query server is built, this file gets replaced by live lookups.
6
+ *
7
+ * ┌──────────────────────────────────────────────────────────────────────┐
8
+ * │ LAST VERIFIED: 2026-03-08T00:00:00Z │
9
+ * │ VERIFIED BY: 708-node scan + manual LCD/RPC checks │
10
+ * │ CHAIN: sentinelhub-2 (sentinelhub v12.0.0, Cosmos 0.47.17)│
11
+ * │ │
12
+ * │ These values are HARDCODED for easy cold-start setup. │
13
+ * │ A future RPC query server will replace static defaults with live │
14
+ * │ endpoint health checks, node scoring, and price feeds. │
15
+ * └──────────────────────────────────────────────────────────────────────┘
16
+ */
17
+
18
+ // ─── Axios adapter fix (MUST run before any HTTP requests) ──────────────────
19
+ // Node.js 18+ uses undici internally. Without this, ~40% of HTTP requests
20
+ // fail with opaque "fetch failed" errors (no stack trace, no errno).
21
+ import axios from 'axios';
22
+ axios.defaults.adapter = 'http';
23
+
24
+ // ─── SDK Version ─────────────────────────────────────────────────────────────
25
+ // This is the npm/semver version for consumers. Internal development iterations
26
+ // (v20, v21, v22, etc.) track feature milestones and are not exposed as exports.
27
+
28
+ export const SDK_VERSION = '1.0.0';
29
+
30
+ // ─── Timestamps ──────────────────────────────────────────────────────────────
31
+
32
+ /** When these defaults were last verified against the live chain */
33
+ export const LAST_VERIFIED = '2026-03-08T00:00:00Z';
34
+
35
+ /** Human-readable note for builders */
36
+ export const HARDCODED_NOTE = 'Static defaults — no RPC query server yet. Verify endpoints are live before production use. See README.md "Hardcoded Defaults" section.';
37
+
38
+ // ─── Chain ───────────────────────────────────────────────────────────────────
39
+
40
+ export const CHAIN_ID = 'sentinelhub-2';
41
+ export const DENOM = 'udvpn';
42
+ export const GAS_PRICE = '0.2udvpn'; // Chain minimum as of 2026-03-08
43
+ export const CHAIN_VERSION = 'v12.0.0'; // sentinelhub version
44
+ export const COSMOS_SDK_VERSION = '0.47.17';
45
+
46
+ // ─── RPC Endpoints (TX broadcast) ────────────────────────────────────────────
47
+ // Ordered by reliability. Primary is tried first, fallbacks on failure.
48
+ // Verified reachable 2026-03-08.
49
+
50
+ export const RPC_ENDPOINTS = [
51
+ { url: 'https://rpc.sentinel.co:443', name: 'Sentinel Official', verified: '2026-03-08' },
52
+ { url: 'https://sentinel-rpc.polkachu.com', name: 'Polkachu', verified: '2026-03-08' },
53
+ { url: 'https://rpc.mathnodes.com', name: 'MathNodes', verified: '2026-03-08' },
54
+ { url: 'https://sentinel-rpc.publicnode.com', name: 'PublicNode', verified: '2026-03-08' },
55
+ { url: 'https://rpc.sentinel.quokkastake.io', name: 'QuokkaStake', verified: '2026-03-08' },
56
+ ];
57
+
58
+ export const DEFAULT_RPC = RPC_ENDPOINTS[0].url;
59
+
60
+ // ─── LCD Endpoints (REST queries) ────────────────────────────────────────────
61
+ // Ordered by reliability. All have same limitations (v3 providers = 501, plan details = 501).
62
+ // Verified reachable 2026-03-08.
63
+
64
+ export const LCD_ENDPOINTS = [
65
+ { url: 'https://lcd.sentinel.co', name: 'Sentinel Official', verified: '2026-03-08' },
66
+ { url: 'https://sentinel-api.polkachu.com', name: 'Polkachu', verified: '2026-03-08' },
67
+ { url: 'https://api.sentinel.quokkastake.io', name: 'QuokkaStake', verified: '2026-03-08' },
68
+ { url: 'https://sentinel-rest.publicnode.com', name: 'PublicNode', verified: '2026-03-08' },
69
+ ];
70
+
71
+ export const DEFAULT_LCD = LCD_ENDPOINTS[0].url;
72
+
73
+ // ─── V2Ray ───────────────────────────────────────────────────────────────────
74
+
75
+ export const V2RAY_VERSION = '5.2.1';
76
+ /** WARNING: v5.44.1 has observatory bugs. Do NOT upgrade past 5.2.1. Verified 2026-03-08. */
77
+ export const V2RAY_VERSION_WARNING = 'v5.2.1 exactly — v5.44.1+ has observatory/balancer bugs that break multi-outbound configs';
78
+
79
+ // ─── Transport Success Rates (from 780-node scan, 2026-03-09) ────────────────
80
+ // Used by buildV2RayClientConfig() to sort outbounds by reliability.
81
+ // Dynamic rates (in-memory) override these when available — see below.
82
+
83
+ export const TRANSPORT_SUCCESS_RATES = {
84
+ 'tcp': { rate: 1.00, sample: 274, note: 'Best — always first choice' },
85
+ 'websocket': { rate: 1.00, sample: 23, note: 'Second choice' },
86
+ 'http': { rate: 1.00, sample: 4, note: '' },
87
+ 'gun': { rate: 1.00, sample: 10, note: 'gun(2) ≠ grpc(3) — different protocols' },
88
+ 'mkcp': { rate: 1.00, sample: 5, note: '' },
89
+ 'grpc/none': { rate: 0.87, sample: 81, note: '70/81 pass. serverName TLS fix applied.' },
90
+ 'quic': { rate: 0.00, sample: 4, note: '0/4 — chacha20 mismatch fixed, low node count' },
91
+ 'grpc/tls': { rate: 0.00, sample: 0, note: 'serverName TLS fix applied. No test nodes available.' },
92
+ };
93
+
94
+ // ─── Dynamic Transport Rate Tracking (persisted to disk) ─────────────────────
95
+ // Runtime success/failure tracking per transport type. Overrides hardcoded
96
+ // TRANSPORT_SUCCESS_RATES when enough samples exist. Persisted to
97
+ // ~/.sentinel-sdk/dynamic-rates.json with TTL eviction on load.
98
+
99
+ import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs';
100
+ import path from 'path';
101
+ import os from 'os';
102
+
103
+ const DYNAMIC_RATES_DIR = path.join(os.homedir(), '.sentinel-sdk');
104
+ const DYNAMIC_RATES_FILE = path.join(DYNAMIC_RATES_DIR, 'dynamic-rates.json');
105
+ const DYNAMIC_RATES_TTL = 7 * 24 * 60 * 60_000; // 7 days
106
+
107
+ const _dynamicRates = new Map(); // transportKey -> { success, fail, updatedAt }
108
+
109
+ /** Load persisted dynamic rates from disk (evict stale entries). */
110
+ function _loadDynamicRates() {
111
+ try {
112
+ if (!existsSync(DYNAMIC_RATES_FILE)) return;
113
+ const raw = JSON.parse(readFileSync(DYNAMIC_RATES_FILE, 'utf-8'));
114
+ const now = Date.now();
115
+ for (const [key, entry] of Object.entries(raw)) {
116
+ if (entry.updatedAt && now - entry.updatedAt < DYNAMIC_RATES_TTL) {
117
+ _dynamicRates.set(key, entry);
118
+ }
119
+ }
120
+ } catch { /* corrupt file — start fresh */ }
121
+ }
122
+
123
+ /** Persist current dynamic rates to disk. */
124
+ function _saveDynamicRates() {
125
+ try {
126
+ if (!existsSync(DYNAMIC_RATES_DIR)) mkdirSync(DYNAMIC_RATES_DIR, { recursive: true, mode: 0o700 });
127
+ const obj = {};
128
+ for (const [key, entry] of _dynamicRates) obj[key] = entry;
129
+ writeFileSync(DYNAMIC_RATES_FILE, JSON.stringify(obj, null, 2), { mode: 0o600 });
130
+ } catch { /* disk write failed — rates stay in memory */ }
131
+ }
132
+
133
+ // Load on module init
134
+ _loadDynamicRates();
135
+
136
+ /** Record a transport connection result. Called automatically by setupV2Ray. */
137
+ export function recordTransportResult(transportKey, success) {
138
+ const entry = _dynamicRates.get(transportKey) || { success: 0, fail: 0, updatedAt: 0 };
139
+ if (success) entry.success++; else entry.fail++;
140
+ entry.updatedAt = Date.now();
141
+ _dynamicRates.set(transportKey, entry);
142
+ _saveDynamicRates();
143
+ }
144
+
145
+ /**
146
+ * Get the dynamic success rate for a transport. Returns null if < 2 samples.
147
+ * Used by transportSortKey() in v3protocol.js to prioritize transports.
148
+ */
149
+ export function getDynamicRate(transportKey) {
150
+ const entry = _dynamicRates.get(transportKey);
151
+ if (!entry) return null;
152
+ const total = entry.success + entry.fail;
153
+ if (total < 2) return null;
154
+ return entry.success / total;
155
+ }
156
+
157
+ /** Get all dynamic rates as { transportKey: { rate, sample } }. */
158
+ export function getDynamicRates() {
159
+ const result = {};
160
+ for (const [key, entry] of _dynamicRates) {
161
+ const total = entry.success + entry.fail;
162
+ if (total > 0) result[key] = { rate: entry.success / total, sample: total };
163
+ }
164
+ return result;
165
+ }
166
+
167
+ /** Clear all dynamic rate data. Pass persist: true to also clear disk. */
168
+ export function resetDynamicRates(persist = false) {
169
+ _dynamicRates.clear();
170
+ if (persist) _saveDynamicRates();
171
+ }
172
+
173
+ // ─── Recommended Starter Nodes ───────────────────────────────────────────────
174
+ // High-reliability nodes from 708-node scan (2026-03-08).
175
+ // These had 100% connection success, low drift, WireGuard or proven V2Ray transports.
176
+ //
177
+ // ⚠ STALE WARNING: Nodes go offline. These are starting points, NOT guarantees.
178
+ // No hardcoded node list — nodes go offline unpredictably.
179
+ // Use queryOnlineNodes() for live, scored results:
180
+ // const nodes = await queryOnlineNodes({ lcdUrl: DEFAULT_LCD, maxNodes: 50 });
181
+ // // Returns nodes sorted by quality score (WG preferred, clock drift penalized)
182
+
183
+ // ─── Known Broken Nodes (blacklist) ──────────────────────────────────────────
184
+ // Nodes with confirmed bugs. Skip these to avoid wasting P2P.
185
+ // Verified 2026-03-08.
186
+
187
+ export const BROKEN_NODES = [
188
+ { address: 'sentnode1qqktst6793vdxknvvkewfcmtv9edh7vvdvavrj', reason: 'nil UUID state — handshake always fails', verified: '2026-03-08' },
189
+ { address: 'sentnode1qx2p7kyep6m44ae47yh9zf3cfxrzrv5zt9vdnj', reason: 'handshake OK but proxy always fails (0 bytes)', verified: '2026-03-08' },
190
+ ];
191
+
192
+ // ─── Pricing Reference (from chain data, 2026-03-08) ─────────────────────────
193
+ // Typical values — actual prices vary per node. These are for estimation only.
194
+
195
+ export const PRICING_REFERENCE = {
196
+ verified: '2026-03-08',
197
+ note: 'Approximate values for cost estimation. Actual prices vary per node and change over time.',
198
+ session: {
199
+ typicalCostDvpn: 0.1, // ~0.04-0.15 P2P per 1GB session
200
+ minBalanceDvpn: 1, // Minimum recommended wallet balance
201
+ minBalanceUdvpn: 1_000_000, // Same in micro-denom
202
+ },
203
+ gasPerMsg: {
204
+ startSession: 200_000, // ~200k gas for MsgStartSession
205
+ startSubscription: 250_000, // ~250k gas for subscription + session
206
+ createPlan: 300_000, // ~300k gas
207
+ startLease: 250_000, // ~250k gas
208
+ batchOf5: 800_000, // ~800k gas for 5 MsgStartSession batch
209
+ },
210
+ averageNodePrices: {
211
+ gigabyteQuoteValue: '40152030', // Average udvpn per GB (0.04 P2P)
212
+ hourlyQuoteValue: '18384000', // Average udvpn per hour (0.018 P2P)
213
+ baseValue: '0.003000000000000000', // Typical base_value (sdk.Dec)
214
+ },
215
+ };
216
+
217
+ // ─── Shared Utilities ─────────────────────────────────────────────────────────
218
+
219
+ /** Promise-based delay. Used across node-connect, wireguard, speedtest. */
220
+ export function sleep(ms) { return new Promise(r => setTimeout(r, ms)); }
221
+
222
+ /** Convert bytes transferred over seconds to Mbps. */
223
+ export function bytesToMbps(bytes, seconds, decimals = null) {
224
+ if (!seconds || seconds <= 0) return 0;
225
+ const mbps = (bytes * 8) / seconds / 1_000_000;
226
+ return decimals !== null ? parseFloat(mbps.toFixed(decimals)) : mbps;
227
+ }
228
+
229
+ // ─── DNS Presets ─────────────────────────────────────────────────────────────
230
+ // DNS servers for WireGuard tunnel. Handshake is default — decentralized,
231
+ // censorship-resistant DNS that resolves both Handshake TLDs and ICANN domains.
232
+ // Matches Sentinel Shield mobile app behavior.
233
+
234
+ export const DNS_PRESETS = Object.freeze({
235
+ handshake: {
236
+ name: 'Handshake',
237
+ servers: ['103.196.38.38', '103.196.38.39'],
238
+ description: 'Decentralized DNS — resolves Handshake + ICANN domains. Censorship-resistant.',
239
+ },
240
+ google: {
241
+ name: 'Google',
242
+ servers: ['8.8.8.8', '8.8.4.4'],
243
+ description: 'Google Public DNS',
244
+ },
245
+ cloudflare: {
246
+ name: 'Cloudflare',
247
+ servers: ['1.1.1.1', '1.0.0.1'],
248
+ description: 'Cloudflare Public DNS',
249
+ },
250
+ });
251
+
252
+ export const DEFAULT_DNS_PRESET = 'handshake';
253
+
254
+ /** Fallback order: handshake → google → cloudflare. */
255
+ export const DNS_FALLBACK_ORDER = ['handshake', 'google', 'cloudflare'];
256
+
257
+ /**
258
+ * Resolve a DNS option into a comma-separated string for WireGuard config.
259
+ * Includes fallback DNS servers — if the primary DNS fails, the OS tries the next ones.
260
+ *
261
+ * @param {string|string[]|undefined} dns - Preset name ('handshake'|'google'|'cloudflare'),
262
+ * array of custom IP strings, or undefined for default (Handshake).
263
+ * @returns {string} DNS string for WireGuard config with fallbacks
264
+ * (e.g. '103.196.38.38, 103.196.38.39, 8.8.8.8, 1.1.1.1')
265
+ */
266
+ export function resolveDnsServers(dns) {
267
+ const primary = _resolvePrimary(dns);
268
+ const primarySet = new Set(primary);
269
+
270
+ // Append one server from each fallback preset not already in the primary list
271
+ const fallbacks = [];
272
+ for (const name of DNS_FALLBACK_ORDER) {
273
+ const preset = DNS_PRESETS[name];
274
+ for (const server of preset.servers) {
275
+ if (!primarySet.has(server)) {
276
+ fallbacks.push(server);
277
+ primarySet.add(server);
278
+ break; // one per preset is enough for fallback
279
+ }
280
+ }
281
+ }
282
+
283
+ return [...primary, ...fallbacks].join(', ');
284
+ }
285
+
286
+ /** Resolve just the primary DNS servers (no fallbacks). */
287
+ function _resolvePrimary(dns) {
288
+ if (!dns) return [...DNS_PRESETS[DEFAULT_DNS_PRESET].servers];
289
+ if (typeof dns === 'string') {
290
+ const preset = DNS_PRESETS[dns.toLowerCase()];
291
+ if (preset) return [...preset.servers];
292
+ return [dns];
293
+ }
294
+ if (Array.isArray(dns) && dns.length > 0) return [...dns];
295
+ return [...DNS_PRESETS[DEFAULT_DNS_PRESET].servers];
296
+ }
297
+
298
+ // ─── Connection Timeouts (ms) ────────────────────────────────────────────────
299
+
300
+ /** Default timeout values used during node connection. Override via opts.timeouts. */
301
+ export const DEFAULT_TIMEOUTS = {
302
+ handshake: 90000, // Max time for WireGuard/V2Ray handshake with node (overloaded nodes need 60-90s)
303
+ nodeStatus: 12000, // Max time to fetch node status from remote URL
304
+ lcdQuery: 15000, // Max time for LCD chain queries
305
+ v2rayReady: 10000, // Max time waiting for V2Ray SOCKS proxy to be ready
306
+ };
307
+
308
+ // ─── Endpoint Health Check ────────────────────────────────────────────────────
309
+
310
+ /**
311
+ * Ping endpoints and return them sorted by latency (fastest first).
312
+ * Unreachable endpoints are moved to the end.
313
+ * @param {Array<{url: string, name: string}>} endpoints
314
+ * @param {number} timeoutMs - Per-endpoint timeout (default 5000ms)
315
+ * @returns {Promise<Array<{url: string, name: string, latencyMs: number|null}>>}
316
+ */
317
+ export async function checkEndpointHealth(endpoints, timeoutMs = 5000) {
318
+ const results = await Promise.all(endpoints.map(async (ep) => {
319
+ const start = Date.now();
320
+ try {
321
+ const controller = new AbortController();
322
+ const timer = setTimeout(() => controller.abort(), timeoutMs);
323
+ // LCD endpoints use /cosmos/base/tendermint/v1beta1/syncing for health check
324
+ // (/status doesn't exist on LCD — that's an RPC endpoint path)
325
+ await axios.get(`${ep.url}/cosmos/base/tendermint/v1beta1/syncing`, { signal: controller.signal, timeout: timeoutMs });
326
+ clearTimeout(timer);
327
+ return { ...ep, latencyMs: Date.now() - start };
328
+ } catch {
329
+ return { ...ep, latencyMs: null };
330
+ }
331
+ }));
332
+ // Sort: reachable first (by latency), unreachable last
333
+ return results.sort((a, b) => {
334
+ if (a.latencyMs != null && b.latencyMs != null) return a.latencyMs - b.latencyMs;
335
+ if (a.latencyMs != null) return -1;
336
+ if (b.latencyMs != null) return 1;
337
+ return 0;
338
+ });
339
+ }
340
+
341
+ // ─── Helper: Try endpoints with fallback ─────────────────────────────────────
342
+
343
+ /**
344
+ * Try an async operation against multiple endpoints, returning the first success.
345
+ * Use this for RPC/LCD operations that should fall back to alternatives.
346
+ *
347
+ * @param {Array<{url: string, name: string}>} endpoints - Ordered list of endpoints
348
+ * @param {function} operation - async (url) => result
349
+ * @param {string} label - For error messages (e.g. 'LCD query', 'RPC connect')
350
+ * @returns {Promise<{result: any, endpoint: string}>}
351
+ */
352
+ export async function tryWithFallback(endpoints, operation, label = 'operation') {
353
+ const errors = [];
354
+ for (const ep of endpoints) {
355
+ try {
356
+ const result = await operation(ep.url);
357
+ return { result, endpoint: ep.url, endpointName: ep.name };
358
+ } catch (err) {
359
+ errors.push({ endpoint: ep.url, name: ep.name, error: err.message });
360
+ }
361
+ }
362
+ const tried = errors.map(e => ` ${e.name} (${e.endpoint}): ${e.error}`).join('\n');
363
+ // Lazy import to avoid circular dependency (errors.js is simple, no deps)
364
+ const { ChainError } = await import('./errors.js');
365
+ throw new ChainError('ALL_ENDPOINTS_FAILED', `${label} failed on all ${endpoints.length} endpoints (verified ${LAST_VERIFIED}):\n${tried}\n\nAll endpoints may be down, or your network may be blocking HTTPS. Try curl-ing the URLs manually.`, { endpoints: errors });
366
+ }
package/disk-cache.js ADDED
@@ -0,0 +1,107 @@
1
+ /**
2
+ * Sentinel SDK — Generic Disk Cache
3
+ *
4
+ * Stale-while-revalidate pattern: returns cached data instantly,
5
+ * refreshes in background. Falls back to stale data on error.
6
+ * Inflight deduplication prevents duplicate concurrent fetches.
7
+ *
8
+ * Usage:
9
+ * import { cached, cacheInvalidate, cacheClear } from './disk-cache.js';
10
+ * const nodes = await cached('nodes', 300_000, () => fetchAllNodes());
11
+ */
12
+
13
+ import { existsSync, readFileSync, writeFileSync, mkdirSync, unlinkSync } from 'fs';
14
+ import path from 'path';
15
+ import os from 'os';
16
+
17
+ // ─── In-Memory Cache ────────────────────────────────────────────────────────
18
+
19
+ const _memCache = new Map(); // key → { data, ts, inflight }
20
+
21
+ /**
22
+ * Fetch data with TTL caching + inflight deduplication + stale fallback.
23
+ *
24
+ * @param {string} key - Cache key
25
+ * @param {number} ttlMs - Time-to-live in milliseconds
26
+ * @param {function} fetchFn - Async function that returns fresh data
27
+ * @returns {Promise<any>} Cached or fresh data
28
+ */
29
+ export function cached(key, ttlMs, fetchFn) {
30
+ const entry = _memCache.get(key);
31
+
32
+ // Fresh cache hit
33
+ if (entry && (Date.now() - entry.ts) < ttlMs) {
34
+ return Promise.resolve(entry.data);
35
+ }
36
+
37
+ // Inflight dedup — don't fetch twice
38
+ if (entry?.inflight) return entry.inflight;
39
+
40
+ const p = fetchFn().then(data => {
41
+ _memCache.set(key, { data, ts: Date.now(), inflight: null });
42
+ return data;
43
+ }).catch(err => {
44
+ const existing = _memCache.get(key);
45
+ if (existing) existing.inflight = null;
46
+ // Stale fallback — return old data if available
47
+ if (existing?.data) return existing.data;
48
+ throw err;
49
+ });
50
+
51
+ _memCache.set(key, { ...(_memCache.get(key) || {}), inflight: p });
52
+ return p;
53
+ }
54
+
55
+ /** Invalidate a single cache entry. */
56
+ export function cacheInvalidate(key) { _memCache.delete(key); }
57
+
58
+ /** Clear all cache entries. */
59
+ export function cacheClear() { _memCache.clear(); }
60
+
61
+ /** Get cache entry metadata (for debugging). */
62
+ export function cacheInfo(key) {
63
+ const entry = _memCache.get(key);
64
+ if (!entry) return null;
65
+ return { ageMs: Date.now() - entry.ts, hasData: !!entry.data, inflight: !!entry.inflight };
66
+ }
67
+
68
+ // ─── Disk Cache ─────────────────────────────────────────────────────────────
69
+
70
+ const CACHE_DIR = path.join(os.homedir(), '.sentinel-sdk', 'cache');
71
+
72
+ /**
73
+ * Save data to disk cache with timestamp.
74
+ * @param {string} key - Cache key (becomes filename)
75
+ * @param {any} data - JSON-serializable data
76
+ */
77
+ export function diskSave(key, data) {
78
+ try {
79
+ if (!existsSync(CACHE_DIR)) mkdirSync(CACHE_DIR, { recursive: true, mode: 0o700 });
80
+ const file = path.join(CACHE_DIR, `${key}.json`);
81
+ writeFileSync(file, JSON.stringify({ data, savedAt: Date.now() }), { mode: 0o600 });
82
+ } catch { /* disk write failure is non-fatal */ }
83
+ }
84
+
85
+ /**
86
+ * Load data from disk cache.
87
+ * @param {string} key - Cache key
88
+ * @param {number} maxAgeMs - Maximum age in ms. Returns null if stale.
89
+ * @returns {{ data: any, savedAt: number, stale: boolean } | null}
90
+ */
91
+ export function diskLoad(key, maxAgeMs) {
92
+ try {
93
+ const file = path.join(CACHE_DIR, `${key}.json`);
94
+ if (!existsSync(file)) return null;
95
+ const raw = JSON.parse(readFileSync(file, 'utf8'));
96
+ const age = Date.now() - (raw.savedAt || 0);
97
+ return { data: raw.data, savedAt: raw.savedAt, stale: age > maxAgeMs };
98
+ } catch { return null; }
99
+ }
100
+
101
+ /** Clear a disk cache entry. */
102
+ export function diskClear(key) {
103
+ try {
104
+ const file = path.join(CACHE_DIR, `${key}.json`);
105
+ if (existsSync(file)) unlinkSync(file);
106
+ } catch { /* non-fatal */ }
107
+ }
@@ -0,0 +1,108 @@
1
+ /**
2
+ * BlueSentinelClient — Extends CosmJS SigningStargateClient with Sentinel query extensions.
3
+ *
4
+ * Follows the TKD Alex pattern: extend the ecosystem standard, don't replace it.
5
+ * Every CosmJS method works. Sentinel-specific additions are additive.
6
+ *
7
+ * Usage:
8
+ * const client = await BlueSentinelClient.connectWithSigner(rpcUrl, signer);
9
+ * const nodes = await client.sentinelQuery.nodes({ status: 1, limit: 100 });
10
+ * const balance = await client.getBalance(address, 'udvpn');
11
+ * const result = await client.signAndBroadcast(address, [msg], fee);
12
+ */
13
+ import { SigningStargateClient, StargateClient, type SigningStargateClientOptions } from '@cosmjs/stargate';
14
+ import { type CometClient } from '@cosmjs/tendermint-rpc';
15
+ import type { OfflineSigner } from '@cosmjs/proto-signing';
16
+ import type { Coin } from '@cosmjs/amino';
17
+ export interface SentinelNode {
18
+ address: string;
19
+ gigabyte_prices: SentinelPrice[];
20
+ hourly_prices: SentinelPrice[];
21
+ remote_addrs: string[];
22
+ status: number;
23
+ }
24
+ export interface SentinelPrice {
25
+ denom: string;
26
+ base_value: string;
27
+ quote_value: string;
28
+ }
29
+ export interface SentinelSession {
30
+ id: bigint;
31
+ acc_address: string;
32
+ node_address: string;
33
+ download_bytes: string;
34
+ upload_bytes: string;
35
+ max_bytes: string;
36
+ status: number;
37
+ }
38
+ export interface PaginationOptions {
39
+ limit?: number;
40
+ key?: Uint8Array;
41
+ }
42
+ export interface NodeQueryOptions extends PaginationOptions {
43
+ status?: number;
44
+ }
45
+ export interface SentinelQueryExtension {
46
+ nodes(opts?: NodeQueryOptions): Promise<SentinelNode[]>;
47
+ node(address: string): Promise<SentinelNode | null>;
48
+ nodesForPlan(planId: number | bigint, opts?: NodeQueryOptions): Promise<SentinelNode[]>;
49
+ balance(address: string, denom?: string): Promise<Coin>;
50
+ sessionsForAccount(address: string, opts?: PaginationOptions): Promise<Uint8Array[]>;
51
+ subscriptionsForAccount(address: string, opts?: PaginationOptions): Promise<Uint8Array[]>;
52
+ plan(planId: number | bigint): Promise<Uint8Array | null>;
53
+ }
54
+ export declare class SentinelQueryClient extends StargateClient {
55
+ readonly sentinelQuery: SentinelQueryExtension;
56
+ private readonly qc;
57
+ protected constructor(tmClient: CometClient);
58
+ static connect(endpoint: string): Promise<SentinelQueryClient>;
59
+ private buildQueryExtension;
60
+ }
61
+ export declare class BlueSentinelClient extends SigningStargateClient {
62
+ readonly sentinelQuery: SentinelQueryExtension;
63
+ private readonly qc;
64
+ protected constructor(tmClient: CometClient, signer: OfflineSigner, options: SigningStargateClientOptions);
65
+ /**
66
+ * Connect with a signer for full TX + query capabilities.
67
+ * This is the primary entry point — matches TKD Alex's pattern exactly.
68
+ */
69
+ static connectWithSigner(endpoint: string, signer: OfflineSigner, options?: SigningStargateClientOptions): Promise<BlueSentinelClient>;
70
+ private buildQueryExtension;
71
+ }
72
+ export interface SentinelEvent {
73
+ type: string;
74
+ attributes: Array<{
75
+ key: string;
76
+ value: string;
77
+ }>;
78
+ }
79
+ export type EventCallback = (event: SentinelEvent) => void;
80
+ /**
81
+ * Real-time event subscription via Tendermint WebSocket.
82
+ *
83
+ * Usage:
84
+ * const ws = await SentinelWsClient.connect('wss://rpc.sentinel.co/websocket');
85
+ * ws.subscribe("tm.event='Tx'", (event) => console.log(event));
86
+ * ws.disconnect();
87
+ */
88
+ export declare class SentinelWsClient {
89
+ private readonly endpoint;
90
+ private ws;
91
+ private listeners;
92
+ private nextId;
93
+ private constructor();
94
+ static connect(endpoint: string): Promise<SentinelWsClient>;
95
+ private open;
96
+ /**
97
+ * Subscribe to chain events matching a Tendermint query.
98
+ *
99
+ * Common queries:
100
+ * - "tm.event='Tx'" — all transactions
101
+ * - "tm.event='Tx' AND sentinel.node.v3.EventCreateSession.session_id EXISTS" — session starts
102
+ * - "tm.event='Tx' AND sentinel.session.v3.EventEnd.session_id EXISTS" — session ends
103
+ */
104
+ subscribe(query: string, callback: EventCallback): void;
105
+ private handleMessage;
106
+ disconnect(): void;
107
+ }
108
+ //# sourceMappingURL=client.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,EACL,qBAAqB,EACrB,cAAc,EAGd,KAAK,4BAA4B,EAElC,MAAM,kBAAkB,CAAC;AAC1B,OAAO,EAAsB,KAAK,WAAW,EAAE,MAAM,wBAAwB,CAAC;AAC9E,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,uBAAuB,CAAC;AAC3D,OAAO,KAAK,EAAE,IAAI,EAAE,MAAM,eAAe,CAAC;AAI1C,MAAM,WAAW,YAAY;IAC3B,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,aAAa,EAAE,CAAC;IACjC,aAAa,EAAE,aAAa,EAAE,CAAC;IAC/B,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;CACrB;AAED,MAAM,WAAW,eAAe;IAC9B,EAAE,EAAE,MAAM,CAAC;IACX,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,MAAM,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,iBAAiB;IAChC,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,UAAU,CAAC;CAClB;AAED,MAAM,WAAW,gBAAiB,SAAQ,iBAAiB;IACzD,MAAM,CAAC,EAAE,MAAM,CAAC;CACjB;AAID,MAAM,WAAW,sBAAsB;IACrC,KAAK,CAAC,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IACxD,IAAI,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACpD,YAAY,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,EAAE,IAAI,CAAC,EAAE,gBAAgB,GAAG,OAAO,CAAC,YAAY,EAAE,CAAC,CAAC;IACxF,OAAO,CAAC,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACxD,kBAAkB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACrF,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,iBAAiB,GAAG,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IAC1F,IAAI,CAAC,MAAM,EAAE,MAAM,GAAG,MAAM,GAAG,OAAO,CAAC,UAAU,GAAG,IAAI,CAAC,CAAC;CAC3D;AA4ID,qBAAa,mBAAoB,SAAQ,cAAc;IACrD,SAAgB,aAAa,EAAE,sBAAsB,CAAC;IACtD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAc;IAEjC,SAAS,aAAa,QAAQ,EAAE,WAAW;WAMrB,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,mBAAmB,CAAC;IAK7E,OAAO,CAAC,mBAAmB;CAyE5B;AAID,qBAAa,kBAAmB,SAAQ,qBAAqB;IAC3D,SAAgB,aAAa,EAAE,sBAAsB,CAAC;IACtD,OAAO,CAAC,QAAQ,CAAC,EAAE,CAAc;IAEjC,SAAS,aACP,QAAQ,EAAE,WAAW,EACrB,MAAM,EAAE,aAAa,EACrB,OAAO,EAAE,4BAA4B;IAOvC;;;OAGG;WACU,iBAAiB,CAC5B,QAAQ,EAAE,MAAM,EAChB,MAAM,EAAE,aAAa,EACrB,OAAO,CAAC,EAAE,4BAA4B,GACrC,OAAO,CAAC,kBAAkB,CAAC;IAQ9B,OAAO,CAAC,mBAAmB;CAyE5B;AAID,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,CAAC;IACb,UAAU,EAAE,KAAK,CAAC;QAAE,GAAG,EAAE,MAAM,CAAC;QAAC,KAAK,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CACnD;AAED,MAAM,MAAM,aAAa,GAAG,CAAC,KAAK,EAAE,aAAa,KAAK,IAAI,CAAC;AAE3D;;;;;;;GAOG;AACH,qBAAa,gBAAgB;IAKP,OAAO,CAAC,QAAQ,CAAC,QAAQ;IAJ7C,OAAO,CAAC,EAAE,CAAa;IACvB,OAAO,CAAC,SAAS,CAA2C;IAC5D,OAAO,CAAC,MAAM,CAAK;IAEnB,OAAO;WAEM,OAAO,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,gBAAgB,CAAC;IAMjE,OAAO,CAAC,IAAI;IAiBZ;;;;;;;OAOG;IACH,SAAS,CAAC,KAAK,EAAE,MAAM,EAAE,QAAQ,EAAE,aAAa,GAAG,IAAI;IAevD,OAAO,CAAC,aAAa;IAmBrB,UAAU,IAAI,IAAI;CAKnB"}