Skip to content

Commit 05d50dc

Browse files
docs+manifest: fix mac/Linux connect path verified against dvpncli source
End-to-end source trace (sentinel-dvpncli + sentinel-go-sdk) surfaced two defects that would block an AI agent on macOS/Linux: 1. `keys add` is INTERACTIVE and has NO --recover flag (only --hd-path, --output-format). It prompts for mnemonic, then a BIP-39 passphrase. The single-line `echo "$MNEMONIC" | keys add` hung on the passphrase prompt. Fixed to `printf '%s\n\n' "$MNEMONIC" | keys add agent --keyring.backend test` (line 1 = mnemonic, blank line 2 = default empty passphrase). 2. `tx session-start` prints {response, result} but does NOT surface the new session id as a clean field — connect <id> needs it. Added the deterministic recovery step: `query sessions --account-addr <sent1...> --subscription-id <id> --output-format json` → newest entry's id. Verified correct (unchanged): connect [id] takes a SESSION id (ExactArgs(1), ParseUint → client.Session), needs no key; tx session-start <node-addr> --subscription-id builds subscriptionv3.MsgStartSessionRequest; --tx.fee-granter-addr / --tx.from-name are real TxConfig flags; --keyring.backend allowed values file/kwallet/memory/os/pass/test (default os); rpc defaults rpc.sentinel.co:443 / sentinelhub-2; keyring name default "sentinel". Surfaces: docs/index.html (CLI code block + embedded manifest + full-flow comment), docs/llms.txt (native-CLI section + flow step 8), server/src/index.ts (/manifest connectMacLinux.steps, tldr, flow step 8). tsc --noEmit passes; embedded JSON validates.
1 parent b2b132f commit 05d50dc

3 files changed

Lines changed: 36 additions & 17 deletions

File tree

docs/index.html

Lines changed: 15 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -54,7 +54,7 @@
5454
"rpcFallbacks": ["https://rpc.sentinel.co:443", "https://sentinel-rpc.polkachu.com", "https://rpc.mathnodes.com", "https://sentinel-rpc.publicnode.com", "https://rpc.sentinel.quokkastake.io", "https://rpc.sentinel.suchnode.net:443"],
5555
"lcdFallbacks": ["https://lcd.sentinel.co", "https://sentinel-api.polkachu.com", "https://api.sentinel.quokkastake.io", "https://sentinel-rest.publicnode.com", "https://api.sentinel.suchnode.net"],
5656
"platforms": "Payment is identical everywhere; only the connect step differs by OS.",
57-
"connectMacLinux": "EASIEST. WireGuard ships with the OS. Use native CLI sentinel-dvpncli (go install github.com/sentinel-official/sentinel-dvpncli@latest): (1) echo \"$MNEMONIC\" | sentinel-dvpncli keys add agent --keyring.backend test; (2) sentinel-dvpncli tx session-start <nodeAddress> --subscription-id <subscriptionId> --tx.fee-granter-addr <feeGranter> --tx.from-name agent --keyring.backend test → capture session id; (3) sentinel-dvpncli connect <sessionId>. Exception: Fedora (SELinux blocks VPNs).",
57+
"connectMacLinux": "EASIEST. WireGuard ships with the OS. Use native CLI sentinel-dvpncli (go install github.com/sentinel-official/sentinel-dvpncli@latest): (1) printf '%s\\n\\n' \"$MNEMONIC\" | sentinel-dvpncli keys add agent --keyring.backend test (keys add is interactive: line 1 = mnemonic, blank line 2 = default passphrase; there is no --recover flag); (2) sentinel-dvpncli tx session-start <nodeAddress> --subscription-id <subscriptionId> --tx.fee-granter-addr <feeGranter> --tx.from-name agent --keyring.backend test; (3) sessionId = sentinel-dvpncli query sessions --account-addr <sent1...> --subscription-id <subscriptionId> --output-format json (newest entry); (4) sentinel-dvpncli connect <sessionId>. Exception: Fedora (SELinux blocks VPNs).",
5858
"connectWindows": "Heavier JS path. await setup() from blue-js-sdk/ai-path — run once on a fresh machine; auto-downloads V2Ray (no admin), checks/installs WireGuard (MSI needs admin); returns { ready, capabilities, recommended, issues }",
5959
"walletCreate": "createWallet() from blue-js-sdk/ai-path → { address, mnemonic }",
6060
"connect": "Windows JS tunnel: connect({ mnemonic, subscriptionId, feeGranter, nodeAddress }) → { connected, ip, protocol }. On macOS/Linux use connectMacLinux (native CLI) instead.",
@@ -2034,15 +2034,16 @@ <h3 style="font-size: 13px; color: var(--accent); margin-bottom: 18px; letter-sp
20342034
</div>
20352035
<pre><code><span class="cm">// Payment (Steps 0-2 above) is identical. macOS &amp; Linux skip the JS connect()</span>
20362036
<span class="cm">// and use Sentinel's native CLI instead &mdash; WireGuard already ships with the OS,</span>
2037-
<span class="cm">// so there's nothing to install for the tunnel. Three flag-driven commands, no prompts.</span>
2037+
<span class="cm">// so there's nothing to install for the tunnel. Four commands, scriptable end to end.</span>
20382038
<span class="cm">// CLI: https://github.com/sentinel-official/sentinel-dvpncli</span>
20392039

20402040
<span class="cm">// Install once (Go 1.24+):</span>
20412041
<span class="cm">// go install github.com/sentinel-official/sentinel-dvpncli@latest</span>
20422042

2043-
<span class="cm">// 1. Import the SAME wallet whose sentinelAddr you sent to x402.</span>
2044-
<span class="cm">// Mnemonic is read from stdin; --keyring.backend test = non-interactive.</span>
2045-
<span class="kw">echo</span> <span class="str">"$AGENT_MNEMONIC"</span> | sentinel-dvpncli keys add agent --keyring.backend test
2043+
<span class="cm">// 1. Import the SAME wallet whose sentinelAddr (sent1...) you sent to x402.</span>
2044+
<span class="cm">// keys add is interactive: prompt 1 = mnemonic, prompt 2 = BIP-39 passphrase.</span>
2045+
<span class="cm">// printf feeds both (mnemonic line + blank line = default empty passphrase).</span>
2046+
<span class="kw">printf</span> <span class="str">'%s\n\n'</span> <span class="str">"$AGENT_MNEMONIC"</span> | sentinel-dvpncli keys add agent --keyring.backend test
20462047

20472048
<span class="cm">// 2. Start the session against the subscription x402 shared with you.</span>
20482049
<span class="cm">// --tx.fee-granter-addr = provision.feeGranter &rarr; operator pays the P2P gas (you pay 0).</span>
@@ -2051,9 +2052,14 @@ <h3 style="font-size: 13px; color: var(--accent); margin-bottom: 18px; letter-sp
20512052
--tx.fee-granter-addr <span class="str">"$FEE_GRANTER"</span> \
20522053
--tx.from-name agent --keyring.backend test \
20532054
--rpc.chain-id sentinelhub-2 --output-format json
2054-
<span class="cm">// &rarr; capture the new session id from the tx result/events</span>
20552055

2056-
<span class="cm">// 3. Bring the tunnel up (takes the SESSION id, not the subscription id).</span>
2056+
<span class="cm">// 3. Get the session id you just created (session-start does not print it cleanly).</span>
2057+
<span class="cm">// AGENT_ADDR = the same sent1... you sent to x402. Newest entry is your session.</span>
2058+
SESSION_ID<span class="op">=</span><span class="str">$(</span>sentinel-dvpncli query sessions \
2059+
--account-addr <span class="str">"$AGENT_ADDR"</span> --subscription-id <span class="str">"$SUBSCRIPTION_ID"</span> \
2060+
--output-format json | jq <span class="str">'.result[-1].id // .response[-1].id'</span><span class="str">)</span>
2061+
2062+
<span class="cm">// 4. Bring the tunnel up (takes the SESSION id, not the subscription id).</span>
20572063
<span class="cm">// WireGuard may need sudo to create the interface, same as any wg client.</span>
20582064
sentinel-dvpncli connect <span class="str">"$SESSION_ID"</span>
20592065
<span class="cm">// &rarr; tunnel up. Tear down with: sentinel-dvpncli tx session-cancel "$SESSION_ID"</span>
@@ -2381,7 +2387,9 @@ <h2>End-to-end: USDC to VPN tunnel</h2>
23812387

23822388
<span class="cm">// ── Step 5: Connect to VPN (0 gas, 0 P2P) ──</span>
23832389
<span class="cm">// This connect() is the Windows path. On macOS/Linux (easiest) use the native CLI instead:</span>
2390+
<span class="cm">// printf '%s\n\n' "$MNEMONIC" | sentinel-dvpncli keys add agent --keyring.backend test</span>
23842391
<span class="cm">// sentinel-dvpncli tx session-start &lt;nodeAddress&gt; --subscription-id &lt;id&gt; --tx.fee-granter-addr &lt;feeGranter&gt; --tx.from-name agent --keyring.backend test</span>
2392+
<span class="cm">// sessionId=$(sentinel-dvpncli query sessions --account-addr &lt;sent1...&gt; --subscription-id &lt;id&gt; --output-format json | jq '.result[-1].id')</span>
23852393
<span class="cm">// sentinel-dvpncli connect &lt;sessionId&gt; (WireGuard ships with the OS; nothing to install)</span>
23862394
<span class="kw">const</span> vpn <span class="op">=</span> <span class="kw">await</span> <span class="fn">connect</span>({
23872395
<span class="prop">mnemonic</span><span class="op">:</span> wallet<span class="op">.</span>mnemonic<span class="op">,</span>

docs/llms.txt

Lines changed: 15 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -59,7 +59,7 @@ Free:
5959
6. Server submits atomic `MsgShareSubscription` + `MsgGrantAllowance` on Sentinel.
6060
7. Server returns 200 `{ provisioned, subscriptionId, planId, feeGranter, nodeAddress, nodeCountry, nodes[], sentinelTxHash, expiresAt }`.
6161
8. Agent connects, using the path for its OS (both consume the same 200 fields):
62-
- **macOS/Linux:** `sentinel-dvpncli tx session-start <nodeAddress> --subscription-id <subscriptionId> --tx.fee-granter-addr <feeGranter> --tx.from-name <key>` → note the returned session id `sentinel-dvpncli connect <sessionId>`. (See "macOS & Linux — native CLI connect".)
62+
- **macOS/Linux:** import the key (`printf '%s\n\n' "$MNEMONIC" | sentinel-dvpncli keys add agent --keyring.backend test`), `sentinel-dvpncli tx session-start <nodeAddress> --subscription-id <subscriptionId> --tx.fee-granter-addr <feeGranter> --tx.from-name agent --keyring.backend test`, resolve the new id with `sentinel-dvpncli query sessions --account-addr <sent1...> --subscription-id <subscriptionId> --output-format json` (newest entry), then `sentinel-dvpncli connect <sessionId>`. (See "macOS & Linux — native CLI connect".)
6363
- **Windows:** `setup()` once on a fresh machine, then `connect({ mnemonic, subscriptionId, feeGranter, nodeAddress })`. (See "Windows — JS-SDK connect".)
6464
9. `MsgStartSession` on Sentinel — gas paid by `feeGranter` (the fee grant the server created in step 6, so the agent's session start costs zero P2P).
6565
10. Direct WireGuard/V2Ray handshake — tunnel up. The operator never sees it.
@@ -130,8 +130,11 @@ sentinel-dvpncli version
130130

131131
```bash
132132
# 1. Import the SAME wallet whose sentinelAddr (sent1...) you sent to x402.
133-
# The mnemonic is read from stdin; --keyring.backend test makes it non-interactive (no OS keychain prompt).
134-
echo "$AGENT_MNEMONIC" | sentinel-dvpncli keys add agent --keyring.backend test
133+
# `keys add` is INTERACTIVE — there is NO --recover flag. It prompts for the mnemonic,
134+
# then a BIP-39 passphrase. Feed BOTH from stdin: line 1 = mnemonic, blank line 2 = default
135+
# (empty) passphrase. A single-line echo would hang on the passphrase prompt.
136+
# --keyring.backend test makes it non-interactive for the OS keychain (no keychain prompt).
137+
printf '%s\n\n' "$AGENT_MNEMONIC" | sentinel-dvpncli keys add agent --keyring.backend test
135138
# (keyring.backend defaults to "os"; use "test" or "file" for scripted/agent use.)
136139

137140
# 2. Start the session against the subscription x402 shared with you.
@@ -144,9 +147,16 @@ sentinel-dvpncli tx session-start "$NODE_ADDRESS" \
144147
--keyring.backend test \
145148
--rpc.chain-id sentinelhub-2 \
146149
--output-format json
147-
# Capture the session id from the tx result / events (it is the session just created on-chain).
148150

149-
# 3. Bring the tunnel up. Takes the SESSION id (not the subscription id).
151+
# 3. Resolve the session id you just created. `tx session-start` prints {response, result}
152+
# but does NOT surface the new id as a clean field, so query it back: filter sessions by
153+
# YOUR account + the subscription and take the newest. AGENT_ADDR = your sent1... address.
154+
SESSION_ID=$(sentinel-dvpncli query sessions \
155+
--account-addr "$AGENT_ADDR" --subscription-id "$SUBSCRIPTION_ID" \
156+
--output-format json | jq '.result[-1].id // .response[-1].id')
157+
158+
# 4. Bring the tunnel up. Takes the SESSION id (not the subscription id). No key needed —
159+
# connect only queries the session on-chain and builds the tunnel.
150160
# WireGuard nodes may need sudo to create the interface — same as any wg client.
151161
sentinel-dvpncli connect "$SESSION_ID"
152162
```

server/src/index.ts

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -296,7 +296,7 @@ app.get('/manifest', (_req, res) => {
296296
tldr: [
297297
'You need: (a) an EVM wallet with USDC on Base, (b) a Sentinel wallet from `createWallet()` in `blue-js-sdk/ai-path`.',
298298
`POST ${network === 'eip155:8453' ? 'https://x402.sentinel.co' : '/'}/vpn/connect/30days with { sentinelAddr } using @x402/fetch to auto-sign the 402. Add { country: "DE" } to get a node in a specific country (GET /nodes lists what is available). Payment is identical on every OS.`,
299-
'Connecting differs by OS. macOS & Linux (easiest): WireGuard ships with the OS — use the native CLI `sentinel-dvpncli` (go install github.com/sentinel-official/sentinel-dvpncli@latest), then `keys add` your mnemonic, `tx session-start <nodeAddress> --subscription-id <id> --tx.fee-granter-addr <feeGranter>`, and `connect <sessionId>`. See sentinel.connectMacLinux. Only Fedora is unsupported (SELinux blocks VPNs).',
299+
'Connecting differs by OS. macOS & Linux (easiest): WireGuard ships with the OS — use the native CLI `sentinel-dvpncli` (go install github.com/sentinel-official/sentinel-dvpncli@latest), then import the key (`printf \'%s\\n\\n\' "$MNEMONIC" | sentinel-dvpncli keys add agent --keyring.backend test`), `tx session-start <nodeAddress> --subscription-id <id> --tx.fee-granter-addr <feeGranter> --tx.from-name agent --keyring.backend test`, resolve the id with `query sessions --account-addr <sent1...> --subscription-id <id> --output-format json`, then `connect <sessionId>`. See sentinel.connectMacLinux. Only Fedora is unsupported (SELinux blocks VPNs).',
300300
'Windows (heavier): run `await setup()` from `blue-js-sdk/ai-path` first — it auto-downloads V2Ray (no admin) and installs WireGuard — then `connect({ mnemonic, subscriptionId, feeGranter, nodeAddress })`. See sentinel.connectWindows.',
301301
'Tunnel is up. You paid $1.00 USDC, zero gas on Base, zero gas on Sentinel.',
302302
],
@@ -442,7 +442,7 @@ app.get('/manifest', (_req, res) => {
442442
{ step: 5, actor: 'facilitator', action: 'Settle USDC on Base (~2s)' },
443443
{ step: 6, actor: 'server', action: 'Atomic MsgShareSubscription + MsgGrantAllowance on Sentinel' },
444444
{ step: 7, actor: 'server', action: 'Respond 200 with subscriptionId, feeGranter, nodeAddress' },
445-
{ step: 8, actor: 'agent', action: 'Connect (OS-specific, same 200 fields). macOS/Linux: sentinel-dvpncli tx session-start <nodeAddress> --subscription-id <id> --tx.fee-granter-addr <feeGranter> --tx.from-name agent, then connect <sessionId>. Windows: setup() once, then connect({ mnemonic, subscriptionId, feeGranter, nodeAddress })' },
445+
{ step: 8, actor: 'agent', action: 'Connect (OS-specific, same 200 fields). macOS/Linux: keys add (printf mnemonic+blank-line | keys add agent --keyring.backend test), tx session-start <nodeAddress> --subscription-id <id> --tx.fee-granter-addr <feeGranter> --tx.from-name agent --keyring.backend test, resolve id via query sessions --account-addr <sent1...> --subscription-id <id> --output-format json, then connect <sessionId>. Windows: setup() once, then connect({ mnemonic, subscriptionId, feeGranter, nodeAddress })' },
446446
{ step: 9, actor: 'agent', action: 'MsgStartSession (gas paid by operator via fee grant)' },
447447
{ step: 10, actor: 'agent', action: 'Direct WireGuard/V2Ray handshake with node — tunnel up' },
448448
],
@@ -519,9 +519,10 @@ app.get('/manifest', (_req, res) => {
519519
install: 'go install github.com/sentinel-official/sentinel-dvpncli@latest (Go 1.24+); binary lands in $(go env GOPATH)/bin',
520520
why: 'Easiest path — WireGuard ships with macOS and mainstream Linux, so nothing to install for the tunnel. Flag-driven, non-interactive.',
521521
steps: [
522-
'keys add: echo "$MNEMONIC" | sentinel-dvpncli keys add agent --keyring.backend test (import the SAME wallet whose sentinelAddr you sent to x402)',
523-
'session-start: sentinel-dvpncli tx session-start <nodeAddress> --subscription-id <subscriptionId> --tx.fee-granter-addr <feeGranter> --tx.from-name agent --keyring.backend test --rpc.chain-id sentinelhub-2 --output-format json (operator fee-grants the gas; capture the session id from the tx events)',
524-
'connect: sentinel-dvpncli connect <sessionId> (WireGuard nodes may need sudo to bring the interface up)',
522+
'keys add: printf \'%s\\n\\n\' "$MNEMONIC" | sentinel-dvpncli keys add agent --keyring.backend test (import the SAME wallet whose sentinelAddr you sent to x402. keys add is INTERACTIVE — no --recover flag: line 1 answers the mnemonic prompt, the blank line 2 accepts the default empty BIP-39 passphrase. A single-line echo would hang on the passphrase prompt.)',
523+
'session-start: sentinel-dvpncli tx session-start <nodeAddress> --subscription-id <subscriptionId> --tx.fee-granter-addr <feeGranter> --tx.from-name agent --keyring.backend test --rpc.chain-id sentinelhub-2 --output-format json (operator fee-grants the gas)',
524+
'resolve session id: sentinel-dvpncli query sessions --account-addr <sent1...> --subscription-id <subscriptionId> --output-format json — session-start does NOT print the new id cleanly, so query it back and take the newest entry (.result[-1].id).',
525+
'connect: sentinel-dvpncli connect <sessionId> (takes the SESSION id, not the subscription id; needs no key — it only queries the session and builds the tunnel. WireGuard nodes may need sudo to bring the interface up)',
525526
],
526527
teardown: 'sentinel-dvpncli tx session-cancel <sessionId>',
527528
defaults: '--rpc.addrs https://rpc.sentinel.co:443, --rpc.chain-id sentinelhub-2, --keyring.name sentinel',

0 commit comments

Comments
 (0)