LayerZeroFault
ai agents-api Updated:

WalletConnect Relay WebSocket Stalls & Ping Timeouts in Headless AI Agents

VV

Written by

Updated:

Technical Guide

WalletConnect v2 relay WebSocket stalls and ping timeouts in headless AI agents occur because NodeJS environments lack native browser WebSocket heartbeat listeners, causing TCP connections through cloud NATs and load balancers to silently drop after 30 seconds of inactivity. When the client fails to respond to relay ping frames, the server terminates the socket without firing an error. Implementing an explicit 15-second heartbeat keepalive interval resolves the drop.

Before executing state changes or signing transactions, verify your cryptographic parameters using our interactive ERC-4337 Calldata Decoder utility.


Technical Forensics: Why Headless WebSockets Die Silently

The WalletConnect v2 protocol relies on a persistent WebSocket connection to the decentralized Relay network (wss://relay.walletconnect.org). Messages are encrypted point-to-point and routed via published topics.

The Idle Timeout Trap in Serverless and Container Runtimes

In consumer browsers, background tabs still receive periodic microtask execution. But in containerized environments (Docker / Kubernetes):

  1. Cloud NAT Gateways: AWS NAT Gateways and Docker bridge networks aggressively drop idle TCP connections after 350 seconds of inactivity.
  2. Missing TCP Keepalives: Standard Node.js ws clients without explicit keepAlive: true and application-level pings do not emit network packets when no transactions are actively occurring.
  3. Ghost Sockets: The Node.js socket object remains in state OPEN (readyState === 1), but the cloud proxy has already severed the connection. When the agent attempts to publish a transaction payload, the write buffer queues indefinitely until a timeout error surfaces 30 seconds later.

Solution 1: Active Heartbeat & Connection Health Monitor

To prevent NAT drops and detect zombie sockets within seconds, attach an explicit heartbeat monitor to the core.relayer instance:

// walletconnect-heartbeat.ts
import { Core } from '@walletconnect/core';
import type { ICore } from '@walletconnect/types';

export function attachRelayerHeartbeat(core: ICore, intervalMs: number = 20000) {
  let isAlive = true;

  // Listen to relay connection events
  core.relayer.on('relayer_connect', () => {
    console.log('[Relayer] WebSocket connected to relay.walletconnect.org');
    isAlive = true;
  });

  core.relayer.on('relayer_disconnect', () => {
    console.warn('[Relayer] Disconnected from relay network. Initializing retry...');
    isAlive = false;
  });

  // Background ping interval
  const pingTimer = setInterval(async () => {
    if (!core.relayer.connected) {
      console.log('[Heartbeat] Relayer disconnected. Forcing reconnect...');
      try {
        await core.relayer.restartTransport();
      } catch (err) {
        console.error('[Heartbeat] Failed to restart transport:', err);
      }
      return;
    }

    // Ping active relay server
    try {
      // Send lightweight heartbeat ping
      const timeoutPromise = new Promise((_, reject) =>
        setTimeout(() => reject(new Error('Heartbeat timeout')), 5000)
      );

      await Promise.race([
        core.relayer.provider.connection.send({
          id: Date.now(),
          jsonrpc: '2.0',
          method: 'waku_ping', // or internal relay ping
          params: {},
        }),
        timeoutPromise,
      ]);

      isAlive = true;
    } catch (error) {
      console.error('[Heartbeat] Ping failed — ghost socket detected. Reconnecting...');
      isAlive = false;
      await core.relayer.restartTransport();
    }
  }, intervalMs);

  return () => clearInterval(pingTimer);
}

Solution 2: Automated Session State Persistence in SQLite

When an agent restarts or recovers from a network drop, losing the in-memory pairing keys means the user must scan a fresh QR code.

Configure ElizaOS or your backend service to use a persistent key-value store rather than volatile memory:

// agent-walletconnect-init.ts
import { SignClient } from '@walletconnect/sign-client';
import { Core } from '@walletconnect/core';

export async function createPersistentAgentSignClient(storagePath: string) {
  const core = new Core({
    projectId: process.env.WALLETCONNECT_PROJECT_ID!,
    // Ensure custom storage adapter points to SQLite / leveldb in Docker volumes:
    storageOptions: {
      database: storagePath, // e.g., '/data/walletconnect-store.json'
    },
  });

  const signClient = await SignClient.init({
    core,
    metadata: {
      name: 'Autonomous Security Sentinel Agent',
      description: 'Headless Web3 Security and Settlement Daemon',
      url: 'https://layerzerofault.site',
      icons: ['https://layerzerofault.site/fallback-og.png'],
    },
  });

  // Handle automatic session expiration pruning
  signClient.on('session_expire', ({ topic }) => {
    console.log(`[Session] Expired topic ${topic} pruned from store`);
  });

  return signClient;
}

Diagnostic Verification via CLI

Verify that your agent server maintains an active TCP connection to WalletConnect relay servers without dropping packets:

# 1. Test WebSocket latency and TLS handshake
curl -i -N -H "Connection: Upgrade" \
     -H "Upgrade: websocket" \
     -H "Host: relay.walletconnect.org" \
     -H "Origin: https://layerzerofault.site" \
     https://relay.walletconnect.org

# 2. Inspect active socket states in Linux / Docker:
netstat -tnp | grep 443 | grep ESTABLISHED

By enforcing an application-level ping cycle, handling abnormal 1006 terminations, and ensuring session keys are backed by persistent storage volumes, autonomous AI agents maintain resilient, 24/7 connectivity to decentralized wallet networks.

[!TIP] Diagnostic Verification & Air-Gapped Testing: Before executing state changes or broad recovery steps, cross-reference failure parameters with our companion guide on WalletConnect Crypto.decode Silent Decryption Failure and verify transaction calldata or cryptographic envelopes using the Master Web3 & RPC Error Code Directory.

Interactive Diagnostic Sandbox

Local Agent API & Calldata Sandboxes

Inspect autonomous agent UserOperations, decode packed execution calldata, and test programmatic authentication payloads locally.

Companion Diagnostic Guide WalletConnect Crypto.decode Silent Decryption Failure →

Fix WalletConnect v2 silent decryption failures where crypto.decode returns undefined. Resolve corrupted session storage and re-initialize symmetric encryption keys.

Read Guide
Contextual Architecture

Related Diagnostic Guides

View All in Silo →

Related Inquiries

Why do WalletConnect pairing connections drop in backend NodeJS processes?

In headless environments, cloud NAT firewalls (such as AWS NAT Gateways or Docker bridge networks) silently drop idle TCP sockets if no traffic passes within their timeout window (often 30–60 seconds). Without browser-level keepalives, the socket becomes a dead half-open connection.

How do I configure an explicit WebSocket heartbeat in WalletConnect Core?

When initializing @walletconnect/core, configure custom relayUrl parameters or wrap the WebSocket provider with an active ping interval: setInterval(() => { if (socket.readyState === WebSocket.OPEN) socket.ping(); }, 15000).

Does pairing topic expiration cause silent connection loss?

Yes. WalletConnect pairing proposals expire after 5 minutes if not acknowledged. Autonomous AI agents must refresh pairing URIs or maintain persistent sessions with wc_sessionPing.

How can I debug UserOperation calldata sent over WalletConnect?

Inspect packed calls and UserOperation payloads offline using our client-side ERC-4337 Calldata Decoder tool.