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
@@ -0,0 +1,1217 @@
1
+ # Sentinel AI Connect -- Complete Guide
2
+
3
+ A step-by-step technical guide for AI agents and developers building autonomous systems with decentralized VPN access. This document covers everything from first install to production deployment.
4
+
5
+ ---
6
+
7
+ ## Table of Contents
8
+
9
+ 1. [Understanding the Sentinel Network](#1-understanding-the-sentinel-network)
10
+ 2. [Setup and Dependencies](#2-setup-and-dependencies)
11
+ 3. [Wallet Management](#3-wallet-management)
12
+ 4. [Connection Lifecycle](#4-connection-lifecycle)
13
+ 5. [Error Handling](#5-error-handling)
14
+ 6. [Token Economics](#6-token-economics)
15
+ 7. [Security Considerations](#7-security-considerations)
16
+ 8. [Integration Patterns](#8-integration-patterns)
17
+ 9. [Autonomous Agent Pattern](#9-autonomous-agent-pattern)
18
+ 10. [Troubleshooting](#10-troubleshooting)
19
+ 11. [Protocol Details](#11-protocol-details)
20
+
21
+ ---
22
+
23
+ ## 1. Understanding the Sentinel Network
24
+
25
+ ### What is Sentinel?
26
+
27
+ Sentinel is a peer-to-peer bandwidth marketplace built on the Cosmos blockchain. Independent node operators around the world share their internet bandwidth and earn P2P tokens in return. Users pay nodes directly -- no middleman, no company, no centralized infrastructure.
28
+
29
+ ### Core Concepts
30
+
31
+ | Concept | Description |
32
+ |---|---|
33
+ | **Node** | A server running the Sentinel node software, offering bandwidth. Identified by `sentnode1...` address. |
34
+ | **Session** | A paid connection between a user and a node. Created by broadcasting a transaction to the blockchain. Has a unique session ID. |
35
+ | **Subscription** | A pre-paid allocation under a plan. Sessions are started within a subscription. |
36
+ | **Plan** | A bundle created by a provider (operator). Users subscribe to plans and connect to linked nodes. |
37
+ | **P2P Token** | The network's native token (chain denom: `udvpn`). 1 P2P = 1,000,000 udvpn. |
38
+ | **Handshake** | The V3 protocol exchange after session creation. Establishes encryption keys (WireGuard) or registers a UUID (V2Ray). |
39
+ | **Tunnel** | The encrypted data channel. WireGuard (kernel-level, changes IP) or V2Ray (SOCKS5 proxy with transport obfuscation). |
40
+
41
+ ### How a Connection Works
42
+
43
+ ```
44
+ Step 1: QUERY Agent queries blockchain LCD for online nodes with udvpn pricing
45
+ Step 2: SELECT SDK picks best node (country, price, protocol, health score)
46
+ Step 3: PAY Agent broadcasts MsgStartSession TX with payment (GB or hours)
47
+ Step 4: WAIT TX included in block, session ID assigned on-chain
48
+ Step 5: HANDSHAKE Agent performs V3 handshake with node's remote address
49
+ - WireGuard: send X25519 public key, receive server pubkey + endpoint
50
+ - V2Ray: send UUID, receive VMess/VLess metadata (transport, port, encryption)
51
+ Step 6: TUNNEL SDK starts local tunnel:
52
+ - WireGuard: install kernel adapter, route traffic through encrypted tunnel
53
+ - V2Ray: start SOCKS5 proxy process, test each transport until one works
54
+ Step 7: VERIFY SDK confirms traffic routes through node (IP address check)
55
+ Step 8: CONNECTED All traffic now goes through the encrypted P2P tunnel
56
+ ```
57
+
58
+ ### Node Types
59
+
60
+ Nodes support one or both protocols:
61
+
62
+ | Protocol | `service_type` | How It Works | Pros | Cons |
63
+ |---|---|---|---|---|
64
+ | **WireGuard** | `1` | Kernel-level encrypted tunnel. All system traffic routed through it. | Faster (10-50+ Mbps), true IP change, OS-level routing | Requires admin/root, single tunnel per system |
65
+ | **V2Ray** | `2` | SOCKS5 proxy with transport obfuscation (TCP, WebSocket, gRPC, QUIC, etc.) | No admin needed, obfuscation for censored networks, userspace | Application must be configured to use SOCKS5 proxy |
66
+
67
+ ### Blockchain Endpoints
68
+
69
+ The SDK uses LCD (REST) endpoints for queries and RPC endpoints for transaction broadcast. Multiple failover endpoints are built in:
70
+
71
+ | Type | Primary | Fallbacks |
72
+ |---|---|---|
73
+ | **LCD** | `https://lcd.sentinel.co` | polkachu, quokkastake, publicnode |
74
+ | **RPC** | `https://rpc.sentinel.co:443` | polkachu, mathnodes, publicnode, quokkastake |
75
+
76
+ All endpoints support automatic failover. If the primary fails, the SDK tries the next one. This is transparent to the caller.
77
+
78
+ ---
79
+
80
+ ## 2. Setup and Dependencies
81
+
82
+ ### Install
83
+
84
+ ```bash
85
+ npm install sentinel-ai-connect
86
+ ```
87
+
88
+ ### Post-Install Setup
89
+
90
+ The `postinstall` script attempts to download V2Ray automatically. If it fails (CI, restricted network), run manually:
91
+
92
+ ```bash
93
+ npx sentinel-ai setup
94
+ ```
95
+
96
+ This does three things:
97
+
98
+ 1. **Downloads V2Ray 5.2.1** to `bin/` (platform-specific binary with SHA256 verification)
99
+ 2. **Checks WireGuard** installation (optional -- prints install instructions if missing)
100
+ 3. **Verifies Node.js** version >= 20
101
+
102
+ ### Verify Setup Programmatically
103
+
104
+ ```js
105
+ import { setup } from 'sentinel-ai-connect';
106
+
107
+ const deps = await setup();
108
+ console.log(deps);
109
+ // { ready: boolean, environment: {...}, preflight: {...}, issues: string[] }
110
+ ```
111
+
112
+ ### V2Ray Version Warning
113
+
114
+ The SDK requires **exactly V2Ray 5.2.1**. Versions 5.44.1+ have observatory/balancer bugs that break multi-outbound configurations. The setup script enforces this version with SHA256 checksum verification. Do not upgrade.
115
+
116
+ ### WireGuard (Optional)
117
+
118
+ WireGuard requires admin/root privileges to install kernel-level network adapters. If your agent runs without admin privileges, use V2Ray nodes only:
119
+
120
+ ```js
121
+ await connect({ mnemonic, serviceType: 'v2ray' });
122
+ ```
123
+
124
+ On Windows, install WireGuard from: https://download.wireguard.com/windows-client/wireguard-installer.exe
125
+
126
+ ---
127
+
128
+ ## 3. Wallet Management
129
+
130
+ ### Creating a New Wallet
131
+
132
+ ```js
133
+ import { createWallet } from 'sentinel-ai-connect';
134
+
135
+ // Generate fresh wallet with random mnemonic
136
+ const { mnemonic, address } = await createWallet();
137
+ console.log(`Address: ${address}`); // sent1...
138
+ console.log(`Mnemonic: ${mnemonic}`); // 12 words
139
+
140
+ // CRITICAL: Store mnemonic securely. It cannot be recovered.
141
+ // NEVER log it, print it, or include it in error reports.
142
+ ```
143
+
144
+ ### Importing an Existing Wallet
145
+
146
+ ```js
147
+ import { importWallet } from 'sentinel-ai-connect';
148
+
149
+ const { address } = await importWallet(process.env.MNEMONIC);
150
+ console.log(`Address: ${address}`);
151
+ ```
152
+
153
+ ### Checking Balance
154
+
155
+ ```js
156
+ import { getBalance } from 'sentinel-ai-connect';
157
+
158
+ const balance = await getBalance(process.env.MNEMONIC);
159
+
160
+ console.log(`${balance.p2p} (${balance.udvpn} udvpn) — funded: ${balance.funded}`);
161
+
162
+ if (!balance.funded) {
163
+ console.error('Balance too low. Fund wallet before connecting.');
164
+ console.log(`Wallet address: ${balance.address}`);
165
+ }
166
+ ```
167
+
168
+ ### Wallet Security Rules
169
+
170
+ | Rule | Reason |
171
+ |---|---|
172
+ | **Never log the mnemonic** | Anyone with the mnemonic controls the wallet and all funds |
173
+ | **Store in environment variable** | `process.env.MNEMONIC`, not in source code or config files |
174
+ | **Never include in error reports** | Stack traces, HTTP headers, and logs must not contain it |
175
+ | **Use `.env` files with `.gitignore`** | Prevent accidental commits |
176
+ | **Key zeroing** | The SDK zeros private key bytes from memory after signing. Do not cache the raw private key yourself. |
177
+
178
+ ### Address Formats
179
+
180
+ | Format | Example | Used For |
181
+ |---|---|---|
182
+ | `sent1...` | `sent12e03wzmxjerwqt63p...` | User wallet address (account) |
183
+ | `sentnode1...` | `sentnode1qtw6mrgef4u...` | Node operator address |
184
+ | `sentprov1...` | `sentprov1qtw6mrgef4u...` | Provider address |
185
+
186
+ These are all derived from the same key. The SDK provides conversion helpers: `sentToSentnode()`, `sentToSentprov()`, `sentprovToSent()`.
187
+
188
+ ---
189
+
190
+ ## 4. Connection Lifecycle
191
+
192
+ ### State Machine
193
+
194
+ ```
195
+ IDLE --> CONNECTING --> CONNECTED --> DISCONNECTING --> IDLE
196
+ | | |
197
+ | +--> ERROR --------+ |
198
+ | | |
199
+ +----------------------------+---------+
200
+ ```
201
+
202
+ ### Minimal Connection
203
+
204
+ ```js
205
+ import { connect, disconnect, isVpnActive } from 'sentinel-ai-connect';
206
+
207
+ const vpn = await connect({ mnemonic: process.env.MNEMONIC });
208
+ // vpn = { sessionId, protocol, nodeAddress, socksPort, socksAuth, dryRun, ip }
209
+
210
+ console.log(`Connected: ${vpn.serviceType} via ${vpn.nodeAddress}`);
211
+ console.log(`Session: ${vpn.sessionId}`);
212
+
213
+ // Check status at any time
214
+ if (isVpnActive()) {
215
+ console.log('VPN is active');
216
+ }
217
+
218
+ // When done
219
+ await disconnect();
220
+ ```
221
+
222
+ ### Connection with Progress Tracking
223
+
224
+ ```js
225
+ import { connect } from 'sentinel-ai-connect';
226
+
227
+ const vpn = await connect({
228
+ mnemonic: process.env.MNEMONIC,
229
+ country: 'Germany',
230
+ onProgress: (step, detail) => {
231
+ // Steps: 'wallet', 'node', 'session', 'handshake', 'tunnel', 'verify', 'proxy'
232
+ console.log(`[${step}] ${detail}`);
233
+ },
234
+ });
235
+ ```
236
+
237
+ Example progress output:
238
+
239
+ ```
240
+ [wallet] Deriving wallet from mnemonic...
241
+ [node] Querying online nodes...
242
+ [node] Found 847 nodes, 23 in Germany
243
+ [node] Selected sentnode1abc... (V2Ray, 0.02 P2P/GB)
244
+ [session] Checking for existing session...
245
+ [session] Broadcasting session TX (per-GB)...
246
+ [session] Session created: 37595661 (per-GB, tx: A1B2C3...)
247
+ [handshake] Performing V3 handshake...
248
+ [handshake] Got V2Ray config: 3 transports
249
+ [tunnel] Testing grpc-none...
250
+ [verify] grpc-none: connected!
251
+ [proxy] Setting system SOCKS proxy -> 127.0.0.1:1080
252
+ ```
253
+
254
+ ### Connection with Cancellation
255
+
256
+ ```js
257
+ const controller = new AbortController();
258
+
259
+ // Cancel after 30 seconds
260
+ setTimeout(() => controller.abort(), 30000);
261
+
262
+ try {
263
+ const vpn = await connect({
264
+ mnemonic: process.env.MNEMONIC,
265
+ signal: controller.signal,
266
+ });
267
+ } catch (err) {
268
+ if (err.code === 'ABORTED') {
269
+ console.log('Connection cancelled');
270
+ }
271
+ }
272
+ ```
273
+
274
+ ### Specific Node Connection
275
+
276
+ ```js
277
+ const vpn = await connect({
278
+ mnemonic: process.env.MNEMONIC,
279
+ nodeAddress: 'sentnode1qtw6mrgef4uhxk0j5dg5wnpwmktxfatqe6yp7q',
280
+ gigabytes: 2, // Pay for 2 GB
281
+ });
282
+ ```
283
+
284
+ ### Hourly Session
285
+
286
+ ```js
287
+ const vpn = await connect({
288
+ mnemonic: process.env.MNEMONIC,
289
+ hours: 4, // Pay for 4 hours instead of per-GB
290
+ });
291
+ ```
292
+
293
+ ### Disconnect
294
+
295
+ `disconnect()` performs these steps in order:
296
+
297
+ 1. Kill V2Ray process (if V2Ray) or remove WireGuard adapter (if WireGuard)
298
+ 2. Clear system SOCKS proxy (if set)
299
+ 3. Disable kill switch (if enabled)
300
+ 4. End session on-chain (fire-and-forget -- does not block on TX confirmation)
301
+ 5. Clear local state files
302
+
303
+ ```js
304
+ await disconnect();
305
+ // Connection is now fully torn down.
306
+ // On-chain session end is fire-and-forget -- it may take a few seconds.
307
+ ```
308
+
309
+ ### Session Recovery
310
+
311
+ If a connection partially succeeds (payment TX broadcast but tunnel failed), the session exists on-chain and can be recovered without paying again. Use the SDK's `recoverSession`:
312
+
313
+ ```js
314
+ import { recoverSession } from 'sentinel-dvpn-sdk';
315
+
316
+ try {
317
+ const vpn = await connect({ mnemonic });
318
+ } catch (err) {
319
+ if (err.code === 'PARTIAL_CONNECTION_FAILED' ||
320
+ err.code === 'SESSION_EXISTS' ||
321
+ err.code === 'SESSION_EXTRACT_FAILED') {
322
+ // Session is on-chain. Recover it.
323
+ const vpn = await recoverSession({
324
+ mnemonic,
325
+ nodeAddress: err.details.nodeAddress,
326
+ });
327
+ }
328
+ }
329
+ ```
330
+
331
+ ### Cleanup Handlers
332
+
333
+ The `sentinel-ai-connect` wrapper automatically registers cleanup handlers when you call `connect()`. If you use the underlying SDK directly, you must register them yourself:
334
+
335
+ ```js
336
+ import { registerCleanupHandlers, connectAuto } from 'sentinel-dvpn-sdk';
337
+
338
+ // Register ONCE at app startup (before any connect call)
339
+ registerCleanupHandlers();
340
+
341
+ // Now connectAuto() will work
342
+ const vpn = await connectAuto({ mnemonic });
343
+ ```
344
+
345
+ Alternatively, use the SDK's `quickConnect()` which auto-registers cleanup handlers:
346
+
347
+ ```js
348
+ import { quickConnect } from 'sentinel-dvpn-sdk';
349
+
350
+ const vpn = await quickConnect({ mnemonic });
351
+ // cleanup handlers are registered automatically
352
+ ```
353
+
354
+ When using `sentinel-ai-connect`'s `connect()`, this is handled for you automatically.
355
+
356
+ ---
357
+
358
+ ## 5. Error Handling
359
+
360
+ ### Error Hierarchy
361
+
362
+ All SDK errors extend `SentinelError`:
363
+
364
+ ```
365
+ SentinelError (base)
366
+ +-- ValidationError Input validation failures
367
+ +-- NodeError Node-level failures
368
+ +-- ChainError Blockchain/transaction failures
369
+ +-- TunnelError Tunnel setup failures
370
+ +-- SecurityError Security-related failures
371
+ ```
372
+
373
+ ### Programmatic Error Handling
374
+
375
+ For advanced error handling, import typed errors from the underlying SDK:
376
+
377
+ ```js
378
+ import { connect } from 'sentinel-ai-connect';
379
+ import {
380
+ SentinelError,
381
+ ValidationError,
382
+ NodeError,
383
+ ChainError,
384
+ TunnelError,
385
+ SecurityError,
386
+ ErrorCodes,
387
+ ERROR_SEVERITY,
388
+ isRetryable,
389
+ userMessage,
390
+ } from 'sentinel-dvpn-sdk';
391
+
392
+ try {
393
+ await connect({ mnemonic });
394
+ } catch (err) {
395
+ // Check error type
396
+ if (err instanceof ValidationError) {
397
+ // Bad input -- fix before retrying
398
+ console.error(`Input error: ${err.code} -- ${err.message}`);
399
+ return;
400
+ }
401
+
402
+ if (err instanceof SecurityError) {
403
+ // Security issue -- do not retry automatically
404
+ console.error(`Security alert: ${err.code}`);
405
+ return;
406
+ }
407
+
408
+ // Check severity
409
+ const severity = ERROR_SEVERITY[err.code];
410
+ switch (severity) {
411
+ case 'fatal':
412
+ // Cannot proceed without fixing the root cause
413
+ console.error(`Fatal: ${userMessage(err)}`);
414
+ break;
415
+ case 'retryable':
416
+ // Try again, possibly with a different node
417
+ console.log(`Retrying: ${userMessage(err)}`);
418
+ await connect({ mnemonic, maxAttempts: 5 });
419
+ break;
420
+ case 'recoverable':
421
+ // Session exists on-chain, recover it
422
+ console.log(`Recovering: ${err.code}`);
423
+ await recoverSession({ mnemonic });
424
+ break;
425
+ case 'infrastructure':
426
+ // System dependency issue
427
+ console.error(`System issue: ${userMessage(err)}`);
428
+ break;
429
+ }
430
+ }
431
+ ```
432
+
433
+ ### Complete Error Code Reference
434
+
435
+ #### Fatal Errors (Do Not Retry)
436
+
437
+ | Code | Class | Cause | Fix |
438
+ |---|---|---|---|
439
+ | `INVALID_MNEMONIC` | ValidationError | Mnemonic is not 12+ valid BIP39 words | Provide a valid BIP39 mnemonic |
440
+ | `INVALID_OPTIONS` | ValidationError | Missing or malformed connect options | Check required fields |
441
+ | `INVALID_NODE_ADDRESS` | ValidationError | Node address not in `sentnode1...` format | Use a valid node address |
442
+ | `INVALID_GIGABYTES` | ValidationError | Gigabytes not an integer 1-100 | Use integer 1-100 |
443
+ | `INVALID_URL` | ValidationError | Malformed URL provided | Check URL format |
444
+ | `INVALID_PLAN_ID` | ValidationError | Plan ID is not a valid number | Use a valid plan ID |
445
+ | `INSUFFICIENT_BALANCE` | ChainError | Wallet has < cost of session | Fund wallet with P2P tokens |
446
+ | `ALREADY_CONNECTED` | SentinelError | `connect()` called while already connected | Call `disconnect()` first |
447
+ | `SESSION_POISONED` | SentinelError | Session previously failed and was marked poisoned | Use `forceNewSession: true` |
448
+ | `ABORTED` | SentinelError | Connection cancelled via AbortController | Intentional cancellation |
449
+ | `UNKNOWN_MSG_TYPE` | ChainError | Protobuf message type not recognized | Update SDK version |
450
+ | `WG_NOT_AVAILABLE` | TunnelError | WireGuard not installed | Install WireGuard or use `serviceType: 'v2ray'` |
451
+
452
+ #### Retryable Errors (Try Again or Switch Nodes)
453
+
454
+ | Code | Class | Cause | Suggested Action |
455
+ |---|---|---|---|
456
+ | `NODE_OFFLINE` | NodeError | Node not responding to status query | Try different node |
457
+ | `NODE_NO_UDVPN` | NodeError | Node does not list udvpn in pricing | Try different node |
458
+ | `NODE_CLOCK_DRIFT` | NodeError | Node clock >120s off (VMess AEAD fails) | Try different node |
459
+ | `NODE_INACTIVE` | NodeError | Node status is inactive on-chain | Try different node |
460
+ | `NODE_NOT_FOUND` | NodeError | Node address not found on chain | Verify address, try different node |
461
+ | `NODE_DATABASE_CORRUPT` | NodeError | Node returned invalid data | Try different node |
462
+ | `V2RAY_ALL_FAILED` | TunnelError | Every V2Ray transport failed | Try different node or WireGuard |
463
+ | `WG_NO_CONNECTIVITY` | TunnelError | WireGuard adapter installed but no traffic | Try different node |
464
+ | `TUNNEL_SETUP_FAILED` | TunnelError | Generic tunnel failure | Retry or try different node |
465
+ | `BROADCAST_FAILED` | ChainError | TX broadcast rejected | Retry after delay (7s between TXs) |
466
+ | `TX_FAILED` | ChainError | TX included in block but failed | Check balance, retry |
467
+ | `LCD_ERROR` | ChainError | LCD endpoint query failed | Automatic failover handles this |
468
+ | `ALL_ENDPOINTS_FAILED` | ChainError | All LCD/RPC endpoints unreachable | Check internet, retry later |
469
+ | `ALL_NODES_FAILED` | SentinelError | Every candidate node failed | Relax filters, increase maxAttempts |
470
+ | `CHAIN_LAG` | ChainError | Session not yet confirmed on node | Wait 10-15s and retry |
471
+
472
+ #### Recoverable Errors (Session Exists On-Chain)
473
+
474
+ | Code | Class | Cause | Action |
475
+ |---|---|---|---|
476
+ | `SESSION_EXISTS` | SentinelError | Active session found for this wallet+node | Call `recoverSession()` |
477
+ | `SESSION_EXTRACT_FAILED` | ChainError | TX succeeded but session ID not extracted | Call `recoverSession()` |
478
+ | `PARTIAL_CONNECTION_FAILED` | SentinelError | Payment OK, tunnel failed | Call `recoverSession()` |
479
+
480
+ #### Infrastructure Errors (Check System)
481
+
482
+ | Code | Class | Cause | Action |
483
+ |---|---|---|---|
484
+ | `V2RAY_NOT_FOUND` | TunnelError | V2Ray binary missing | Run `npx sentinel-ai setup` |
485
+ | `TLS_CERT_CHANGED` | SecurityError | Node TLS cert differs from pinned cert | Investigate -- possible MITM |
486
+
487
+ ### Error Details
488
+
489
+ Every error includes a `.details` object with structured context:
490
+
491
+ ```js
492
+ catch (err) {
493
+ console.log(err.code); // 'NODE_OFFLINE'
494
+ console.log(err.message); // 'Node sentnode1abc... did not respond within 15s'
495
+ console.log(err.details); // { nodeAddress: 'sentnode1abc...', timeout: 15000 }
496
+ console.log(err.name); // 'NodeError'
497
+ }
498
+ ```
499
+
500
+ ---
501
+
502
+ ## 6. Token Economics
503
+
504
+ ### P2P Token Basics
505
+
506
+ | Property | Value |
507
+ |---|---|
508
+ | Display name | P2P |
509
+ | Chain denom | `udvpn` (micro-dvpn) |
510
+ | Conversion | 1 P2P = 1,000,000 udvpn |
511
+ | Blockchain | Cosmos (sentinelhub-2) |
512
+ | Gas price | 0.2 udvpn per gas unit |
513
+
514
+ ### Session Pricing
515
+
516
+ Nodes set their own prices. Two pricing models:
517
+
518
+ | Model | How It Works | Typical Range |
519
+ |---|---|---|
520
+ | **Per-GB** | Pay upfront for N gigabytes. Session ends when data is consumed. | 5,000-50,000 udvpn/GB |
521
+ | **Per-Hour** | Pay upfront for N hours. Session ends when time expires. | 10,000-100,000 udvpn/hour |
522
+
523
+ ### Cost Estimation
524
+
525
+ ```js
526
+ import { listNodes, estimateSessionPrice, formatP2P } from 'sentinel-dvpn-sdk';
527
+
528
+ const nodes = await listNodes();
529
+ for (const node of nodes.slice(0, 5)) {
530
+ const cost = estimateSessionPrice(node, { gigabytes: 1 });
531
+ console.log(`${node.address}: ${formatP2P(cost)} per GB`);
532
+ }
533
+ ```
534
+
535
+ ### Transaction Costs
536
+
537
+ Every on-chain action costs gas:
538
+
539
+ | Operation | Approximate Gas | Approximate Cost |
540
+ |---|---|---|
541
+ | Start session | ~200,000 | ~40,000 udvpn (0.04 P2P) |
542
+ | End session | ~150,000 | ~30,000 udvpn (0.03 P2P) |
543
+ | Subscribe to plan | ~250,000 | ~50,000 udvpn (0.05 P2P) |
544
+
545
+ ### Budget Planning for Agents
546
+
547
+ For an agent that connects 10 times per day, 1 GB per session:
548
+
549
+ ```
550
+ Daily cost estimate:
551
+ 10 sessions x 50,000 udvpn/GB = 500,000 udvpn (bandwidth)
552
+ 10 start TXs x 40,000 udvpn = 400,000 udvpn (gas)
553
+ 10 end TXs x 30,000 udvpn = 300,000 udvpn (gas)
554
+ ------------------------------------------------
555
+ Total: ~1,200,000 udvpn/day (1.2 P2P)
556
+ ```
557
+
558
+ ### Auto-Funding Pattern
559
+
560
+ For fully autonomous agents, monitor balance and trigger swap when low:
561
+
562
+ ```js
563
+ import { getBalance } from 'sentinel-ai-connect';
564
+
565
+ async function ensureFunded(mnemonic, minUdvpn = 500000) {
566
+ const balance = await getBalance(mnemonic);
567
+
568
+ if (balance.udvpn < minUdvpn) {
569
+ // Trigger Osmosis swap or alert operator
570
+ console.error(`Low balance: ${balance.udvpn} udvpn. Need ${minUdvpn}.`);
571
+ console.error(`Fund address: ${balance.address}`);
572
+ return false;
573
+ }
574
+ return true;
575
+ }
576
+ ```
577
+
578
+ ---
579
+
580
+ ## 7. Security Considerations
581
+
582
+ ### Mnemonic Security
583
+
584
+ The mnemonic is the master key to the wallet. Anyone with it can spend all funds.
585
+
586
+ ```js
587
+ // CORRECT: Load from environment
588
+ const mnemonic = process.env.MNEMONIC;
589
+
590
+ // WRONG: Hardcoded in source
591
+ const mnemonic = 'word1 word2 word3 ...'; // NEVER DO THIS
592
+
593
+ // WRONG: Logged to console
594
+ console.log(`Using mnemonic: ${mnemonic}`); // NEVER DO THIS
595
+
596
+ // WRONG: Included in error report
597
+ throw new Error(`Failed with mnemonic ${mnemonic}`); // NEVER DO THIS
598
+ ```
599
+
600
+ For autonomous agents, consider:
601
+
602
+ - Store mnemonic in a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)
603
+ - Use a dedicated wallet with limited funds (not your main wallet)
604
+ - Monitor wallet balance and set alerts for unexpected withdrawals
605
+
606
+ ### Key Zeroing
607
+
608
+ The SDK zeros private key material from memory after use:
609
+
610
+ ```js
611
+ // Internal SDK behavior:
612
+ const privKey = await derivePrivateKey(mnemonic);
613
+ const signature = sign(privKey, data);
614
+ privKey.fill(0); // Zero the key buffer
615
+ ```
616
+
617
+ Do not cache raw private keys in your own code. Let the SDK manage key lifecycle.
618
+
619
+ ### Tunnel Verification
620
+
621
+ After connecting, verify that traffic actually routes through the tunnel:
622
+
623
+ ```js
624
+ import { connect } from 'sentinel-ai-connect';
625
+ import { verifyConnection } from 'sentinel-dvpn-sdk';
626
+
627
+ const vpn = await connect({ mnemonic });
628
+ const check = await verifyConnection({ timeoutMs: 8000 });
629
+
630
+ if (check.working) {
631
+ console.log(`VPN IP: ${check.vpnIp}`);
632
+ } else {
633
+ console.error('Traffic is NOT going through VPN!');
634
+ await disconnect();
635
+ }
636
+ ```
637
+
638
+ ### DNS Leak Prevention
639
+
640
+ By default, the SDK uses Handshake DNS (103.196.38.38). This prevents DNS queries from leaking to your ISP.
641
+
642
+ ```js
643
+ // Default: Handshake DNS (decentralized, no logging)
644
+ await connect({ mnemonic, dns: 'handshake' });
645
+
646
+ // Alternative: Google DNS
647
+ await connect({ mnemonic, dns: 'google' });
648
+
649
+ // Alternative: Cloudflare DNS
650
+ await connect({ mnemonic, dns: 'cloudflare' });
651
+ ```
652
+
653
+ For maximum security, enable DNS leak prevention:
654
+
655
+ ```js
656
+ import { enableDnsLeakPrevention, disableDnsLeakPrevention } from 'sentinel-dvpn-sdk';
657
+
658
+ enableDnsLeakPrevention(); // Forces ALL DNS through tunnel
659
+ // ... use VPN ...
660
+ disableDnsLeakPrevention(); // Restore default DNS
661
+ ```
662
+
663
+ ### Kill Switch
664
+
665
+ The kill switch blocks all non-tunnel traffic at the OS firewall level. If the VPN drops, no traffic leaks.
666
+
667
+ ```js
668
+ await connect({
669
+ mnemonic,
670
+ killSwitch: true, // Block all non-tunnel traffic
671
+ });
672
+ ```
673
+
674
+ **Warning:** If the connection drops and the agent crashes, the kill switch persists. The system will have no internet until the kill switch is explicitly disabled or the firewall rules are manually removed.
675
+
676
+ ### TOFU TLS (Trust On First Use)
677
+
678
+ The SDK pins the TLS certificate of each node on first connection. If the certificate changes on subsequent connections, it throws `TLS_CERT_CHANGED`. This detects man-in-the-middle attacks but also triggers on legitimate certificate rotations.
679
+
680
+ ```js
681
+ import { clearKnownNode } from 'sentinel-dvpn-sdk';
682
+
683
+ try {
684
+ await connect({ mnemonic, nodeAddress: 'sentnode1abc...' });
685
+ } catch (err) {
686
+ if (err.code === 'TLS_CERT_CHANGED') {
687
+ // Option 1: Alert and abort (safest)
688
+ console.error('Possible MITM attack on node');
689
+
690
+ // Option 2: Clear pinned cert and retry (if you trust the node)
691
+ clearKnownNode('sentnode1abc...');
692
+ await connect({ mnemonic, nodeAddress: 'sentnode1abc...' });
693
+ }
694
+ }
695
+ ```
696
+
697
+ ---
698
+
699
+ ## 8. Integration Patterns
700
+
701
+ ### Pattern 1: Embedded Library
702
+
703
+ The simplest pattern. Import `sentinel-ai-connect` directly in your agent.
704
+
705
+ ```js
706
+ import { connect, disconnect, isVpnActive } from 'sentinel-ai-connect';
707
+
708
+ class MyAgent {
709
+ async doWorkWithPrivacy() {
710
+ if (!isVpnActive()) {
711
+ await connect({ mnemonic: process.env.MNEMONIC, country: 'Germany' });
712
+ }
713
+
714
+ // All HTTP requests now go through VPN
715
+ const response = await fetch('https://target-api.example.com/data');
716
+ const data = await response.json();
717
+
718
+ await disconnect();
719
+ return data;
720
+ }
721
+ }
722
+ ```
723
+
724
+ ### Pattern 2: Long-Running Daemon
725
+
726
+ For agents that need persistent VPN access, combine the simple API with SDK-level features:
727
+
728
+ ```js
729
+ import { connect, disconnect } from 'sentinel-ai-connect';
730
+ import {
731
+ autoReconnect,
732
+ registerCleanupHandlers,
733
+ events,
734
+ } from 'sentinel-dvpn-sdk';
735
+
736
+ registerCleanupHandlers();
737
+
738
+ // Connect once
739
+ const vpn = await connect({
740
+ mnemonic: process.env.MNEMONIC,
741
+ onProgress: (step, detail) => console.log(`[vpn] [${step}] ${detail}`),
742
+ });
743
+
744
+ // Auto-reconnect on failure
745
+ const monitor = autoReconnect({
746
+ mnemonic: process.env.MNEMONIC,
747
+ pollIntervalMs: 10000,
748
+ maxRetries: 10,
749
+ backoffMs: [2000, 5000, 10000, 30000, 60000],
750
+ onReconnecting: (n) => console.log(`[vpn] Reconnecting (attempt ${n})...`),
751
+ onReconnected: (r) => console.log(`[vpn] Reconnected to ${r.nodeAddress}`),
752
+ onGaveUp: () => {
753
+ console.error('[vpn] All reconnect attempts failed');
754
+ process.exit(1);
755
+ },
756
+ });
757
+
758
+ // Listen for events
759
+ events.on('disconnected', ({ reason }) => {
760
+ console.log(`[vpn] Disconnected: ${reason}`);
761
+ });
762
+
763
+ // Graceful shutdown
764
+ process.on('SIGTERM', async () => {
765
+ monitor.stop();
766
+ await disconnect();
767
+ process.exit(0);
768
+ });
769
+ ```
770
+
771
+ ### Pattern 3: On-Demand VPN
772
+
773
+ Connect only when needed, disconnect when done. Minimizes cost.
774
+
775
+ ```js
776
+ import { connect, disconnect, isVpnActive } from 'sentinel-ai-connect';
777
+
778
+ async function withVpn(country, fn) {
779
+ let retries = 3;
780
+ while (retries > 0) {
781
+ try {
782
+ const vpn = await connect({
783
+ mnemonic: process.env.MNEMONIC,
784
+ country,
785
+ });
786
+
787
+ try {
788
+ return await fn(vpn);
789
+ } finally {
790
+ await disconnect();
791
+ }
792
+ } catch (err) {
793
+ retries--;
794
+ if (retries === 0) throw err;
795
+ if (err.message.includes('insufficient')) throw err; // Don't retry balance errors
796
+ console.log(`VPN connect failed, ${retries} retries left: ${err.message}`);
797
+ }
798
+ }
799
+ }
800
+
801
+ // Usage
802
+ const result = await withVpn('Germany', async (vpn) => {
803
+ const res = await fetch('https://api.example.com/geo-restricted-data');
804
+ return res.json();
805
+ });
806
+ ```
807
+
808
+ ### Pattern 4: Multi-Country Rotation
809
+
810
+ Connect to different countries sequentially for geo-distributed operations:
811
+
812
+ ```js
813
+ import { connect, disconnect } from 'sentinel-ai-connect';
814
+
815
+ const countries = ['Germany', 'Japan', 'Brazil', 'Australia', 'South Africa'];
816
+
817
+ for (const country of countries) {
818
+ await connect({
819
+ mnemonic: process.env.MNEMONIC,
820
+ country,
821
+ onProgress: (step, detail) => console.log(`[${country}] [${step}] ${detail}`),
822
+ });
823
+
824
+ // Do work from this country's IP
825
+ const res = await fetch('https://api.example.com/local-data');
826
+ console.log(`${country}: ${res.status}`);
827
+
828
+ await disconnect();
829
+
830
+ // Wait between sessions to avoid chain rate limits
831
+ await new Promise(r => setTimeout(r, 7000));
832
+ }
833
+ ```
834
+
835
+ ### Pattern 5: SOCKS5 Proxy (V2Ray)
836
+
837
+ V2Ray creates a local SOCKS5 proxy. Route specific traffic through it:
838
+
839
+ ```js
840
+ import { connect, disconnect } from 'sentinel-ai-connect';
841
+ import { SocksProxyAgent } from 'socks-proxy-agent';
842
+ import axios from 'axios';
843
+
844
+ const vpn = await connect({
845
+ mnemonic: process.env.MNEMONIC,
846
+ serviceType: 'v2ray',
847
+ });
848
+
849
+ // Create agent for the SOCKS5 proxy
850
+ const agent = new SocksProxyAgent(`socks5://127.0.0.1:${vpn.socksPort}`);
851
+
852
+ // Route specific requests through VPN
853
+ const vpnResponse = await axios.get('https://api.ipify.org', {
854
+ httpAgent: agent,
855
+ httpsAgent: agent,
856
+ });
857
+ console.log(`VPN IP: ${vpnResponse.data}`);
858
+
859
+ // Direct request (not through VPN)
860
+ const directResponse = await axios.get('https://api.ipify.org');
861
+ console.log(`Direct IP: ${directResponse.data}`);
862
+
863
+ await disconnect();
864
+ ```
865
+
866
+ **Important:** Node.js native `fetch()` silently ignores SOCKS5 proxy configuration. Use `axios` with an explicit agent, not `fetch()`.
867
+
868
+ ---
869
+
870
+ ## 9. Autonomous Agent Pattern
871
+
872
+ A complete pattern for a fully autonomous AI agent that manages its own VPN lifecycle, monitors balance, and handles all error cases.
873
+
874
+ ```js
875
+ import { connect, disconnect, isVpnActive, createWallet, getBalance } from 'sentinel-ai-connect';
876
+ import {
877
+ autoReconnect,
878
+ registerCleanupHandlers,
879
+ createClient,
880
+ verifyConnection,
881
+ events,
882
+ ErrorCodes,
883
+ isRetryable,
884
+ } from 'sentinel-dvpn-sdk';
885
+
886
+ class AutonomousVpnAgent {
887
+ constructor(mnemonic, opts = {}) {
888
+ this.mnemonic = mnemonic;
889
+ this.minBalance = opts.minBalance || 500000; // 0.5 P2P
890
+ this.preferredCountry = opts.country || null;
891
+ this.monitor = null;
892
+ this.running = false;
893
+
894
+ registerCleanupHandlers();
895
+ }
896
+
897
+ // ── Lifecycle ───────────────────────────────────────
898
+
899
+ async start() {
900
+ this.running = true;
901
+
902
+ // Check balance before connecting
903
+ const funded = await this.checkBalance();
904
+ if (!funded) {
905
+ throw new Error(`Insufficient balance. Need ${this.minBalance} udvpn minimum.`);
906
+ }
907
+
908
+ // Connect with retry
909
+ await this.connectWithRetry(3);
910
+
911
+ // Start auto-reconnect monitor
912
+ this.monitor = autoReconnect({
913
+ mnemonic: this.mnemonic,
914
+ pollIntervalMs: 10000,
915
+ maxRetries: 10,
916
+ backoffMs: [2000, 5000, 10000, 30000, 60000],
917
+ onReconnecting: (n) => this.log(`Reconnecting (attempt ${n})...`),
918
+ onReconnected: (result) => this.log(`Reconnected via ${result.nodeAddress}`),
919
+ onGaveUp: () => {
920
+ this.log('Auto-reconnect exhausted. Attempting full restart...');
921
+ this.restart();
922
+ },
923
+ });
924
+
925
+ // Periodic balance check
926
+ this.balanceTimer = setInterval(() => this.checkBalance(), 300000); // Every 5 min
927
+
928
+ this.log('Agent started. VPN active.');
929
+ }
930
+
931
+ async stop() {
932
+ this.running = false;
933
+ if (this.monitor) this.monitor.stop();
934
+ if (this.balanceTimer) clearInterval(this.balanceTimer);
935
+ if (isVpnActive()) await disconnect();
936
+ this.log('Agent stopped.');
937
+ }
938
+
939
+ async restart() {
940
+ if (!this.running) return;
941
+ try {
942
+ if (isVpnActive()) await disconnect();
943
+ } catch {}
944
+ // Wait before reconnecting
945
+ await new Promise(r => setTimeout(r, 10000));
946
+ if (this.running) await this.start();
947
+ }
948
+
949
+ // ── Connection ──────────────────────────────────────
950
+
951
+ async connectWithRetry(maxRetries) {
952
+ for (let i = 0; i < maxRetries; i++) {
953
+ try {
954
+ const vpn = await connect({
955
+ mnemonic: this.mnemonic,
956
+ country: this.preferredCountry,
957
+ maxAttempts: 5,
958
+ onProgress: (step, detail) => this.log(`[${step}] ${detail}`),
959
+ });
960
+
961
+ // Verify tunnel works
962
+ const check = await verifyConnection({ timeoutMs: 8000 });
963
+ if (check.working) {
964
+ this.log(`Connected. VPN IP: ${check.vpnIp}`);
965
+ return vpn;
966
+ }
967
+
968
+ this.log('Tunnel verification failed. Disconnecting and retrying...');
969
+ await disconnect();
970
+ } catch (err) {
971
+ this.log(`Connect attempt ${i + 1}/${maxRetries} failed: ${err.code} -- ${err.message}`);
972
+
973
+ if (err.code === ErrorCodes.INSUFFICIENT_BALANCE) {
974
+ throw err; // Cannot recover without funding
975
+ }
976
+
977
+ if (i < maxRetries - 1) {
978
+ const delay = Math.min(5000 * (i + 1), 30000);
979
+ await new Promise(r => setTimeout(r, delay));
980
+ }
981
+ }
982
+ }
983
+ throw new Error(`Failed to connect after ${maxRetries} attempts`);
984
+ }
985
+
986
+ // ── Balance Monitoring ──────────────────────────────
987
+
988
+ async checkBalance() {
989
+ try {
990
+ const balance = await getBalance(this.mnemonic);
991
+
992
+ this.log(`Balance: ${balance.p2p} (${balance.udvpn} udvpn)`);
993
+
994
+ if (balance.udvpn < this.minBalance) {
995
+ this.log(`WARNING: Balance below minimum (${this.minBalance} udvpn). Fund wallet.`);
996
+ // Here you could: trigger Osmosis swap, send alert, call webhook, etc.
997
+ return false;
998
+ }
999
+ return true;
1000
+ } catch (err) {
1001
+ this.log(`Balance check failed: ${err.message}`);
1002
+ return true; // Assume funded on query failure
1003
+ }
1004
+ }
1005
+
1006
+ // ── Logging ─────────────────────────────────────────
1007
+
1008
+ log(msg) {
1009
+ const ts = new Date().toISOString();
1010
+ console.log(`[${ts}] [vpn-agent] ${msg}`);
1011
+ }
1012
+ }
1013
+
1014
+ // ── Usage ────────────────────────────────────────────
1015
+
1016
+ const agent = new AutonomousVpnAgent(process.env.MNEMONIC, {
1017
+ country: 'Germany',
1018
+ minBalance: 1000000, // 1 P2P
1019
+ });
1020
+
1021
+ await agent.start();
1022
+
1023
+ // Agent is now running with auto-reconnect and balance monitoring.
1024
+ // Stop when done:
1025
+ // await agent.stop();
1026
+ ```
1027
+
1028
+ ### Key Design Decisions for Autonomous Agents
1029
+
1030
+ | Decision | Recommendation | Reason |
1031
+ |---|---|---|
1032
+ | **Pricing model** | Per-GB (not per-hour) | Agents may be idle between tasks. Per-hour drains balance on timer. |
1033
+ | **Node selection** | Auto (not specific node) | Specific nodes go offline. Auto-select falls back to alternatives. |
1034
+ | **Kill switch** | Off for agents | If kill switch persists after crash, agent loses all connectivity. |
1035
+ | **Cleanup handlers** | Always register | Prevents orphaned tunnels that kill system internet. |
1036
+ | **Balance monitoring** | Check every 5 minutes | Catch low balance before a connection attempt fails. |
1037
+ | **Error recovery** | Use `recoverSession()` for recoverable errors | Avoids paying twice for the same session. |
1038
+ | **TX timing** | Wait 7s between transactions | Chain rate limits cause failures with rapid TX. |
1039
+
1040
+ ---
1041
+
1042
+ ## 10. Troubleshooting
1043
+
1044
+ ### Common Issues
1045
+
1046
+ #### "V2Ray binary not found"
1047
+
1048
+ ```bash
1049
+ npx sentinel-ai setup
1050
+ ```
1051
+
1052
+ The setup script downloads V2Ray 5.2.1 to the `bin/` directory. If running in CI or a restricted environment, download manually and set `v2rayExePath`:
1053
+
1054
+ ```js
1055
+ await connect({ mnemonic, v2rayExePath: '/path/to/v2ray' });
1056
+ ```
1057
+
1058
+ #### "All V2Ray transport combinations failed"
1059
+
1060
+ The node may be overloaded or have misconfigured transports. Solutions:
1061
+
1062
+ 1. Try a different node: `await connect({ mnemonic })` (auto-selects)
1063
+ 2. Try WireGuard: `await connect({ mnemonic, serviceType: 'wireguard' })`
1064
+ 3. Check if the node has peers > 0 (if yes, the transport works -- the issue is on our side)
1065
+
1066
+ #### "WireGuard not available"
1067
+
1068
+ WireGuard requires admin/root. Either:
1069
+
1070
+ 1. Run with admin privileges
1071
+ 2. Use V2Ray only: `await connect({ mnemonic, serviceType: 'v2ray' })`
1072
+
1073
+ #### "Insufficient balance"
1074
+
1075
+ Fund the wallet with P2P tokens. Check current balance:
1076
+
1077
+ ```js
1078
+ import { getBalance } from 'sentinel-ai-connect';
1079
+
1080
+ const balance = await getBalance(mnemonic);
1081
+ console.log(`${balance.p2p} — funded: ${balance.funded}`);
1082
+ console.log(`Send P2P tokens to: ${balance.address}`);
1083
+ ```
1084
+
1085
+ #### "Already connected"
1086
+
1087
+ Call `disconnect()` before connecting again:
1088
+
1089
+ ```js
1090
+ import { disconnect, isVpnActive, connect } from 'sentinel-ai-connect';
1091
+
1092
+ if (isVpnActive()) {
1093
+ await disconnect();
1094
+ }
1095
+ await connect({ mnemonic });
1096
+ ```
1097
+
1098
+ #### "Connection was cancelled"
1099
+
1100
+ An `AbortController` signal was triggered. This is intentional -- the agent or user cancelled.
1101
+
1102
+ #### Native `fetch()` does not use SOCKS5 proxy
1103
+
1104
+ Node.js native `fetch()` ignores SOCKS5 proxy settings. Use `axios` with `socks-proxy-agent`:
1105
+
1106
+ ```js
1107
+ import { SocksProxyAgent } from 'socks-proxy-agent';
1108
+ import axios from 'axios';
1109
+
1110
+ const agent = new SocksProxyAgent(`socks5://127.0.0.1:${vpn.socksPort}`);
1111
+ const res = await axios.get(url, { httpAgent: agent, httpsAgent: agent });
1112
+ ```
1113
+
1114
+ #### Connection succeeds but IP does not change (V2Ray)
1115
+
1116
+ V2Ray creates a SOCKS5 proxy, not a system-wide tunnel. Either:
1117
+
1118
+ 1. Set `systemProxy: true` (default) to auto-configure Windows system proxy
1119
+ 2. Explicitly route HTTP requests through the SOCKS5 proxy with `socks-proxy-agent`
1120
+ 3. Use WireGuard (`serviceType: 'wireguard'`) for true system-wide IP change
1121
+
1122
+ ### Chain-Specific Timing
1123
+
1124
+ | Constraint | Value | Reason |
1125
+ |---|---|---|
1126
+ | Minimum between TXs | 7 seconds | Sequence number conflicts |
1127
+ | Session confirmation delay | 5-15 seconds | Block time + node sync |
1128
+ | LCD query timeout | 15 seconds | Large response payloads |
1129
+ | Handshake timeout | 30 seconds | Node may be slow to respond |
1130
+ | V2Ray startup timeout | 15 seconds | Binary load + transport negotiation |
1131
+ | WireGuard peer registration | 1-5 seconds | Node registers the peer key |
1132
+
1133
+ ---
1134
+
1135
+ ## 11. Protocol Details
1136
+
1137
+ ### V3 Handshake
1138
+
1139
+ The handshake is the cryptographic exchange between agent and node after session creation.
1140
+
1141
+ **Signature format:**
1142
+
1143
+ ```
1144
+ message = BigEndian_uint64(sessionId) + raw_peer_data_json_bytes
1145
+ signature = ECDSA_sign(SHA256(message), private_key)
1146
+ ```
1147
+
1148
+ **Critical:** Sign the raw bytes, not base64-encoded bytes. This is a common implementation mistake.
1149
+
1150
+ **WireGuard handshake payload:**
1151
+
1152
+ ```json
1153
+ {
1154
+ "session_id": "37595661",
1155
+ "public_key": "<X25519 public key, base64>",
1156
+ "nonce": "<random nonce>"
1157
+ }
1158
+ ```
1159
+
1160
+ **V2Ray handshake payload:**
1161
+
1162
+ ```json
1163
+ {
1164
+ "session_id": "37595661",
1165
+ "uuid": "<generated UUID>",
1166
+ "nonce": "<random nonce>"
1167
+ }
1168
+ ```
1169
+
1170
+ ### Chain Query Paths (V3)
1171
+
1172
+ All Sentinel-specific queries use V3 paths. V2 paths return "Not Implemented" except provider (still V2).
1173
+
1174
+ | Query | LCD Path |
1175
+ |---|---|
1176
+ | Online nodes | `/sentinel/node/v3/nodes?status=1&pagination.limit=5000` |
1177
+ | Single node | `/sentinel/node/v3/nodes/{sentnode1...}` |
1178
+ | Account sessions | `/sentinel/session/v3/accounts/{sent1...}/sessions` |
1179
+ | Account subscriptions | `/sentinel/subscription/v3/accounts/{sent1...}/subscriptions` |
1180
+ | Session allocations | `/sentinel/session/v3/sessions/{id}/allocations` |
1181
+ | Plan by ID | `/sentinel/plan/v3/plans/{id}` |
1182
+ | Plan nodes | `/sentinel/node/v3/plans/{id}/nodes` |
1183
+ | Provider (V2!) | `/sentinel/provider/v2/providers/{sentprov1...}` |
1184
+ | Balance | `/cosmos/bank/v1beta1/balances/{sent1...}` |
1185
+
1186
+ ### V3 Field Name Changes
1187
+
1188
+ If you interact with the chain directly, note these V3 field name changes:
1189
+
1190
+ | V3 (Current) | V2 (Deprecated) |
1191
+ |---|---|
1192
+ | `service_type` | `type` |
1193
+ | `remote_addrs` (array) | `remote_url` (string) |
1194
+ | `acc_address` | `address` |
1195
+ | Session wrapped in `base_session` | Flat fields |
1196
+ | `status=1` (active) | `status=STATUS_ACTIVE` |
1197
+
1198
+ ### Transport Types (V2Ray)
1199
+
1200
+ V2Ray nodes advertise one or more transports. The SDK tries each in order of reliability:
1201
+
1202
+ | Code | Transport | Reliability | Notes |
1203
+ |---|---|---|---|
1204
+ | 7 | TCP | ~70% | Most common, generally reliable |
1205
+ | 8 | WebSocket | ~65% | Good for censored networks |
1206
+ | 3 | gRPC | ~58% | Without TLS only; gRPC+TLS = 0% |
1207
+ | 2 | gun | ~40% | Raw H2 stream (different from gRPC) |
1208
+ | 4 | HTTP | ~30% | HTTP/1.1 transport |
1209
+ | 5 | mKCP | ~20% | UDP-based, unreliable in practice |
1210
+ | 6 | QUIC | ~15% | UDP, often blocked |
1211
+ | 1 | DomainSocket | 0% | Unix sockets, not usable remotely |
1212
+
1213
+ **Important:** Transport codes 2 (gun) and 3 (gRPC) are different protocols. Gun is raw HTTP/2, gRPC uses the gRPC library. Do not treat them as equivalent.
1214
+
1215
+ ---
1216
+
1217
+ *Built for the machines that will inherit the open internet.*