blue-js-sdk 2.4.0 → 2.7.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 (58) hide show
  1. package/README.md +3 -3
  2. package/app-helpers.js +55 -0
  3. package/chain/broadcast.js +27 -0
  4. package/chain/fee-grants.js +271 -5
  5. package/chain/index.js +8 -2
  6. package/chain/queries.js +177 -3
  7. package/chain/rpc.js +117 -4
  8. package/cli.js +26 -5
  9. package/client.js +79 -7
  10. package/connection/connect.js +119 -21
  11. package/connection/disconnect.js +93 -12
  12. package/connection/index.js +2 -0
  13. package/connection/logger.js +66 -0
  14. package/connection/resilience.js +12 -7
  15. package/connection/state.js +21 -12
  16. package/connection/tunnel.js +24 -8
  17. package/cosmjs-setup.js +68 -2
  18. package/docs/PRIVY-INTEGRATION.md +177 -0
  19. package/errors.js +167 -0
  20. package/index.js +75 -2
  21. package/node-connect.js +190 -50
  22. package/operator.js +26 -0
  23. package/package.json +11 -11
  24. package/session-manager.js +68 -0
  25. package/speedtest.js +139 -0
  26. package/test-all-logic.js +8 -6
  27. package/test-e2e.js +138 -0
  28. package/test-mainnet.js +2 -2
  29. package/test-plan-connect-e2e.js +235 -0
  30. package/test-subscription-flows.js +14 -4
  31. package/types/connection.d.ts +6 -2
  32. package/types/index.d.ts +2 -2
  33. package/ai-path/ADMIN-ELEVATION.md +0 -116
  34. package/ai-path/AI-MANIFESTO.md +0 -185
  35. package/ai-path/BREAKING.md +0 -74
  36. package/ai-path/CHECKLIST.md +0 -619
  37. package/ai-path/CONNECTION-STEPS.md +0 -724
  38. package/ai-path/DECISION-TREE.md +0 -422
  39. package/ai-path/DEPENDENCIES.md +0 -459
  40. package/ai-path/E2E-FLOW.md +0 -1707
  41. package/ai-path/FAILURES.md +0 -410
  42. package/ai-path/GUIDE.md +0 -1315
  43. package/ai-path/README.md +0 -599
  44. package/ai-path/SPLIT-TUNNEL.md +0 -266
  45. package/ai-path/cli.js +0 -548
  46. package/ai-path/connect.js +0 -1028
  47. package/ai-path/discover.js +0 -178
  48. package/ai-path/environment.js +0 -266
  49. package/ai-path/errors.js +0 -86
  50. package/ai-path/examples/autonomous-agent.mjs +0 -220
  51. package/ai-path/examples/multi-region.mjs +0 -174
  52. package/ai-path/examples/one-shot.mjs +0 -31
  53. package/ai-path/index.js +0 -79
  54. package/ai-path/pricing.js +0 -137
  55. package/ai-path/recommend.js +0 -413
  56. package/ai-path/run-admin.vbs +0 -25
  57. package/ai-path/setup.js +0 -291
  58. package/ai-path/wallet.js +0 -137
package/ai-path/GUIDE.md DELETED
@@ -1,1315 +0,0 @@
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
- **Fee-granted disconnect:** When the connection was established with `feeGranter`, the SDK remembers the granter address and uses it for the `MsgCancelSessionRequest` TX on disconnect. If the fee grant has expired or been exhausted by disconnect time, the session ends naturally when the subscription allocation expires. No tokens are lost.
310
-
311
- ### Session Recovery
312
-
313
- 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`:
314
-
315
- ```js
316
- import { recoverSession } from 'sentinel-dvpn-sdk';
317
-
318
- try {
319
- const vpn = await connect({ mnemonic });
320
- } catch (err) {
321
- if (err.code === 'PARTIAL_CONNECTION_FAILED' ||
322
- err.code === 'SESSION_EXISTS' ||
323
- err.code === 'SESSION_EXTRACT_FAILED') {
324
- // Session is on-chain. Recover it.
325
- const vpn = await recoverSession({
326
- mnemonic,
327
- nodeAddress: err.details.nodeAddress,
328
- });
329
- }
330
- }
331
- ```
332
-
333
- ### Cleanup Handlers
334
-
335
- 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:
336
-
337
- ```js
338
- import { registerCleanupHandlers, connectAuto } from 'sentinel-dvpn-sdk';
339
-
340
- // Register ONCE at app startup (before any connect call)
341
- registerCleanupHandlers();
342
-
343
- // Now connectAuto() will work
344
- const vpn = await connectAuto({ mnemonic });
345
- ```
346
-
347
- Alternatively, use the SDK's `quickConnect()` which auto-registers cleanup handlers:
348
-
349
- ```js
350
- import { quickConnect } from 'sentinel-dvpn-sdk';
351
-
352
- const vpn = await quickConnect({ mnemonic });
353
- // cleanup handlers are registered automatically
354
- ```
355
-
356
- When using `sentinel-ai-connect`'s `connect()`, this is handled for you automatically.
357
-
358
- ---
359
-
360
- ## 5. Error Handling
361
-
362
- ### Error Hierarchy
363
-
364
- All SDK errors extend `SentinelError`:
365
-
366
- ```
367
- SentinelError (base)
368
- +-- ValidationError Input validation failures
369
- +-- NodeError Node-level failures
370
- +-- ChainError Blockchain/transaction failures
371
- +-- TunnelError Tunnel setup failures
372
- +-- SecurityError Security-related failures
373
- ```
374
-
375
- ### Programmatic Error Handling
376
-
377
- For advanced error handling, import typed errors from the underlying SDK:
378
-
379
- ```js
380
- import { connect } from 'sentinel-ai-connect';
381
- import {
382
- SentinelError,
383
- ValidationError,
384
- NodeError,
385
- ChainError,
386
- TunnelError,
387
- SecurityError,
388
- ErrorCodes,
389
- ERROR_SEVERITY,
390
- isRetryable,
391
- userMessage,
392
- } from 'sentinel-dvpn-sdk';
393
-
394
- try {
395
- await connect({ mnemonic });
396
- } catch (err) {
397
- // Check error type
398
- if (err instanceof ValidationError) {
399
- // Bad input -- fix before retrying
400
- console.error(`Input error: ${err.code} -- ${err.message}`);
401
- return;
402
- }
403
-
404
- if (err instanceof SecurityError) {
405
- // Security issue -- do not retry automatically
406
- console.error(`Security alert: ${err.code}`);
407
- return;
408
- }
409
-
410
- // Check severity
411
- const severity = ERROR_SEVERITY[err.code];
412
- switch (severity) {
413
- case 'fatal':
414
- // Cannot proceed without fixing the root cause
415
- console.error(`Fatal: ${userMessage(err)}`);
416
- break;
417
- case 'retryable':
418
- // Try again, possibly with a different node
419
- console.log(`Retrying: ${userMessage(err)}`);
420
- await connect({ mnemonic, maxAttempts: 5 });
421
- break;
422
- case 'recoverable':
423
- // Session exists on-chain, recover it
424
- console.log(`Recovering: ${err.code}`);
425
- await recoverSession({ mnemonic });
426
- break;
427
- case 'infrastructure':
428
- // System dependency issue
429
- console.error(`System issue: ${userMessage(err)}`);
430
- break;
431
- }
432
- }
433
- ```
434
-
435
- ### Complete Error Code Reference
436
-
437
- #### Fatal Errors (Do Not Retry)
438
-
439
- | Code | Class | Cause | Fix |
440
- |---|---|---|---|
441
- | `INVALID_MNEMONIC` | ValidationError | Mnemonic is not 12+ valid BIP39 words | Provide a valid BIP39 mnemonic |
442
- | `INVALID_OPTIONS` | ValidationError | Missing or malformed connect options | Check required fields |
443
- | `INVALID_NODE_ADDRESS` | ValidationError | Node address not in `sentnode1...` format | Use a valid node address |
444
- | `INVALID_GIGABYTES` | ValidationError | Gigabytes not an integer 1-100 | Use integer 1-100 |
445
- | `INVALID_URL` | ValidationError | Malformed URL provided | Check URL format |
446
- | `INVALID_PLAN_ID` | ValidationError | Plan ID is not a valid number | Use a valid plan ID |
447
- | `INSUFFICIENT_BALANCE` | ChainError | Wallet has < cost of session | Fund wallet with P2P tokens |
448
- | `ALREADY_CONNECTED` | SentinelError | `connect()` called while already connected | Call `disconnect()` first |
449
- | `SESSION_POISONED` | SentinelError | Session previously failed and was marked poisoned | Use `forceNewSession: true` |
450
- | `ABORTED` | SentinelError | Connection cancelled via AbortController | Intentional cancellation |
451
- | `UNKNOWN_MSG_TYPE` | ChainError | Protobuf message type not recognized | Update SDK version |
452
- | `WG_NOT_AVAILABLE` | TunnelError | WireGuard not installed | Install WireGuard or use `serviceType: 'v2ray'` |
453
-
454
- #### Retryable Errors (Try Again or Switch Nodes)
455
-
456
- | Code | Class | Cause | Suggested Action |
457
- |---|---|---|---|
458
- | `NODE_OFFLINE` | NodeError | Node not responding to status query | Try different node |
459
- | `NODE_NO_UDVPN` | NodeError | Node does not list udvpn in pricing | Try different node |
460
- | `NODE_CLOCK_DRIFT` | NodeError | Node clock >120s off (VMess AEAD fails) | Try different node |
461
- | `NODE_INACTIVE` | NodeError | Node status is inactive on-chain | Try different node |
462
- | `NODE_NOT_FOUND` | NodeError | Node address not found on chain | Verify address, try different node |
463
- | `NODE_DATABASE_CORRUPT` | NodeError | Node returned invalid data | Try different node |
464
- | `V2RAY_ALL_FAILED` | TunnelError | Every V2Ray transport failed | Try different node or WireGuard |
465
- | `WG_NO_CONNECTIVITY` | TunnelError | WireGuard adapter installed but no traffic | Try different node |
466
- | `TUNNEL_SETUP_FAILED` | TunnelError | Generic tunnel failure | Retry or try different node |
467
- | `BROADCAST_FAILED` | ChainError | TX broadcast rejected | Retry after delay (7s between TXs) |
468
- | `TX_FAILED` | ChainError | TX included in block but failed | Check balance, retry |
469
- | `LCD_ERROR` | ChainError | LCD endpoint query failed | Automatic failover handles this |
470
- | `ALL_ENDPOINTS_FAILED` | ChainError | All LCD/RPC endpoints unreachable | Check internet, retry later |
471
- | `ALL_NODES_FAILED` | SentinelError | Every candidate node failed | Relax filters, increase maxAttempts |
472
- | `CHAIN_LAG` | ChainError | Session not yet confirmed on node | Wait 10-15s and retry |
473
-
474
- #### Recoverable Errors (Session Exists On-Chain)
475
-
476
- | Code | Class | Cause | Action |
477
- |---|---|---|---|
478
- | `SESSION_EXISTS` | SentinelError | Active session found for this wallet+node | Call `recoverSession()` |
479
- | `SESSION_EXTRACT_FAILED` | ChainError | TX succeeded but session ID not extracted | Call `recoverSession()` |
480
- | `PARTIAL_CONNECTION_FAILED` | SentinelError | Payment OK, tunnel failed | Call `recoverSession()` |
481
-
482
- #### Infrastructure Errors (Check System)
483
-
484
- | Code | Class | Cause | Action |
485
- |---|---|---|---|
486
- | `V2RAY_NOT_FOUND` | TunnelError | V2Ray binary missing | Run `npx sentinel-ai setup` |
487
- | `TLS_CERT_CHANGED` | SecurityError | Node TLS cert differs from pinned cert | Investigate -- possible MITM |
488
-
489
- ### Error Details
490
-
491
- Every error includes a `.details` object with structured context:
492
-
493
- ```js
494
- catch (err) {
495
- console.log(err.code); // 'NODE_OFFLINE'
496
- console.log(err.message); // 'Node sentnode1abc... did not respond within 15s'
497
- console.log(err.details); // { nodeAddress: 'sentnode1abc...', timeout: 15000 }
498
- console.log(err.name); // 'NodeError'
499
- }
500
- ```
501
-
502
- ---
503
-
504
- ## 6. Token Economics
505
-
506
- ### P2P Token Basics
507
-
508
- | Property | Value |
509
- |---|---|
510
- | Display name | P2P |
511
- | Chain denom | `udvpn` (micro-dvpn) |
512
- | Conversion | 1 P2P = 1,000,000 udvpn |
513
- | Blockchain | Cosmos (sentinelhub-2) |
514
- | Gas price | 0.2 udvpn per gas unit |
515
-
516
- ### Session Pricing
517
-
518
- Nodes set their own prices. Two pricing models:
519
-
520
- | Model | How It Works | Typical Range |
521
- |---|---|---|
522
- | **Per-GB** | Pay upfront for N gigabytes. Session ends when data is consumed. | 5,000-50,000 udvpn/GB |
523
- | **Per-Hour** | Pay upfront for N hours. Session ends when time expires. | 10,000-100,000 udvpn/hour |
524
-
525
- ### Cost Estimation
526
-
527
- ```js
528
- import { listNodes, estimateSessionPrice, formatP2P } from 'sentinel-dvpn-sdk';
529
-
530
- const nodes = await listNodes();
531
- for (const node of nodes.slice(0, 5)) {
532
- const cost = estimateSessionPrice(node, { gigabytes: 1 });
533
- console.log(`${node.address}: ${formatP2P(cost)} per GB`);
534
- }
535
- ```
536
-
537
- ### Transaction Costs
538
-
539
- Every on-chain action costs gas:
540
-
541
- | Operation | Approximate Gas | Approximate Cost |
542
- |---|---|---|
543
- | Start session | ~200,000 | ~40,000 udvpn (0.04 P2P) |
544
- | End session | ~150,000 | ~30,000 udvpn (0.03 P2P) |
545
- | Subscribe to plan | ~250,000 | ~50,000 udvpn (0.05 P2P) |
546
-
547
- ### Budget Planning for Agents
548
-
549
- For an agent that connects 10 times per day, 1 GB per session:
550
-
551
- ```
552
- Daily cost estimate:
553
- 10 sessions x 50,000 udvpn/GB = 500,000 udvpn (bandwidth)
554
- 10 start TXs x 40,000 udvpn = 400,000 udvpn (gas)
555
- 10 end TXs x 30,000 udvpn = 300,000 udvpn (gas)
556
- ------------------------------------------------
557
- Total: ~1,200,000 udvpn/day (1.2 P2P)
558
- ```
559
-
560
- ### Auto-Funding Pattern
561
-
562
- For fully autonomous agents, monitor balance and trigger swap when low:
563
-
564
- ```js
565
- import { getBalance } from 'sentinel-ai-connect';
566
-
567
- async function ensureFunded(mnemonic, minUdvpn = 500000) {
568
- const balance = await getBalance(mnemonic);
569
-
570
- if (balance.udvpn < minUdvpn) {
571
- // Trigger Osmosis swap or alert operator
572
- console.error(`Low balance: ${balance.udvpn} udvpn. Need ${minUdvpn}.`);
573
- console.error(`Fund address: ${balance.address}`);
574
- return false;
575
- }
576
- return true;
577
- }
578
- ```
579
-
580
- ---
581
-
582
- ## 7. Security Considerations
583
-
584
- ### Mnemonic Security
585
-
586
- The mnemonic is the master key to the wallet. Anyone with it can spend all funds.
587
-
588
- ```js
589
- // CORRECT: Load from environment
590
- const mnemonic = process.env.MNEMONIC;
591
-
592
- // WRONG: Hardcoded in source
593
- const mnemonic = 'word1 word2 word3 ...'; // NEVER DO THIS
594
-
595
- // WRONG: Logged to console
596
- console.log(`Using mnemonic: ${mnemonic}`); // NEVER DO THIS
597
-
598
- // WRONG: Included in error report
599
- throw new Error(`Failed with mnemonic ${mnemonic}`); // NEVER DO THIS
600
- ```
601
-
602
- For autonomous agents, consider:
603
-
604
- - Store mnemonic in a secrets manager (AWS Secrets Manager, HashiCorp Vault, etc.)
605
- - Use a dedicated wallet with limited funds (not your main wallet)
606
- - Monitor wallet balance and set alerts for unexpected withdrawals
607
-
608
- ### Key Zeroing
609
-
610
- The SDK zeros private key material from memory after use:
611
-
612
- ```js
613
- // Internal SDK behavior:
614
- const privKey = await derivePrivateKey(mnemonic);
615
- const signature = sign(privKey, data);
616
- privKey.fill(0); // Zero the key buffer
617
- ```
618
-
619
- Do not cache raw private keys in your own code. Let the SDK manage key lifecycle.
620
-
621
- ### Tunnel Verification
622
-
623
- After connecting, verify that traffic actually routes through the tunnel:
624
-
625
- ```js
626
- import { connect } from 'sentinel-ai-connect';
627
- import { verifyConnection } from 'sentinel-dvpn-sdk';
628
-
629
- const vpn = await connect({ mnemonic });
630
- const check = await verifyConnection({ timeoutMs: 8000 });
631
-
632
- if (check.working) {
633
- console.log(`VPN IP: ${check.vpnIp}`);
634
- } else {
635
- console.error('Traffic is NOT going through VPN!');
636
- await disconnect();
637
- }
638
- ```
639
-
640
- ### DNS Leak Prevention
641
-
642
- By default, the SDK uses Handshake DNS (103.196.38.38). This prevents DNS queries from leaking to your ISP.
643
-
644
- ```js
645
- // Default: Handshake DNS (decentralized, no logging)
646
- await connect({ mnemonic, dns: 'handshake' });
647
-
648
- // Alternative: Google DNS
649
- await connect({ mnemonic, dns: 'google' });
650
-
651
- // Alternative: Cloudflare DNS
652
- await connect({ mnemonic, dns: 'cloudflare' });
653
- ```
654
-
655
- For maximum security, enable DNS leak prevention:
656
-
657
- ```js
658
- import { enableDnsLeakPrevention, disableDnsLeakPrevention } from 'sentinel-dvpn-sdk';
659
-
660
- enableDnsLeakPrevention(); // Forces ALL DNS through tunnel
661
- // ... use VPN ...
662
- disableDnsLeakPrevention(); // Restore default DNS
663
- ```
664
-
665
- ### Kill Switch
666
-
667
- The kill switch blocks all non-tunnel traffic at the OS firewall level. If the VPN drops, no traffic leaks.
668
-
669
- ```js
670
- await connect({
671
- mnemonic,
672
- killSwitch: true, // Block all non-tunnel traffic
673
- });
674
- ```
675
-
676
- **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.
677
-
678
- ### TOFU TLS (Trust On First Use)
679
-
680
- 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.
681
-
682
- ```js
683
- import { clearKnownNode } from 'sentinel-dvpn-sdk';
684
-
685
- try {
686
- await connect({ mnemonic, nodeAddress: 'sentnode1abc...' });
687
- } catch (err) {
688
- if (err.code === 'TLS_CERT_CHANGED') {
689
- // Option 1: Alert and abort (safest)
690
- console.error('Possible MITM attack on node');
691
-
692
- // Option 2: Clear pinned cert and retry (if you trust the node)
693
- clearKnownNode('sentnode1abc...');
694
- await connect({ mnemonic, nodeAddress: 'sentnode1abc...' });
695
- }
696
- }
697
- ```
698
-
699
- ---
700
-
701
- ## 8. Integration Patterns
702
-
703
- ### Pattern 1: Embedded Library
704
-
705
- The simplest pattern. Import `sentinel-ai-connect` directly in your agent.
706
-
707
- ```js
708
- import { connect, disconnect, isVpnActive } from 'sentinel-ai-connect';
709
-
710
- class MyAgent {
711
- async doWorkWithPrivacy() {
712
- if (!isVpnActive()) {
713
- await connect({ mnemonic: process.env.MNEMONIC, country: 'Germany' });
714
- }
715
-
716
- // All HTTP requests now go through VPN
717
- const response = await fetch('https://target-api.example.com/data');
718
- const data = await response.json();
719
-
720
- await disconnect();
721
- return data;
722
- }
723
- }
724
- ```
725
-
726
- ### Pattern 2: Long-Running Daemon
727
-
728
- For agents that need persistent VPN access, combine the simple API with SDK-level features:
729
-
730
- ```js
731
- import { connect, disconnect } from 'sentinel-ai-connect';
732
- import {
733
- autoReconnect,
734
- registerCleanupHandlers,
735
- events,
736
- } from 'sentinel-dvpn-sdk';
737
-
738
- registerCleanupHandlers();
739
-
740
- // Connect once
741
- const vpn = await connect({
742
- mnemonic: process.env.MNEMONIC,
743
- onProgress: (step, detail) => console.log(`[vpn] [${step}] ${detail}`),
744
- });
745
-
746
- // Auto-reconnect on failure
747
- const monitor = autoReconnect({
748
- mnemonic: process.env.MNEMONIC,
749
- pollIntervalMs: 10000,
750
- maxRetries: 10,
751
- backoffMs: [2000, 5000, 10000, 30000, 60000],
752
- onReconnecting: (n) => console.log(`[vpn] Reconnecting (attempt ${n})...`),
753
- onReconnected: (r) => console.log(`[vpn] Reconnected to ${r.nodeAddress}`),
754
- onGaveUp: () => {
755
- console.error('[vpn] All reconnect attempts failed');
756
- process.exit(1);
757
- },
758
- });
759
-
760
- // Listen for events
761
- events.on('disconnected', ({ reason }) => {
762
- console.log(`[vpn] Disconnected: ${reason}`);
763
- });
764
-
765
- // Graceful shutdown
766
- process.on('SIGTERM', async () => {
767
- monitor.stop();
768
- await disconnect();
769
- process.exit(0);
770
- });
771
- ```
772
-
773
- ### Pattern 3: On-Demand VPN
774
-
775
- Connect only when needed, disconnect when done. Minimizes cost.
776
-
777
- ```js
778
- import { connect, disconnect, isVpnActive } from 'sentinel-ai-connect';
779
-
780
- async function withVpn(country, fn) {
781
- let retries = 3;
782
- while (retries > 0) {
783
- try {
784
- const vpn = await connect({
785
- mnemonic: process.env.MNEMONIC,
786
- country,
787
- });
788
-
789
- try {
790
- return await fn(vpn);
791
- } finally {
792
- await disconnect();
793
- }
794
- } catch (err) {
795
- retries--;
796
- if (retries === 0) throw err;
797
- if (err.message.includes('insufficient')) throw err; // Don't retry balance errors
798
- console.log(`VPN connect failed, ${retries} retries left: ${err.message}`);
799
- }
800
- }
801
- }
802
-
803
- // Usage
804
- const result = await withVpn('Germany', async (vpn) => {
805
- const res = await fetch('https://api.example.com/geo-restricted-data');
806
- return res.json();
807
- });
808
- ```
809
-
810
- ### Pattern 4: Multi-Country Rotation
811
-
812
- Connect to different countries sequentially for geo-distributed operations:
813
-
814
- ```js
815
- import { connect, disconnect } from 'sentinel-ai-connect';
816
-
817
- const countries = ['Germany', 'Japan', 'Brazil', 'Australia', 'South Africa'];
818
-
819
- for (const country of countries) {
820
- await connect({
821
- mnemonic: process.env.MNEMONIC,
822
- country,
823
- onProgress: (step, detail) => console.log(`[${country}] [${step}] ${detail}`),
824
- });
825
-
826
- // Do work from this country's IP
827
- const res = await fetch('https://api.example.com/local-data');
828
- console.log(`${country}: ${res.status}`);
829
-
830
- await disconnect();
831
-
832
- // Wait between sessions to avoid chain rate limits
833
- await new Promise(r => setTimeout(r, 7000));
834
- }
835
- ```
836
-
837
- ### Pattern 5: SOCKS5 Proxy (V2Ray)
838
-
839
- V2Ray creates a local SOCKS5 proxy. Route specific traffic through it:
840
-
841
- ```js
842
- import { connect, disconnect } from 'sentinel-ai-connect';
843
- import { SocksProxyAgent } from 'socks-proxy-agent';
844
- import axios from 'axios';
845
-
846
- const vpn = await connect({
847
- mnemonic: process.env.MNEMONIC,
848
- serviceType: 'v2ray',
849
- });
850
-
851
- // Create agent for the SOCKS5 proxy
852
- const agent = new SocksProxyAgent(`socks5://127.0.0.1:${vpn.socksPort}`);
853
-
854
- // Route specific requests through VPN
855
- const vpnResponse = await axios.get('https://api.ipify.org', {
856
- httpAgent: agent,
857
- httpsAgent: agent,
858
- });
859
- console.log(`VPN IP: ${vpnResponse.data}`);
860
-
861
- // Direct request (not through VPN)
862
- const directResponse = await axios.get('https://api.ipify.org');
863
- console.log(`Direct IP: ${directResponse.data}`);
864
-
865
- await disconnect();
866
- ```
867
-
868
- **Important:** Node.js native `fetch()` silently ignores SOCKS5 proxy configuration. Use `axios` with an explicit agent, not `fetch()`.
869
-
870
- ### Pattern 6: Operator-Provisioned Mode (Zero P2P / Fee-Granted)
871
-
872
- When an operator has provisioned access for the agent (e.g., via x402 payment protocol), the agent connects with **zero P2P tokens**. The operator's fee grant covers all gas costs.
873
-
874
- This mode requires three things from the operator:
875
- 1. **Subscription share** -- operator added agent's `sent1...` address to their plan subscription
876
- 2. **Fee grant** -- operator created a fee allowance so agent pays 0 gas on Sentinel
877
- 3. **Node address** -- a node linked to the operator's plan
878
-
879
- ```js
880
- import { connect, disconnect } from 'sentinel-ai-connect';
881
-
882
- // Agent has 0 P2P — operator covers gas via fee grant
883
- const vpn = await connect({
884
- mnemonic: process.env.MNEMONIC,
885
-
886
- // Operator-provisioned fields (from provisioning response)
887
- subscriptionId: 12345, // Operator's subscription
888
- feeGranter: 'sent1operatoraddress...', // Operator pays gas
889
- nodeAddress: 'sentnode1abc...', // Plan node
890
-
891
- onProgress: (step, detail) => console.log(`[${step}] ${detail}`),
892
- });
893
-
894
- console.log(`Connected: ${vpn.protocol} | IP: ${vpn.ip}`);
895
- await disconnect();
896
- ```
897
-
898
- **How it works:**
899
- - Balance check is skipped when `feeGranter` is set (agent may have 0 P2P)
900
- - Fee grant is validated before connecting via RPC (protobuf, ~250ms) with LCD fallback
901
- - Session TX is broadcast via `broadcastWithFeeGrant` — chain deducts gas from granter
902
- - Disconnect also uses fee grant for the `MsgCancelSessionRequest` TX
903
- - If fee grant fails at any step, SDK falls back to agent's own balance (if available)
904
-
905
- #### Fee Grant Pre-Check (Step 3.5)
906
-
907
- Before attempting the session TX, the SDK validates the fee grant on-chain. This runs between the balance check (Step 3) and node selection (Step 4):
908
-
909
- 1. **Query grant** — RPC first (`rpcQueryFeeGrant`, protobuf ABCI query, ~250ms), LCD fallback (`queryFeeGrant` via `tryWithFallback`, ~880ms). Matches the SDK standard: all chain queries use RPC with LCD as a safety net.
910
- 2. **Existence check** — `FEE_GRANT_NOT_FOUND` if no grant exists from granter to grantee.
911
- 3. **Expiration check** — `FEE_GRANT_EXPIRED` if the grant's expiration is in the past. Warns if < 1 hour remaining.
912
- 4. **Spend limit check** — `FEE_GRANT_EXHAUSTED` if `spend_limit` has < 20,000 udvpn remaining (minimum for one session TX).
913
- 5. **Allowed messages check** — Warns (non-blocking) if `MsgStartSession` / `MsgStartSessionRequest` is not in the grant's `allowed_messages` list.
914
-
915
- The grant structure on-chain is `AllowedMsgAllowance` wrapping a `BasicAllowance`. The SDK uses robust `@type`-based detection to handle both wrapped and unwrapped grant formats.
916
-
917
- **Fee grant pre-check errors:**
918
-
919
- | Error Code | Meaning | `nextAction` | Action |
920
- |---|---|---|---|
921
- | `FEE_GRANT_NOT_FOUND` | No grant from granter to grantee on-chain | `request_fee_grant` | Request provisioning from operator |
922
- | `FEE_GRANT_EXPIRED` | Grant existed but expiration is past | `request_fee_grant_renewal` | Request grant renewal from operator |
923
- | `FEE_GRANT_EXHAUSTED` | Grant spend limit < 20,000 udvpn | `request_fee_grant_renewal` | Request operator to top up grant |
924
-
925
- All errors include `err.code`, `err.nextAction`, and `err.details` (with `granter`, `grantee`, and context-specific fields) for programmatic handling by agents.
926
-
927
- #### Crash Recovery
928
-
929
- The `feeGranter` is persisted to `credentials.enc.json` (AES-256-GCM encrypted) alongside session credentials. If the agent process crashes and restarts:
930
-
931
- 1. `tryFastReconnect()` loads credentials from disk
932
- 2. Restores `state._feeGranter` from `saved.feeGranter`
933
- 3. Tunnel is restored (WireGuard adapter or V2Ray process)
934
- 4. Disconnect correctly uses fee grant — no tokens needed
935
-
936
- Without this persistence, a crashed agent with 0 P2P would be unable to end its session on-chain after recovery.
937
-
938
- #### Auto-Reconnect
939
-
940
- `autoReconnect()` dispatches based on the original connection mode:
941
-
942
- - `opts.subscriptionId` → `connectViaSubscription(opts)` — fee grant preserved
943
- - `opts.planId` → `connectViaPlan(opts)` — fee grant preserved
944
- - Neither → `connectAuto(opts)` — self-pay mode
945
-
946
- This ensures fee-granted agents don't fall back to direct payment (which would fail with 0 P2P).
947
-
948
- #### Fee-Granted Disconnect
949
-
950
- When connected with `feeGranter`, the SDK stores the granter address in `state._feeGranter`. On `disconnect()`, `_endSessionOnChain` uses `broadcastWithFeeGrant()` for the `MsgCancelSessionRequest` TX. All 9 disconnect call sites (graceful, abort, error cleanup, crash handler, etc.) pass through `state._feeGranter`.
951
-
952
- If the fee grant has expired or been exhausted by disconnect time, the session ends naturally when the subscription allocation expires. No tokens are lost.
953
-
954
- **Alternative: Plan mode** -- if the agent doesn't have a specific subscription ID but knows the plan:
955
-
956
- ```js
957
- const vpn = await connect({
958
- mnemonic: process.env.MNEMONIC,
959
- planId: 42, // Subscribe + connect
960
- feeGranter: 'sent1operatoraddress...', // Operator pays gas
961
- });
962
- ```
963
-
964
- Plan mode subscribes to the plan AND starts a session in one flow. Use `subscriptionId` when the operator already provisioned a subscription; use `planId` when the agent subscribes itself.
965
-
966
- ---
967
-
968
- ## 9. Autonomous Agent Pattern
969
-
970
- A complete pattern for a fully autonomous AI agent that manages its own VPN lifecycle, monitors balance, and handles all error cases.
971
-
972
- ```js
973
- import { connect, disconnect, isVpnActive, createWallet, getBalance } from 'sentinel-ai-connect';
974
- import {
975
- autoReconnect,
976
- registerCleanupHandlers,
977
- createClient,
978
- verifyConnection,
979
- events,
980
- ErrorCodes,
981
- isRetryable,
982
- } from 'sentinel-dvpn-sdk';
983
-
984
- class AutonomousVpnAgent {
985
- constructor(mnemonic, opts = {}) {
986
- this.mnemonic = mnemonic;
987
- this.minBalance = opts.minBalance || 500000; // 0.5 P2P
988
- this.preferredCountry = opts.country || null;
989
- this.monitor = null;
990
- this.running = false;
991
-
992
- registerCleanupHandlers();
993
- }
994
-
995
- // ── Lifecycle ───────────────────────────────────────
996
-
997
- async start() {
998
- this.running = true;
999
-
1000
- // Check balance before connecting
1001
- const funded = await this.checkBalance();
1002
- if (!funded) {
1003
- throw new Error(`Insufficient balance. Need ${this.minBalance} udvpn minimum.`);
1004
- }
1005
-
1006
- // Connect with retry
1007
- await this.connectWithRetry(3);
1008
-
1009
- // Start auto-reconnect monitor
1010
- this.monitor = autoReconnect({
1011
- mnemonic: this.mnemonic,
1012
- pollIntervalMs: 10000,
1013
- maxRetries: 10,
1014
- backoffMs: [2000, 5000, 10000, 30000, 60000],
1015
- onReconnecting: (n) => this.log(`Reconnecting (attempt ${n})...`),
1016
- onReconnected: (result) => this.log(`Reconnected via ${result.nodeAddress}`),
1017
- onGaveUp: () => {
1018
- this.log('Auto-reconnect exhausted. Attempting full restart...');
1019
- this.restart();
1020
- },
1021
- });
1022
-
1023
- // Periodic balance check
1024
- this.balanceTimer = setInterval(() => this.checkBalance(), 300000); // Every 5 min
1025
-
1026
- this.log('Agent started. VPN active.');
1027
- }
1028
-
1029
- async stop() {
1030
- this.running = false;
1031
- if (this.monitor) this.monitor.stop();
1032
- if (this.balanceTimer) clearInterval(this.balanceTimer);
1033
- if (isVpnActive()) await disconnect();
1034
- this.log('Agent stopped.');
1035
- }
1036
-
1037
- async restart() {
1038
- if (!this.running) return;
1039
- try {
1040
- if (isVpnActive()) await disconnect();
1041
- } catch {}
1042
- // Wait before reconnecting
1043
- await new Promise(r => setTimeout(r, 10000));
1044
- if (this.running) await this.start();
1045
- }
1046
-
1047
- // ── Connection ──────────────────────────────────────
1048
-
1049
- async connectWithRetry(maxRetries) {
1050
- for (let i = 0; i < maxRetries; i++) {
1051
- try {
1052
- const vpn = await connect({
1053
- mnemonic: this.mnemonic,
1054
- country: this.preferredCountry,
1055
- maxAttempts: 5,
1056
- onProgress: (step, detail) => this.log(`[${step}] ${detail}`),
1057
- });
1058
-
1059
- // Verify tunnel works
1060
- const check = await verifyConnection({ timeoutMs: 8000 });
1061
- if (check.working) {
1062
- this.log(`Connected. VPN IP: ${check.vpnIp}`);
1063
- return vpn;
1064
- }
1065
-
1066
- this.log('Tunnel verification failed. Disconnecting and retrying...');
1067
- await disconnect();
1068
- } catch (err) {
1069
- this.log(`Connect attempt ${i + 1}/${maxRetries} failed: ${err.code} -- ${err.message}`);
1070
-
1071
- if (err.code === ErrorCodes.INSUFFICIENT_BALANCE) {
1072
- throw err; // Cannot recover without funding
1073
- }
1074
-
1075
- if (i < maxRetries - 1) {
1076
- const delay = Math.min(5000 * (i + 1), 30000);
1077
- await new Promise(r => setTimeout(r, delay));
1078
- }
1079
- }
1080
- }
1081
- throw new Error(`Failed to connect after ${maxRetries} attempts`);
1082
- }
1083
-
1084
- // ── Balance Monitoring ──────────────────────────────
1085
-
1086
- async checkBalance() {
1087
- try {
1088
- const balance = await getBalance(this.mnemonic);
1089
-
1090
- this.log(`Balance: ${balance.p2p} (${balance.udvpn} udvpn)`);
1091
-
1092
- if (balance.udvpn < this.minBalance) {
1093
- this.log(`WARNING: Balance below minimum (${this.minBalance} udvpn). Fund wallet.`);
1094
- // Here you could: trigger Osmosis swap, send alert, call webhook, etc.
1095
- return false;
1096
- }
1097
- return true;
1098
- } catch (err) {
1099
- this.log(`Balance check failed: ${err.message}`);
1100
- return true; // Assume funded on query failure
1101
- }
1102
- }
1103
-
1104
- // ── Logging ─────────────────────────────────────────
1105
-
1106
- log(msg) {
1107
- const ts = new Date().toISOString();
1108
- console.log(`[${ts}] [vpn-agent] ${msg}`);
1109
- }
1110
- }
1111
-
1112
- // ── Usage ────────────────────────────────────────────
1113
-
1114
- const agent = new AutonomousVpnAgent(process.env.MNEMONIC, {
1115
- country: 'Germany',
1116
- minBalance: 1000000, // 1 P2P
1117
- });
1118
-
1119
- await agent.start();
1120
-
1121
- // Agent is now running with auto-reconnect and balance monitoring.
1122
- // Stop when done:
1123
- // await agent.stop();
1124
- ```
1125
-
1126
- ### Key Design Decisions for Autonomous Agents
1127
-
1128
- | Decision | Recommendation | Reason |
1129
- |---|---|---|
1130
- | **Pricing model** | Per-GB (not per-hour) | Agents may be idle between tasks. Per-hour drains balance on timer. |
1131
- | **Node selection** | Auto (not specific node) | Specific nodes go offline. Auto-select falls back to alternatives. |
1132
- | **Kill switch** | Off for agents | If kill switch persists after crash, agent loses all connectivity. |
1133
- | **Cleanup handlers** | Always register | Prevents orphaned tunnels that kill system internet. |
1134
- | **Balance monitoring** | Check every 5 minutes | Catch low balance before a connection attempt fails. |
1135
- | **Error recovery** | Use `recoverSession()` for recoverable errors | Avoids paying twice for the same session. |
1136
- | **TX timing** | Wait 7s between transactions | Chain rate limits cause failures with rapid TX. |
1137
-
1138
- ---
1139
-
1140
- ## 10. Troubleshooting
1141
-
1142
- ### Common Issues
1143
-
1144
- #### "V2Ray binary not found"
1145
-
1146
- ```bash
1147
- npx sentinel-ai setup
1148
- ```
1149
-
1150
- 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`:
1151
-
1152
- ```js
1153
- await connect({ mnemonic, v2rayExePath: '/path/to/v2ray' });
1154
- ```
1155
-
1156
- #### "All V2Ray transport combinations failed"
1157
-
1158
- The node may be overloaded or have misconfigured transports. Solutions:
1159
-
1160
- 1. Try a different node: `await connect({ mnemonic })` (auto-selects)
1161
- 2. Try WireGuard: `await connect({ mnemonic, serviceType: 'wireguard' })`
1162
- 3. Check if the node has peers > 0 (if yes, the transport works -- the issue is on our side)
1163
-
1164
- #### "WireGuard not available"
1165
-
1166
- WireGuard requires admin/root. Either:
1167
-
1168
- 1. Run with admin privileges
1169
- 2. Use V2Ray only: `await connect({ mnemonic, serviceType: 'v2ray' })`
1170
-
1171
- #### "Insufficient balance"
1172
-
1173
- Fund the wallet with P2P tokens. Check current balance:
1174
-
1175
- ```js
1176
- import { getBalance } from 'sentinel-ai-connect';
1177
-
1178
- const balance = await getBalance(mnemonic);
1179
- console.log(`${balance.p2p} — funded: ${balance.funded}`);
1180
- console.log(`Send P2P tokens to: ${balance.address}`);
1181
- ```
1182
-
1183
- #### "Already connected"
1184
-
1185
- Call `disconnect()` before connecting again:
1186
-
1187
- ```js
1188
- import { disconnect, isVpnActive, connect } from 'sentinel-ai-connect';
1189
-
1190
- if (isVpnActive()) {
1191
- await disconnect();
1192
- }
1193
- await connect({ mnemonic });
1194
- ```
1195
-
1196
- #### "Connection was cancelled"
1197
-
1198
- An `AbortController` signal was triggered. This is intentional -- the agent or user cancelled.
1199
-
1200
- #### Native `fetch()` does not use SOCKS5 proxy
1201
-
1202
- Node.js native `fetch()` ignores SOCKS5 proxy settings. Use `axios` with `socks-proxy-agent`:
1203
-
1204
- ```js
1205
- import { SocksProxyAgent } from 'socks-proxy-agent';
1206
- import axios from 'axios';
1207
-
1208
- const agent = new SocksProxyAgent(`socks5://127.0.0.1:${vpn.socksPort}`);
1209
- const res = await axios.get(url, { httpAgent: agent, httpsAgent: agent });
1210
- ```
1211
-
1212
- #### Connection succeeds but IP does not change (V2Ray)
1213
-
1214
- V2Ray creates a SOCKS5 proxy, not a system-wide tunnel. Either:
1215
-
1216
- 1. Set `systemProxy: true` (default) to auto-configure Windows system proxy
1217
- 2. Explicitly route HTTP requests through the SOCKS5 proxy with `socks-proxy-agent`
1218
- 3. Use WireGuard (`serviceType: 'wireguard'`) for true system-wide IP change
1219
-
1220
- ### Chain-Specific Timing
1221
-
1222
- | Constraint | Value | Reason |
1223
- |---|---|---|
1224
- | Minimum between TXs | 7 seconds | Sequence number conflicts |
1225
- | Session confirmation delay | 5-15 seconds | Block time + node sync |
1226
- | LCD query timeout | 15 seconds | Large response payloads |
1227
- | Handshake timeout | 30 seconds | Node may be slow to respond |
1228
- | V2Ray startup timeout | 15 seconds | Binary load + transport negotiation |
1229
- | WireGuard peer registration | 1-5 seconds | Node registers the peer key |
1230
-
1231
- ---
1232
-
1233
- ## 11. Protocol Details
1234
-
1235
- ### V3 Handshake
1236
-
1237
- The handshake is the cryptographic exchange between agent and node after session creation.
1238
-
1239
- **Signature format:**
1240
-
1241
- ```
1242
- message = BigEndian_uint64(sessionId) + raw_peer_data_json_bytes
1243
- signature = ECDSA_sign(SHA256(message), private_key)
1244
- ```
1245
-
1246
- **Critical:** Sign the raw bytes, not base64-encoded bytes. This is a common implementation mistake.
1247
-
1248
- **WireGuard handshake payload:**
1249
-
1250
- ```json
1251
- {
1252
- "session_id": "37595661",
1253
- "public_key": "<X25519 public key, base64>",
1254
- "nonce": "<random nonce>"
1255
- }
1256
- ```
1257
-
1258
- **V2Ray handshake payload:**
1259
-
1260
- ```json
1261
- {
1262
- "session_id": "37595661",
1263
- "uuid": "<generated UUID>",
1264
- "nonce": "<random nonce>"
1265
- }
1266
- ```
1267
-
1268
- ### Chain Query Paths (V3)
1269
-
1270
- All Sentinel-specific queries use V3 paths. V2 paths return "Not Implemented" except provider (still V2).
1271
-
1272
- | Query | LCD Path |
1273
- |---|---|
1274
- | Online nodes | `/sentinel/node/v3/nodes?status=1&pagination.limit=5000` |
1275
- | Single node | `/sentinel/node/v3/nodes/{sentnode1...}` |
1276
- | Account sessions | `/sentinel/session/v3/accounts/{sent1...}/sessions` |
1277
- | Account subscriptions | `/sentinel/subscription/v3/accounts/{sent1...}/subscriptions` |
1278
- | Session allocations | `/sentinel/session/v3/sessions/{id}/allocations` |
1279
- | Plan by ID | `/sentinel/plan/v3/plans/{id}` |
1280
- | Plan nodes | `/sentinel/node/v3/plans/{id}/nodes` |
1281
- | Provider (V2!) | `/sentinel/provider/v2/providers/{sentprov1...}` |
1282
- | Balance | `/cosmos/bank/v1beta1/balances/{sent1...}` |
1283
-
1284
- ### V3 Field Name Changes
1285
-
1286
- If you interact with the chain directly, note these V3 field name changes:
1287
-
1288
- | V3 (Current) | V2 (Deprecated) |
1289
- |---|---|
1290
- | `service_type` | `type` |
1291
- | `remote_addrs` (array) | `remote_url` (string) |
1292
- | `acc_address` | `address` |
1293
- | Session wrapped in `base_session` | Flat fields |
1294
- | `status=1` (active) | `status=STATUS_ACTIVE` |
1295
-
1296
- ### Transport Types (V2Ray)
1297
-
1298
- V2Ray nodes advertise one or more transports. The SDK tries each in order of reliability:
1299
-
1300
- | Code | Transport | Reliability | Notes |
1301
- |---|---|---|---|
1302
- | 7 | TCP | ~70% | Most common, generally reliable |
1303
- | 8 | WebSocket | ~65% | Good for censored networks |
1304
- | 3 | gRPC | ~58% | Without TLS only; gRPC+TLS = 0% |
1305
- | 2 | gun | ~40% | Raw H2 stream (different from gRPC) |
1306
- | 4 | HTTP | ~30% | HTTP/1.1 transport |
1307
- | 5 | mKCP | ~20% | UDP-based, unreliable in practice |
1308
- | 6 | QUIC | ~15% | UDP, often blocked |
1309
- | 1 | DomainSocket | 0% | Unix sockets, not usable remotely |
1310
-
1311
- **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.
1312
-
1313
- ---
1314
-
1315
- *Built for the machines that will inherit the open internet.*