Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 18 additions & 8 deletions _code-samples/verify-credential/js/verify_credential.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
import { Client, rippleTimeToISOTime, convertStringToHex } from "xrpl"

const client = new Client("wss://s.devnet.rippletest.net:51233")
const client = new Client("wss://xrplcluster.com")
await client.connect()

const SUBJECT_ADDRESS = "rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA"
const ISSUER_ADDRESS = "rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS"
const SUBJECT_ADDRESS = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"
const ISSUER_ADDRESS = "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX"
const CREDENTIAL_TYPE = convertStringToHex("my_credential").toUpperCase()

// Look up Credential ledger entry --------------------------------------------
Expand All @@ -29,6 +29,7 @@ try {
} else {
console.error(err)
}
client.disconnect()
process.exit(1)
}

Expand All @@ -40,6 +41,7 @@ console.log(JSON.stringify(credential, null, 2))
const lsfAccepted = 0x00010000
if (!(credential.Flags & lsfAccepted)) {
console.log("Credential is not accepted.")
client.disconnect()
process.exit(2)
}

Expand All @@ -49,17 +51,25 @@ if (credential.Expiration) {
console.log(`Credential has expiration: ${expirationTime}`)
console.log("Looking up validated ledger to check for expiration.")

const ledgerResponse = await client.request({
command: "ledger",
ledger_index: "validated",
})
let ledgerResponse
try {
ledgerResponse = await client.request({
command: "ledger",
ledger_index: "validated",
})
} catch (err) {
console.error("Error looking up most recent validated ledger:", err)
client.disconnect()
process.exit(3)
}

const closeTime = rippleTimeToISOTime(ledgerResponse.result.ledger.close_time)
console.log(`Most recent validated ledger was at: ${closeTime}`)

if (new Date(closeTime) > new Date(expirationTime)) {
console.log("Credential is expired.")
process.exit(3)
client.disconnect()
process.exit(4)
}
}

Expand Down
32 changes: 12 additions & 20 deletions _code-samples/verify-credential/py/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,26 +8,18 @@ Quick install & usage:
python -m venv .venv
source .venv/bin/activate
pip install -r requirements.txt
./verify_credential.py
python verify_credential.py
```

`verify_credential.py` can also be used as a commandline utility. Full usage statement:

```sh
$ ./verify_credential.py -h
usage: verify_credential.py [-h] [-b] [-n {devnet,testnet,mainnet}]
[issuer] [subject] [credential_type]

Verify an XRPL credential

positional arguments:
issuer Credential issuer address as base58.
subject Credential subject (holder) address as base58.
credential_type Credential type as string

options:
-h, --help show this help message and exit
-b, --binary Use binary (hexadecimal) for credential_type
-n, --network {devnet,testnet,mainnet}
Use the specified network for lookup
The output should look something like this:

```text
Looking up credential...
{'ledger_index': 'validated', 'method': 'ledger_entry', 'api_version': 2, 'credential': {'subject': 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn', 'issuer': 'ra5nK24KXen9AHvsdFTKHSANinZseWnPcX', 'credential_type': '6D795F63726564656E7469616C'}, 'binary': False}
Found credential:
{'CredentialType': '6D795F63726564656E7469616C', 'Expiration': 1199232000, 'Flags': 65536, 'Issuer': 'ra5nK24KXen9AHvsdFTKHSANinZseWnPcX', 'IssuerNode': '0', 'LedgerEntryType': 'Credential', 'PreviousTxnID': '2D3A865C3AAC50A20ACC40E3644D93FE5FEF2C8CFF21B1E0580ADB34D9487A9E', 'PreviousTxnLgrSeq': 102230586, 'Subject': 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn', 'SubjectNode': '0', 'index': '1CA15B7FBD6E0BEE616F2B5FF479D9F6EBEF5D93073FA03F734081AF46804B47'}
Credential has expiration: 2038-01-01T00:00:00+00:00
Looking up validated ledger to check for expiration.
Most recent validated ledger was at: 2026-02-17T18:47:52+00:00
Credential is valid.
```
212 changes: 55 additions & 157 deletions _code-samples/verify-credential/py/verify_credential.py
Original file line number Diff line number Diff line change
@@ -1,167 +1,65 @@
#!/usr/bin/env python

import argparse
import logging
import sys
from binascii import hexlify
from re import match

from xrpl.clients import JsonRpcClient
from xrpl.models.requests import LedgerEntry, Ledger
from xrpl.utils import ripple_time_to_datetime

# Set up logging --------------------------------------------------------------
# Use WARNING by default in case verify_credential is called from elsewhere.
logger = logging.getLogger("verify_credential")
logger.setLevel(logging.WARNING)
logger.addHandler(logging.StreamHandler(sys.stderr))

# Define an error to throw when XRPL lookup fails unexpectedly ----------------
class XRPLLookupError(Exception):
def __init__(self, xrpl_response):
self.body = xrpl_response.result

# Main function ---------------------------------------------------------------
def verify_credential(client:JsonRpcClient,
issuer:str,
subject:str,
credential_type:str="",
credential_type_hex:str=""):
"""
Check whether an XRPL account holds a specified credential,
as of the most recently validated ledger.

Paramters:
client - JsonRpcClient for the XRPL network to use.
issuer - Address of the credential issuer, in base58
subject - Address of the credential holder/subject, in base58
credential_type - Credential type to check for as a string,
which will be encoded as UTF-8 (1-64 bytes long).
credential_type_hex - Credential type (binary) as hexadecimal.
verbose - If true, print details to stdout during lookup.
You must provide either credential_type or credential_type_hex.

Returns True if the account holds the specified, valid credential.
Returns False if the credential is missing, expired, or not accepted.
"""
# Handle function inputs --------------------------------------------------
if not (credential_type or credential_type_hex):
raise ValueError("Provide a non-empty credential_type or " +
"credential_type_hex")
if credential_type and credential_type_hex:
raise ValueError("Provide either credential_type or " +
"credential_type_hex, but not both")

# Encode credential_type as uppercase hex, if needed
if credential_type:
credential_type_hex = hexlify(credential_type.encode("utf-8")
).decode("ascii")
logger.info("Encoded credential_type as hex: "+credential_type_hex.upper())
credential_type_hex = credential_type_hex.upper()

if len(credential_type_hex) % 2 or \
not match(r"[0-9A-F]{2,128}", credential_type_hex):
# Hexadecimal is always 2 chars per byte, so an odd length is invalid.
raise ValueError("credential_type_hex must be 1-64 bytes as hexadecimal.")

# Perform XRPL lookup of Credential ledger entry --------------------------
ledger_entry_request = LedgerEntry(
credential={
"subject": subject,
"issuer": issuer,
"credential_type": credential_type_hex
},
ledger_index="validated"
)
logger.info("Looking up credential...")
logger.info(ledger_entry_request.to_dict())
client = JsonRpcClient("https://xrplcluster.com")
SUBJECT_ADDRESS = "rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn"
ISSUER_ADDRESS = "ra5nK24KXen9AHvsdFTKHSANinZseWnPcX"
CREDENTIAL_TYPE = hexlify("my_credential".encode("utf-8")).decode("ascii").upper()

# Look up Credential ledger entry ----------------------------------------------
ledger_entry_request = LedgerEntry(
credential={
"subject": SUBJECT_ADDRESS,
"issuer": ISSUER_ADDRESS,
"credential_type": CREDENTIAL_TYPE,
},
ledger_index="validated",
)
print("Looking up credential...")
print(ledger_entry_request.to_dict())
try:
xrpl_response = client.request(ledger_entry_request)
except Exception as err:
print("Error: ledger_entry failed with error:", err)
exit(1)

if xrpl_response.status != "success":
if xrpl_response.result["error"] == "entryNotFound":
logger.info("Credential was not found")
return False
# Other errors, for example invalidly-specified addresses.
raise XRPLLookupError(xrpl_response)

credential = xrpl_response.result["node"]
logger.info("Found credential:")
logger.info(credential)

# Confirm that the credential has been accepted ---------------------------
lsfAccepted = 0x00010000
if not credential["Flags"] & lsfAccepted:
logger.info("Credential is not accepted.")
return False

# Confirm that the credential is not expired ------------------------------
if credential.get("Expiration"):
expiration_time = ripple_time_to_datetime(credential["Expiration"])
logger.info("Credential has expiration: "+expiration_time.isoformat())
logger.info("Looking up validated ledger to check for expiration.")

ledger_response = client.request(Ledger(ledger_index="validated"))
if ledger_response.status != "success":
raise XRPLLookupError(ledger_response)
close_time = ripple_time_to_datetime(
ledger_response.result["ledger"]["close_time"]
)
logger.info("Most recent validated ledger is: "+close_time.isoformat())

if close_time > expiration_time:
logger.info("Credential is expired.")
return False

# Credential has passed all checks. ---------------------------------------
logger.info("Credential is valid.")
return True

# Commandline usage -----------------------------------------------------------
if __name__=="__main__":
NETWORKS = {
# JSON-RPC URLs of public servers
"devnet": "https://s.devnet.rippletest.net:51234/",
"testnet": "https://s.altnet.rippletest.net:51234/",
"mainnet": "https://xrplcluster.com/"
}

# Parse arguments ---------------------------------------------------------
parser = argparse.ArgumentParser(description="Verify an XRPL credential")
parser.add_argument("issuer", type=str, nargs="?",
help="Credential issuer address as base58.",
default="rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS")
parser.add_argument("subject", type=str, nargs="?",
help="Credential subject (holder) address as base58.",
default="rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA")
parser.add_argument("credential_type", type=str, nargs="?",
help="Credential type as string",
default="my_credential")
parser.add_argument("-b", "--binary", action="store_true",
help="Use binary (hexadecimal) for credential_type")
parser.add_argument("-n", "--network", choices=NETWORKS.keys(),
help="Use the specified network for lookup",
default="devnet")
parser.add_argument("-q", "--quiet", action="store_true",
help="Don't print log messages.")
args = parser.parse_args()

# Call verify_credential with appropriate args ----------------------------
client = JsonRpcClient(NETWORKS[args.network])
if not args.quiet:
# Use INFO level by default when called from the commandline.
logger.setLevel(logging.INFO)

if args.binary:
result = verify_credential(client,
issuer=args.issuer,
subject=args.subject,
credential_type_hex=args.credential_type)
else:
result = verify_credential(client,
issuer=args.issuer,
subject=args.subject,
credential_type=args.credential_type)

# Return a nonzero exit code if credential verification failed. -----------
if not result:
if xrpl_response.status != "success":
if xrpl_response.result["error"] == "entryNotFound":
print("Credential was not found")
exit(1)
# Other errors, for example invalidly-specified addresses.
print("Unexpected error looking up credential:", xrpl_response.result)

credential = xrpl_response.result["node"]
print("Found credential:")
print(credential)

# Check if the credential has been accepted ------------------------------------
lsfAccepted = 0x00010000
if not credential["Flags"] & lsfAccepted:
print("Credential is not accepted.")
exit(2)

# Confirm that the credential is not expired -----------------------------------
if credential.get("Expiration"):
expiration_time = ripple_time_to_datetime(credential["Expiration"])
print("Credential has expiration:", expiration_time.isoformat())
print("Looking up validated ledger to check for expiration.")

ledger_response = client.request(Ledger(ledger_index="validated"))
if ledger_response.status != "success":
print("Error looking up most recent validated ledger:", ledger_response.result)
exit(3)
close_time = ripple_time_to_datetime(ledger_response.result["ledger"]["close_time"])
print("Most recent validated ledger was at:", close_time.isoformat())

if close_time > expiration_time:
print("Credential is expired.")
exit(4)

# Credential has passed all checks. --------------------------------------------
print("Credential is valid.")
Loading