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/cli.js ADDED
@@ -0,0 +1,491 @@
1
+ #!/usr/bin/env node
2
+
3
+ // ─── Sentinel SDK CLI ───
4
+ //
5
+ // Command-line interface for common Sentinel operations.
6
+ // No GUI needed — scriptable, pipe-friendly, perfect for automation.
7
+ //
8
+ // Usage:
9
+ // node js-sdk/cli.js <command> [options]
10
+ //
11
+ // Examples:
12
+ // node js-sdk/cli.js balance
13
+ // node js-sdk/cli.js nodes --country Germany --limit 10
14
+ // node js-sdk/cli.js connect sentnode1abc...
15
+ // node js-sdk/cli.js plan-create --gb 10 --days 30 --price 1000000
16
+ // node js-sdk/cli.js grant-subscribers --plan 42
17
+
18
+ import {
19
+ createWallet,
20
+ generateWallet,
21
+ getBalance,
22
+ isMnemonicValid,
23
+ createClient,
24
+ fetchActiveNodes,
25
+ getNodePrices,
26
+ getNetworkOverview,
27
+ filterNodes,
28
+ queryPlanNodes,
29
+ queryPlanSubscribers,
30
+ getPlanStats,
31
+ discoverPlans,
32
+ hasActiveSubscription,
33
+ querySubscriptions,
34
+ findExistingSession,
35
+ connectDirect,
36
+ connectAuto,
37
+ connectViaSubscription,
38
+ disconnect,
39
+ isConnected,
40
+ getStatus,
41
+ registerCleanupHandlers,
42
+ verifyDependencies,
43
+ formatDvpn,
44
+ shortAddress,
45
+ buildFeeGrantMsg,
46
+ buildRevokeFeeGrantMsg,
47
+ queryFeeGrants,
48
+ queryFeeGrantsIssued,
49
+ getExpiringGrants,
50
+ grantPlanSubscribers,
51
+ broadcastWithFeeGrant,
52
+ broadcast,
53
+ createSafeBroadcaster,
54
+ sentToSentprov,
55
+ sentToSentnode,
56
+ encodeMsgCreatePlan,
57
+ encodeMsgLinkNode,
58
+ encodeMsgUnlinkNode,
59
+ encodeMsgUpdatePlanStatus,
60
+ encodeMsgRegisterProvider,
61
+ encodeMsgUpdateProviderStatus,
62
+ encodeMsgStartLease,
63
+ encodeMsgEndLease,
64
+ encodeMsgStartSubscription,
65
+ LCD_ENDPOINTS,
66
+ DEFAULT_RPC,
67
+ DNS_PRESETS,
68
+ DEFAULT_DNS_PRESET,
69
+ DNS_FALLBACK_ORDER,
70
+ resolveDnsServers,
71
+ } from './index.js';
72
+
73
+ import { config } from 'dotenv';
74
+ config({ path: '.env' });
75
+
76
+ // ─── Helpers ───
77
+
78
+ const MNEMONIC = process.env.MNEMONIC;
79
+ const args = process.argv.slice(2);
80
+ const command = args[0];
81
+
82
+ function flag(name, defaultVal) {
83
+ const idx = args.indexOf(`--${name}`);
84
+ if (idx === -1) return defaultVal;
85
+ return args[idx + 1] || true;
86
+ }
87
+
88
+ function die(msg) {
89
+ console.error(`Error: ${msg}`);
90
+ process.exit(1);
91
+ }
92
+
93
+ async function getWallet() {
94
+ if (!MNEMONIC) die('MNEMONIC not set in .env');
95
+ const { wallet, account } = await createWallet(MNEMONIC);
96
+ return { wallet, account, address: account.address };
97
+ }
98
+
99
+ async function getRpc() {
100
+ const { wallet, account } = await getWallet();
101
+ const client = await createClient(flag('rpc', DEFAULT_RPC), wallet);
102
+ return { client, wallet, account, address: account.address };
103
+ }
104
+
105
+ function json(obj) {
106
+ console.log(JSON.stringify(obj, (k, v) => typeof v === 'bigint' ? v.toString() : v, 2));
107
+ }
108
+
109
+ // ─── Commands ───
110
+
111
+ const commands = {
112
+
113
+ async help() {
114
+ console.log(`
115
+ Sentinel SDK CLI — command-line tools for Sentinel dVPN
116
+
117
+ WALLET
118
+ balance Show wallet balance
119
+ generate Generate new mnemonic + address
120
+ address Show wallet address + provider address
121
+
122
+ NODES
123
+ nodes [--country X] [--limit N] List active nodes
124
+ node-prices <sentnode1...> Show node pricing
125
+ network Network overview (total nodes, by country)
126
+
127
+ PLANS
128
+ plans Discover plans on chain
129
+ plan-stats <planId> Plan statistics (subscribers, nodes, revenue)
130
+ plan-nodes <planId> List nodes in a plan
131
+ plan-subscribers <planId> List plan subscribers
132
+ plan-create --gb N --days N --price N Create a new plan
133
+ plan-activate <planId> Activate a plan
134
+ plan-link <planId> <sentnode1> Link a node to a plan
135
+ plan-unlink <planId> <sentnode1> Unlink a node from a plan
136
+
137
+ SUBSCRIPTIONS
138
+ subscriptions List your subscriptions
139
+ subscribe <planId> Subscribe to a plan
140
+ has-subscription <planId> Check if subscribed to a plan
141
+
142
+ SESSIONS
143
+ find-session <sentnode1...> Find existing session for a node
144
+
145
+ FEE GRANTS
146
+ grants-received List fee grants you've received
147
+ grants-issued List fee grants you've issued
148
+ grant <sent1...> [--amount N] Grant fee allowance to an address
149
+ grant-subscribers <planId> Batch grant all plan subscribers
150
+ expiring-grants [--days 7] List grants expiring soon
151
+
152
+ CONNECTION
153
+ connect <sentnode1...> [--gb N] [--dns X] Connect to a specific node
154
+ connect-auto [--country X] [--dns X] Auto-connect to best node
155
+ disconnect Disconnect VPN
156
+ status Show connection status
157
+
158
+ DNS
159
+ dns Show DNS presets and current resolution
160
+ dns --preset <name> Show resolved DNS for a preset
161
+ dns --custom <ip1,ip2> Show resolved DNS for custom IPs
162
+
163
+ SYSTEM
164
+ deps Check V2Ray + WireGuard availability
165
+ endpoints Test LCD endpoint health
166
+
167
+ Options:
168
+ --rpc <url> RPC endpoint (default: ${DEFAULT_RPC})
169
+ --lcd <url> LCD endpoint (default: ${LCD_ENDPOINTS[0].url || LCD_ENDPOINTS[0]})
170
+ --country <name> Filter by country
171
+ --limit <n> Limit results
172
+ --gb <n> Gigabytes (default: 1)
173
+ --days <n> Duration in days
174
+ --price <udvpn> Price in micro-denomination
175
+ --amount <udvpn> Fee grant amount in udvpn
176
+ --plan <id> Plan ID
177
+ --dns <preset> DNS preset: handshake (default), google, cloudflare, or custom IPs
178
+
179
+ Environment:
180
+ MNEMONIC BIP39 mnemonic phrase (in .env file)
181
+ `);
182
+ },
183
+
184
+ // ─── Wallet ───
185
+
186
+ async balance() {
187
+ const { client, address } = await getRpc();
188
+ const balance = await getBalance(client, address);
189
+ console.log(`Address: ${address}`);
190
+ console.log(`Balance: ${formatDvpn(balance.udvpn)} (${balance.udvpn} udvpn)`);
191
+ },
192
+
193
+ async generate() {
194
+ const { mnemonic, account } = await generateWallet();
195
+ console.log(`Mnemonic: ${mnemonic}`);
196
+ console.log(`Address: ${account.address}`);
197
+ console.log(`\nSave the mnemonic in your .env file as MNEMONIC="..."`);
198
+ },
199
+
200
+ async address() {
201
+ const { address } = await getWallet();
202
+ console.log(`Account: ${address}`);
203
+ console.log(`Provider: ${sentToSentprov(address)}`);
204
+ console.log(`Node: ${sentToSentnode(address)}`);
205
+ },
206
+
207
+ // ─── Nodes ───
208
+
209
+ async nodes() {
210
+ const country = flag('country', null);
211
+ const limit = parseInt(flag('limit', '20'));
212
+ let nodes = await fetchActiveNodes();
213
+ if (country) nodes = filterNodes(nodes, { country });
214
+ nodes = nodes.slice(0, limit);
215
+ console.log(`${nodes.length} nodes:`);
216
+ for (const n of nodes) {
217
+ const addr = shortAddress(n.address, 15, 6);
218
+ const url = n.remote_addrs?.[0] || n.remote_url || '?';
219
+ console.log(` ${addr} ${url} ${n.service_type === 2 ? 'WG' : 'V2'}`);
220
+ }
221
+ },
222
+
223
+ async 'node-prices'() {
224
+ const nodeAddr = args[1];
225
+ if (!nodeAddr) die('Usage: node-prices <sentnode1...>');
226
+ const prices = await getNodePrices(nodeAddr);
227
+ json(prices);
228
+ },
229
+
230
+ async network() {
231
+ const overview = await getNetworkOverview();
232
+ json(overview);
233
+ },
234
+
235
+ // ─── Plans ───
236
+
237
+ async plans() {
238
+ const plans = await discoverPlans();
239
+ console.log(`${plans.length} plans found:`);
240
+ for (const p of plans) {
241
+ console.log(` Plan ${p.id}: ${p.subscribers} subscribers, ${p.nodeCount} nodes`);
242
+ }
243
+ },
244
+
245
+ async 'plan-stats'() {
246
+ const planId = parseInt(args[1]);
247
+ if (!planId) die('Usage: plan-stats <planId>');
248
+ const { address } = await getWallet();
249
+ const stats = await getPlanStats(planId, address);
250
+ json(stats);
251
+ },
252
+
253
+ async 'plan-nodes'() {
254
+ const planId = parseInt(args[1]);
255
+ if (!planId) die('Usage: plan-nodes <planId>');
256
+ const nodes = await queryPlanNodes(planId);
257
+ console.log(`${nodes.length} nodes in plan ${planId}:`);
258
+ for (const n of nodes) {
259
+ console.log(` ${n.address} ${n.remote_addrs?.[0] || '?'}`);
260
+ }
261
+ },
262
+
263
+ async 'plan-subscribers'() {
264
+ const planId = parseInt(args[1]);
265
+ if (!planId) die('Usage: plan-subscribers <planId>');
266
+ const subs = await queryPlanSubscribers(planId);
267
+ console.log(`${subs.length} subscribers:`);
268
+ for (const s of subs) {
269
+ console.log(` ${s.address} status=${s.status}`);
270
+ }
271
+ },
272
+
273
+ async 'plan-create'() {
274
+ const gb = parseInt(flag('gb', '10'));
275
+ const days = parseInt(flag('days', '30'));
276
+ const price = flag('price', null);
277
+ if (!price) die('Usage: plan-create --gb 10 --days 30 --price 1000000');
278
+ const { client, address } = await getRpc();
279
+ const provAddr = sentToSentprov(address);
280
+ const msg = encodeMsgCreatePlan({
281
+ from: provAddr,
282
+ bytes: String(BigInt(gb) * 1000000000n),
283
+ duration: days * 86400,
284
+ prices: [{ denom: 'udvpn', base_value: '1', quote_value: price }],
285
+ });
286
+ const result = await broadcast(client, address, [{ typeUrl: '/sentinel.plan.v3.MsgCreatePlanRequest', value: msg }]);
287
+ json({ txHash: result.transactionHash, code: result.code });
288
+ },
289
+
290
+ async 'plan-activate'() {
291
+ const planId = parseInt(args[1]);
292
+ if (!planId) die('Usage: plan-activate <planId>');
293
+ const { client, address } = await getRpc();
294
+ const provAddr = sentToSentprov(address);
295
+ const msg = encodeMsgUpdatePlanStatus({ from: provAddr, id: planId, status: 1 });
296
+ const result = await broadcast(client, address, [{ typeUrl: '/sentinel.plan.v3.MsgUpdatePlanStatusRequest', value: msg }]);
297
+ json({ txHash: result.transactionHash, code: result.code });
298
+ },
299
+
300
+ // ─── Subscriptions ───
301
+
302
+ async subscriptions() {
303
+ const { address } = await getWallet();
304
+ const subs = await querySubscriptions(LCD_ENDPOINTS[0]?.url || LCD_ENDPOINTS[0], address);
305
+ json(subs);
306
+ },
307
+
308
+ async subscribe() {
309
+ const planId = parseInt(args[1]);
310
+ if (!planId) die('Usage: subscribe <planId>');
311
+ const { client, address } = await getRpc();
312
+ const msg = encodeMsgStartSubscription({ from: address, id: planId, denom: 'udvpn' });
313
+ const result = await broadcast(client, address, [{ typeUrl: '/sentinel.subscription.v3.MsgStartRequest', value: msg }]);
314
+ json({ txHash: result.transactionHash, code: result.code });
315
+ },
316
+
317
+ async 'has-subscription'() {
318
+ const planId = parseInt(args[1]);
319
+ if (!planId) die('Usage: has-subscription <planId>');
320
+ const { address } = await getWallet();
321
+ const has = await hasActiveSubscription(address, planId);
322
+ console.log(has ? 'Yes — active subscription exists' : 'No — not subscribed');
323
+ },
324
+
325
+ // ─── Sessions ───
326
+
327
+ async 'find-session'() {
328
+ const nodeAddr = args[1];
329
+ if (!nodeAddr) die('Usage: find-session <sentnode1...>');
330
+ const { address } = await getWallet();
331
+ const sessionId = await findExistingSession(LCD_ENDPOINTS[0]?.url || LCD_ENDPOINTS[0], address, nodeAddr);
332
+ console.log(sessionId ? `Session: ${sessionId}` : 'No active session found');
333
+ },
334
+
335
+ // ─── Fee Grants ───
336
+
337
+ async 'grants-received'() {
338
+ const { address } = await getWallet();
339
+ const grants = await queryFeeGrants(LCD_ENDPOINTS[0]?.url || LCD_ENDPOINTS[0], address);
340
+ json(grants);
341
+ },
342
+
343
+ async 'grants-issued'() {
344
+ const { address } = await getWallet();
345
+ const grants = await queryFeeGrantsIssued(LCD_ENDPOINTS[0]?.url || LCD_ENDPOINTS[0], address);
346
+ json(grants);
347
+ },
348
+
349
+ async 'grant-subscribers'() {
350
+ const planId = parseInt(flag('plan', args[1]));
351
+ if (!planId) die('Usage: grant-subscribers <planId>');
352
+ const { address } = await getWallet();
353
+ const result = await grantPlanSubscribers(planId, {
354
+ granterAddress: address,
355
+ lcdUrl: LCD_ENDPOINTS[0]?.url || LCD_ENDPOINTS[0],
356
+ });
357
+ json(result);
358
+ },
359
+
360
+ async 'expiring-grants'() {
361
+ const days = parseInt(flag('days', '7'));
362
+ const { address } = await getWallet();
363
+ const expiring = await getExpiringGrants(
364
+ LCD_ENDPOINTS[0]?.url || LCD_ENDPOINTS[0],
365
+ address, days, 'granter',
366
+ );
367
+ json(expiring);
368
+ },
369
+
370
+ // ─── Connection ───
371
+
372
+ async connect() {
373
+ registerCleanupHandlers();
374
+ const nodeAddr = args[1];
375
+ if (!nodeAddr) die('Usage: connect <sentnode1...>');
376
+ const gb = parseInt(flag('gb', '1'));
377
+ const dns = flag('dns', undefined);
378
+ const result = await connectDirect({
379
+ mnemonic: MNEMONIC,
380
+ nodeAddress: nodeAddr,
381
+ gigabytes: gb,
382
+ dns,
383
+ onProgress: (step, detail) => console.log(`[${step}] ${detail}`),
384
+ });
385
+ json(result);
386
+ console.log(`\nDNS: ${resolveDnsServers(dns)}`);
387
+ console.log('VPN connected. Press Ctrl+C to disconnect.');
388
+ await new Promise(() => {}); // Keep alive
389
+ },
390
+
391
+ async 'connect-auto'() {
392
+ registerCleanupHandlers();
393
+ const country = flag('country', undefined);
394
+ const dns = flag('dns', undefined);
395
+ const result = await connectAuto({
396
+ mnemonic: MNEMONIC,
397
+ countries: country ? [country] : undefined,
398
+ dns,
399
+ onProgress: (step, detail) => console.log(`[${step}] ${detail}`),
400
+ });
401
+ json(result);
402
+ console.log(`\nDNS: ${resolveDnsServers(dns)}`);
403
+ console.log('VPN connected. Press Ctrl+C to disconnect.');
404
+ await new Promise(() => {});
405
+ },
406
+
407
+ // ─── DNS ───
408
+
409
+ async dns() {
410
+ const preset = flag('preset', undefined);
411
+ const custom = flag('custom', undefined);
412
+
413
+ if (custom) {
414
+ const ips = custom.split(',').map(s => s.trim());
415
+ console.log(`Custom DNS: ${ips.join(', ')}`);
416
+ console.log(`Resolved (with fallbacks): ${resolveDnsServers(ips)}`);
417
+ return;
418
+ }
419
+
420
+ if (preset) {
421
+ const p = DNS_PRESETS[preset.toLowerCase()];
422
+ if (!p) die(`Unknown preset: ${preset}. Available: ${Object.keys(DNS_PRESETS).join(', ')}`);
423
+ console.log(`Preset: ${p.name}`);
424
+ console.log(`Servers: ${p.servers.join(', ')}`);
425
+ console.log(`Resolved (with fallbacks): ${resolveDnsServers(preset)}`);
426
+ return;
427
+ }
428
+
429
+ // Show all presets
430
+ console.log('DNS Presets:');
431
+ console.log(` Default: ${DEFAULT_DNS_PRESET}`);
432
+ console.log(` Fallback order: ${DNS_FALLBACK_ORDER.join(' → ')}\n`);
433
+ for (const [key, p] of Object.entries(DNS_PRESETS)) {
434
+ const isDefault = key === DEFAULT_DNS_PRESET ? ' (default)' : '';
435
+ console.log(` ${key}${isDefault}`);
436
+ console.log(` ${p.description}`);
437
+ console.log(` Servers: ${p.servers.join(', ')}`);
438
+ console.log(` With fallbacks: ${resolveDnsServers(key)}`);
439
+ console.log();
440
+ }
441
+ },
442
+
443
+ async disconnect() {
444
+ await disconnect();
445
+ console.log('Disconnected.');
446
+ },
447
+
448
+ async status() {
449
+ if (isConnected()) {
450
+ json(getStatus());
451
+ } else {
452
+ console.log('Not connected.');
453
+ }
454
+ },
455
+
456
+ // ─── System ───
457
+
458
+ async deps() {
459
+ const deps = verifyDependencies();
460
+ json(deps);
461
+ },
462
+
463
+ async endpoints() {
464
+ const endpoints = LCD_ENDPOINTS.map(e => typeof e === 'string' ? e : e.url);
465
+ for (const url of endpoints) {
466
+ const start = Date.now();
467
+ try {
468
+ const res = await fetch(`${url}/cosmos/base/tendermint/v1beta1/node_info`, {
469
+ signal: AbortSignal.timeout(5000),
470
+ });
471
+ const ms = Date.now() - start;
472
+ console.log(` ${res.ok ? 'OK' : 'ERR'} ${ms}ms ${url}`);
473
+ } catch {
474
+ console.log(` FAIL ${url}`);
475
+ }
476
+ }
477
+ },
478
+ };
479
+
480
+ // ─── Run ───
481
+
482
+ if (!command || command === 'help' || command === '--help' || command === '-h') {
483
+ commands.help();
484
+ } else if (commands[command]) {
485
+ commands[command]().catch(err => {
486
+ console.error(`Error: ${err.message}`);
487
+ process.exit(1);
488
+ });
489
+ } else {
490
+ die(`Unknown command: ${command}. Run with --help for usage.`);
491
+ }