LayerZeroFault
ai agents-api

Fix: WalletConnect Relay WebSocket Stalls & Ping Timeouts in Headless AI Agents

VV

Written by

Fact-Checked on September 11, 2026

Verified Expert

Fix: WalletConnect Relay WebSocket Stalls & Ping Timeouts in Headless AI Agents

Autonomous Web3 agents (built on frameworks like ElizaOS, Biconomy AI, or custom LangChain trading loops) frequently interact with wallets and dApps via WalletConnect v2. Unlike human users clicking wallet connect buttons in a browser, autonomous agents run continuously as headless Node.js processes on cloud servers (AWS EC2, Railway, Docker).

However, engineers operating autonomous agent swarms frequently notice that after 15 to 45 minutes of uptime, the agent stops responding to wallet signatures and RPC requests:

[WalletConnect] Connection to wss://relay.walletconnect.org/ stalled...
[RelayClient] WebSocket error: code 1006 (Abnormal Closure)
[AgentRuntime] Error: Pairing ping timeout after 30000ms. No response from remote peer.
[AgentRuntime] Fatal: Session topic 7b9a1c... dropped from relay registry.

If your agent is also experiencing silent decryption failures when reading RPC responses, see our companion diagnostic guide on Fixing WalletConnect Crypto.decode Silent Decryption Failures.


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.

Placeholder: Sequence Diagram of WalletConnect WebSocket Keepalive Heartbeat vs Cloud NAT Timeout Drop

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;
}

Placeholder: State Machine Architecture Showing Automatic Transport Reconnection and Session Storage Sync


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.

Partner Spotlight: Gate.io

Trade Securely on Gate.io

Don't risk your assets on centralized silos or unverified endpoints. Trade securely on Gate.io with deep liquidity and institutional-grade security protocols.

Claim $100 Sign-up Bonus

Official Partner Referral Link

Related Inquiries

Why do WalletConnect relay connections stall in headless Node.js environments?

In browser environments, window focus events and service workers assist in keeping WebSocket connections alive. In headless Node.js daemons (such as ElizaOS agents or server-side bots), background event loops without active HTTP traffic can experience NAT timeout drops or unacknowledged WebSocket ping/pong frames, causing the relay socket to silently hang without triggering close events.

What causes WebSocket error code 1006 on relay.walletconnect.org?

Error 1006 (Abnormal Closure) is emitted when the underlying TCP connection terminates abruptly without a standard WebSocket close handshake. This is typically triggered by cloud load balancer timeouts, intermediate proxy idle-connection drops after 60 seconds, or expired session keys.

How can autonomous agents maintain persistent WalletConnect connections?

Implement an aggressive application-level heartbeat ping every 15–20 seconds, configure automatic reconnection with exponential backoff on the RelayClient, and persist active session topics in Redis or SQLite to restore state on reboot.