API Deep Dive: Check Operations
Complete API reference with practical examples
Learning Objectives
Implement all check-related API methods with proper parameter validation and error handling
Design efficient query patterns for check discovery and state monitoring across large datasets
Build real-time subscription systems for check state changes using WebSocket streams
Optimize API usage patterns for production scale with rate limiting and connection management
Troubleshoot complex check operation failures using comprehensive error analysis frameworks
This lesson provides comprehensive coverage of every API method, parameter, and pattern needed to build production-grade check applications on the XRP Ledger. You'll master the technical implementation details that separate proof-of-concept code from enterprise-ready systems.
- **Implement** all check-related API methods with proper parameter validation and error handling
- **Design** efficient query patterns for check discovery and state monitoring across large datasets
- **Build** real-time subscription systems for check state changes using WebSocket streams
- **Optimize** API usage patterns for production scale with rate limiting and connection management
- **Troubleshoot** complex check operation failures using comprehensive error analysis frameworks
This lesson bridges the gap between understanding check concepts and implementing production systems. While Lesson 2 covered the conceptual lifecycle and Lesson 3 addressed security patterns, this lesson focuses exclusively on the technical API implementation details that determine whether your check integration succeeds or fails in production.
The content assumes you understand basic XRPL API patterns from the XRPL APIs & Integration course, but provides complete parameter references and examples specific to checks. Every code example is production-ready, not simplified demonstration code.
Your Approach Should Be
Reference-first
Bookmark the parameter tables -- you'll return to them constantly during development
Error-driven
Pay special attention to error handling patterns, as check operations have unique failure modes
Scale-conscious
Consider the performance implications of each pattern from the start, not as an afterthought
Monitoring-enabled
Build observability into your check operations from day one
Essential Check API Concepts
| Concept | Definition | Why It Matters | Related Concepts |
|---|---|---|---|
| **Check Object ID** | 256-bit hash uniquely identifying a check on the ledger | Required for all check operations after creation; immutable once assigned | Transaction hash, ledger sequence, object indexing |
| **Destination Tag Binding** | Optional 32-bit integer that restricts check cashing to specific recipient subaccounts | Enables precise routing in multi-user systems without additional validation logic | Payment channels, escrow conditions, memo fields |
| **Expiration Enforcement** | Server-side validation that prevents operations on expired checks | Eliminates race conditions between client-side expiration checks and actual operations | Time-based conditions, ledger time, consensus timing |
| **Send Max Validation** | Real-time verification that sender has sufficient balance for the maximum check amount | Prevents check creation that can never be cashed, improving user experience | Account reserves, trust line limits, DEX liquidity |
| **Partial Cashing Logic** | API parameters that enable cashing checks for less than their full amount | Critical for liquidity management and payment splitting scenarios | Delivered amount, currency precision, remainder handling |
| **Check State Transitions** | Deterministic progression from created → cashed/cancelled with no reversibility | Understanding state transitions prevents impossible operations and API errors | Transaction finality, ledger consensus, state machines |
| **Subscription Filtering** | WebSocket stream parameters that deliver only relevant check events to your application | Essential for scalable real-time systems that monitor thousands of checks | Event streams, bandwidth optimization, client-side filtering |
The CheckCreate transaction type enables programmatic creation of payment authorization instruments with sophisticated parameter controls. Unlike simple payments, check creation requires careful parameter validation and balance verification to ensure the resulting check can actually be cashed.
Core Parameters and Validation
The CheckCreate transaction accepts seven primary parameters, each serving specific business logic requirements:
- **Account** (required): The check sender's XRPL address, which must have sufficient balance to cover both the check amount and the 10-drop transaction fee
- **Destination** (required): The intended check recipient's address. This can be any valid XRPL address, including addresses that don't yet exist on the ledger
- **SendMax** (required): The maximum amount the sender authorizes for this check. Uses standard XRPL currency format supporting both XRP and issued currencies
- **DestinationTag** (optional): A 32-bit unsigned integer that creates additional recipient specificity
- **Expiration** (optional): A 32-bit unsigned integer representing the Ripple timestamp after which the check becomes uncashable
- **InvoiceID** (optional): A 256-bit arbitrary value for external system correlation
// XRP check for 100 XRP maximum
const xrpCheck = {
TransactionType: "CheckCreate",
Account: "rSenderAddress...",
Destination: "rRecipientAddress...",
SendMax: "100000000" // 100 XRP in drops
};
// USD check for $500 maximum
const usdCheck = {
TransactionType: "CheckCreate",
Account: "rSenderAddress...",
Destination: "rRecipientAddress...",
SendMax: {
currency: "USD",
value: "500",
issuer: "rGatewayAddress..."
}
};Advanced Parameter Patterns
Production check systems often require sophisticated parameter combinations that address specific business requirements:
const payrollCheck = {
TransactionType: "CheckCreate",
Account: "rCompanyTreasury...",
Destination: "rEmployeeAddress...",
SendMax: "5000000000", // 5000 XRP salary
DestinationTag: 1001, // Payroll system identifier
InvoiceID: "PAY-2024-03-15-EMP-12345"
};
const promotionalCheck = {
TransactionType: "CheckCreate",
Account: "rMerchantAddress...",
Destination: "rCustomerAddress...",
SendMax: {
currency: "USD",
value: "50",
issuer: "rStablecoinIssuer..."
},
Expiration: Math.floor(Date.now() / 1000) + (7 * 24 * 3600), // 7 days
InvoiceID: "PROMO-SPRING-2024-50USD"
};The XRPL API performs real-time balance verification during CheckCreate submission to prevent creation of uncashable checks. This validation occurs at transaction processing time, not during initial API call validation.
For XRP checks, the system verifies the sender's XRP balance exceeds the SendMax amount plus applicable reserves and fees. The reserve calculation includes the base reserve (10 XRP) plus owner reserve (2 XRP per owned object) plus the additional reserve for the check object itself.
Error Handling Patterns
CheckCreate operations can fail with specific error codes that require different handling strategies: **tecUNFUNDED_PAYMENT** (insufficient balance), **tecNO_LINE** (missing trust line), **tecNO_AUTH** (lacks authorization), **tefPAST_SEQ** (outdated sequence), **telINSUF_FEE_P** (insufficient fee).
Deep Insight: Production Balance Verification Many production check systems implement dual-layer balance verification: client-side pre-validation before transaction submission and server-side handling of validation failures. The client-side check prevents unnecessary transaction fees for obviously invalid operations, while server-side handling addresses race conditions where balances change between validation and submission. This pattern reduces user frustration and transaction costs while maintaining system reliability.
The CheckCash transaction type converts authorized payment instruments into actual value transfers with sophisticated amount control and validation logic. Unlike CheckCreate, which primarily validates authorization and balance sufficiency, CheckCash performs complex currency conversion, partial payment logic, and finality enforcement.
Amount Specification Strategies
CheckCash transactions support three distinct amount specification patterns, each optimized for different use cases:
- **Full amount cashing** omits the Amount parameter, instructing the ledger to transfer the complete SendMax value
- **Exact amount cashing** specifies a precise Amount parameter that must be less than or equal to the check's SendMax value
- **Maximum available cashing** combines Amount specification with DeliverMin parameters to handle currency conversion scenarios
const fullCashTransaction = {
TransactionType: "CheckCash",
Account: "rRecipientAddress...",
CheckID: "checkObjectID...",
// Amount omitted = cash full check value
};
const partialCashTransaction = {
TransactionType: "CheckCash",
Account: "rRecipientAddress...",
CheckID: "checkObjectID...",
Amount: "25000000" // Cash exactly 25 XRP from larger check
};Currency Conversion and Cross-Currency Checks
CheckCash operations support automatic currency conversion when the check recipient desires a different currency than the check's SendMax specification. This functionality leverages the XRPL's built-in decentralized exchange to provide seamless multi-currency check processing.
const conversionCashTransaction = {
TransactionType: "CheckCash",
Account: "rRecipientAddress...",
CheckID: "usdCheckID...", // Check created for USD
Amount: {
currency: "EUR",
value: "450",
issuer: "rEuroIssuer..."
},
DeliverMin: {
currency: "EUR",
value: "440",
issuer: "rEuroIssuer..."
}
};When checks include DestinationTag parameters, CheckCash transactions must include matching DestinationTag values for successful processing. This validation occurs at the protocol level and cannot be bypassed through API manipulation.
Expiration Enforcement and Timing
The XRPL enforces check expiration at transaction processing time using the ledger's consensus timestamp. Network latency, transaction queue delays, and ledger processing time can cause transactions submitted before expiration to fail if they process after expiration.
// Safe expiration checking pattern
const currentLedger = await api.getLedger();
const checkExpiration = checkObject.Expiration;
const safetyMargin = 30; // 30 seconds
if (currentLedger.ledger.close_time + safetyMargin >= checkExpiration) {
throw new Error("Check expires too soon for safe cashing");
}Error Handling and Recovery Patterns
CheckCash operations encounter unique error conditions: **tecNO_PERMISSION** (destination tag mismatch), **tecEXPIRED** (check expired), **tecINSUFFICIENT_RESERVE** (recipient lacks reserve), **tecPATH_DRY** (insufficient liquidity), **tecUNFUNDED** (sender lacks balance).
Investment Implication: Currency Conversion Risk Cross-currency check cashing introduces exchange rate risk between check creation and cashing times. For business applications, this risk can be substantial -- a USD check cashed for EUR might deliver significantly different value depending on market movements. Applications should implement rate monitoring, conversion limits, and clear user communication about exchange rate exposure when implementing cross-currency check functionality.
Effective check management requires sophisticated query capabilities that enable applications to discover, monitor, and analyze check states across large datasets. The XRPL provides multiple query methods optimized for different access patterns and performance requirements.
Account-Based Check Discovery
The account_objects method provides comprehensive check discovery for specific accounts, returning all check objects where the specified account serves as either sender or recipient. This method supports pagination and filtering for large-scale applications.
const accountChecks = await api.request({
command: "account_objects",
account: "rAccountAddress...",
type: "check",
limit: 200,
marker: previousResponseMarker // For pagination
});- **CheckID**: The unique identifier for ledger operations
- **Account**: The check sender's address
- **Destination**: The intended recipient's address
- **SendMax**: The maximum authorized amount
- **Sequence**: The sender's account sequence when the check was created
- **DestinationTag**: Optional routing identifier
- **Expiration**: Optional expiration timestamp
- **InvoiceID**: Optional external correlation identifier
Advanced Filtering and Search Patterns
Production applications often require more sophisticated search capabilities than basic account enumeration. The XRPL API supports several advanced filtering patterns:
// Currency-specific filtering
const usdChecks = accountChecks.account_objects.filter(check => {
if (typeof check.SendMax === 'string') return false; // XRP check
return check.SendMax.currency === 'USD' &&
check.SendMax.issuer === 'rUSDIssuer...';
});
// Expiration-based filtering
const currentTime = Math.floor(Date.now() / 1000);
const expiringChecks = accountChecks.account_objects.filter(check => {
return check.Expiration &&
check.Expiration < currentTime + (24 * 3600); // Within 24 hours
});
// Amount-range filtering
const largeChecks = accountChecks.account_objects.filter(check => {
const amount = typeof check.SendMax === 'string' ?
parseInt(check.SendMax) : parseFloat(check.SendMax.value);
return amount >= 1000000; // 1M+ drops or currency units
});For applications with known CheckID values, the ledger_entry method provides direct access to specific check objects without account enumeration overhead. This method returns complete check metadata with additional ledger context including LedgerEntryType, Flags, LedgerIndex, and transaction history.
Historical Transaction Analysis
Understanding check lifecycle requires access to historical transaction data. The account_tx method enables comprehensive transaction history analysis with check-specific filtering for audit trails, performance metrics, usage patterns, and error analysis.
async function getAllChecks(account) {
const allChecks = [];
let marker = undefined;
do {
const response = await api.request({
command: "account_objects",
account: account,
type: "check",
limit: 400, // Maximum supported limit
marker: marker
});
allChecks.push(...response.account_objects);
marker = response.marker;
} while (marker);
return allChecks;
}Query Rate Limiting
Public XRPL API endpoints implement rate limiting that can severely impact applications making frequent queries. Production systems should implement connection pooling, request queuing, and exponential backoff strategies. Consider dedicated API infrastructure for high-frequency applications or implement local ledger synchronization for query-intensive use cases.
Production check applications require real-time awareness of check state changes to provide responsive user experiences and enable automated processing workflows. The XRPL WebSocket API provides sophisticated subscription capabilities optimized for check monitoring scenarios.
Stream Subscription Architecture
WebSocket connections to XRPL servers support multiple concurrent subscriptions with granular filtering capabilities. Check monitoring typically requires three subscription types working in coordination:
- **Account-based subscriptions** deliver all transactions affecting specific accounts, including check creation, cashing, and cancellation events
- **Transaction stream subscriptions** provide access to all validated transactions network-wide with client-side filtering capabilities
- **Ledger stream subscriptions** deliver ledger close notifications that enable applications to maintain synchronized state with the network consensus
const ws = new WebSocket('wss://s1.ripple.com:443');
ws.on('open', () => {
// Subscribe to account transactions
ws.send(JSON.stringify({
command: 'subscribe',
accounts: ['rAccount1...', 'rAccount2...'],
streams: ['transactions']
}));
});
ws.on('message', (data) => {
const message = JSON.parse(data);
if (message.type === 'transaction' &&
['CheckCreate', 'CheckCash', 'CheckCancel'].includes(message.transaction.TransactionType)) {
handleCheckTransaction(message.transaction);
}
});Event Processing and State Management
Real-time check monitoring requires sophisticated event processing logic that handles transaction validation, state transitions, and error conditions:
function handleCheckTransaction(transaction) {
const txType = transaction.TransactionType;
const txResult = transaction.meta.TransactionResult;
// Only process successful transactions
if (txResult !== 'tesSUCCESS') {
handleFailedCheckTransaction(transaction, txResult);
return;
}
switch (txType) {
case 'CheckCreate':
const newCheckID = findCreatedCheckID(transaction.meta);
handleCheckCreated(transaction, newCheckID);
break;
case 'CheckCash':
handleCheckCashed(transaction);
break;
case 'CheckCancel':
handleCheckCancelled(transaction);
break;
}
}The transaction metadata provides essential information for state management: CreatedNode entries identify newly created check objects with their assigned CheckID values, DeletedNode entries identify removed check objects from cashing or cancellation, and AffectedNodes provide complete list of ledger objects modified by the transaction.
function findCreatedCheckID(transactionMeta) {
const createdNodes = transactionMeta.AffectedNodes.filter(node =>
node.CreatedNode && node.CreatedNode.LedgerEntryType === 'Check'
);
if (createdNodes.length === 1) {
return createdNodes[0].CreatedNode.LedgerIndex;
}
throw new Error('Unable to identify created check ID');
}Connection Management and Resilience
Production WebSocket implementations require robust connection management to handle network interruptions, server maintenance, and connection failures:
class CheckMonitor {
constructor(servers = ['wss://s1.ripple.com:443', 'wss://s2.ripple.com:443']) {
this.servers = servers;
this.currentServer = 0;
this.reconnectDelay = 1000;
this.maxReconnectDelay = 30000;
this.subscriptions = [];
}
connect() {
const serverUrl = this.servers[this.currentServer];
this.ws = new WebSocket(serverUrl);
this.ws.on('open', () => {
console.log(`Connected to ${serverUrl}`);
this.reconnectDelay = 1000;
this.reestablishSubscriptions();
});
this.ws.on('close', () => {
this.scheduleReconnect();
});
this.ws.on('error', (error) => {
console.error('WebSocket error:', error);
this.rotateServer();
this.scheduleReconnect();
});
}
scheduleReconnect() {
setTimeout(() => this.connect(), this.reconnectDelay);
this.reconnectDelay = Math.min(this.reconnectDelay * 2, this.maxReconnectDelay);
}
rotateServer() {
this.currentServer = (this.currentServer + 1) % this.servers.length;
}
}High-volume applications must implement efficient filtering to process only relevant events while minimizing bandwidth and processing overhead. WebSocket streams deliver events in ledger order, but applications must handle potential message reordering or duplication during connection disruptions.
Deep Insight: Event-Driven Architecture Benefits Real-time check monitoring enables sophisticated business logic that would be impossible with polling-based approaches. Applications can implement automatic check expiration warnings, dynamic pricing based on current market conditions, and instant payment confirmations. The key architectural advantage is moving from reactive (user-initiated queries) to proactive (system-initiated notifications) patterns that dramatically improve user experience and operational efficiency.
Scaling check applications to production volumes requires sophisticated optimization strategies that address API efficiency, connection management, and data processing performance. These patterns distinguish prototype applications from enterprise-grade systems.
Connection Pooling and Request Management
High-throughput check applications must implement connection pooling to efficiently manage API requests across multiple concurrent operations:
class XRPLConnectionPool {
constructor(servers, poolSize = 5) {
this.servers = servers;
this.poolSize = poolSize;
this.connections = [];
this.requestQueue = [];
this.activeRequests = 0;
this.maxConcurrentRequests = 10;
}
async initialize() {
for (let i = 0; i < this.poolSize; i++) {
const api = new ripple.RippleAPI({
server: this.servers[i % this.servers.length]
});
await api.connect();
this.connections.push(api);
}
}
async executeRequest(requestFn) {
return new Promise((resolve, reject) => {
this.requestQueue.push({ requestFn, resolve, reject });
this.processQueue();
});
}
async processQueue() {
if (this.activeRequests >= this.maxConcurrentRequests ||
this.requestQueue.length === 0) {
return;
}
const { requestFn, resolve, reject } = this.requestQueue.shift();
const api = this.getAvailableConnection();
this.activeRequests++;
try {
const result = await requestFn(api);
resolve(result);
} catch (error) {
reject(error);
} finally {
this.activeRequests--;
this.processQueue(); // Process next queued request
}
}
getAvailableConnection() {
// Round-robin connection selection
return this.connections[this.activeRequests % this.connections.length];
}
}Caching and State Management
Production check applications require sophisticated caching strategies that balance data freshness with API efficiency:
class CheckStateManager {
constructor(ttl = 30000) { // 30 second TTL
this.cache = new Map();
this.ttl = ttl;
this.pendingRequests = new Map();
}
async getCheck(checkID) {
const cached = this.cache.get(checkID);
if (cached && Date.now() - cached.timestamp < this.ttl) {
return cached.data;
}
// Prevent duplicate requests for same check
if (this.pendingRequests.has(checkID)) {
return this.pendingRequests.get(checkID);
}
const requestPromise = this.fetchCheckFromAPI(checkID);
this.pendingRequests.set(checkID, requestPromise);
try {
const checkData = await requestPromise;
this.cache.set(checkID, {
data: checkData,
timestamp: Date.now()
});
return checkData;
} finally {
this.pendingRequests.delete(checkID);
}
}
invalidateCheck(checkID) {
this.cache.delete(checkID);
}
async fetchCheckFromAPI(checkID) {
const response = await api.request({
command: 'ledger_entry',
check: checkID
});
return response.node;
}
}Batch Processing and Bulk Operations
Applications managing hundreds or thousands of checks must implement batch processing patterns to minimize API overhead:
class CheckBatchProcessor {
constructor(batchSize = 50, processingInterval = 5000) {
this.batchSize = batchSize;
this.processingInterval = processingInterval;
this.pendingOperations = [];
this.processing = false;
}
queueOperation(operation) {
this.pendingOperations.push(operation);
if (!this.processing) {
this.startProcessing();
}
}
async startProcessing() {
this.processing = true;
while (this.pendingOperations.length > 0) {
const batch = this.pendingOperations.splice(0, this.batchSize);
await this.processBatch(batch);
if (this.pendingOperations.length > 0) {
await this.delay(this.processingInterval);
}
}
this.processing = false;
}
async processBatch(operations) {
const promises = operations.map(op => this.executeOperation(op));
const results = await Promise.allSettled(promises);
results.forEach((result, index) => {
if (result.status === 'rejected') {
this.handleOperationError(operations[index], result.reason);
}
});
}
delay(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
}Error Handling and Retry Logic
Production systems require sophisticated error handling that distinguishes between temporary failures requiring retry and permanent failures requiring different handling:
class RobustCheckAPI {
constructor() {
this.retryDelays = [1000, 2000, 4000, 8000, 16000]; // Exponential backoff
this.permanentErrors = new Set([
'tecNO_PERMISSION',
'tecEXPIRED',
'tefPAST_SEQ',
'temINVALID'
]);
}
async executeWithRetry(operation, maxRetries = 5) {
for (let attempt = 0; attempt <= maxRetries; attempt++) {
try {
return await operation();
} catch (error) {
if (this.isPermanentError(error) || attempt === maxRetries) {
throw error;
}
const delay = this.retryDelays[Math.min(attempt, this.retryDelays.length - 1)];
await this.delay(delay);
}
}
}
isPermanentError(error) {
const errorCode = this.extractErrorCode(error);
return this.permanentErrors.has(errorCode);
}
extractErrorCode(error) {
// Extract error code from various error response formats
if (error.resultCode) return error.resultCode;
if (error.engine_result) return error.engine_result;
if (error.message && error.message.includes('tec')) {
return error.message.match(/tec\w+/)[0];
}
return 'UNKNOWN_ERROR';
}
}Performance Monitoring and Metrics
Production check applications should implement comprehensive performance monitoring to identify bottlenecks and optimization opportunities:
class CheckPerformanceMonitor {
constructor() {
this.metrics = {
apiCalls: 0,
cacheHits: 0,
cacheMisses: 0,
errors: 0,
avgResponseTime: 0,
operationCounts: new Map()
};
this.responseTimes = [];
}
recordAPICall(operation, responseTime, success) {
this.metrics.apiCalls++;
this.responseTimes.push(responseTime);
if (this.responseTimes.length > 1000) {
this.responseTimes = this.responseTimes.slice(-1000); // Keep last 1000
}
this.metrics.avgResponseTime = this.responseTimes.reduce((a, b) => a + b) /
this.responseTimes.length;
if (!success) {
this.metrics.errors++;
}
const count = this.metrics.operationCounts.get(operation) || 0;
this.metrics.operationCounts.set(operation, count + 1);
}
getCacheHitRate() {
const total = this.metrics.cacheHits + this.metrics.cacheMisses;
return total > 0 ? this.metrics.cacheHits / total : 0;
}
getMetricsSummary() {
return {
...this.metrics,
cacheHitRate: this.getCacheHitRate(),
errorRate: this.metrics.errors / this.metrics.apiCalls
};
}
}Rate Limiting Considerations
XRPL public servers implement rate limiting that becomes more aggressive during network congestion. Production applications should monitor their rate limit consumption and implement graceful degradation strategies. Consider operating dedicated infrastructure for high-volume applications or implementing local ledger synchronization to reduce API dependency for read operations.
What's Proven vs. What's Uncertain vs. What's Risky
What's Proven
- API Reliability: The XRPL check APIs have demonstrated consistent behavior across millions of transactions since their introduction in 2018
- Performance Scalability: WebSocket subscription patterns can efficiently monitor thousands of checks simultaneously with minimal bandwidth overhead
- Cross-Currency Functionality: Currency conversion through CheckCash operations leverages the XRPL's mature DEX infrastructure
- Security Model: The check authorization model has proven resistant to common payment fraud patterns
What's Uncertain
- Long-term API Stability: While current APIs are stable, future XRPL amendments could modify check behavior or introduce new parameters
- Rate Limiting Evolution: Public API rate limits may become more restrictive as network usage grows (probability: 40-60% within 2 years)
- Cross-Currency Liquidity: The reliability of currency conversion depends on DEX liquidity, which can vary significantly
- WebSocket Scaling Limits: While current WebSocket infrastructure handles thousands of concurrent connections, ultimate scaling limits remain untested
What's Risky
**Single Point of Failure**: Applications relying solely on public XRPL infrastructure face availability risks during network maintenance. **API Key Dependencies**: Many production applications require API keys for enhanced rate limits, creating operational dependencies. **State Synchronization Gaps**: Network interruptions can create gaps in real-time monitoring. **Currency Conversion Slippage**: Cross-currency check operations expose users to exchange rate risk and potential slippage.
The XRPL check APIs provide a robust foundation for production payment applications, but successful implementation requires significant engineering investment in error handling, performance optimization, and operational monitoring. The APIs themselves are well-designed and reliable, but the complexity lies in building resilient systems around them that handle edge cases, scale effectively, and provide excellent user experiences.
Assignment: Build a comprehensive TypeScript/JavaScript library that provides production-grade check management capabilities with full API coverage, error handling, and performance optimization.
Requirements
Part 1: Core API Wrapper (40%)
Implement complete CheckCreate, CheckCash, and CheckCancel transaction builders with parameter validation, error handling, and retry logic. Include comprehensive TypeScript interfaces for all parameters and response types.
Part 2: Query and Monitoring System (35%)
Build efficient check discovery methods with pagination, filtering, and caching. Implement WebSocket-based real-time monitoring with connection management and event processing.
Part 3: Performance and Operations (25%)
Add connection pooling, batch processing capabilities, performance metrics collection, and comprehensive logging for production observability.
- **API Coverage and Correctness** (30%): All check operations implemented with proper parameter validation and error handling
- **Performance and Scalability** (25%): Efficient caching, connection pooling, and batch processing implementation
- **Real-time Monitoring** (20%): Robust WebSocket handling with reconnection logic and event processing
- **Code Quality and Documentation** (15%): Clean architecture, comprehensive documentation, and TypeScript type safety
- **Production Readiness** (10%): Logging, metrics, error reporting, and operational considerations
This library becomes the foundation for any serious check application, demonstrating mastery of XRPL APIs and production engineering patterns that employers value highly.
Question 1: Parameter Validation Strategy
A CheckCreate transaction fails with "tecUNFUNDED_PAYMENT" despite the sender having sufficient XRP balance. What is the most likely cause and appropriate handling strategy?
- A) Network congestion causing temporary API failures -- retry with exponential backoff
- B) The sender's account reserve requirements increased due to new owned objects -- verify total reserve needs
- C) Invalid destination address format causing transaction rejection -- validate address before submission
- D) Insufficient transaction fee for current network load -- increase fee and resubmit
Correct Answer: B tecUNFUNDED_PAYMENT specifically indicates insufficient balance for the requested operation. If the sender appears to have sufficient XRP, the most likely cause is that their effective available balance is reduced by reserve requirements (10 XRP base + 2 XRP per owned object). The check creation itself adds another owned object, requiring additional reserve. Applications should calculate total reserve requirements including the new check object before validating balance sufficiency.
Question 2: WebSocket Event Processing
Your application receives multiple CheckCreate events for the same transaction hash but with different CheckID values. What is the correct interpretation and handling approach?
- A) This indicates a network error -- ignore duplicate events and process only the first occurrence
- B) Multiple checks were created in a single transaction -- process each CheckID as a separate check object
- C) The XRPL is experiencing consensus issues -- wait for ledger validation before processing any events
- D) WebSocket connection instability is causing message duplication -- implement deduplication based on transaction hash
Correct Answer: B A single transaction can create multiple check objects, each receiving a unique CheckID. This is valid XRPL behavior when applications create multiple checks in batch operations. The correct handling is to process each CheckID as a separate check object, extracting all CheckID values from the transaction metadata's CreatedNode entries. Deduplication should be based on CheckID, not transaction hash.
Question 3: Cross-Currency Conversion Risk
A CheckCash transaction for currency conversion specifies Amount: "100 EUR", DeliverMin: "95 EUR", but fails with "tecPATH_DRY". What does this indicate and how should the application respond?
- A) The original check has expired -- verify check expiration and inform the user about timing constraints
- B) Insufficient DEX liquidity for the requested conversion -- reduce conversion amount or try alternative currency pairs
- C) The recipient lacks authorization for EUR trust lines -- guide user through trust line establishment process
- D) Network congestion preventing transaction processing -- retry with higher transaction fees
Correct Answer: B tecPATH_DRY specifically indicates insufficient liquidity in the XRPL DEX to complete the requested currency conversion between the check's original currency and the desired EUR amount. The application should either suggest reducing the conversion amount, trying alternative currency pairs with better liquidity, or cashing the check in its original currency. This error is unrelated to expiration, trust line authorization, or network fees.
Question 4: Production Caching Strategy
Your check monitoring application serves 10,000+ users and makes frequent API calls to verify check states. Which caching approach provides the best balance of performance and data freshness?
- A) Cache all check data indefinitely until receiving WebSocket state change notifications for specific CheckIDs
- B) Implement 30-second TTL caching with WebSocket-triggered cache invalidation for modified checks
- C) Use no caching to ensure real-time data accuracy for all user requests
- D) Cache only static check metadata (SendMax, Destination) while always fetching dynamic state information
Correct Answer: B A 30-second TTL with WebSocket-triggered invalidation provides optimal balance. The TTL prevents serving stale data if WebSocket events are missed, while WebSocket invalidation ensures immediate updates for state changes. Option A risks serving stale data if events are missed, Option C creates unnecessary API load, and Option D doesn't optimize the most frequent queries. This pattern handles both normal operations and edge cases like connection interruptions.
Question 5: Error Handling Classification
Your application receives a "tefPAST_SEQ" error when submitting a CheckCash transaction. What is the correct classification and handling approach?
- A) Temporary error -- retry immediately with the same sequence number to ensure transaction processing
- B) Permanent error -- abandon this transaction attempt and refresh account sequence for future operations
- C) Network error -- switch to backup XRPL server and resubmit the identical transaction
- D) User error -- request user confirmation before retrying with corrected parameters
Correct Answer: B tefPAST_SEQ indicates the transaction sequence number is outdated, meaning another transaction from the same account was processed first. This is a permanent error for this specific transaction -- retrying with the same sequence will always fail. The correct approach is to abandon this transaction attempt, refresh the account's current sequence number, and prepare a new transaction with the updated sequence if the operation is still needed. This error indicates a race condition or stale sequence number, not network issues.
- **XRPL Documentation:**
- [Check Transaction Types](https://xrpl.org/known-amendments.html#checks) - Official XRPL documentation for check transactions
- [WebSocket API Reference](https://xrpl.org/websocket-api.html) - Complete WebSocket API documentation with examples
- [Transaction Metadata Format](https://xrpl.org/transaction-metadata.html) - Understanding transaction result metadata
- **API Integration Guides:**
- [Error Handling Best Practices](https://xrpl.org/reliable-transaction-submission.html) - XRPL guidance for production transaction handling
- [Rate Limiting and Performance](https://xrpl.org/public-servers.html) - Public server limitations and optimization strategies
Next Lesson Preview:
Lesson 5 explores advanced check integration patterns, including escrow combinations, payment channel interactions, and multi-signature workflows that enable sophisticated business applications beyond basic payment authorization.
Knowledge Check
Knowledge Check
Question 1 of 5A CheckCreate transaction fails with 'tecUNFUNDED_PAYMENT' despite sufficient XRP balance. What is the most likely cause?
Key Takeaways
Parameter mastery enables sophisticated business logic but requires careful validation and error handling
Real-time monitoring transforms user experience through proactive notifications and instant confirmations
Production optimization through connection pooling, caching, and batch processing is essential for scale