Your First XRPL Connection | XRPL Development 101 | XRP Academy - XRP Academy
Skip to main content
intermediate45 min

Your First XRPL Connection

Learning Objectives

Install and configure the xrpl.js (JavaScript) and xrpl-py (Python) libraries in your development environment

Connect to XRPL networks (testnet, devnet, mainnet) and understand when to use each

Execute basic API calls to query server information and verify connectivity

Implement proper client lifecycle management including connection handling and graceful disconnection

Handle common connection errors and implement basic retry logic

The XRP Ledger processes thousands of transactions per second across a global network of validators. To interact with this network—whether you're checking a balance, sending a payment, or trading on the DEX—you need a reliable connection to an XRPL server.

This sounds trivial. It isn't.

Connection management is where many blockchain applications fail in production. They work fine in development, then fall apart when networks become congested, servers go down, or WebSocket connections drop unexpectedly. We're going to start with proper foundations so you don't learn these lessons the hard way with real money on the line.

What we're building today: A robust connection module that you'll use throughout this course and can adapt for production applications.


XRPL has official client libraries for multiple languages. We'll focus on the two most popular:

Library Language Best For Documentation
xrpl.js JavaScript/TypeScript Web apps, Node.js backends xrpl.org/docs/references/xrpljs
xrpl-py Python Scripts, data analysis, backend services xrpl-py.readthedocs.io

Both libraries provide equivalent functionality. Choose based on your team's expertise and project requirements. This course provides examples in both languages.

Other official libraries exist for Java, C++, and other languages, but JavaScript and Python cover 90%+ of XRPL development.

  • Node.js 18 or higher (check with `node --version`)
  • npm or yarn package manager

Installation:

# Create a new project directory
mkdir xrpl-dev-course
cd xrpl-dev-course

# Initialize npm project
npm init -y

# Install xrpl.js
npm install xrpl

# Optional but recommended: TypeScript support
npm install typescript @types/node ts-node --save-dev
npx tsc --init

Verify installation:

// test-install.js
const xrpl = require('xrpl');
console.log('xrpl.js version:', xrpl.Client ? 'Installed successfully' : 'Installation failed');
console.log('Ready to connect to XRPL!');

Run with: node test-install.js

  • Python 3.8 or higher (check with `python --version` or `python3 --version`)
  • pip package manager

Installation:

# Create and activate virtual environment (recommended)
python -m venv xrpl-env
source xrpl-env/bin/activate  # On Windows: xrpl-env\Scripts\activate

# Install xrpl-py
pip install xrpl-py

# Optional: Install additional tools
pip install python-dotenv  # For environment variables
pip install pytest         # For testing later

Verify installation:

# test_install.py
import xrpl
print(f"xrpl-py version: {xrpl.__version__}")
print("Ready to connect to XRPL!")

Run with: python test_install.py

Organize your code for maintainability from the start:

xrpl-dev-course/
├── src/
│   ├── connection/        # Connection management (this lesson)
│   ├── accounts/          # Account operations (Lesson 2)
│   ├── payments/          # Payment transactions (Lesson 3)
│   └── utils/             # Shared utilities
├── tests/                 # Test files
├── examples/              # Standalone examples
├── .env                   # Environment variables (never commit!)
├── .gitignore
├── package.json           # (JavaScript)
└── requirements.txt       # (Python)

Critical: Add .env to your .gitignore immediately. You'll store sensitive configuration there later.


XRPL runs multiple networks for different purposes:

Network Purpose XRP Value Use Case
Mainnet Production Real money Live applications
Testnet Testing Free (faucet) Development, integration testing
Devnet Unstable testing Free (faucet) Testing new features before testnet

Golden Rule: Never develop against mainnet. Use testnet for all learning and testing. Only connect to mainnet when deploying production code with real value at stake.

Each network has public servers you can connect to:

Testnet (Use this for learning):

WebSocket: wss://s.altnet.rippletest.net:51233
JSON-RPC:  https://s.altnet.rippletest.net:51234 
Faucet:    https://faucet.altnet.rippletest.net/accounts 
Explorer:  https://testnet.xrpl.org 

Devnet (For bleeding-edge features):

WebSocket: wss://s.devnet.rippletest.net:51233
JSON-RPC:  https://s.devnet.rippletest.net:51234 
Faucet:    https://faucet.devnet.rippletest.net/accounts 

Mainnet (Production only):

WebSocket: wss://xrplcluster.com (community)
WebSocket: wss://s1.ripple.com:51233 (Ripple)
WebSocket: wss://s2.ripple.com:51233 (Ripple backup)
Explorer:  https://livenet.xrpl.org 

For development, use the official testnet server. For production, consider:

  1. Public servers (free, shared, rate-limited)
  2. Commercial providers (paid, dedicated, higher limits)
  3. Your own node (full control, requires infrastructure)

Most applications start with public servers and migrate to dedicated infrastructure as they scale. We'll discuss this more in Lesson 19 (Production Deployment).


// src/connection/basic-connect.js
const xrpl = require('xrpl');

async function connectToXRPL() {
// Create client instance pointing to testnet
const client = new xrpl.Client('wss://s.altnet.rippletest.net:51233');

console.log('Connecting to XRPL Testnet...');

// Establish WebSocket connection
await client.connect();

console.log('Connected!');

// Query server information to verify connection
const serverInfo = await client.request({
command: 'server_info'
});

console.log('Server version:', serverInfo.result.info.build_version);
console.log('Ledger sequence:', serverInfo.result.info.validated_ledger.seq);
console.log('Server state:', serverInfo.result.info.server_state);

// Always disconnect when done
await client.disconnect();
console.log('Disconnected.');
}

// Run the function
connectToXRPL().catch(console.error);
```

Run it:

node src/connection/basic-connect.js

Expected output:

Connecting to XRPL Testnet...
Connected!
Server version: 2.2.0
Ledger sequence: 45678901
Server state: full
Disconnected.
# src/connection/basic_connect.py
import asyncio
from xrpl.asyncio.clients import AsyncWebsocketClient
from xrpl.models.requests import ServerInfo

async def connect_to_xrpl():
# Create client instance pointing to testnet
url = "wss://s.altnet.rippletest.net:51233"

print("Connecting to XRPL Testnet...")

Use async context manager for automatic connection handling

async with AsyncWebsocketClient(url) as client:
    print("Connected!")

Query server information to verify connection

    response = await client.request(ServerInfo())

info = response.result["info"]
print(f"Server version: {info['build_version']}")
print(f"Ledger sequence: {info['validated_ledger']['seq']}")
print(f"Server state: {info['server_state']}")

print("Disconnected.")

Run the async function

if name == "main":
asyncio.run(connect_to_xrpl())
```

Run it:

python src/connection/basic_connect.py

The server_info response contains valuable information:

{
  "result": {
    "info": {
      "build_version": "2.2.0",           // Server software version
      "complete_ledgers": "32570-45678901", // Available ledger history
      "hostid": "DEMO",                    // Server identifier
      "server_state": "full",              // Server operational state
      "validated_ledger": {
        "age": 3,                          // Seconds since last close
        "base_fee_xrp": 0.00001,          // Current base transaction fee
        "hash": "ABC123...",               // Ledger hash
        "reserve_base_xrp": 10,            // Account reserve requirement
        "reserve_inc_xrp": 2,              // Owner reserve per object
        "seq": 45678901                    // Current ledger sequence
      },
      "validation_quorum": 28,             // Validators needed for consensus
      "validator_list_expires": "2025-12-31" // UNL expiration
    },
    "status": "success"
  }
}
  • **server_state**: Should be "full" or "proposing" for a healthy server
  • **validated_ledger.seq**: Current ledger number (increases every 3-5 seconds)
  • **reserve_base_xrp**: Minimum XRP to activate an account (currently 10 XRP)
  • **base_fee_xrp**: Minimum transaction fee (currently 0.00001 XRP = 10 drops)

The basic examples above work for testing but will fail in production:

  1. Network interruptions: WiFi drops, server restarts, routing issues
  2. Server overload: Public servers can become congested
  3. Timeout issues: Long-running connections may be terminated
  4. No error recovery: Single failure crashes the application
// src/connection/robust-client.js
const xrpl = require('xrpl');

class XRPLConnection {
constructor(options = {}) {
this.servers = options.servers || [
'wss://s.altnet.rippletest.net:51233' // Testnet default
];
this.currentServerIndex = 0;
this.client = null;
this.isConnected = false;
this.reconnectAttempts = 0;
this.maxReconnectAttempts = options.maxReconnectAttempts || 5;
this.reconnectDelay = options.reconnectDelay || 1000; // ms
}

async connect() {
const server = this.servers[this.currentServerIndex];

console.log(Connecting to ${server}...);

this.client = new xrpl.Client(server);

// Set up event handlers before connecting
this.client.on('connected', () => {
console.log('WebSocket connected');
this.isConnected = true;
this.reconnectAttempts = 0;
});

this.client.on('disconnected', (code) => {
console.log(Disconnected with code: ${code});
this.isConnected = false;
this.handleDisconnect();
});

this.client.on('error', (error) => {
console.error('Connection error:', error.message);
});

try {
await this.client.connect();
return true;
} catch (error) {
console.error(Failed to connect to ${server}:, error.message);
return this.tryNextServer();
}
}

async tryNextServer() {
this.currentServerIndex = (this.currentServerIndex + 1) % this.servers.length;

if (this.reconnectAttempts >= this.maxReconnectAttempts) {
throw new Error(Failed to connect after ${this.maxReconnectAttempts} attempts);
}

this.reconnectAttempts++;
const delay = this.reconnectDelay * Math.pow(2, this.reconnectAttempts - 1);

console.log(Retrying in ${delay}ms (attempt ${this.reconnectAttempts})...);
await this.sleep(delay);

return this.connect();
}

async handleDisconnect() {
if (this.reconnectAttempts < this.maxReconnectAttempts) {
console.log('Attempting to reconnect...');
try {
await this.connect();
} catch (error) {
console.error('Reconnection failed:', error.message);
}
}
}

async request(requestObject) {
if (!this.isConnected) {
throw new Error('Not connected to XRPL');
}

try {
return await this.client.request(requestObject);
} catch (error) {
// Handle specific errors
if (error.message.includes('not connected')) {
await this.connect();
return await this.client.request(requestObject);
}
throw error;
}
}

async disconnect() {
if (this.client) {
this.maxReconnectAttempts = 0; // Prevent auto-reconnect
await this.client.disconnect();
this.client = null;
this.isConnected = false;
}
}

sleep(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}

getClient() {
return this.client;
}
}

// Example usage
async function main() {
const connection = new XRPLConnection({
servers: [
'wss://s.altnet.rippletest.net:51233',
'wss://s.devnet.rippletest.net:51233' // Fallback
],
maxReconnectAttempts: 3,
reconnectDelay: 1000
});

try {
await connection.connect();

const serverInfo = await connection.request({
command: 'server_info'
});

console.log('Successfully connected!');
console.log('Ledger:', serverInfo.result.info.validated_ledger.seq);

// Keep connection alive for demonstration
// In real apps, you'd do work here

await connection.disconnect();

} catch (error) {
console.error('Fatal error:', error.message);
process.exit(1);
}
}

module.exports = { XRPLConnection };

if (require.main === module) {
main();
}
```

# src/connection/robust_client.py
import asyncio
import logging
from typing import List, Optional, Any
from xrpl.asyncio.clients import AsyncWebsocketClient
from xrpl.models.requests import Request, ServerInfo

logging.basicConfig(level=logging.INFO)
logger = logging.getLogger(name)

class XRPLConnection:
def init(
self,
servers: Optional[List[str]] = None,
max_reconnect_attempts: int = 5,
reconnect_delay: float = 1.0
):
self.servers = servers or ["wss://s.altnet.rippletest.net:51233"]
self.current_server_index = 0
self.client: Optional[AsyncWebsocketClient] = None
self.is_connected = False
self.reconnect_attempts = 0
self.max_reconnect_attempts = max_reconnect_attempts
self.reconnect_delay = reconnect_delay

async def connect(self) -> bool:
server = self.servers[self.current_server_index]
logger.info(f"Connecting to {server}...")

try:
self.client = AsyncWebsocketClient(server)
await self.client.open()
self.is_connected = True
self.reconnect_attempts = 0
logger.info("Connected successfully!")
return True

except Exception as e:
logger.error(f"Failed to connect to {server}: {e}")
return await self._try_next_server()

async def _try_next_server(self) -> bool:
self.current_server_index = (self.current_server_index + 1) % len(self.servers)

if self.reconnect_attempts >= self.max_reconnect_attempts:
raise ConnectionError(
f"Failed to connect after {self.max_reconnect_attempts} attempts"
)

self.reconnect_attempts += 1
delay = self.reconnect_delay * (2 ** (self.reconnect_attempts - 1))

logger.info(f"Retrying in {delay}s (attempt {self.reconnect_attempts})...")
await asyncio.sleep(delay)

return await self.connect()

async def request(self, request_obj: Request) -> Any:
if not self.is_connected or self.client is None:
raise ConnectionError("Not connected to XRPL")

try:
return await self.client.request(request_obj)
except Exception as e:
if "not connected" in str(e).lower():
await self.connect()
return await self.client.request(request_obj)
raise

async def disconnect(self):
if self.client:
self.max_reconnect_attempts = 0 # Prevent auto-reconnect
await self.client.close()
self.client = None
self.is_connected = False
logger.info("Disconnected.")

async def aenter(self):
await self.connect()
return self

async def aexit(self, exc_type, exc_val, exc_tb):
await self.disconnect()

async def main():
connection = XRPLConnection(
servers=[
"wss://s.altnet.rippletest.net:51233",
"wss://s.devnet.rippletest.net:51233" # Fallback
],
max_reconnect_attempts=3,
reconnect_delay=1.0
)

try:
async with connection:
response = await connection.request(ServerInfo())
info = response.result["info"]

print(f"Successfully connected!")
print(f"Ledger: {info['validated_ledger']['seq']}")

except Exception as e:
logger.error(f"Fatal error: {e}")
raise

if name == "main":
asyncio.run(main())
```

Our production-ready client implements:

  1. Multiple server fallback: If one server fails, try another
  2. Exponential backoff: Wait longer between each retry (1s, 2s, 4s...)
  3. Connection state tracking: Know if you're connected before making requests
  4. Event handling: React to disconnections and errors
  5. Automatic reconnection: Recover from transient failures
  6. Clean shutdown: Graceful disconnection prevents resource leaks

For most applications, you want one persistent connection shared across your codebase:

// src/connection/singleton.js
const { XRPLConnection } = require('./robust-client');

let instance = null;

async function getConnection() {
if (!instance) {
instance = new XRPLConnection({
servers: ['wss://s.altnet.rippletest.net:51233']
});
await instance.connect();
}
return instance;
}

async function closeConnection() {
if (instance) {
await instance.disconnect();
instance = null;
}
}

module.exports = { getConnection, closeConnection };
```

Usage:

const { getConnection, closeConnection } = require('./connection/singleton');

async function checkBalance(address) {
const connection = await getConnection();
const response = await connection.request({
command: 'account_info',
account: address
});
return response.result.account_data.Balance;
}

// When shutting down your app:
process.on('SIGINT', async () => {
await closeConnection();
process.exit(0);
});
```

For high-throughput applications, multiple connections can increase performance:

// src/connection/pool.js
const { XRPLConnection } = require('./robust-client');

class ConnectionPool {
constructor(size = 3, servers) {
this.size = size;
this.servers = servers;
this.connections = [];
this.currentIndex = 0;
}

async initialize() {
for (let i = 0; i < this.size; i++) {
const conn = new XRPLConnection({ servers: this.servers });
await conn.connect();
this.connections.push(conn);
}
}

getConnection() {
// Round-robin distribution
const conn = this.connections[this.currentIndex];
this.currentIndex = (this.currentIndex + 1) % this.size;
return conn;
}

async closeAll() {
await Promise.all(this.connections.map(c => c.disconnect()));
this.connections = [];
}
}

module.exports = { ConnectionPool };
```

  • High request volume (hundreds of requests per second)
  • Need to isolate subscriptions from queries
  • WebSocket frame size limitations
  • Learning and development
  • Low-traffic applications
  • Simple scripts

Connecting to XRPL is straightforward with modern libraries, but production reliability requires defensive programming. The patterns in this lesson will save you from the most common failure modes. Don't skip error handling "because it's just a prototype"—prototypes have a way of becoming production code.


Assignment: Set up a complete XRPL development environment and verify connectivity with a test script.

Requirements:

  • Install Node.js 18+ OR Python 3.8+

  • Create project directory structure as shown in Section 1.4

  • Install xrpl.js OR xrpl-py (or both)

  • Create .gitignore with proper exclusions

  • Write a script that connects to testnet

  • Query and display server_info

  • Properly disconnect when done

  • Handle connection errors gracefully

  • Implement the XRPLConnection class (JavaScript or Python)

  • Support multiple fallback servers

  • Implement exponential backoff retry logic

  • Export for use in other modules

  • Run your connection script 5 times

  • Verify it succeeds consistently

  • Test error handling by using an invalid server URL

  • Document any issues encountered

  • Environment properly configured (25%)

  • Basic connection works correctly (25%)

  • Robust connection handles errors (30%)

  • Code is clean and documented (20%)

Time investment: 1-2 hours
Value: This connection module will be used in every subsequent lesson


1. Network Selection Question:

You're building a prototype payment application to demonstrate to your team. Which XRPL network should you use?

A) Mainnet, because you want to test with real transactions
B) Testnet, because it's functionally identical to mainnet but uses free test XRP
C) Devnet, because it has the newest features
D) Any network works equally well for prototyping

Correct Answer: B
Explanation: Testnet is the correct choice for development and prototyping. It mirrors mainnet functionality but uses free XRP from the faucet, so mistakes don't cost real money. Mainnet should only be used for production code with real value. Devnet is for testing bleeding-edge features that haven't reached testnet yet—it's less stable and not appropriate for general development.


2. Client Lifecycle Question:

What happens if you repeatedly create new XRPL client connections without calling disconnect()?

A) Nothing—the garbage collector handles cleanup automatically
B) Performance improves due to connection pooling
C) Resource exhaustion: leaked WebSocket connections consume memory and may hit server limits
D) The server will automatically close old connections

Correct Answer: C
Explanation: Each client connection consumes resources (memory, file descriptors, server-side tracking). Without explicit disconnection, these accumulate. While JavaScript/Python garbage collectors eventually clean up unreferenced objects, WebSocket connections may remain open. Servers may also rate-limit or ban clients that open excessive connections. Always call disconnect() when done.


3. Error Handling Question:

Your application loses its WebSocket connection to the XRPL server. What's the best recovery strategy?

A) Immediately try to reconnect to the same server
B) Crash the application and alert the user to restart manually
C) Try to reconnect with exponential backoff, optionally falling back to alternative servers
D) Continue operating without a connection; requests will queue automatically

Correct Answer: C
Explanation: Exponential backoff prevents hammering a potentially overloaded server while still attempting recovery. Falling back to alternative servers provides redundancy if the primary server is down. Immediate reconnection (A) can worsen server load issues. Crashing (B) provides poor user experience for transient failures. XRPL clients don't queue requests (D)—they'll fail without a connection.


4. server_info Analysis Question:

You query server_info and see "server_state": "tracking". What does this indicate?

A) The server is operating normally and ready for all requests
B) The server is synchronizing and may not have complete ledger data
C) The server is tracking your requests for rate limiting
D) The server is in maintenance mode

Correct Answer: B
Explanation: Server states indicate operational readiness. "tracking" means the server is catching up with the network and may not have validated ledger data. Healthy servers show "full" (fully synchronized, not proposing) or "proposing" (validator actively participating in consensus). "tracking" servers can serve requests but may have incomplete or slightly stale data. Wait for "full" state before relying on the server for critical operations.


5. Production Readiness Question:

Which of the following is NOT a recommended practice for production XRPL connections?

A) Implementing automatic reconnection with exponential backoff
B) Using multiple fallback server endpoints
C) Hardcoding testnet URLs in production configuration
D) Monitoring connection state and implementing health checks

Correct Answer: C
Explanation: Hardcoding testnet URLs in production would mean your application connects to a test network with worthless XRP instead of the real mainnet. This is a configuration error that would cause all transactions to be meaningless. Production applications should have clearly separated configurations for different environments, with safeguards against accidentally deploying with wrong network settings.


For Next Lesson:
Before Lesson 2, ensure your development environment is working and you can successfully run the robust connection module. Lesson 2 will build on this foundation to create and manage XRPL accounts programmatically.


End of Lesson 1

Total words: ~4,200
Estimated completion time: 45 minutes reading + 1-2 hours for deliverable

Key Takeaways

1

Use testnet for all development

: Real XRP should only touch mainnet production code. Testnet is functionally identical and free.

2

The client lifecycle matters

: Connect → Use → Disconnect. Leaked connections cause resource exhaustion and weird bugs.

3

Plan for failure

: Networks fail, servers go down, WebSockets disconnect. Your code should handle this gracefully, not crash.

4

server_info is your health check

: Before doing anything else, verify your connection works and understand current network state (fees, reserves, ledger sequence).

5

Start simple, add complexity as needed

: The basic connection works for learning. Add pooling and advanced error handling when you actually need it. ---