Client Libraries Deep Dive - xrpl.js, xrpl-py, and xrpl4j | XRPL APIs & Integration | XRP Academy - XRP Academy
3 free lessons remaining this month

Free preview access resets monthly

Upgrade for Unlimited
Skip to main content
intermediate55 min

Client Libraries Deep Dive - xrpl.js, xrpl-py, and xrpl4j

Learning Objectives

Compare the three official client libraries in terms of features, design patterns, and trade-offs

Use xrpl.js for common operations (connect, query, transaction submission)

Evaluate when to use library abstractions versus direct API calls

Navigate library documentation and source code for advanced usage

Assess community libraries and their appropriate use cases

In Lessons 2 and 3, you built connection managers and HTTP clients from scratch. That's valuable for understanding—but in production, you'll likely use a library. Here's why:

What Libraries Give You:

  • Write WebSocket connection management
  • Implement JSON-RPC request formatting
  • Build transaction construction from scratch
  • Handle binary encoding manually
  • Implement cryptographic signing
  • Parse and validate responses
  • Handle all error codes

WITH LIBRARY:
const client = new xrpl.Client(url)
await client.connect()
const response = await client.request({ command: 'server_info' })


Libraries encapsulate years of edge-case handling, protocol quirks, and community-discovered bugs. They're tested against multiple rippled versions and handle details you'd otherwise learn the hard way.

But Libraries Aren't Magic:

  • What happens during connect()?
  • How does the library handle reconnection?
  • What's the default timeout?
  • How are transactions actually encoded?

This lesson teaches you to use libraries effectively while understanding what they do under the hood.


Three official libraries are maintained by the XRPL Foundation:

Library Language Repository Maturity
xrpl.js JavaScript/TypeScript github.com/XRPLF/xrpl.js Most mature, largest community
xrpl-py Python github.com/XRPLF/xrpl-py Mature, well-documented
xrpl4j Java github.com/XRPLF/xrpl4j Mature, enterprise-focused
FEATURE                  xrpl.js      xrpl-py      xrpl4j
────────────────────────────────────────────────────────────
WebSocket Client         ✓            ✓            ✓
JSON-RPC Client          ✓            ✓            ✓
Transaction Building     ✓            ✓            ✓
Transaction Signing      ✓            ✓            ✓
Binary Codec             ✓            ✓            ✓
Address Validation       ✓            ✓            ✓
Key Generation           ✓            ✓            ✓
Browser Support          ✓            ✗            ✗
TypeScript Types         Native       Type hints   Native
Async/Await              ✓            ✓            CompletableFuture
Auto-Reconnect           Partial      Manual       Manual
Request Retries          Manual       Manual       Manual
Built-in Sugar Methods   ✓            Limited      Limited

xrpl.js: Developer Experience First

// High-level "sugar" methods
const balance = await client.getXrpBalance(address)
const balances = await client.getBalances(address)

// But also raw access
const response = await client.request({
  command: 'account_info',
  account: address
})

Philosophy: Make common things easy, complex things possible.

xrpl-py: Explicit and Typed

from xrpl.clients import JsonRpcClient
from xrpl.models.requests import AccountInfo

# Explicit request models
client = JsonRpcClient("https://s1.ripple.com:51234") 
request = AccountInfo(account=address)
response = client.request(request)

Philosophy: Explicit is better than implicit. Strong typing catches errors early.

xrpl4j: Enterprise Java Patterns

// Immutable objects, builder pattern
AccountInfoRequest request = AccountInfoRequest.builder()
    .account(Address.of("rXXX..."))
    .ledgerIndex(LedgerIndex.VALIDATED)
    .build();

AccountInfoResult result = client.send(request, AccountInfoResult.class);

Philosophy: Type safety and immutability for enterprise reliability.


npm install xrpl
# or
yarn add xrpl

Basic Setup:

const xrpl = require('xrpl')
// or ES modules
import xrpl from 'xrpl'
// or specific imports
import { Client, Wallet, xrpToDrops } from 'xrpl'
const xrpl = require('xrpl')

async function main() {
// Create client
const client = new xrpl.Client('wss://s1.ripple.com:51233')

// Register event handlers before connecting
client.on('connected', () => console.log('Connected'))
client.on('disconnected', (code) => console.log(Disconnected: ${code}))
client.on('error', (error) => console.error('Error:', error))

// Connect
await client.connect()

// Verify connection
console.log('Is connected:', client.isConnected())

// ... do work ...

// Clean disconnect
await client.disconnect()
}

main().catch(console.error)
```

Raw API Request:

// Low-level: exactly like raw WebSocket/JSON-RPC
const response = await client.request({
  command: 'account_info',
  account: 'rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9',
  ledger_index: 'validated'
})

console.log(response.result.account_data.Balance)

Sugar Methods (High-Level):

// xrpl.js provides convenience methods for common operations

// Get XRP balance (returns number in XRP, not drops)
const xrpBalance = await client.getXrpBalance(address)

// Get all balances (XRP + issued currencies)
const balances = await client.getBalances(address)

// Get account info with type-safe response
const info = await client.request({
  command: 'account_info',
  account: address
})
// Generate new wallet
const wallet = xrpl.Wallet.generate()
console.log('Address:', wallet.address)
console.log('Public Key:', wallet.publicKey)
console.log('Private Key:', wallet.privateKey)  // Keep secret!
console.log('Seed:', wallet.seed)  // Keep secret!

// Create wallet from seed
const walletFromSeed = xrpl.Wallet.fromSeed('sXXXXXXXXXX...')

// Create wallet from mnemonic (if using BIP-39)
// Note: xrpl.js doesn't natively support mnemonics; use additional libraries

// Derive wallet from entropy
const walletFromEntropy = xrpl.Wallet.fromEntropy(entropyBuffer)
```

// Create payment transaction
const payment = {
  TransactionType: 'Payment',
  Account: wallet.address,
  Destination: 'rDestination...',
  Amount: xrpl.xrpToDrops('25'),  // Convert 25 XRP to drops
  // Optional fields
  DestinationTag: 12345,
  Fee: '12',  // in drops
}

// Method 1: Auto-fill and submit
const result = await client.submitAndWait(payment, { wallet })
console.log('Result:', result.result.meta.TransactionResult)

// Method 2: More control
// Step 1: Auto-fill (adds Sequence, Fee, LastLedgerSequence)
const prepared = await client.autofill(payment)

// Step 2: Sign
const signed = wallet.sign(prepared)

// Step 3: Submit
const submitted = await client.submitAndWait(signed.tx_blob)
```

// Subscribe to ledger stream
await client.request({
  command: 'subscribe',
  streams: ['ledger']
})

// Handle ledger events
client.on('ledgerClosed', (ledger) => {
console.log(Ledger ${ledger.ledger_index} closed)
console.log(Transactions: ${ledger.txn_count})
})

// Subscribe to account transactions
await client.request({
command: 'subscribe',
accounts: ['rAccountToMonitor...']
})

client.on('transaction', (tx) => {
console.log('Transaction:', tx.transaction.hash)
})
```

// Amount conversions
const drops = xrpl.xrpToDrops('100')     // "100000000"
const xrp = xrpl.dropsToXrp('100000000') // "100"

// Time conversions
const rippleTime = xrpl.unixTimeToRippleTime(Date.now() / 1000)
const unixTime = xrpl.rippleTimeToUnixTime(rippleTime)

// Address validation
const isValid = xrpl.isValidAddress('rXXX...')
const isClassic = xrpl.isValidClassicAddress('rXXX...')
const isXAddress = xrpl.isValidXAddress('XXXX...')

// Hash computation
const txHash = xrpl.hashes.hashSignedTx(signedTxBlob)
```


pip install xrpl-py

Basic Setup:

from xrpl.clients import JsonRpcClient, WebsocketClient
from xrpl.models.requests import AccountInfo, ServerInfo
from xrpl.wallet import Wallet
from xrpl.transaction import submit_and_wait

JSON-RPC Client (Synchronous):

from xrpl.clients import JsonRpcClient

client = JsonRpcClient("https://s1.ripple.com:51234") 

# Make requests
from xrpl.models.requests import AccountInfo

request = AccountInfo(
    account="rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9",
    ledger_index="validated"
)
response = client.request(request)
print(response.result)

WebSocket Client (Synchronous):

from xrpl.clients import WebsocketClient

# Context manager handles connection
with WebsocketClient("wss://s1.ripple.com:51233") as client:
    response = client.request(AccountInfo(account="rXXX..."))
    print(response.result)

Async Clients:

import asyncio
from xrpl.asyncio.clients import AsyncJsonRpcClient, AsyncWebsocketClient

async def main():
    async with AsyncWebsocketClient("wss://s1.ripple.com:51233") as client:
        response = await client.request(AccountInfo(account="rXXX..."))
        print(response.result)

asyncio.run(main())

xrpl-py uses typed request models:

from xrpl.models.requests import (
    AccountInfo,
    AccountLines,
    AccountTx,
    Tx,
    ServerInfo,
    Submit,
    BookOffers
)

# Each request has typed parameters
request = AccountInfo(
    account="rXXX...",
    ledger_index="validated",
    strict=True  # Optional parameters
)

# IDE will autocomplete and type-check
from xrpl.wallet import Wallet, generate_faucet_wallet
from xrpl.core.keypairs import derive_keypair, derive_classic_address

Generate new wallet

wallet = Wallet.create()
print(f"Address: {wallet.classic_address}")
print(f"Seed: {wallet.seed}")

From seed

wallet = Wallet.from_seed("sXXXXXX...")

Generate and fund on testnet

wallet = generate_faucet_wallet(client) # Only works on testnet
```

from xrpl.models.transactions import Payment
from xrpl.utils import xrp_to_drops
from xrpl.transaction import submit_and_wait, autofill_and_sign

Build transaction

payment = Payment(
account=wallet.classic_address,
destination="rDestination...",
amount=xrp_to_drops(25),
destination_tag=12345
)

Method 1: All-in-one

response = submit_and_wait(payment, client, wallet)
print(f"Result: {response.result['meta']['TransactionResult']}")

Method 2: Step by step

from xrpl.transaction import autofill, sign, submit_and_wait

Autofill adds Sequence, Fee, LastLedgerSequence

filled = autofill(payment, client)

Sign

signed = sign(filled, wallet)

Submit and wait

response = submit_and_wait(signed, client)
```

from xrpl.utils import (
    xrp_to_drops,
    drops_to_xrp,
    ripple_time_to_datetime,
    datetime_to_ripple_time
)
from xrpl.core.addresscodec import (
    is_valid_classic_address,
    is_valid_xaddress,
    classic_address_to_xaddress,
    xaddress_to_classic_address
)

Conversions

drops = xrp_to_drops(100) # "100000000"
xrp = drops_to_xrp("100000000") # Decimal('100')

Address validation

is_valid = is_valid_classic_address("rXXX...")
```


<dependency>
    <groupId>org.xrpl</groupId>
    <artifactId>xrpl4j-core</artifactId>
    <version>3.x.x</version>
</dependency>
<dependency>
    <groupId>org.xrpl</groupId>
    <artifactId>xrpl4j-client</artifactId>
    <version>3.x.x</version>
</dependency>
import org.xrpl.xrpl4j.client.JsonRpcClientErrorException;
import org.xrpl.xrpl4j.client.XrplClient;
import org.xrpl.xrpl4j.model.client.common.LedgerIndex;
import org.xrpl.xrpl4j.model.client.accounts.AccountInfoRequest;
import org.xrpl.xrpl4j.model.client.accounts.AccountInfoResult;
import org.xrpl.xrpl4j.model.transactions.Address;

import okhttp3.HttpUrl;

public class XrplExample {
public static void main(String[] args) throws JsonRpcClientErrorException {
// Create client
XrplClient client = new XrplClient(
HttpUrl.parse("https://s1.ripple.com:51234" target="_blank" rel="noopener noreferrer" class="text-cyan-400 hover:text-cyan-300 underline hover:no-underline transition-colors inline-flex items-center gap-1">https://s1.ripple.com:51234">https://s1.ripple.com:51234 ")
);

// Make request
AccountInfoRequest request = AccountInfoRequest.builder()
.account(Address.of("rN7n3473SaZBCG4dFL83w7a1RXtXtbk2D9"))
.ledgerIndex(LedgerIndex.VALIDATED)
.build();

AccountInfoResult result = client.accountInfo(request);

System.out.println("Balance: " + result.accountData().balance());
}
}
```

xrpl4j uses immutable objects with builders:

// All model objects are immutable
Payment payment = Payment.builder()
    .account(Address.of("rSender..."))
    .destination(Address.of("rDestination..."))
    .amount(XrpCurrencyAmount.ofDrops(25000000))  // 25 XRP in drops
    .destinationTag(UnsignedInteger.valueOf(12345))
    .fee(XrpCurrencyAmount.ofDrops(12))
    .sequence(UnsignedInteger.valueOf(1))
    .build();

// Objects cannot be modified after creation
// This prevents accidental mutation bugs
import org.xrpl.xrpl4j.crypto.keys.KeyPair;
import org.xrpl.xrpl4j.crypto.keys.Seed;
import org.xrpl.xrpl4j.crypto.signing.SingleSignedTransaction;
import org.xrpl.xrpl4j.crypto.signing.SignatureService;
import org.xrpl.xrpl4j.crypto.signing.bc.BcSignatureService;

// Generate new seed
Seed seed = Seed.ed25519Seed();

// Derive key pair
KeyPair keyPair = seed.deriveKeyPair();
Address address = keyPair.publicKey().deriveAddress();

// Sign transaction
SignatureService signatureService = new BcSignatureService();
SingleSignedTransaction signed = signatureService.sign(
keyPair.privateKey(),
payment
);
```

// Compiler catches type errors
Address address = Address.of("rXXX...");  // Validated at creation

// Won't compile: can't use String where Address expected
// AccountInfoRequest.builder().account("rXXX...").build(); // Error!

// Currency amounts are typed
XrpCurrencyAmount xrp = XrpCurrencyAmount.ofDrops(1000000);
IssuedCurrencyAmount usd = IssuedCurrencyAmount.builder()
.currency("USD")
.issuer(Address.of("rIssuer..."))
.value("100.00")
.build();
```


✓ Standard operations (query accounts, submit simple payments)
✓ Transaction building (complex object construction)
✓ Cryptographic operations (signing, key derivation)
✓ Binary encoding (transaction serialization)
✓ Type validation (address formats, amount conversions)
✓ You want IDE autocomplete and type checking
✓ Library doesn't support a specific method
✓ You need maximum control over request format
✓ Library abstraction doesn't fit your use case
✓ Performance critical (avoid object creation overhead)
✓ Debugging protocol-level issues
✓ Library has a bug you need to work around

Use Library: Transaction Building

// Library handles all the details
const payment = {
  TransactionType: 'Payment',
  Account: wallet.address,
  Destination: 'rDest...',
  Amount: xrpl.xrpToDrops('25')
}
const prepared = await client.autofill(payment)
const signed = wallet.sign(prepared)

vs raw:

// Manual transaction construction
const tx = {
  TransactionType: 'Payment',
  Account: wallet.address,
  Destination: 'rDest...',
  Amount: '25000000',  // Must know drops
  Fee: '12',  // Must calculate or lookup
  Sequence: ???,  // Must query
  LastLedgerSequence: ???,  // Must calculate
  SigningPubKey: wallet.publicKey,
  // Then serialize to binary, sign, re-serialize with signature
  // This is complex and error-prone
}

Use Raw API: Custom Query

// Raw API for queries library doesn't wrap
const response = await client.request({
  command: 'book_offers',
  taker_gets: { currency: 'XRP' },
  taker_pays: { currency: 'USD', issuer: 'rIssuer...' },
  limit: 100
})
COMMON LIMITATIONS:

1. Reconnection: Most libraries don't auto-reconnect well

1. Subscriptions: Basic support, no gap handling

1. Error Handling: Generic errors, limited context

1. Performance: Object creation overhead

1. New Features: Lag behind rippled releases

---

When documentation falls short, read the source:

// xrpl.js: See how autofill works
// https://github.com/XRPLF/xrpl.js/blob/main/packages/xrpl/src/sugar/autofill.ts 

// Key insight: autofill queries account_info for Sequence,
// fee for Fee, and calculates LastLedgerSequence
# xrpl-py: Understanding transaction submission
# https://github.com/XRPLF/xrpl-py/blob/main/xrpl/transaction/main.py 

# See exactly how submit_and_wait handles responses
GETTING HELP:

- Active community
- Library maintainers participate
- #xrpl-js, #xrpl-py channels

- Search existing issues first
- Detailed bug reports help

- Tag: [xrp-ledger], [xrpl], [ripple]
- Older answers may be outdated

- Funding for tools and libraries
- Community projects to learn from

---

Beyond official libraries, community libraries serve specific needs:

CRITERIA FOR EVALUATION:

1. Maintenance Status

1. Test Coverage

1. Documentation

1. Community Adoption

1. Security
NOTE: Community libraries change frequently.
Always verify current status before adopting.
  • Ripple API wrappers for other languages (Go, Rust, Ruby)
  • Specialized tools (QR generators, address validators)
  • Framework integrations (React hooks, Django)
  • Testnet utilities

Check GitHub: github.com/topics/xrpl
```


Official libraries are well-maintained: Regular updates, security patches, community support

Type safety prevents bugs: xrpl4j and TypeScript catch errors at compile time

Transaction building is complex: Libraries handle binary encoding, signing—don't reinvent this

Libraries improve productivity: Common tasks are much simpler with good abstractions

⚠️ Library feature parity: Not all libraries support all XRPL features equally

⚠️ Performance overhead: Object creation and validation add overhead (usually negligible)

⚠️ Breaking changes: Library updates may require code changes

⚠️ Community library longevity: Projects may become unmaintained

🔴 Assuming library handles everything: Reconnection, subscriptions, error handling often need custom code

🔴 Using outdated library versions: Security vulnerabilities, missing features

🔴 Trusting community libraries blindly: Review code, especially for key handling

🔴 Over-abstracting: Sometimes raw API is clearer and more maintainable

Official libraries are excellent for common operations—transaction building, signing, and basic queries. They're not complete solutions; you'll still need custom code for connection management, production error handling, and advanced use cases. Know what your library does well, know its limitations, and don't hesitate to drop to raw API when needed.


Assignment: Create a decision framework for choosing XRPL client libraries.

Requirements:

Part 1: Feature Matrix (30%)

  • Supported transaction types
  • Client capabilities (WebSocket, JSON-RPC, both)
  • Utility functions available
  • Type safety features
  • Testing utilities
  • Documentation quality

Rate each category 1-5 with brief justification.

Part 2: Use Case Recommendations (40%)

For each scenario, recommend a library and explain why:

  1. E-commerce payment integration (Node.js backend)
  2. Data analysis scripts (one-off queries)
  3. Enterprise payment processing (high reliability requirements)
  4. Browser-based wallet (client-side JavaScript)
  5. Serverless functions (AWS Lambda)
  6. Trading bot (high-frequency operations)
  7. Mobile app backend (REST API serving mobile clients)
  8. Blockchain analytics platform (processing millions of transactions)

Part 3: When to Go Raw (30%)

  • Specific examples

  • Code snippets comparing library vs raw approach

  • Reasoning for each case

  • Accuracy of feature comparison: 30%

  • Quality of use case analysis: 35%

  • Depth of raw API discussion: 25%

  • Clarity and organization: 10%

Time Investment: 2-3 hours

Submission: Document (Markdown or PDF) with tables and code examples

Value: This guide becomes your team's reference for starting new XRPL projects. Share it.


1. Library Selection (Tests Application):

You're building a serverless function on AWS Lambda that checks XRP balances. Which library setup is most appropriate?

A) xrpl.js with WebSocket client for real-time updates
B) xrpl-py with JsonRpcClient for stateless requests
C) xrpl4j with persistent connections for performance
D) No library—raw HTTP is simpler for Lambda

Correct Answer: B

Explanation: Lambda functions are stateless and short-lived, making JSON-RPC ideal (no connection state to maintain). xrpl-py's JsonRpcClient fits this model perfectly. Option A's WebSocket is wrong for serverless. Option C's persistent connections contradict Lambda's stateless model. Option D ignores that libraries handle useful things like response parsing and error handling—using a library is still valuable.


2. Library Capabilities (Tests Knowledge):

What do all three official XRPL libraries (xrpl.js, xrpl-py, xrpl4j) provide?

A) Automatic WebSocket reconnection with subscription restoration
B) Transaction building, signing, and binary encoding
C) Built-in caching for repeated queries
D) Native support for hardware wallet signing

Correct Answer: B

Explanation: All three libraries handle transaction building (constructing proper transaction objects), signing (cryptographic operations), and binary encoding (serialization for submission). None provide robust automatic reconnection with subscription restoration (A)—this requires custom code. None include built-in caching (C). Hardware wallet support (D) requires additional integrations.


3. Design Philosophy (Tests Understanding):

xrpl4j uses immutable objects with builders. What problem does this design solve?

A) It makes the code run faster
B) It prevents accidental object mutation that could cause bugs
C) It's required by the XRPL protocol
D) It reduces memory usage

Correct Answer: B

Explanation: Immutable objects can't be modified after creation, preventing bugs where code accidentally changes a transaction after it's been signed or shared. In enterprise Java, this pattern catches errors at compile time and makes concurrent code safer. Option A is false—immutability can actually be slightly slower. Option C is wrong—it's a library design choice, not protocol requirement. Option D is false—immutable objects may use slightly more memory.


4. Raw API Use Case (Tests Critical Thinking):

When would using raw API calls be preferable to library methods?

A) Always—libraries add unnecessary overhead
B) When the library doesn't support a specific XRPL feature you need
C) Never—libraries are always better
D) Only for security-critical operations

Correct Answer: B

Explanation: Libraries may lag behind rippled features or not support certain commands. When you need functionality the library doesn't provide, raw API calls are necessary. Option A is wrong—libraries provide significant value for common operations. Option C is wrong—there are legitimate cases for raw API. Option D is backwards—security-critical operations (like signing) are where you most want library support.


5. Library Limitations (Tests Comprehension):

Which limitation is common across all official XRPL libraries?

A) They can't connect to public servers
B) They don't support transaction signing
C) They require manual reconnection logic for production reliability
D) They only support testnet

Correct Answer: C

Explanation: While libraries handle basic connection, robust production reconnection with exponential backoff, jitter, subscription restoration, and gap detection typically requires custom code. All libraries support public servers (A is wrong), all support signing (B is wrong), and all support mainnet (D is wrong).


For Next Lesson:
Lesson 5 covers the core API methods for querying account, ledger, and server information—the building blocks of every XRPL integration. We'll go deep on response structures, pagination, and the nuances that matter.


End of Lesson 4

Total words: ~4,600
Estimated completion time: 55 minutes reading + 2-3 hours for deliverable


  1. Equips students to make informed library choices
  2. Demonstrates practical usage of all three official libraries
  3. Sets realistic expectations about library limitations
  4. Teaches self-sufficiency through documentation and source reading
  • "Which library should I use?" → Depends on your language and use case
  • "Why doesn't the library do X?" → Libraries can't solve everything; know the limits
  • "Should I always use a library?" → Usually yes for transactions, sometimes no for queries

Teaching Strategy:
This lesson is heavier on explanation than previous lessons. Students need context to make good tool choices. The deliverable forces them to synthesize this understanding into a reusable resource.

Lesson 5 Setup:
Now students can connect and use libraries. Lesson 5 teaches what to do with that connection—the core query methods that every integration needs.

Key Takeaways

1

Three official libraries serve different ecosystems:

xrpl.js for JavaScript (most mature), xrpl-py for Python (well-typed), xrpl4j for Java (enterprise patterns). Choose based on your platform.

2

Libraries excel at transaction handling:

Building, signing, and submitting transactions is complex. Use libraries for this—don't reinvent cryptographic wheels.

3

Libraries have gaps:

Reconnection, advanced subscriptions, and new features often need custom code. Plan for this.

4

Read the source when stuck:

Documentation can't cover everything. Library source code reveals exactly how things work.

5

Evaluate community libraries carefully:

Check maintenance status, security practices, and community adoption before depending on third-party code. ---