# Monitoring and Tools

[← Back to Building Amendments: Lifecycle and Core Protocol Impact](/core-dev-bootcamp/module10.md)

***

### Introduction

Effective amendment monitoring is crucial for node operators, validators, exchanges, and any entity depending on XRPL. Proper monitoring allows detection of imminent changes, planning of updates, and quick response to problems.

In this section, we explore the available tools for monitoring amendment status: the `feature` RPC command, interpretation of status fields, blockchain explorers, and advanced techniques for tracking an amendment's progression across the network.

These tools are indispensable for maintaining a robust and responsive XRPL infrastructure.

***

## RPC feature Command

### Basic Usage

The `feature` command is the primary tool for querying amendment status on a rippled node.

**Syntax**:

```bash
# List all amendments
rippled feature

# Query a specific amendment
rippled feature <amendment_hash>

# Enable voting (admin only)
rippled feature <amendment_hash> accept

# Disable voting (admin only)
rippled feature <amendment_hash> reject
```

**Example for Subscriptions**:

```bash
rippled feature 7B73B9E8D8E6E8E8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8
```

### Response Format

**Complete response**:

```json
{
  "result": {
    "features": {
      "7B73B9E8D8E6E8E8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8": {
        "count": 28,
        "enabled": false,
        "majority": 806021535,
        "name": "Subscriptions",
        "supported": true,
        "threshold": 28,
        "validations": 35
      }
    },
    "status": "success"
  }
}
```

### Field Interpretation

#### count

**Type**: `number`

**Meaning**: Number of UNL validators currently voting for the amendment.

**Example**: `"count": 28` means 28 validators have included this amendment in their recent validations.

**Usage**: Compare with `threshold` to know if the 80% threshold is exceeded (count > threshold).

#### enabled

**Type**: `boolean`

**Meaning**: Is the amendment activated on the network?

* `true`: The amendment is activated and its rules are applied
* `false`: The amendment is not yet activated

**Example**: `"enabled": false` means Subscriptions is not yet active.

**Usage**: Determines if new features are available.

#### majority

**Type**: `number` (XRPL Time) or `null`

**Meaning**: Timestamp (in seconds since 2000-01-01 00:00:00 UTC) when the amendment first exceeded the 80% threshold (count > threshold).

* Present: The amendment exceeded the threshold and is in stability period
* `null`: The amendment has not exceeded the threshold

**Example**: `"majority": 806021535`

**Human date conversion**:

```javascript
// XRPL Time -> Unix Time
const xrplTime = 806021535;
const unixTime = xrplTime + 946684800;  // Ripple Epoch (2000-01-01)
const date = new Date(unixTime * 1000);
console.log(date.toISOString());
// Output: 2025-07-20T14:32:15.000Z
```

**Usage**: Calculate when the amendment will be activated (majority + 2 weeks).

#### name

**Type**: `string`

**Meaning**: Human-readable name of the amendment.

**Example**: `"name": "Subscriptions"`

**Usage**: Human identification of the amendment (the hash is hard to memorize).

#### supported

**Type**: `boolean`

**Meaning**: Does this node support the amendment (does it have the implementation code)?

* `true`: The node can apply the amendment rules
* `false`: The node does not recognize or does not support the amendment

**Example**: `"supported": true`

**Usage**: Know if the node is up to date. If `supported: false` and the amendment has a majority, urgent upgrade is needed.

#### threshold

**Type**: `number`

**Meaning**: Absolute number calculated as `floor(validations * 0.8)`. To reach majority, `count > threshold` (strictly greater) is required.

**Calculation**: `threshold = floor(validations * 0.8)`

**Example**: `"threshold": 28` with 35 validations

* To exceed 80%: count > 28, so minimum 29 votes required
* Actual percentage: 29/35 = 82.9% > 80% ✓

**Usage**: Know how many additional votes are needed to reach majority (count must be > threshold).

#### validations

**Type**: `number`

**Meaning**: Total number of trusted validators (UNL) for the node.

**Example**: `"validations": 35`

**Usage**: Calculate current support percentage: `(count / validations) * 100`

***

## Activation ETA Calculation

### Formula

When an amendment has a majority, you can calculate when it will be activated:

```
activation_time = majority_time + 2_weeks
```

In seconds:

```
activation_time = majority + (2 * 7 * 24 * 60 * 60)
                = majority + 1209600
```

### Calculation Script

**JavaScript**:

```javascript
function calculateActivationETA(majority) {
  if (!majority) return null;

  // Convert XRPL Time to Unix Time
  const majorityUnix = majority + 946684800;

  // Add 2 weeks
  const activationUnix = majorityUnix + (14 * 24 * 60 * 60);

  // Calculate time remaining
  const now = Math.floor(Date.now() / 1000);
  const timeUntil = activationUnix - now;

  return {
    activationDate: new Date(activationUnix * 1000),
    timeUntil: timeUntil,
    days: Math.floor(timeUntil / 86400),
    hours: Math.floor((timeUntil % 86400) / 3600),
    minutes: Math.floor((timeUntil % 3600) / 60)
  };
}

// Example
const eta = calculateActivationETA(806021535);
console.log(`Activation: ${eta.activationDate.toISOString()}`);
console.log(`Time until: ${eta.days}d ${eta.hours}h ${eta.minutes}m`);
```

**Python**:

```python
from datetime import datetime, timedelta

def calculate_activation_eta(majority):
    if not majority:
        return None

    # XRPL Epoch: 2000-01-01 00:00:00 UTC
    ripple_epoch = datetime(2000, 1, 1)

    # Convert majority to datetime
    majority_dt = ripple_epoch + timedelta(seconds=majority)

    # Add 2 weeks
    activation_dt = majority_dt + timedelta(weeks=2)

    # Time remaining
    time_until = activation_dt - datetime.utcnow()

    return {
        'activation_date': activation_dt.isoformat(),
        'time_until': str(time_until),
        'days': time_until.days,
        'seconds': time_until.seconds
    }

# Example
eta = calculate_activation_eta(806021535)
print(f"Activation: {eta['activation_date']}")
print(f"Time until: {eta['days']}d {eta['seconds']//3600}h")
```

### Automated Monitoring

**Continuous monitoring script**:

```bash
#!/bin/bash
# monitor_amendment.sh

AMENDMENT_HASH="7B73B9E8D8E6E8E8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8"

while true; do
  RESULT=$(rippled feature $AMENDMENT_HASH)

  ENABLED=$(echo $RESULT | jq -r '.result.features."'$AMENDMENT_HASH'".enabled')
  MAJORITY=$(echo $RESULT | jq -r '.result.features."'$AMENDMENT_HASH'".majority')
  COUNT=$(echo $RESULT | jq -r '.result.features."'$AMENDMENT_HASH'".count')
  THRESHOLD=$(echo $RESULT | jq -r '.result.features."'$AMENDMENT_HASH'".threshold')

  echo "$(date '+%Y-%m-%d %H:%M:%S') - Status: enabled=$ENABLED, count=$COUNT/$THRESHOLD, majority=$MAJORITY"

  if [ "$ENABLED" = "true" ]; then
    echo "Amendment is ACTIVATED!"
    exit 0
  fi

  if [ "$MAJORITY" != "null" ]; then
    # Calculate ETA (simplified)
    ACTIVATION_TIME=$((MAJORITY + 1209600))
    NOW=$(date +%s)
    TIME_UNTIL=$((ACTIVATION_TIME - NOW + 946684800))
    DAYS=$((TIME_UNTIL / 86400))
    echo "  -> Activation ETA: ${DAYS} days"
  fi

  sleep 300  # Check every 5 minutes
done
```

***

## Ledger Explorers

### XRPL Explorers

Several web explorers allow visualizing amendment status:

#### 1. Bithomp Explorer

**URL**: <https://bithomp.com/amendments>

**Features**:

* List of all known amendments
* Real-time status (enabled, majority, voting)
* Vote progression graphs
* Historical timeline

**Example display**:

```
Subscriptions
Hash: 7B73B9E8...E8F8
Status: MAJORITY REACHED
Votes: 29/35 (82.9%)
Majority Since: 2025-04-20 14:32:15 UTC
Activation ETA: 2025-05-04 14:32:15 UTC
```

#### 2. XRPL.org Amendment Tracker

**URL**: <https://livenet.xrpl.org/amendments>

**Features**:

* Overview of all amendments
* Filters by status (enabled, voting, obsolete)
* Links to XLS documentation
* Activation history

#### 3. XRPScan

**URL**: <https://xrpscan.com/amendments>

**Features**:

* Graphical visualization of support
* Email notifications for status changes
* API for dashboard integration

### Pseudo-transaction Visualization

`EnableAmendment` pseudo-transactions can be visualized in explorers:

**Example of tfGotMajority transaction**:

```
Transaction Hash: ABC123...
Ledger: 86245789
Type: EnableAmendment
Account: rrrrrrrrrrrrrrrrrrrrrhoLvTp
Amendment: 7B73B9E8D8E6E8E8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8A8B8C8D8E8F8
Flags: tfGotMajority
Result: tesSUCCESS
```

**Search in Bithomp**:

```
https://bithomp.com/ledger/86245789
```

Allows viewing all pseudo-transactions in the ledger.

***

## API and Integration

### WebSocket Streaming

Monitor amendment changes in real-time via WebSocket:

**Subscribe to validations**:

```javascript
const WebSocket = require('ws');
const ws = new WebSocket('wss://xrplcluster.com/');

ws.on('open', () => {
  // Subscribe to validations
  ws.send(JSON.stringify({
    command: 'subscribe',
    streams: ['validations']
  }));
});

ws.on('message', (data) => {
  const msg = JSON.parse(data);

  if (msg.type === 'validationReceived') {
    const validation = msg.validation;

    // Extract voted amendments
    if (validation.amendments) {
      console.log(`Validator ${validation.validation_public_key} votes for:`,
                  validation.amendments);

      // Check Subscriptions
      if (validation.amendments.includes('7B73B9E8...')) {
        console.log('-> Subscriptions included!');
      }
    }
  }
});
```

### HTTP Polling

Periodically query status:

```javascript
const axios = require('axios');

async function checkAmendmentStatus(hash) {
  const response = await axios.post('https://xrplcluster.com/', {
    method: 'feature',
    params: [{
      feature: hash
    }]
  });

  return response.data.result;
}

// Poll every 5 minutes
setInterval(async () => {
  const status = await checkAmendmentStatus('7B73B9E8...');
  console.log('Amendment status:', status);

  if (status.features[hash].enabled) {
    console.log('ACTIVATED!');
    process.exit(0);
  }
}, 5 * 60 * 1000);
```

***

## Command Line Tools

### Scripting with jq

Extract specific information with `jq`:

**Get vote count**:

```bash
rippled feature 7B73B9E8... | jq '.result.features | .[].count'
```

**Check if enabled**:

```bash
ENABLED=$(rippled feature 7B73B9E8... | jq -r '.result.features | .[].enabled')
if [ "$ENABLED" = "true" ]; then
  echo "Amendment is active"
else
  echo "Amendment not yet active"
fi
```

**List all enabled amendments**:

```bash
rippled feature | jq -r '.result.features | to_entries[] | select(.value.enabled == true) | .value.name'
```

**List amendments in majority**:

```bash
rippled feature | jq -r '.result.features | to_entries[] | select(.value.majority != null) | "\(.value.name): \(.value.majority)"'
```

### Wrapper Scripts

**check\_amendments.sh**: Complete verification script

```bash
#!/bin/bash

echo "=== XRPL Amendment Status ==="
echo

# Get complete list
FEATURES=$(rippled feature)

# Enabled amendments
echo "ACTIVATED:"
echo "$FEATURES" | jq -r '.result.features | to_entries[] | select(.value.enabled == true) | "  - \(.value.name)"'
echo

# Amendments in majority
echo "IN MAJORITY (waiting for activation):"
echo "$FEATURES" | jq -r '.result.features | to_entries[] | select(.value.majority != null and .value.enabled == false) | "  - \(.value.name) (ETA: \(.value.majority + 1209600 - 946684800))"'
echo

# Amendments voting
echo "VOTING (not yet majority):"
echo "$FEATURES" | jq -r '.result.features | to_entries[] | select(.value.enabled == false and .value.majority == null and .value.count > 0) | "  - \(.value.name): \(.value.count)/\(.value.threshold) votes"'
echo

# Unsupported amendments
echo "UNSUPPORTED (require upgrade):"
echo "$FEATURES" | jq -r '.result.features | to_entries[] | select(.value.supported == false) | "  - \(.value.name)"'
```

***

## Alerting and Notifications

### Alert Configuration

**Prometheus + Grafana**:

```yaml
# prometheus.yml
scrape_configs:
  - job_name: 'rippled'
    static_configs:
      - targets: ['localhost:5005']
    metrics_path: '/metrics'
```

**Example exported metric**:

```
# HELP rippled_amendment_votes Number of votes for an amendment
# TYPE rippled_amendment_votes gauge
rippled_amendment_votes{name="Subscriptions",hash="7B73B9E8..."} 28

# HELP rippled_amendment_threshold Vote threshold for majority
# TYPE rippled_amendment_threshold gauge
rippled_amendment_threshold{name="Subscriptions"} 28

# HELP rippled_amendment_enabled Whether amendment is enabled
# TYPE rippled_amendment_enabled gauge
rippled_amendment_enabled{name="Subscriptions"} 0
```

**Grafana Alert**:

```
Alert: Amendment Majority Reached
Condition: rippled_amendment_votes >= rippled_amendment_threshold
Duration: 5m
Severity: WARNING
Notification: #xrpl-alerts
```

### Email Notifications

**Script with sendmail**:

```bash
#!/bin/bash

PREVIOUS_STATUS=""

while true; do
  RESULT=$(rippled feature 3B95AC15...)
  MAJORITY=$(echo $RESULT | jq -r '.result.features | .[].majority')

  if [ "$MAJORITY" != "null" ] && [ "$PREVIOUS_STATUS" != "majority" ]; then
    # Send email
    echo "Amendment Subscriptions has reached majority!" | \
      mail -s "XRPL Alert: Amendment Majority" admin@example.com
    PREVIOUS_STATUS="majority"
  fi

  sleep 300
done
```

***

## Custom Dashboard

### Web Interface Example

**HTML + JavaScript**:

```html
<!DOCTYPE html>
<html>
<head>
  <title>XRPL Amendment Monitor</title>
  <style>
    .amendment { border: 1px solid #ccc; margin: 10px; padding: 10px; }
    .enabled { background-color: #d4edda; }
    .majority { background-color: #fff3cd; }
    .voting { background-color: #d1ecf1; }
  </style>
</head>
<body>
  <h1>XRPL Amendment Status</h1>
  <div id="amendments"></div>

  <script>
    async function loadAmendments() {
      const response = await fetch('https://xrplcluster.com/', {
        method: 'POST',
        headers: { 'Content-Type': 'application/json' },
        body: JSON.stringify({ method: 'feature', params: [{}] })
      });

      const data = await response.json();
      const features = data.result.features;
      const container = document.getElementById('amendments');

      for (const [hash, info] of Object.entries(features)) {
        const div = document.createElement('div');
        div.className = 'amendment';

        if (info.enabled) {
          div.classList.add('enabled');
          div.innerHTML = `
            <h3>${info.name}</h3>
            <p>Status: ✅ ACTIVATED</p>
          `;
        } else if (info.majority) {
          div.classList.add('majority');
          const eta = new Date((info.majority + 1209600 + 946684800) * 1000);
          div.innerHTML = `
            <h3>${info.name}</h3>
            <p>Status: ⏳ MAJORITY</p>
            <p>Votes: ${info.count}/${info.validations} (${Math.round(info.count/info.validations*100)}%)</p>
            <p>Activation ETA: ${eta.toLocaleString()}</p>
          `;
        } else if (info.count > 0) {
          div.classList.add('voting');
          div.innerHTML = `
            <h3>${info.name}</h3>
            <p>Status: 🗳️ VOTING</p>
            <p>Votes: ${info.count}/${info.threshold} (${Math.round(info.count/info.validations*100)}%)</p>
          `;
        } else {
          continue;  // Ignore amendments with no activity
        }

        container.appendChild(div);
      }
    }

    loadAmendments();
    setInterval(loadAmendments, 60000);  // Refresh every minute
  </script>
</body>
</html>
```

***

## Third-Party Tools

### XRPL Amendment Notifier

Hypothetical email/SMS notification service:

```bash
# Subscribe to notifications
curl -X POST https://xrpl-notifier.com/api/subscribe \
  -d email=admin@example.com \
  -d amendment=7B73B9E8...
```

### Discord/Slack Bots

Bot posting to a channel:

```
XRPL Bot [12:34:56]
🗳️ Amendment Update: Subscriptions
Votes: 29/35 (82.9%)
Status: MAJORITY REACHED
Activation ETA: 2025-05-04 14:32:15 UTC

@channel Please prepare for activation in 14 days!
```


---

# Agent Instructions: Querying This Documentation

If you need additional information that is not directly available in this page, you can query the documentation dynamically by asking a question.

Perform an HTTP GET request on the current page URL with the `ask` query parameter:

```
GET https://docs.xrpl-commons.org/core-dev-bootcamp/module10/amendment-monitoring.md?ask=<question>
```

The question should be specific, self-contained, and written in natural language.
The response will contain a direct answer to the question and relevant excerpts and sources from the documentation.

Use this mechanism when the answer is not explicitly present in the current page, you need clarification or additional context, or you want to retrieve related documentation sections.
