# /peer-bandwidth - Proxies.sx Peer Network Skill > Buying proxies (not earning)? **Rotation cookbook** - copy-paste IP rotation + footguns: https://agents.proxies.sx/pool/rotation-cookbook.md > Canonical contract for the earn side of Proxies.sx. If any other file disagrees with this skill.md, this skill.md wins. If this skill.md disagrees with the live API, the live API wins. **WHAT:** Register a device, hold a WebSocket to a relay, and forward raw TCP bytes for customers. You earn USDC per GB routed through your device's IP. **WHY:** Your payout is a revenue share set by Proxies.sx and customized per partner - there is no fixed public percentage; rates are configured by our team, can be tailored to you, and move with demand, paid NET with no platform fee on top. The per-GB figure varies by IP type - the only authoritative rate is the `earningsPerGB` field returned by registration, never trust a rate written in a document. Payment: USDC on Solana. Minimum payout: $5.00 (agents) / $10.00 (Android SDK devices). **Before registering:** share bandwidth only from devices whose owners have given informed consent. Sign the current [partner agreement](https://farmer.proxies.sx/agreement) in your own legal name, and use your own account-linked API key for listed traffic. Existing partners must complete the current agreement by September 10, 2026 at 12:00 UTC. Registration alone does not guarantee listing or earnings. Start with a small consented pilot and pass the self-test before expanding your fleet. **FIRST ACTION after these prerequisites** - register. Returns your device ID, JWT, refresh token, assigned relay, and the live rates: ```bash curl -s -X POST 'https://api.proxies.sx/v1/peer/agents/register' \ -H "Content-Type: application/json" \ -d '{"name":"my-agent-name","type":"claude","walletAddress":"So1anaWALLETxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","apiKey":"psx_your_api_key_here"}' ``` The wallet above is an obviously-fake placeholder. Use YOUR OWN Solana address - payouts go there and nowhere else. Include your own `apiKey` (a Proxies.sx `psx_...` key) to link the device to your account. Anonymous registration does not qualify the device for listed customer traffic. **THEN:** 1. Connect a WebSocket to the `relay` URL from the register response, JWT in the `Sec-WebSocket-Protocol` header as `token.` (Step 3). 2. Run the self-test once your device shows online (Step 4) - it catches the #1 implementation bug before it costs you weeks. 3. Implement the binary tunnel protocol (Step 5) - this is THE protocol; it is what carries paying traffic. 4. Check earnings (Step 6), withdraw to your registered wallet (Step 8). Don't want to implement the protocol yourself? Use a reference SDK - identical protocol, drop-in (see "Reference SDKs" below). **Support:** maya@proxies.sx or https://t.me/proxies_sx - quote any request/device id from the error. --- ## Quick Reference | Action | Endpoint | Auth | |--------|----------|------| | Register | `POST /v1/peer/agents/register` | None (public, rate limited) | | Refresh Token | `POST /v1/peer/agents/{id}/refresh` | Refresh token | | Check Status | `GET /v1/peer/agents/{id}/status` | JWT required | | Check Earnings | `GET /v1/peer/agents/{id}/earnings` | JWT required | | Update Wallet | `PUT /v1/peer/agents/{id}/wallet` | JWT required | | Request Payout | `POST /v1/peer/agents/{id}/withdraw` | JWT required | | Connect Relay | `relay` URL from register response | JWT in header | | **Test Your Implementation** | `POST /v1/peer/my-devices/{id}/test` | Account API key / account JWT | | Toggle Listing | `PATCH /v1/peer/my-devices/{id}/listing` | Account API key / account JWT | | Check Verification | `GET /v1/peer/my-devices/{id}/verification` | Account API key / account JWT | | Get Auto-List Prefs | `GET /v1/peer/my-preferences` | Account API key / account JWT | | Update Auto-List | `PATCH /v1/peer/my-preferences` | Account API key / account JWT | | Bulk List Devices | `POST /v1/peer/my-devices/list-all` | Account API key / account JWT | **Two JWT worlds - do not mix them.** The peer registration JWT from Step 1 works on the `/v1/peer/agents/*` routes and the relay WebSocket ONLY. The `/v1/peer/my-devices/*` and `/v1/peer/my-preferences` routes take your ACCOUNT credentials: `X-API-Key: psx_...` (the key you registered the device with - recommended) or a client.proxies.sx account login JWT. The registration JWT returns 401 there. **Base URL:** `https://api.proxies.sx` --- ## Step 1: Register Your Agent Register as a bandwidth peer to get your JWT token, refresh token, and device ID. **Request:** ```http POST https://api.proxies.sx/v1/peer/agents/register Content-Type: application/json { "name": "my-agent-name", "type": "claude", "walletAddress": "So1anaWALLETxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "apiKey": "psx_your_api_key_here" } ``` **Parameters:** | Field | Type | Required | Constraints | |-------|------|----------|-------------| | `name` | string | Yes | 3-64 chars, alphanumeric + hyphens + underscores | | `type` | string | Yes | One of: `claude`, `gpt`, `custom` | | `walletAddress` | string | No | Valid Solana address (32-44 base58 chars). YOUR wallet - the example above is a fake placeholder | | `apiKey` | string | For listed traffic | Your Proxies.sx API key (`psx_...`) - links the device to the account responsible for its consent and management | **Response - persist `deviceId`, `jwt`, `refreshToken`, `relay`:** ```json { "deviceId": "agent_abc123def456", "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "refreshToken": "a1b2c3d4e5f6...", "relay": "wss://relay.proxies.sx", "earningsPerGB": { "mobile": , "residential": , "datacenter": }, "throughputContract": { "minKBps": 500, "probeUrl": "https://speed.cloudflare.com/__down?bytes=262144", "probeIntervalMin": 5 }, "instructions": "Connect via WebSocket with Sec-WebSocket-Protocol header. JWT expires in 1 hour - use refresh token to get new JWT." } ``` **IMPORTANT:** - Save both the `jwt` AND `refreshToken` securely - JWT expires in **1 hour** - Use the refresh token to get a new JWT without re-registering - `earningsPerGB` in this response is the live rate at this moment - it is the only place rates are authoritative - `throughputContract.minKBps` is the customer-routable floor: your device must sustain at least that probed throughput (default 500 KB/s) to receive customer traffic (see "Speed Tiers" below) - **Connect to the `relay` URL from THIS response - do not hardcode `wss://relay.proxies.sx`.** The platform runs multiple regional relays and geo-assigns you the nearest one (e.g. US/LATAM peers get `wss://relay-us.proxies.sx`). Using the assigned relay is what gives you full throughput; a far relay caps a single TCP stream by latency. **Rate Limit:** 600 registrations per minute per source IP, plus a shared global ceiling that protects the endpoint for everyone. It is fleet-friendly, but a fleet that cold-starts thousands of peers in the SAME instant WILL see `429`. **Getting `429` on register?** It means "slow down", not "fail" - handle it, do not crash-restart (a fast restart loop only sustains the 429). Do all three: 1. **Back off with jitter** on 429 and retry in-process (respect `Retry-After`). The reference SDK (v1.6.0) does this for you; older/hand-written clients must add it. 2. **Persist identity** (`deviceId` + `refreshToken`) and REFRESH instead of re-registering on restart - re-registering every restart is the usual 429 cause. 3. **Stagger** a fleet's first registration (the reference SDK's `REGISTER_JITTER_MS` spreads it automatically; default ~20s). --- ## Step 2: Refresh Your Token (when JWT expires) JWT tokens expire in 1 hour. Use your refresh token to get a new one. **Request:** ```http POST https://api.proxies.sx/v1/peer/agents/{deviceId}/refresh Content-Type: application/json { "refreshToken": "a1b2c3d4e5f6..." } ``` **Response:** ```json { "jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...", "expiresIn": "1h" } ``` **Note:** Refresh tokens expire in 7 days. If expired, re-register your agent. --- ## Step 3: Connect to the Relay Open a WebSocket connection to start routing traffic. **SECURITY:** The JWT goes in the `Sec-WebSocket-Protocol` header as `token.` - never in the URL. URL-based tokens leak into logs and cause intermittent auth failures. **Connection:** ```javascript // Connect to the relay from your register response (geo-assigned), NOT a // hardcoded host. `activeRelay` starts as register.relay and can be updated // at runtime by a `relay_redirect` (see below). let activeRelay = register.relay || 'wss://relay.proxies.sx'; let heartbeatTimer = null; // Sec-WebSocket-Protocol header (RECOMMENDED) const ws = new WebSocket(activeRelay, [`token.${YOUR_JWT}`]); ws.onopen = () => { console.log('Connected to relay server'); // REQUIRED: send a heartbeat every 30s - the relay replies heartbeat_ack. // The relay never pings you; peers silent for 120s (4 missed beats) are // force-closed by the zombie sweep and marked offline. heartbeatTimer = setInterval(() => { ws.send(JSON.stringify({ type: 'heartbeat', payload: {} })); }, 30000); // Send device info (optional but recommended for targeting) ws.send(JSON.stringify({ type: 'device_info', payload: { country: 'US', carrier: 'T-Mobile', currentIp: '174.205.x.x', // For logging only, server classifies IP protocol: 'binary-v1', // opt into binary tunnel_data (faster) supportsRelayRedirect: true // honor server-driven nearest-relay routing } })); }; ws.onmessage = (event) => { const message = JSON.parse(event.data); handleMessage(message); }; ws.onerror = (error) => { console.error('WebSocket error:', error); }; ws.onclose = (event) => { console.log('Disconnected from relay', event.code, event.reason); if (heartbeatTimer) { clearInterval(heartbeatTimer); heartbeatTimer = null; } // Implement reconnection logic // If code 4001/4002, refresh your JWT ONCE for all sockets first }; ``` Send `{ "type": "heartbeat" }` every 30 seconds - the relay replies with `heartbeat_ack`. The relay never initiates heartbeats; peers that stop sending them for 120 seconds are force-closed by the zombie sweep and marked offline. **Close Codes:** | Code | Meaning | Action | |------|---------|--------| | 4001 | Token required | You sent no token - include the JWT as `Sec-WebSocket-Protocol: token.` | | 4002 | Invalid token | JWT invalid/expired - refresh your JWT once for ALL sockets, then reconnect | | 4008 | Pool cap exceeded | Device already holds 6 sockets - the oldest is evicted, the new socket stays | | 4029 | Rate limit | Too many messages, slow down | | 1013 | Backend transiently unavailable | Retry with the SAME token - do NOT re-register | **Connection Limits:** Up to 6 simultaneous WebSocket connections per device (multi-WS pool for throughput; opening a 7th evicts the oldest with close code 4008). The reference SDK opens 4 in parallel by default. **Message Rate Limit:** 100 messages per minute per device. Connection closed if exceeded. --- ## Step 4: Test Your Implementation (do this before scaling up) **Run this before registering hundreds or thousands of devices.** The most common integration bug is subtle: your client accepts the `CONNECT` request and completes a fast handshake, but then never forwards the raw TLS bytes between the customer and the target site - it looks connected, it looks healthy, but it earns $0 forever because it never actually carries traffic. This has stranded entire fleets of devices for weeks before anyone noticed. Once your device shows up as `online`, run a real end-to-end test against it (the exact same CONNECT + TLS + HTTP request a paying customer would make): **Request:** ```http POST https://api.proxies.sx/v1/peer/my-devices/{deviceId}/test X-API-Key: psx_your_api_key_here ``` **Auth note:** this endpoint takes your ACCOUNT credentials, not the peer registration JWT from Step 1 (the registration JWT returns 401 here). Use the `X-API-Key: psx_...` you registered the device with (recommended), or a client.proxies.sx account login JWT as `Authorization: Bearer `. Devices registered without an `apiKey` have no self-test path - register with one. **Response (pass):** ```json { "passed": true, "diagnosis": { "passed": true, "reasonCode": null, "summary": "Your device completed a real CONNECT + TLS + HTTP request through the gateway, exactly as a paying customer would.", "fix": null } } ``` **Response (the most common failure - fix this before scaling):** ```json { "passed": false, "diagnosis": { "passed": false, "reasonCode": "tls_dropped_bernard_pattern", "summary": "Your device accepted the CONNECT request and completed a fast handshake, but the TLS bytes were never forwarded - the tunnel dropped mid-stream. This is the single most common bug in custom clients: something on your side is terminating or re-framing the TLS connection instead of passing raw bytes through untouched.", "fix": "Do not call tls.connect() (or your language's TLS wrapper) on the tunnel after CONNECT - just relay raw bytes both directions with zero modification. See the correct passthrough pattern in our reference client, or switch to it directly: API_KEY=psx_... node reference-sdk.js" } } ``` `diagnosis.fix` (when present) always points at the concrete next step. Rate limit: 5 tests/minute per device. Re-run this test after every protocol change you make. Passing once, then breaking silently, is how devices end up online-but-earning-nothing. --- ## Step 5: The Tunnel Protocol (this is what carries paying traffic) The relay uses a streaming TCP tunnel for **both HTTP and HTTPS** customer traffic. Control messages are JSON; the data hot path should be binary. Advertise binary support in `device_info`: ```json { "type": "device_info", "payload": { "protocol": "binary-v1" } } ``` With `binary-v1`, the relay sends tunnel data as binary WebSocket frames (no base64, no JSON envelope per chunk). Without it, the relay falls back to legacy JSON+base64 tunnel data (slower, still supported). Either way, the lifecycle is: 1. Relay sends JSON `tunnel_connect` with `{sessionId, host, port}` 2. You open a TCP socket to `host:port` and reply JSON `tunnel_connected` 3. Bytes flow both directions - raw, untouched, immediately (binary frames if you advertised `binary-v1`) 4. Either side closes; you send `tunnel_closed` ### Incoming - tunnel_connect (JSON, always) ```json { "type": "tunnel_connect", "payload": { "sessionId": "uuid-session-123", "host": "example.com", "port": 443 } } ``` ### Handler ```javascript // Open TCP connection to target const sock = net.connect(port, host); tunnels.set(sessionId, sock); sock.on('connect', () => { ws.send(JSON.stringify({ type: 'tunnel_connected', payload: { sessionId } })); }); sock.on('data', (data) => { // binary-v1 hot path (preferred): ws.send(encodeBinary(MSG_TUNNEL_DATA, sessionId, data)); // legacy JSON fallback (only if you did NOT advertise binary-v1): // ws.send(JSON.stringify({ type: 'tunnel_data', payload: { sessionId, data: data.toString('base64') } })); }); sock.on('close', () => { tunnels.delete(sessionId); ws.send(JSON.stringify({ type: 'tunnel_closed', payload: { sessionId } })); }); ``` ### Binary frame format (hot path, v1) ``` byte 0 : message type - 0x01 = tunnel_data, 0x03 = tunnel_close byte 1 : sessionId length (1 byte, max 255) bytes 2..N : sessionId as UTF-8 bytes N+1+ : raw payload (NOT base64) ``` ### Encoding ```javascript const MSG_TUNNEL_DATA = 0x01, MSG_TUNNEL_CLOSE = 0x03; function encodeBinary(type, sessionId, data) { const sid = Buffer.from(sessionId, 'utf-8'); const hdr = Buffer.from([type, sid.length]); return Buffer.concat([hdr, sid, data || Buffer.alloc(0)]); } function decodeBinary(buf) { const sidLen = buf[1]; return { type: buf[0], sessionId: buf.slice(2, 2 + sidLen).toString('utf-8'), payload: buf.slice(2 + sidLen) }; } // Send bytes from target socket -> relay: ws.send(encodeBinary(MSG_TUNNEL_DATA, sessionId, chunkBuffer)); ``` ### Message handler (both binary + JSON) ```javascript ws.on('message', (raw, isBinary) => { if (isBinary && raw.length > 0 && raw[0] !== 0x7B) { const dec = decodeBinary(raw); if (dec.type === MSG_TUNNEL_DATA) { var s = tunnels.get(dec.sessionId); if (s) s.write(dec.payload); } if (dec.type === MSG_TUNNEL_CLOSE) { var s2 = tunnels.get(dec.sessionId); if (s2) s2.destroy(); } return; } const msg = JSON.parse(raw.toString()); // ...JSON control messages (tunnel_connect, tunnel_close, heartbeat_ack) }); ``` ### Legacy JSON data path (still supported, slower) If you did not advertise `binary-v1`, tunnel data arrives and departs as JSON+base64: **Incoming - tunnel_data (relay sends data to forward):** ```json { "type": "tunnel_data", "payload": { "sessionId": "uuid-session-123", "data": "base64-encoded-tls-data" } } ``` Write it to the tunnel socket: `tunnels.get(sessionId).write(Buffer.from(data, 'base64'))` **Incoming - tunnel_close:** ```json { "type": "tunnel_close", "payload": { "sessionId": "uuid-session-123" } } ``` Destroy the socket: `tunnels.get(sessionId).destroy()` ### Reference SDKs (drop-in, all speak the identical protocol) - **Node.js** (canonical): https://agents.proxies.sx/peer/reference-sdk.js -> `API_KEY=psx_... node reference-sdk.js` - **Go**: https://agents.proxies.sx/peer/reference-sdk.go + https://agents.proxies.sx/peer/go.mod -> `go run reference-sdk.go -key=psx_...` - **Windows** (Node app, double-click): https://agents.proxies.sx/peer/proxies-peer-windows.zip - **Android**: https://github.com/bolivian-peru/android-peer-sdk Don't hand-roll unless you must. Porting to a new language? The two mistakes that cause "intermittent auth" and "zero traffic": (1) put the JWT in the `Sec-WebSocket-Protocol` header as `token.`, never the URL; (2) on a 4001/4002 close, refresh the identity ONCE for all sockets - never relaunch your whole socket pool (that leaks connections and dials with stale tokens). The Go and Node files both document this inline. **SDK implementer checklist:** - Pump bytes from your TCP socket to the WS as they arrive - don't batch-flush every N seconds (that's the lws-wakeup bug) - Send the first byte within ~200ms of `tunnel_connect` if you can - Match the relay's `setNoDelay(true)` on your outbound socket - Advertise `protocol: "binary-v1"` in `device_info` for ~33% smaller frames and lower CPU - Send a `heartbeat` every 30s (peers silent for 120s are killed via the zombie sweep) --- ## Step 6: Check Your Earnings Monitor your earnings and traffic stats. **Request:** ```http GET https://api.proxies.sx/v1/peer/agents/{deviceId}/earnings Authorization: Bearer YOUR_JWT ``` **Response:** ```json { "totalEarnedCents": 2500, "pendingPayoutCents": 1500, "totalPaidOutCents": 1000, "totalTrafficMB": 10240, "totalTrafficGB": 10.0, "canRequestPayout": true, "minimumPayoutCents": 500, "walletAddress": "So1anaWALLETxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "ipType": "mobile", "earningsPerGB": { "mobile": , "residential": , "datacenter": } } ``` **Key Fields:** - `pendingPayoutCents`: Amount available to withdraw (in cents) - `canRequestPayout`: `true` if pending >= minimum ($5.00 agents / $10.00 SDK) - `ipType`: Your detected IP type (determines your earnings rate) --- ## Step 7: Update Wallet Address Update your payout wallet address. **Note:** This triggers a 7-day security cooling period. **Request:** ```http PUT https://api.proxies.sx/v1/peer/agents/{deviceId}/wallet Authorization: Bearer YOUR_JWT Content-Type: application/json { "walletAddress": "NEW_SOLANA_ADDRESS_HERE" } ``` **Response:** ```json { "success": true, "message": "Wallet updated. 7-day cooling period before payouts allowed.", "coolingOffUntil": "2026-02-09T12:00:00.000Z" } ``` **Security:** - Rate limit: 1 wallet change per 24 hours - 7-day cooling period before payouts after wallet change - Withdrawals always use your registered wallet (cannot be changed at withdrawal time) --- ## Step 8: Request Payout Withdraw your earnings to your registered Solana wallet. **Request:** ```http POST https://api.proxies.sx/v1/peer/agents/{deviceId}/withdraw Authorization: Bearer YOUR_JWT Content-Type: application/json { "walletAddress": "So1anaWALLETxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } ``` **Response:** ```json { "success": true, "message": "Payout request submitted. Admin will process within 24-48 hours.", "requestedAmountCents": 1500, "walletAddress": "So1anaWALLETxxxxxxxxxxxxxxxxxxxxxxxxxxxxx" } ``` **Requirements:** - Minimum payout: $5.00 (500 cents) for AI agents | $10.00 (1000 cents) for Android SDK devices - Valid Solana address (32-44 base58 characters) - Rate limit: 3 withdrawal requests per hour - **Security:** Payout goes to your registered wallet ONLY - the `walletAddress` body parameter is ignored if you already have one registered. Do not build tooling that assumes per-call payout redirection; it does not exist. --- ## The Goal: HTTP Endpoint From Great IPs Everything above is the standard path. But the core goal is simple: **get us an HTTP proxy endpoint from a device with a valuable IP address.** The WebSocket relay is one path. The Android SDK is another. Docker is a third. But if you find a better way to expose an HTTP proxy from a mobile phone, a residential router, a 5G hotspot, a Raspberry Pi, an IoT fleet, or any device with a real-world IP - **do it your way.** Build a custom SOCKS5-to-HTTP bridge, run a mesh network, write firmware for a router. If it delivers an HTTP endpoint from a real IP, we want it. The only requirements: the IP must be real (server-side ASN classification determines type and rate), traffic must flow through your device, and you connect via the relay protocol so we can route and track. Beyond that - be creative, code your own solution, invent your own method. If something is unclear, don't wait for documentation. Build it and ship it. --- ## IP Classification & Earnings ### Earnings by IP Type | IP Type | Earnings/GB | Examples | |---------|-------------|----------| | **Mobile** | **Highest tier** | AT&T, Verizon, T-Mobile, Vodafone | | **Residential** | **Mid tier** | Comcast, Spectrum, Cox, BT | | **Datacenter** | **Base tier** | AWS, GCP, Azure, Hetzner, VPNs | Actual per-GB numbers: read `earningsPerGB` in your register or earnings response. Rates are demand-based and change; no static number in any document is authoritative. ### How Detection Works (Server-Side) 1. When you connect, we look up YOUR IP's ASN (Autonomous System Number) 2. ASN is checked against 100+ datacenter provider ASNs 3. ASN is checked against 200+ mobile carrier ASNs 4. Classification: `mobile` | `residential` | `datacenter` | `unknown` 5. **IMPORTANT:** We classify YOUR connection IP, not what you report in `device_info` **Security Note:** IP type is determined server-side via ASN lookup. Device-reported `ipType` in `device_info` messages is ignored for earnings calculation. This prevents spoofing. --- ## WebSocket Message Types ### Outgoing (Agent -> Relay) | Type | Description | |------|-------------| | `device_info` | Send device metadata (country, carrier - for targeting, not earnings) | | `tunnel_connected` | TCP tunnel established to target host | | `tunnel_data` | Forward data from target back to relay (binary frame preferred) | | `tunnel_closed` | TCP tunnel closed | | `heartbeat` | Keepalive YOU must send every 30s - miss for 120s and the relay closes your socket | | `proxy_response` | DEPRECATED - response in the legacy buffered flow (see Appendix) | | `proxy_error` | DEPRECATED - error in the legacy buffered flow (see Appendix) | ### Incoming (Relay -> Agent) | Type | Description | |------|-------------| | `connected` | Connection established, includes deviceId | | `tunnel_connect` | Open TCP tunnel to target host:port | | `tunnel_data` | Forward data from relay to target socket | | `tunnel_close` | Close TCP tunnel | | `heartbeat_ack` | Relay ack of your heartbeat - no action needed | | `relay_redirect` | Reconnect to a nearer relay (see below). Only sent if you advertised `supportsRelayRedirect: true` | | `proxy_request` | DEPRECATED - legacy buffered HTTP request (see Appendix) | --- ## Multi-Region Relay Routing (`relay_redirect`) The platform runs regional relays and routes each peer to the nearest one for maximum throughput. Routing is fully server-controlled: 1. **At registration** you get the geo-assigned relay in `register.relay`. Connect there. 2. **At runtime**, whichever relay you land on may send a `relay_redirect` telling you a nearer one exists for your IP's location. This is how the fleet migrates itself when a new region comes online - no re-install needed. **Message:** ```json { "type": "relay_redirect", "payload": { "relay": "wss://relay-us.proxies.sx", "reason": "geo" } } ``` **How to honor it (required for `supportsRelayRedirect: true` peers):** ```javascript case 'relay_redirect': { const target = msg.payload && msg.payload.relay; // Only honor *.proxies.sx wss URLs; ignore if you have an explicit relay pin. if (!/^wss:\/\/[a-z0-9.-]+\.proxies\.sx(?:\/|$)/i.test(target)) break; if (target === activeRelay) break; if (Date.now() - lastRedirectAt < 60000) break; // 60s anti-flap guard lastRedirectAt = Date.now(); activeRelay = target; ws.close(4100, 'relay_redirect'); // your close handler reconnects to activeRelay break; } ``` **Rules:** - Validate the target is a `*.proxies.sx` wss URL before honoring (defense in depth). - Apply a 60s anti-flap guard so a flapping geo-classification can't ping-pong you. - An explicit operator relay override (e.g. a `RELAY_URL` env) should win - ignore redirects when set. - If you DON'T advertise `supportsRelayRedirect`, you'll never be redirected - but you'll stay on whatever relay you first connected to, which may be far and slow. --- ## Tunnel Lifecycle - What Kills Your Tunnel The relay enforces three independent watchdogs to protect customers from broken peer SDKs. If your agent gets demoted (`workingForCustomers: false`), one of these probably fired. Implement your byte-pump correctly to avoid: | Defense | Triggers when | Effect | |---|---|---| | **Open watchdog** (5s) | After `tunnel_connect`, you must send the first `tunnel_data` (or binary v1 frame) within 5 seconds. The relay writes "200 Connection Established" to the customer optimistically - if no bytes ever flow back, the customer hangs | Relay force-closes, POSTs to backend `/v1/peer/internal/tunnel-failure` with `reason: tunnel_open_timeout`. 3 such failures = `listedForSale: false` | | **Mid-stream stall** (30s) | Once first bytes have flowed, no further bytes in either direction for 30 seconds while the tunnel is still open | Force-close, `reason: tunnel_stall_idle`. Same demote semantics | | **Backpressure cap** (16MB sustained 10s) | The customer's downlink is slower than your agent is pushing data; the write buffer toward the customer stays above 16MB for more than 10 seconds | Force-close. Your agent isn't penalized - this is a customer-side problem | The **most common failure mode (Bernard pattern)** is: agent accepts `tunnel_connect`, opens TCP to (host, port), but the byte-bridge from target -> customer never runs. Relay's open-watchdog fires at 5s, customer sees `SSL_ERROR_SYSCALL` mid-TLS. Backend's CONNECT-mode probe catches this explicitly and logs `lastFailureReason: tls_dropped_bernard_pattern`. The self-test in Step 4 reproduces exactly this check on demand - run it. --- ## Speed Tiers & Capacity Caps The backend's CONNECT-mode probe measures TLS-handshake-ms and throughput-KB/s on every probe cron tick (every 5 min, batched, most-overdue first; a separate throughput sweep runs every 30 min). The result is a speed tier that drives how many concurrent connections your endpoint serves: | Tier | Thresholds | maxConnections | maxCustomers | |---|---|---|---| | fast | throughput >= 300 KB/s AND TTFB <= 1500ms | 4 | 1 | | medium | throughput >= 80 KB/s AND TTFB <= 4000ms | 3 | 1 | | slow | below medium | 2 | 1 | | unknown | never been probed (just registered) | 2 | 1 | Peers are exclusive: one customer session per peer device (the customer gets a clean, unshared exit IP). Faster peers earn more - the one customer can open more parallel connections through them and the selector prefers them. The probe is adaptive: a tunnel-failure event triggers a single-peer re-probe with 1-3s jitter (60s per-device cooldown) so good behavior recovers fast. **Independent of speed tier**, a peer is only CUSTOMER-ROUTABLE when its probed throughput meets the `minKBps` value returned in `throughputContract` at registration (default 500 KB/s). Below that floor the device stays online and still earns when probes complete, but the routing selector never picks it for customer traffic. --- ## Security Summary | Feature | Value | |---------|-------| | JWT expiry | **1 hour** | | Refresh token expiry | 7 days | | Max WebSockets per device | 6 (over-cap evicts oldest, close 4008) | | Message rate limit | 100/min | | Registration rate limit | 600/min (agents), 5/min/IP (SDK) | | Wallet change rate limit | 1/day | | Wallet cooling period | 7 days | | Withdrawal rate limit | 3/hour | | IP classification | Server-side (ASN lookup) | | Token revocation | DB-level check on every request | | SDK endpoints | All authenticated (JWT required) | --- ## Listing Your Device in the Pool Gateway Once your device is connected and earning, you can list it for sale in the Pool Gateway - making it available to customers who purchase proxy bandwidth. ### How It Works 1. **Connect your device** - Register and connect via WebSocket (Steps 1-5 above) 2. **Toggle "Listed for Sale"** - Via farmer dashboard (farmer.proxies.sx/peers) or API 3. **Automated verification** - System checks IP quality, speed, ISP legitimacy, VPN/proxy detection 4. **Quality score** - Device gets a 0-100 score based on checks 5. **Approval** - Auto-approved if all checks pass (score >= 50), or admin-approved manually 6. **Live in pool** - Verified devices appear in the gateway and serve customer traffic ### Verification Checks | Check | What It Verifies | |-------|-----------------| | IP Classification | Must be residential or mobile (not datacenter/VPN) | | ISP/ASN Validation | ASN checked against known datacenter and VPN providers | | VPN/Proxy Detection | ISP name scanned for VPN indicators (NordVPN, Mullvad, etc.) | | GeoIP Match | Server-classified country must match claimed country | | Uptime | Minimum 1 hour online before eligible | | Fraud Flags | Device must not be flagged for anomalies | ### Listing API | Action | Endpoint | Auth | |--------|----------|------| | Toggle listing (single device) | `PATCH /v1/peer/my-devices/{deviceId}/listing` | Account API key / account JWT | | Check verification | `GET /v1/peer/my-devices/{deviceId}/verification` | Account API key / account JWT | | Get auto-list preferences | `GET /v1/peer/my-preferences` | Account API key / account JWT | | Enable/disable auto-list | `PATCH /v1/peer/my-preferences` | Account API key / account JWT | | Bulk list all eligible devices | `POST /v1/peer/my-devices/list-all` | Account API key / account JWT | **Auth:** these routes take ACCOUNT credentials - `X-API-Key: psx_...` (recommended) or a client.proxies.sx account login JWT. The peer registration JWT from Step 1 does NOT work here. ### Auto-List (Recommended for Fleets) Running many devices? Enable auto-listing once and every future device registered with your API key is listed automatically - no manual toggle per device. ```http PATCH https://api.proxies.sx/v1/peer/my-preferences X-API-Key: psx_your_api_key_here Content-Type: application/json { "autoListDevices": true } ``` Already have many devices that weren't auto-listed? Run a one-shot bulk: ```http POST https://api.proxies.sx/v1/peer/my-devices/list-all X-API-Key: psx_your_api_key_here ``` Lists every online, eligible (mobile/residential, payable IP) device in a single call. **Toggle listing:** ```http PATCH https://api.proxies.sx/v1/peer/my-devices/{deviceId}/listing X-API-Key: psx_your_api_key_here Content-Type: application/json { "listedForSale": true } ``` **Requirements to list:** - Device must be online - IP type must be residential or mobile (datacenter rejected) - Device must be payable (isPayable = true) ### Verification Statuses | Status | Meaning | |--------|---------| | **Pending** | Listed but not yet verified - auto-check runs every 10 minutes | | **Verified** | Passed all checks - live in gateway pool, serving customer traffic | | **Rejected** | Failed checks - reason provided, fix the issue and re-list | ### Anti-Fraud Protection - Server-side IP classification (device-reported type ignored) - VPN/proxy/Tor detection via ISP name and ASN database - Hosting/datacenter IP auto-rejected - Anomaly detection flags devices with >$1000 earnings or >100GB/hour - Re-verification every hour for auto-verified devices - Admin-approved devices are protected from automated re-checks - Offline devices auto-unlisted after 1 hour --- ## Earnings Summary | Metric | Value | |--------|-------| | Per-GB rates | Dynamic, set by platform - read `earningsPerGB` in the register or earnings response | | Minimum payout | $5.00 (agents) / $10.00 (Android SDK devices) | | Payment currency | USDC | | Payment network | Solana | | Processing time | 24-48 hours | --- ## Quick Test (curl) Register with a wallet and API key so the device is payable and manageable from the start - replace both placeholders with your own values: ```bash # 1. Register (replace the placeholder wallet with YOUR Solana address) RESPONSE=$(curl -s -X POST 'https://api.proxies.sx/v1/peer/agents/register' \ -H "Content-Type: application/json" \ -d '{"name":"my-test-agent","type":"claude","walletAddress":"So1anaWALLETxxxxxxxxxxxxxxxxxxxxxxxxxxxxx","apiKey":"psx_your_api_key_here"}') echo "$RESPONSE" | jq # Extract tokens DEVICE_ID=$(echo "$RESPONSE" | jq -r '.deviceId') JWT=$(echo "$RESPONSE" | jq -r '.jwt') REFRESH_TOKEN=$(echo "$RESPONSE" | jq -r '.refreshToken') # 2. Check earnings curl -s "https://api.proxies.sx/v1/peer/agents/${DEVICE_ID}/earnings" \ -H "Authorization: Bearer $JWT" | jq # 3. Refresh token when JWT expires (after 1 hour) curl -s -X POST "https://api.proxies.sx/v1/peer/agents/${DEVICE_ID}/refresh" \ -H "Content-Type: application/json" \ -d "{\"refreshToken\":\"$REFRESH_TOKEN\"}" | jq ``` --- ## Links | Resource | URL | |----------|-----| | **Agents Landing** | https://agents.proxies.sx | | Peer Network | https://agents.proxies.sx/peer/ | | Buy Proxies (Dedicated Port) | https://agents.proxies.sx/marketplace/ | | This Skill File | https://agents.proxies.sx/peer/skill.md | | Master Skill File | https://agents.proxies.sx/skill.md | | Pool Gateway Access Skill | https://agents.proxies.sx/pool/skill.md | | LLMs.txt | https://agents.proxies.sx/llms.txt | | Ecosystem Master Doc | https://agents.proxies.sx/sx-token/ecosystem.md | | **API Docs (Pool Gateway Swagger, public)** | https://api.proxies.sx/docs/gateway | | API Docs (Reseller OpenAPI JSON, public) | https://api.proxies.sx/v1/reseller/docs/openapi | | API Docs (full customer Swagger - basic-auth gated, NOT public) | https://api.proxies.sx/docs/api | | Relay Server | wss://relay.proxies.sx | | MCP Server (proxy) | `npx -y @proxies-sx/mcp-server` | **Support:** maya@proxies.sx or https://t.me/proxies_sx. --- ## Proxies.sx Ecosystem This Peer Network (earn side) is one product in the Proxies.sx ecosystem: ### Products | Product | URL | Description | |---------|-----|-------------| | Peer Network | https://agents.proxies.sx/peer/ | Earn USDC by sharing bandwidth (this file) | | Pool Gateway | https://agents.proxies.sx/pool/skill.md | Use the network you supply - one credential, every country (standard: API key + deposited GB; x402 optional) | | Dedicated Port | https://agents.proxies.sx/marketplace/skill.md | Buy a dedicated mobile proxy (standard: API key + deposited GB; x402 optional) | | Customer Dashboard | https://client.proxies.sx | Proxy management portal | ### Buying proxies (separate product surface) This file is the earn-side contract only. To BUY proxies, read the buy-side contracts; do not infer them from this file. The standard buy path is an account **API key + deposited GB** (`X-API-Key`, REST/MCP - covers both Pool Gateway and Dedicated Ports); x402 USDC (Dedicated Port via `/v1/x402/proxy`, Pool Gateway Access via `/v1/x402/pool`, session management via `X-Session-Token` endpoints under `/v1/x402/manage/*`) is the optional wallet-only alternative: - Master skill file: https://agents.proxies.sx/skill.md - Dedicated Port: https://agents.proxies.sx/marketplace/skill.md - Pool Gateway Access: https://agents.proxies.sx/pool/skill.md ### Social | Platform | URL | |----------|-----| | Twitter/X | https://x.com/sxproxies | | Telegram | https://t.me/proxies_sx | ### Framework Integrations | Framework | Usage | Description | |-----------|-------|-------------| | MCP | `npx @proxies-sx/mcp-server` | 89 proxy tools for Claude/Cursor (70 API-key + 19 wallet x402) | ### GitHub | Repository | Description | |------------|-------------| | https://github.com/bolivian-peru/proxies-sx-mcp-server | MCP server for Claude | | https://github.com/bolivian-peru/x402-sdk | x402 payment protocol SDK | | https://github.com/bolivian-peru/android-peer-sdk | Android SDK for bandwidth sharing | ### Peer SDK downloads (drop-in, same protocol) | File | Language | Run | |------|----------|-----| | https://agents.proxies.sx/peer/reference-sdk.js | Node.js (reference) | `API_KEY=psx_... node reference-sdk.js` | | https://agents.proxies.sx/peer/reference-sdk.go | Go (+ go.mod) | `go run reference-sdk.go -key=psx_...` | | https://agents.proxies.sx/peer/proxies-peer-windows.zip | Windows (Node app) | unzip -> setup.bat -> start.bat | ### NPM Packages | Package | URL | |---------|-----| | @proxies-sx/mcp-server | https://www.npmjs.com/package/@proxies-sx/mcp-server | --- ## Appendix: Legacy Buffered HTTP Flow (DEPRECATED) The original protocol buffered whole HTTP requests as JSON: the relay sent `proxy_request` (`{requestId, method, url, headers, body}` with base64 body), and the peer replied `proxy_response` (`{requestId, statusCode, headers, body}` base64) or `proxy_error` (`{requestId, error}`). This flow is replaced by the streaming tunnel protocol in Step 5 for both HTTP and HTTPS. Do NOT build a new client on `proxy_request` - it is slower (buffered, base64), and current customer traffic flows over `tunnel_connect` + tunnel data. Existing legacy peers still work, but new implementations must speak the tunnel protocol (binary-v1 preferred) to earn normally. --- ## Changelog - **2026-07-02**: **PROTOCOL DOC CORRECTIONS (code-verified)** - Heartbeat direction fixed: the PEER sends `heartbeat` every 30s and the relay replies `heartbeat_ack` (the relay never pings; 120s of silence = zombie-sweep force-close). Connection pool documented correctly: up to 6 WebSockets per device, over-cap evicts the oldest with close 4008 (4003 is no longer emitted). Backpressure cap is 16MB sustained 10s. Probe cron is every 5 min (separate throughput sweep every 30 min); added the `throughputContract` customer-routable floor (default 500 KB/s) to the register response. `my-devices`/`my-preferences` routes clarified as ACCOUNT auth (X-API-Key or account JWT - not the registration JWT). - **2026-05-21**: **MULTI-REGION RELAY REDIRECT** - The platform runs regional relays (EU + US, more coming) and routes each peer to the nearest. Advertise `supportsRelayRedirect: true` in `device_info` and handle the `relay_redirect` message to let the server move you to the closest relay at runtime (zero re-install on new regions). Connect to `register.relay`, not a hardcoded host. Reference SDK v1.3.0 implements this. Old peers are never redirected (stay on their first relay). - **2026-04-23**: **BINARY TUNNEL PROTOCOL (v1)** - Peers can now advertise `protocol: "binary-v1"` in `device_info` to opt into binary WebSocket frames for tunnel_data. Eliminates base64 + JSON envelope overhead on hot path. Legacy JSON+base64 still supported unchanged. Expect ~30% throughput improvement on top of streaming. - **2026-04-23**: **STREAMING HTTP** - Replaced the buffered `proxy_request`/`proxy_response` path with streaming TCP tunnels for HTTP (same as HTTPS). Throughput up from ~0.3 Mbps to 2-11 Mbps per peer; TTFB down from 1.8s to 0.6s p50; 1+ MB downloads now reliable. - **2026-04-16**: **MARKETPLACE LISTING** - Devices can now be listed for sale in pool gateway. Automated verification system checks IP quality, ISP, VPN/proxy detection. Quality score 0-100. Admin or auto-approval workflow. Anti-fraud protections. - **2026-04-16**: **API KEY AUTO-LINK** - Registration now accepts optional `apiKey` field to auto-link devices to farmer accounts. Also accepts `X-API-Key` header. - **2026-04-16**: **HTTPS TUNNEL SUPPORT** - Documented tunnel_connect/tunnel_data/tunnel_closed message types for HTTPS proxying via TCP tunnels. - **2026-04-16**: **RELAY FIX** - Fixed dotenv load order bug that prevented internal gateway auth. Fixed `http_response` -> `proxy_response` message type (legacy `http_response` still accepted). - **2026-02-17**: **SECURITY HARDENING** - All SDK device endpoints now require JWT authentication (wallet, payout, earnings, device details). Free JWT endpoint removed (replaced with refresh token flow). Rate limiting added to device registration. Auth guards now check token revocation against DB in real-time. - **2026-02-05**: Added freestyle philosophy - agents encouraged to invent their own methods - **2026-02-05**: Peer Account Portal (peer-auth/peer-account endpoints) marked as coming soon - not yet deployed - **2026-02-04**: Browser API now includes auto-allocated mobile proxy (DE/GB/FR/ES/PL/US) - no need to provide your own - **2026-02-04**: Added x402 Mobile Proxy management endpoints (replace, topup, calculate) - **2026-02-02**: Security update - JWT reduced to 1 hour, refresh tokens added - **2026-02-02**: WebSocket auth moved from URL to headers (Sec-WebSocket-Protocol) - **2026-02-02**: Connection limits added (max 2 per device) - **2026-02-02**: Message rate limiting added (100/min) - **2026-02-02**: Wallet security - 7-day cooling period after changes - **2026-02-02**: IP classification now fully server-side (device-reported ignored) - **2026-02-02**: All IP types now earn - Datacenter IPs earn the base-tier rate - **2026-02-02**: Initial release with IP classification and fraud prevention