You need to have the xrpl_vl_tool in the same directory at the moment.
./xrpdiff.sh https://vl.ripple.com https://unl.xrplf.org/unl/next.json| #!/usr/bin/env bash | |
| set -euo pipefail | |
| # Usage: xrpdiff.sh URL1 URL2 | |
| # Optionally set XRPL_VL_TOOL (defaults to ./xrpl_vl_tool) | |
| # XRPL_VL_TOOL=/path/to/xrpl_vl_tool ./xrpdiff.sh https://vl.ripple.com https://unl.xrplf.org | |
| XRPL_VL_TOOL="${XRPL_VL_TOOL:-./xrpl_vl_tool}" | |
| URL1="${1:?usage: $0 URL1 URL2}" | |
| URL2="${2:?usage: $0 URL1 URL2}" | |
| load() { "$XRPL_VL_TOOL" load "$1"; } | |
| summarize() { | |
| load "$1" | awk ' | |
| BEGIN { cnt=""; seqno=""; expires_str=""; ver=""; master="" } | |
| /^There are/ { | |
| cnt=$3 | |
| tmp=$0 | |
| sub(/^.*Sequence is:[[:space:]]*/, "", tmp) | |
| seqno=tmp; sub(/[^0-9].*$/, "", seqno) | |
| tmp=$0 | |
| sub(/^.*Expires:[[:space:]]*/, "", tmp) | |
| expires_str=tmp; sub(/[[:space:]]*\|.*$/, "", expires_str) | |
| tmp=$0 | |
| sub(/^.*Version:[[:space:]]*/, "", tmp) | |
| ver=tmp; sub(/[^0-9].*$/, "", ver) | |
| } | |
| /^Publisher Master Public Key:/ { | |
| sub(/^Publisher Master Public Key:[[:space:]]*/, "", $0) | |
| master=$0 | |
| } | |
| END { | |
| printf "count=%s sequence=%s expires=\"%s\" version=%s master=%s\n", cnt, seqno, expires_str, ver, master | |
| } | |
| ' | |
| } | |
| # HEX-only (for set math) | |
| keys() { | |
| load "$1" | awk '/^Validator:/ { print $2 }' | LC_ALL=C sort -u | |
| } | |
| # HEX|NODE|DOMAIN (NODE is base58 in parens; DOMAIN may be empty) | |
| # HEX|NODE|DOMAIN (NODE is base58 in parens; DOMAIN may be empty) | |
| key_node_domain() { | |
| load "$1" | awk -F'|' ' | |
| /^Validator:/ { | |
| left = $1 | |
| sub(/^Validator:[[:space:]]*/, "", left) | |
| # HEX is the first token before any space | |
| hex = left | |
| sub(/[[:space:]].*$/, "", hex) | |
| # NODE: extract text between first "(" and next ")" | |
| node = "" | |
| lp = index(left, "(") | |
| if (lp > 0) { | |
| rest = substr(left, lp + 1) | |
| rp = index(rest, ")") | |
| if (rp > 0) { | |
| node = substr(rest, 1, rp - 1) | |
| } | |
| } | |
| # DOMAIN is field 3 (after the second "|"), if present | |
| dom = ($3 == "" ? "" : $3) | |
| sub(/^[[:space:]]*/, "", dom) | |
| print hex "|" node "|" dom | |
| } | |
| ' | LC_ALL=C sort -u | |
| } | |
| # Temp files | |
| k1=$(mktemp); k2=$(mktemp) | |
| f1=$(mktemp); f2=$(mktemp) | |
| trap 'rm -f "$k1" "$k2" "$f1" "$f2"' EXIT | |
| keys "$URL1" > "$k1" | |
| keys "$URL2" > "$k2" | |
| key_node_domain "$URL1" > "$f1" | |
| key_node_domain "$URL2" > "$f2" | |
| echo "== Summary ==" | |
| echo "URL1: $URL1" | |
| summarize "$URL1" | |
| echo "URL2: $URL2" | |
| summarize "$URL2" | |
| echo | |
| fmt_line() { | |
| # args: hex node domain | |
| local hex="$1" node="$2" dom="$3" | |
| if [ -n "$node" ] && [ -n "$dom" ]; then | |
| printf "%s (%s) | %s\n" "$hex" "$node" "$dom" | |
| elif [ -n "$node" ]; then | |
| printf "%s (%s)\n" "$hex" "$node" | |
| elif [ -n "$dom" ]; then | |
| printf "%s | %s\n" "$hex" "$dom" | |
| else | |
| printf "%s\n" "$hex" | |
| fi | |
| } | |
| # Added | |
| echo "== Added (present in URL2, not in URL1) ==" | |
| comm -13 "$k1" "$k2" | awk -v F="$f2" ' | |
| BEGIN { | |
| FS="|" | |
| while ((getline < F) > 0) { m[$1]=$2 "|" $3 } | |
| close(F) | |
| } | |
| { | |
| hex=$0 | |
| split(m[hex], parts, "|") | |
| node=parts[1]; dom=parts[2] | |
| printf " + %s", hex | |
| if (node != "") printf " (%s)", node | |
| if (dom != "") printf " | %s", dom | |
| printf "\n" | |
| } | |
| ' | |
| echo | |
| # Removed | |
| echo "== Removed (present in URL1, not in URL2) ==" | |
| comm -23 "$k1" "$k2" | awk -v F="$f1" ' | |
| BEGIN { | |
| FS="|" | |
| while ((getline < F) > 0) { m[$1]=$2 "|" $3 } | |
| close(F) | |
| } | |
| { | |
| hex=$0 | |
| split(m[hex], parts, "|") | |
| node=parts[1]; dom=parts[2] | |
| printf " - %s", hex | |
| if (node != "") printf " (%s)", node | |
| if (dom != "") printf " | %s", dom | |
| printf "\n" | |
| } | |
| ' | |
| echo | |
| # Domain changes | |
| echo "== Domain changes (same key, different domain) ==" | |
| awk -F'|' ' | |
| FNR==NR { node1[$1]=$2; dom1[$1]=$3; next } | |
| { | |
| hex=$1; node2=$2; dom2=$3 | |
| if (hex in dom1 && dom1[hex] != dom2) { | |
| printf " * %s", hex | |
| if (node2 != "") printf " (%s)", node2 | |
| printf "\n URL1: %s\n URL2: %s\n", dom1[hex], dom2 | |
| } | |
| } | |
| ' "$f1" "$f2" || true | |
| echo |
Hey @mankins
I see "aureusox.com" being removed, and "bifrostwallet.com" is added to the output.
Is this accurate?
@Vjkhannaripple the above output just shows a sample for this script run! ...not related to our next unl publish.
this is the current state of the next unl publish relative to the current, according to this script.
./xrpdiff.sh https://vl.ripple.com https://unl.xrplf.org/unl/next.json
== Summary ==
URL1: https://vl.ripple.com
count=35 sequence=81 expires="2026-01-17 10:09:21" version=1 master=ED2677ABFFD1B33AC6FBC3062B71F1E8397C1505E1C42C64D11AD1B28FF73F4734
URL2: https://unl.xrplf.org/unl/next.json
count=35 sequence=2025111163 expires="2026-03-11 15:55:38" version=1 master=ED42AEC58B701EEBB77356FFFEC26F83C1F0407263530F068C7C73D392C7E06FD1
== Added (present in URL2, not in URL1) ==
+ ED6FCBE961C9B67924155C84AE192023606385DC7BDED3ECFDB6F117FBE12EE8C3 (nHUUgpUVNxXfxkkoyh2QDjfLfHapcut8gYwKeShnJYd3SdPui19A) | peersyst.cloud
+ EDD3DB9E85A9B26772464BE9FCE120007E504AAF7AFF4648FA24E155B35A48FE6D (nHUxjxKPeErbN7pNk9UWA5Ee7ZPMtesSeRGJtmdqkTxe94tqM2YX) | tequ.dev
== Removed (present in URL1, not in URL2) ==
- ED580AD4FA5DA989FA999535ECC20197A5B53A1A49A971F6652ED8D5D466CA605D (nHUpDEZX5Zy9auiu4yhDmhirNu6PyB1LvzQEL9Mxmqjr818w663q) | xspectar.com
- EDC2A138B3771C208965596D4D372331C17A5476BD2CE2BC7A6D3CD273DF330D99 (nHUq9tJvSyoXQKhRytuWeydpPjvTz3M9GfUpEqfsg9xsewM7KkkK)
== Domain changes (same key, different domain) ==
Ah thanks @mankins . This is helpful.
Appreciate the quick turnaround.
In case anyone needs it, here's the mentioned tool: https://github.com/elmurci/xrpl-vl-tool
sample output: