Merge branch 'master' into refactor_payment_modular_tuts

This commit is contained in:
Dennis Dawson
2025-05-13 07:31:45 -07:00
committed by GitHub
27 changed files with 731 additions and 141 deletions

View File

@@ -1,17 +0,0 @@
import { DocSearch } from '@docsearch/react';
import { useThemeHooks } from '@redocly/theme/core/hooks';
export function AlgoliaSearch() {
const { useL10n } = useThemeHooks()
let { lang } = useL10n()
return (
<DocSearch
appId="R39QY3MZC7"
indexName="xrpl"
apiKey="3431349deec23b0bc3dcd3424beb9a6e"
searchParameters={{
facetFilters: ['lang:'+lang],
}}
/>
)
}

View File

@@ -4,7 +4,7 @@ import { LanguagePicker } from "@redocly/theme/components/LanguagePicker/Languag
import { slugify } from "../../helpers";
import { Link } from "@redocly/theme/components/Link/Link";
import { ColorModeSwitcher } from "@redocly/theme/components/ColorModeSwitcher/ColorModeSwitcher";
import { AlgoliaSearch } from "./AlgoliaSearch";
import { Search } from "@redocly/theme/components/Search/Search";
import arrowUpRight from "../../../static/img/icons/arrow-up-right-custom.svg";
// @ts-ignore
@@ -139,7 +139,7 @@ export function Navbar(props) {
<NavItems>
{navItems}
<div id="topnav-search" className="nav-item search">
<AlgoliaSearch />
<Search className="topnav-search"/>
</div>
<div id="topnav-language" className="nav-item">
<LanguagePicker

View File

@@ -145,7 +145,7 @@ export function Badge(props: {
}
}
type TryItServer = 's1' | 's2' | 'xrplcluster' | 'testnet' | 'devnet'
type TryItServer = 's1' | 's2' | 'xrplcluster' | 'testnet' | 'devnet' | 'testnet-clio' | 'devnet-clio'
export function TryIt(props: {
method: string,
@@ -164,6 +164,10 @@ export function TryIt(props: {
use_server = "?server=wss%3A%2F%2Fs.devnet.rippletest.net%3A51233%2F"
} else if (props.server == 'testnet') {
use_server = "?server=wss%3A%2F%2Fs.altnet.rippletest.net%3A51233%2F"
} else if (props.server == 'testnet-clio') {
use_server = "?server=wss%3A%2F%2Fclio.altnet.rippletest.net%3A51233%2F"
} else if (props.server == 'devnet-clio') {
use_server = "?server=wss%3A%2F%2Fclio.devnet.rippletest.net%3A51233%2F"
}
const to_path = `/resources/dev-tools/websocket-api-tool${use_server}#${props.method}`
return (

View File

@@ -10,7 +10,7 @@
"https-browserify": "^1.0.0",
"stream-browserify": "^3.0.0",
"stream-http": "^3.2.0",
"vite": "^4.5.13"
"vite": "^4.5.14"
},
"dependencies": {
"dotenv": "^16.0.3",

View File

@@ -679,10 +679,10 @@ util-deprecate@^1.0.1, util-deprecate@~1.0.1:
resolved "https://registry.yarnpkg.com/util-deprecate/-/util-deprecate-1.0.2.tgz#450d4dc9fa70de732762fbd2d4a28981419a0ccf"
integrity sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==
vite@^4.5.13:
version "4.5.13"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.13.tgz#778534a947112c6c455e89737730fae5d458a294"
integrity sha512-Hgp8IF/yZDzKsN1hQWOuQZbrKiaFsbQud+07jJ8h9m9PaHWkpvZ5u55Xw5yYjWRXwRQ4jwFlJvY7T7FUJG9MCA==
vite@^4.5.14:
version "4.5.14"
resolved "https://registry.yarnpkg.com/vite/-/vite-4.5.14.tgz#2e652bc1d898265d987d6543ce866ecd65fa4086"
integrity sha512-+v57oAaoYNnO3hIu5Z/tJRZjq5aHM2zDve9YZ8HngVHbhk66RStobhb1sqPMIPEleV6cNKYK4eGrAbE9Ulbl2g==
dependencies:
esbuild "^0.18.10"
postcss "^8.4.27"

View File

@@ -16,6 +16,6 @@
"express": "^5.1.0",
"inquirer": "^12.5.2",
"morgan": "^1.10.0",
"xrpl": "^4.2.0"
"xrpl": "^4.2.5"
}
}

View File

@@ -0,0 +1,32 @@
# Verify Credential - Javascript sample code
Verifies that a specific credential exists on the XRPL and is valid.
Quick install & usage:
```sh
npm install
```
`verify_credential.js` can also be used as a commandline utility. Full usage statement:
```sh
$ ./verify_credential.js -h
Usage: verify-credential [options] [issuer] [subject] [credential_type]
Verify an XRPL credential
Arguments:
issuer Credential issuer address as base58 (default:
"rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS")
subject Credential subject (holder) address as base58 (default:
"rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA")
credential_type Credential type as string. (default: "my_credential")
Options:
-b, --binary Use binary (hexadecimal) for credential_type
-n, --network <network> {devnet,testnet,mainnet} Use the specified network for lookup (default: "devnet")
-q, --quiet Don't print log messages
-h, --help display help for command
```

View File

@@ -0,0 +1,18 @@
{
"name": "issuer_service",
"version": "1.0.0",
"description": "",
"main": "verify_credential.js",
"scripts": {
"test": "echo \"Error: no test specified\" && exit 1"
},
"keywords": [],
"author": "",
"license": "ISC",
"type": "module",
"dependencies": {
"commander": "^13.1.0",
"winston": "^3.17.0",
"xrpl": "^4.2.5"
}
}

View File

@@ -0,0 +1,184 @@
#!/usr/bin/env node
import { Command } from "commander";
import { Client, rippleTimeToISOTime, convertStringToHex } from "xrpl";
import winston from "winston";
// Set up logging --------------------------------------------------------------
// Use WARNING by default in case verify_credential is called from elsewhere.
const logger = winston.createLogger({
level: "warn",
transports: [new winston.transports.Console()],
format: winston.format.simple(),
});
// Define an error to throw when XRPL lookup fails unexpectedly
class XRPLLookupError extends Error {
constructor(error) {
super("XRPL look up error");
this.name = "XRPLLookupError";
this.body = error;
}
}
const CREDENTIAL_REGEX = /^[0-9A-F]{2,128}$/;
// See https://xrpl.org/docs/references/protocol/ledger-data/ledger-entry-types/credential#credential-flags
// to learn more about the lsfAccepted flag.
const LSF_ACCEPTED = 0x00010000;
async function verifyCredential(client, issuer, subject, credentialType, binary=false) {
/**
* Check whether an XRPL account holds a specified credential,
* as of the most recently validated ledger.
* Parameters:
* client - Client for interacting with rippled servers.
* issuer - Address of the credential issuer, in base58.
* subject - Address of the credential holder/subject, in base58.
* credentialType - Credential type to check for as a string,
* which will be encoded as UTF-8 (1-128 characters long).
* binary - Specifies that the credential type is provided in hexadecimal format.
* You must provide the credential_type as input.
* Returns True if the account holds the specified, valid credential.
* Returns False if the credential is missing, expired, or not accepted.
*/
// Encode credentialType as uppercase hex, if needed
let credentialTypeHex = "";
if (binary) {
credentialTypeHex = credentialType.toUpperCase();
} else {
credentialTypeHex = convertStringToHex(credentialType).toUpperCase();
logger.info(`Encoded credential_type as hex: ${credentialTypeHex}`);
}
if (credentialTypeHex.length % 2 !== 0 || !CREDENTIAL_REGEX.test(credentialTypeHex)) {
// Hexadecimal is always 2 chars per byte, so an odd length is invalid.
throw new Error("Credential type must be 128 characters as hexadecimal.");
}
// Perform XRPL lookup of Credential ledger entry --------------------------
const ledgerEntryRequest = {
command: "ledger_entry",
credential: {
subject: subject,
issuer: issuer,
credential_type: credentialTypeHex,
},
ledger_index: "validated",
};
logger.info("Looking up credential...");
logger.info(JSON.stringify(ledgerEntryRequest, null, 2));
let xrplResponse;
try {
xrplResponse = await client.request(ledgerEntryRequest);
} catch (err) {
if (err.data?.error === "entryNotFound") {
logger.info("Credential was not found");
return false;
} else {
// Other errors, for example invalidly specified addresses.
throw new XRPLLookupError(err?.data || err);
}
}
const credential = xrplResponse.result.node;
logger.info("Found credential:");
logger.info(JSON.stringify(credential, null, 2));
// Check if the credential has been accepted ---------------------------
if (!(credential.Flags & LSF_ACCEPTED)) {
logger.info("Credential is not accepted.");
return false
}
// Confirm that the credential is not expired ------------------------------
if (credential.Expiration) {
const expirationTime = rippleTimeToISOTime(credential.Expiration);
logger.info(`Credential has expiration: ${expirationTime}`);
logger.info("Looking up validated ledger to check for expiration.");
let ledgerResponse;
try {
ledgerResponse = await client.request({
command: "ledger",
ledger_index: "validated",
});
} catch (err) {
throw new XRPLLookupError(err?.data || err);
}
const closeTime = rippleTimeToISOTime(ledgerResponse.result.ledger.close_time);
logger.info(`Most recent validated ledger is: ${closeTime}`);
if (new Date(closeTime) > new Date(expirationTime)) {
logger.info("Credential is expired.");
return false;
}
}
// Credential has passed all checks ---------------------------------------
logger.info("Credential is valid.");
return true;
}
// Commandline usage -----------------------------------------------------------
async function main() {
// Websocket URLs of public servers
const NETWORKS = {
devnet: "wss://s.devnet.rippletest.net:51233",
testnet: "wss://s.altnet.rippletest.net:51233",
mainnet: "wss://xrplcluster.com/",
};
// Parse arguments ---------------------------------------------------------
let result = false
const program = new Command();
program
.name("verify-credential")
.description("Verify an XRPL credential")
.argument("[issuer]", "Credential issuer address as base58", "rEzikzbnH6FQJ2cCr4Bqmf6c3jyWLzkonS")
.argument("[subject]", "Credential subject (holder) address as base58", "rsYhHbanGpnYe3M6bsaMeJT5jnLTfDEzoA")
.argument("[credential_type]", "Credential type as string.", "my_credential")
.option("-b, --binary", "Use binary (hexadecimal) for credential_type")
.option(
`-n, --network <network> {${Object.keys(NETWORKS)}}`,
"Use the specified network for lookup",
(value) => {
if (!Object.keys(NETWORKS).includes(value)) {
throw new Error(`Must be one of: ${Object.keys(NETWORKS)}`);
}
return value;
},
"devnet"
)
.option("-q, --quiet", "Don't print log messages")
// Call verify_credential with appropriate args ----------------------------
.action(async (issuer, subject, credentialType, options) => {
const client = new Client(NETWORKS[options.network]);
await client.connect();
// Use INFO level by default when called from the commandline.
if (!options.quiet) { logger.level = "info" }
// Commander.js automatically sets options.binary to a boolean:
// - If you provide -b or --binary on the command line then options.binary = true
// - If you do not provide it then options.binary = false
result = await verifyCredential(client, issuer, subject, credentialType, options.binary);
await client.disconnect();
});
await program.parseAsync(process.argv);
// Return a nonzero exit code if credential verification failed -----------
if (!result) {
process.exit(1);
}
}
main().catch((err) => {
console.error("Fatal startup error:", err);
process.exit(1);
});

View File

@@ -13,7 +13,7 @@ The Authorized Trust Lines feature enables issuers to create tokens that can onl
To use the Authorized Trust Lines feature, enable the **Require Auth** flag on your issuing account. While the setting is enabled, other accounts can only hold tokens you issue if you have authorized those accounts' trust lines to your issuing account.
You can authorize a trust line by sending a [TrustSet transaction][] from your issuing address, configuring the trust line between your account and the account to authorize. After you have authorized a trust line, you can never revoke that authorization. (You can, however, [freeze](freezes.md) that trust line if you need to.)
You can authorize a trust line by sending a [TrustSet transaction][] from your issuing address, configuring the trust line between your account and the account to authorize. After you have authorized a trust line, you can't revoke that authorization, but the authorization status is reset if the trust line is automatically deleted for being otherwise in its default state. (You can, however, [freeze](freezes.md) that trust line if you need to.)
The transaction to authorize a trust line must be signed by the issuing address, which unfortunately means an increased risk exposure for that address.

View File

@@ -3,156 +3,150 @@ blurb: Get the holders for a given `MPTokenIssuanceID` and ledger sequence.
labels:
- Accounts
- XRP
- Multi Purpose Tokens, MPTs
---
# mpt_holders
[[Source]](https://github.com/XRPLF/clio/blob/develop/src/rpc/handlers/MPTHolders.cpp "Source")
_(Requires the [MPTokensV1 amendment][] {% not-enabled /%})_
For a given `MPTokenIssuanceID` and ledger sequence, `mpt_holders` returns all holders of that MPT and their balance. This method likely returns very large data sets, so you should expect to implement paging via the `marker` field. This API is only available using Clio, not rippled.
For a given `MPTokenIssuanceID` and ledger sequence, `mpt_holders` returns all holders of that [MPT](../../../../concepts/tokens/fungible-tokens/multi-purpose-tokens.md) and their balance. This method likely returns very large data sets, so you should expect to implement paging via the `marker` field. This API is only available using Clio, not `rippled`. {% badge href="https://github.com/XRPLF/clio/releases/tag/2.3.0" %}New in: Clio v2.3.0{% /badge %}
## Request Format
*Websocket*
{% tabs %}
{% tab label="WebSocket" %}
```json
{
"command": "mpt_holders",
"mpt_issuance_id": "00070C4495F14B0E44F78A264E41713C64B5F89242540EE255534400000000000000",
"mpt_issuance_id": "0024D204E07DDDFBCD83B1649C07FE27FD536A3A32E6FDD8",
"ledger_index": "validated"
}
```
{% /tab %}
*JSON-RPC*
{% tab label="JSON-RPC" %}
```json
{
"method": "mpt_holders",
"params": [
{
"mpt_issuance_id": "00070C4495F14B0E44F78A264E41713C64B5F89242540EE255534400000000000000",
"mpt_issuance_id": "0024D204E07DDDFBCD83B1649C07FE27FD536A3A32E6FDD8",
"ledger_index": "validated"
}
]
}
```
{% /tab %}
{% /tabs %}
{% try-it method="mpt_holders" server="devnet-clio" /%}
The request contains the following parameters:
| Field | Type | Required? | Description |
|:------------------|:---------------------|:----------|-------------|
| `mpt_issuance_id` | string | Yes | The `MPTokenIssuance` to query. |
| `ledger_index` | string or number (positive integer) | No | The ledger index of the max ledger to use, ora shortcut string to choose a ledger automatically. You must specify either ledger_index or ledger_hash. |
| `ledger_hash` | string | No | A 32-byte hex string for the ledger version to use. You must specify either ledger_index or ledger_hash. |
| `marker` | string | No | Used to continue your query where it left off in paginating. |
| `limit` | number (positive integer) | No | Specify a limit to the number of MPTs returned. |
| Field | Type | Required? | Description |
|:------------------|:---------------------------|:----------|-------------|
| `mpt_issuance_id` | String | Yes | The `MPTokenIssuance` to query. |
| `ledger_index` | [Ledger Index][] | No | The [Ledger Index][] of the max ledger to use, or a shortcut string to choose a ledger automatically. You must specify either `ledger_index` or `ledger_hash`. See [Specifying Ledgers][].|
| `ledger_hash` | String | No | A 32-byte hex string for the ledger version to use. You must specify either `ledger_index` or ledger_hash. See [Specifying Ledgers][]. |
| `marker` | [Marker][] | No | Used to continue your query where it left off in paginating. |
| `limit` | Number (positive integer) | No | Specify a limit to the number of MPTs returned. |
## Response Format
{% tabs %}
{% tab label="WebSocket" %}
```json
{
"mpt_issuance_id": "000004C463C52827307480341125DA0577DEFC38405B0E3E",
"limit":50,
"ledger_index": 2,
"mptokens": [{
"account": "rEiNkzogdHEzUxPfsri5XSMqtXUixf2Yx",
"result": {
"mpt_issuance_id": "0024D204E07DDDFBCD83B1649C07FE27FD536A3A32E6FDD8",
"limit": 50,
"ledger_index": 2414929,
"mptokens": [
{
"account": "rfyWeQpYM3vCXRHA9cMLs2ZEdZv1F1jzm9",
"flags": 0,
"mpt_amount": "20",
"mptoken_index": "36D91DEE5EFE4A93119A8B84C944A528F2B444329F3846E49FE921040DE17E65"
},
{
"account": "rrnAZCqMahreZrKMcZU3t2DZ6yUndT4ubN",
"flags": 0,
"mpt_amount": "1",
"mptoken_index": "D137F2E5A5767A06CB7A8F060ADE442A30CFF95028E1AF4B8767E3A56877205A"
}],
"mpt_amount": "200",
"mptoken_index": "22F99DCD55BCCF3D68DC3E4D6CF12602006A7563A6BE93FC57FD63298BCCEB13"
}
],
"validated": true
},
"status": "success",
"type": "response",
"warnings": [
{
"id": 2001,
"message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request"
}
]
}
```
{% /tab %}
{% tab label="JSON-RPC" %}
```json
200 OK
{
"result": {
"mpt_issuance_id": "0024D204E07DDDFBCD83B1649C07FE27FD536A3A32E6FDD8",
"limit": 50,
"ledger_index": 2415033,
"mptokens": [
{
"account": "rfyWeQpYM3vCXRHA9cMLs2ZEdZv1F1jzm9",
"flags": 0,
"mpt_amount": "200",
"mptoken_index": "22F99DCD55BCCF3D68DC3E4D6CF12602006A7563A6BE93FC57FD63298BCCEB13"
}
],
"validated": true,
"status": "success"
},
"warnings": [
{
"id": 2001,
"message": "This is a clio server. clio only serves validated data. If you want to talk to rippled, include 'ledger_index':'current' in your request"
}
]
}
```
{% /tab %}
{% /tabs %}
### Response Fields
The response follows the [standard format][], with the result containing the following fields:
| Field | Type | Description |
|:-----------------------|:--------|:------------------------------------------|
| `mpt_issuance_id` | string | The `MPTokenIssuance` queried |
| `mptokens` | array | An array of mptokens. Includes all relevant fields in the underlying MPToken object. |
| `marker` | string | Used to continue querying where we left off when paginating. Omitted if there are no more entries after this result. |
| `limit` | number | The limit, as specfied in the request
| `ledger_index` | number | The index of the ledger used. |
| Field | Type | Description |
|:-----------------------|:-----------------|:------------------------------------------|
| `mpt_issuance_id` | String | The `MPTokenIssuance` queried. |
| `mptokens` | Array | An array of [MPTokens](#mptoken). Includes all relevant fields in the underlying `MPToken` object. |
| `marker` | [Marker][] | Used to continue querying where we left off when paginating. Omitted if there are no more entries after this result. |
| `limit` | Number | The limit, as specified in the request. |
| `ledger_index` | [Ledger Index][] | The index of the ledger used. |
| `validated` | Boolean | If `true`, the ledger has been validated by the consensus process and is immutable. Otherwise, the contents of the ledger are not final and may change. In Clio, this is _always_ true as Clio stores and returns validated ledger data. |
An `mptoken` object has the following parameters:
#### MPToken
| Field | Type | Description |
|:-----------------------|:--------|:------------------------------------------|
| `account` | string | The account address of the holder who owns the `MPToken`. |
| `flags` | number | The flags assigned to the`MPToken` object. |
| `mpt_amount` | string | Base 10-encoded amount of the holder's balance. |
| `mptoken_index` | string | Key of the `MPToken` object. |
An `MPToken` object has the following parameters:
##### Example
Example of a `tx` response:
| Field | Type | Description |
|:-----------------------|:------------------|:------------------------------------------|
| `account` | String | The account address of the holder who owns the `MPToken`. |
| `flags` | Number | The flags assigned to the`MPToken` object. |
| `mpt_amount` | [String Number][] | Specifies a positive amount of tokens currently held by the owner. |
| `mptoken_index` | String | Key of the `MPToken` object. |
```json
{
"result": {
"Account": "rBT9cUqK6UvpvZhPFNQ2qpUTin8rDokBeL",
"AssetScale": 2,
"Fee": "10",
"Flags": 64,
"Sequence": 303,
"SigningPubKey": "ED39955DEA2D083C6CBE459951A0A84DB337925389ACA057645EE6E6BA99D4B2AE",
"TransactionType": "MPTokenIssuanceCreate",
"TxnSignature": "80D7B7409980BE9854F7217BB8E836C8A2A191E766F24B5EF2EA7609E1420AABE6A1FDB3038468679081A45563B4D0B49C08F4F70F64E41B578F288A208E4206",
"ctid": "C000013100000000",
"date": 760643692,
"hash": "E563D7942E3E4A79AD73EC12E9E4C44B7C9950DF7BF5FDB75FAD0F5CE0554DB3",
"inLedger": 305,
"ledger_index": 305,
"meta": {
"AffectedNodes": [...],
"TransactionIndex": 0,
"TransactionResult": "tesSUCCESS",
"mpt_issuance_id": "0000012F72A341F09A988CDAEA4FF5BE31F25B402C550ABE"
},
"status": "success",
"validated": true
}
}
```
## Possible Errors
##### Object
An `mpt_issuance_id` field is provided in JSON MPTokenIssuance objects (not available for binary). The following APIs are impacted: `ledger_data` and `account_objects`.
##### Example
Example of an `account_objects` response:
```json
{
"result": {
"account": "rBT9cUqK6UvpvZhPFNQ2qpUTin8rDokBeL",
"account_objects": [
{
"AssetScale": 2,
"Flags": 64,
"Issuer": "rBT9cUqK6UvpvZhPFNQ2qpUTin8rDokBeL",
"LedgerEntryType": "MPTokenIssuance",
"OutstandingAmount": "100",
"OwnerNode": "0",
"PreviousTxnID": "BDC5ECA6B115C74BF4DA83E36325A2F55DF9E2C968A5CC15EB4D009D87D5C7CA",
"PreviousTxnLgrSeq": 308,
"Sequence": 303,
"index": "75EC6F2939ED6C5798A5F369A0221BC4F6DDC50F8614ECF72E3B976351057A63",
"mpt_issuance_id": "0000012F72A341F09A988CDAEA4FF5BE31F25B402C550ABE"
}
],
"ledger_current_index": 309,
"status": "success",
"validated": false
}
}
```
- Any of the [universal error types][].
- `invalidParams` - One or more fields are specified incorrectly, or one or more required fields are missing.
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -63,6 +63,6 @@ Transactions of the TrustSet type support additional values in the [`Flags` fiel
If a transaction tries to enable No Ripple but cannot, it fails with the result code `tecNO_PERMISSION`. Before the [fix1578 amendment][] became enabled, such a transaction would result in `tesSUCCESS` (making any other changes it could) instead.
The Auth flag of a trust line does not determine whether the trust line counts towards its owner's XRP reserve requirement. However, an enabled Auth flag prevents the trust line from being in its default state. An authorized trust line can never be deleted. An issuer can pre-authorize a trust line with the `tfSetfAuth` flag only, even if the limit and balance of the trust line are 0.
The Auth flag of a trust line does not determine whether the trust line counts towards its owner's XRP reserve requirement. An issuer can pre-authorize a trust line with the `tfSetfAuth` flag only, even if the limit and balance of the trust line are 0.
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -357,8 +357,7 @@ Using this service as a base, you can extend the service with more features, suc
Alternatively, you can use credentials to for various purposes, such as:
- Define a [Permissioned Domain](/docs/concepts/tokens/decentralized-exchange/permissioned-domains) that uses your credentials to grant access to features on the XRP Ledger.
<!-- TODO: Uncomment once tutorial is ready. -->
<!-- - [Verify credentials](../compliance/verify-credential.md) manually to grant access to services that exist off-ledger. -->
- [Verify credentials](../compliance/verify-credential.md) manually to grant access to services that exist off-ledger.
## See Also

View File

@@ -0,0 +1,13 @@
---
metadata:
indexPage: true
seo:
description: Transact with confidence using the XRP Ledger's suite of compliance features for following government regulations and security practices.
---
# Transact with Confidence Using Compliance Features
The XRP Ledger has a rich suite of features designed to help financial institutions of all sizes engage with DeFi technology while complying with government regulations domestically and internationally.
See the following tutorials for examples of how to put these features to work:
{% child-pages /%}

View File

@@ -0,0 +1,320 @@
---
seo:
description: Verify that an account holds a valid credential on the XRP Ledger.
labels:
- Credentials
---
# Verify Credentials in Javascript
This tutorial describes how to verify that an account holds a valid [credential](/docs/concepts/decentralized-storage/credentials) on the XRP Ledger, which has different use cases depending on the type of credential and the meaning behind it. A few possible reasons to verify a credential include:
- Confirming that a recipient has passed a background check before sending a payment.
- Checking a person's professional certifications, after verifying their identity with a [DID](/docs/concepts/decentralized-storage/decentralized-identifiers).
- Displaying a player's achievements in a blockchain-connected game.
This tutorial uses sample code in Javascript using the [xrpl-js library](../index.md).
## Prerequisites
- You must have Node.js installed and know how to run Javascript code from the command line. Node.js v18 is required for xrpl.js.
- You should have a basic understanding of the XRP Ledger.
- The credential you want to verify should exist in the ledger already, and you should know the addresses of both the issuer and the holder, as well as the official credential type you want to check.
- For sample code showing how to create credentials, see [Build a Credential Issuing Service](../build-apps/credential-issuing-service.md).
## Setup
First, download the complete sample code for this tutorial from GitHub:
- {% repo-link path="_code-samples/verify-credential/js/" %}Verify Credential sample code{% /repo-link %}
Then, in the appropriate directory, install dependencies:
```sh
npm install
```
This installs the appropriate version of the `xrpl.js` library and a few other dependencies. You can view all dependencies in the {% repo-link path="_code-samples/verify-credentials/js/package.json" %}`package.json`{% /repo-link %} file.
## Overview
The Verify Credential sample code consists of one file, `verify_credential.js`, and contains two main parts:
1. A function, `verifyCredential(...)` which can be called with appropriate arguments to verify that a credential exists and is valid. This function can be imported into other code to be used as part of a larger application.
2. A commandline utility that can be used to verify any credential.
## Usage
To test that you have the code installed and working properly, you can run the commandline utility with no arguments to check the existence of a default credential on Devnet, such as:
```sh
node verify_credential.js
```
If all goes well, you should see output such as the following:
```text
info: Encoded credential_type as hex: 5465737443726564656E7469616C
info: Looking up credential...
info: {
"command": "ledger_entry",
"credential": {
"subject": "rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG",
"issuer": "rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3",
"credential_type": "5465737443726564656E7469616C"
},
"ledger_index": "validated"
}
info: Found credential:
info: {
"CredentialType": "5465737443726564656E7469616C",
"Flags": 65536,
"Issuer": "rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3",
"IssuerNode": "0",
"LedgerEntryType": "Credential",
"PreviousTxnID": "B078C70D17820069BDF913146F9908A209B4E10794857A3E474F4C9C5A35CA6A",
"PreviousTxnLgrSeq": 1768183,
"Subject": "rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG",
"SubjectNode": "0",
"index": "F2ACB7292C4F4ACB18010251F1653934DC17F06AA5BDE7F484F65B5A648D70CB"
}
info: Credential is valid.
```
If the code reports that the credential was not found when called with no arguments, it's possible that the example credential has been deleted or the Devnet has been reset. If you have another credential you can verify, you can provide the details as commandline arguments. For example:
```sh
node verify_credential.js rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3 rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG TestCredential
```
A full usage statement is available with the `-h` flag.
### Other Examples
The following examples show other possible scenarios. The data for these examples may or may not still be present in Devnet. For example, anyone can delete an expired credential.
{% tabs %}
{% tab label="Valid with Expiration" %}
```text
$ ./verify_credential.js rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3 rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG TCredential777
info: Encoded credential_type as hex: 5443726564656E7469616C373737
info: Looking up credential...
info: {
"command": "ledger_entry",
"credential": {
"subject": "rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG",
"issuer": "rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3",
"credential_type": "5443726564656E7469616C373737"
},
"ledger_index": "validated"
}
info: Found credential:
info: {
"CredentialType": "5443726564656E7469616C373737",
"Expiration": 798647105,
"Flags": 65536,
"Issuer": "rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3",
"IssuerNode": "0",
"LedgerEntryType": "Credential",
"PreviousTxnID": "D32F66D1446C810BF4E6310E21111C0CE027140292347F0C7A73322F08C07D7E",
"PreviousTxnLgrSeq": 2163057,
"Subject": "rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG",
"SubjectNode": "0",
"URI": "746573745F757269",
"index": "6E2AF1756C22BF7DC3AA47AD303C985026585B54425E7FACFAD6CD1867DD39C2"
}
info: Credential has expiration: 2025-04-22T14:25:05.000Z
info: Looking up validated ledger to check for expiration.
info: Most recent validated ledger is: 2025-04-22T13:47:30.000Z
info: Credential is valid.
```
{% /tab %}
{% tab label="Expired" %}
```text
$ ./verify_credential.js rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3 rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG TCredential777
info: Encoded credential_type as hex: 5443726564656E7469616C373737
info: Looking up credential...
info: {
"command": "ledger_entry",
"credential": {
"subject": "rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG",
"issuer": "rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3",
"credential_type": "5443726564656E7469616C373737"
},
"ledger_index": "validated"
}
info: Found credential:
info: {
"CredentialType": "5443726564656E7469616C373737",
"Expiration": 798647105,
"Flags": 65536,
"Issuer": "rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3",
"IssuerNode": "0",
"LedgerEntryType": "Credential",
"PreviousTxnID": "D32F66D1446C810BF4E6310E21111C0CE027140292347F0C7A73322F08C07D7E",
"PreviousTxnLgrSeq": 2163057,
"Subject": "rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG",
"SubjectNode": "0",
"URI": "746573745F757269",
"index": "6E2AF1756C22BF7DC3AA47AD303C985026585B54425E7FACFAD6CD1867DD39C2"
}
info: Credential has expiration: 2025-04-22T14:25:05.000Z
info: Looking up validated ledger to check for expiration.
info: Most recent validated ledger is: 2025-04-22T14:40:00.000Z
info: Credential is expired.
```
{% /tab %}
{% tab label="Unaccepted" %}
```text
$ ./verify_credential.js rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3 rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG Tst9Credential
info: Encoded credential_type as hex: 5473743943726564656E7469616C
info: Looking up credential...
info: {
"command": "ledger_entry",
"credential": {
"subject": "rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG",
"issuer": "rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3",
"credential_type": "5473743943726564656E7469616C"
},
"ledger_index": "validated"
}
info: Found credential:
info: {
"CredentialType": "5473743943726564656E7469616C",
"Expiration": 797007605,
"Flags": 0,
"Issuer": "rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3",
"IssuerNode": "0",
"LedgerEntryType": "Credential",
"PreviousTxnID": "A7A5F2AF66222B7ECDBC005477BDDCE35E1460FC53339A7800CBDE79DBBB6FE4",
"PreviousTxnLgrSeq": 1633091,
"Subject": "rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG",
"SubjectNode": "0",
"URI": "746573745F757269",
"index": "4282469903F9046C8E559447CB1B17A18362E2AFC04399BB7516EFB0B1413EAB"
}
info: Credential is not accepted.
```
{% /tab %}
{% tab label="Hexadecimal Credential Type" %}
```text
$ ./verify_credential.js rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3 rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG 5473743343726564656E7469616C -b
info: Looking up credential...
info: {
"command": "ledger_entry",
"credential": {
"subject": "rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG",
"issuer": "rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3",
"credential_type": "5473743343726564656E7469616C"
},
"ledger_index": "validated"
}
info: Found credential:
info: {
"CredentialType": "5473743343726564656E7469616C",
"Flags": 65536,
"Issuer": "rPLY4DWhB4VA7PPZ8nvZLhShXeVZqeKif3",
"IssuerNode": "0",
"LedgerEntryType": "Credential",
"PreviousTxnID": "062DA0586A57A32220785159D98F5206A14C4B98F5A7D8A9BCDB6836E33C45FE",
"PreviousTxnLgrSeq": 1768019,
"Subject": "rBqPPjAW6ubfFdmwERgajvgP5LtM4iQSQG",
"SubjectNode": "0",
"URI": "746573745F757269",
"index": "8548D8DC544153044D17E38499F8CB4E00E40A93085FD979AB8B949806668843"
}
info: Credential is valid.
```
{% /tab %}
{% /tabs %}
## Code Walkthrough
### 1. Initial setup
The `verify_credential.js` file implements the code for this tutorial.
This file can be run as a commandline tool, so it starts with a [shebang](https://en.wikipedia.org/wiki/Shebang_(Unix)). Then it imports the relevant dependencies, including the specific parts of the `xrpl.js` library:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" before="// Set up logging" /%}
The next section of the code sets the default log level for messages that might be written to the console through the utility:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Set up logging" before="// Define an error to throw" /%}
Then it defines a type of exception to throw if something goes wrong when connecting to the XRP Ledger:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Define an error to throw" before="const CREDENTIAL" /%}
Finally, a regular expression to validate the credential format and the [lsfAccepted](../../../references/protocol/ledger-data/ledger-entry-types/credential.md#credential-flags) flag are defined as constants for use further on in the code.
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="const CREDENTIAL" before="async function verifyCredential" /%}
### 2. verifyCredential function
The `verifyCredential(...)` function performs the main work for this tutorial. The function definition and comments define the parameters:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="async function verifyCredential" before="// Encode credentialType as uppercase hex" /%}
The XRP Ledger APIs require the credential type to be hexadecimal, so it converts the user input if necessary:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Encode credentialType as uppercase hex" before="// Perform XRPL lookup" /%}
Next, it calls the [ledger_entry method](/docs/references/http-websocket-apis/public-api-methods/ledger-methods/ledger_entry#get-credential-entry) to look up the requested Credential ledger entry:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Perform XRPL lookup" before="// Check if the credential has been accepted" /%}
If it succeeds in finding the credential, the function continues by checking that the credential has been accepted by its holder. Since anyone can issue a credential to anyone else, a credential is only considered valid if its subject has accepted it.
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="Check if the credential has been accepted" before="// Confirm that the credential is not expired" /%}
Then, if the credential has an expiration time, the function checks that the credential is not expired. If the credential has no expiration, this step can be skipped. A credential is officially considered expired if its expiration time is before the [official close time](/docs/concepts/ledgers/ledger-close-times) of the most recently validated ledger. This is more universal than comparing the expiration to your own local clock. Thus, the code uses the [ledger method][] to look up the most recently validated ledger:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Confirm that the credential is not expired" before="// Credential has passed all checks" /%}
If none of the checks up to this point have returned a `false` value, then the credential must be valid. This concludes the `verifyCredential(...)` function:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Credential has passed all checks" before="// Commandline usage" /%}
### 3. Commandline interface
This file also implements a commandline utility which runs when the file is executed directly as a Node.js script. Some variables, such as the set of available networks, are only needed for this mode:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Commandline usage" before="// Parse arguments" /%}
Then it uses the [commander package](https://www.npmjs.com/package/commander) to define and parse the arguments that the user can pass from the commandline:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Parse arguments" before="// Call verify_credential" /%}
After parsing the commandline args, it sets the appropriate values and passes them to `verifyCredential(...)` to perform the credential verification:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Call verify_credential" before="// Return a nonzero exit code" /%}
It returns a nonzero exit code if this credential was not verified. This can be useful for shell scripts:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="// Return a nonzero exit code" before="main().catch" /%}
Finally, the code runs the `main()` function:
{% code-snippet file="/_code-samples/verify-credential/js/verify_credential.js" language="js" from="main().catch" /%}
## Next Steps
Now that you know how to use `xrpl.js` to verify credentials, you can try building this or related steps together into a bigger project. For example:
- Incorporate credential verification into a [wallet application](../build-apps/build-a-desktop-wallet-in-javascript.md).
- Issue your own credentials with a [credential issuing service](../build-apps/credential-issuing-service.md).
## See Also
- [Verify Credentials in Python](../../python/compliance/verify-credential.md)
{% raw-partial file="/docs/_snippets/common-links.md" /%}

View File

@@ -14,8 +14,6 @@
"dependencies": {
"@codemirror/state": "6.5.2",
"@codemirror/view": "^6.22.2",
"@docsearch/react": "^3.8.0",
"@esbuild/darwin-arm64": "^0.25.4",
"@lezer/highlight": "^1.2.0",
"@redocly/realm": "0.120.2",
"@uiw/codemirror-themes": "4.21.21",

View File

@@ -23,6 +23,21 @@ responseHeaders:
'/@l10n/es-ES/**':
- name: X-Robots-Tag
value: noindex
search:
engine: typesense
ai:
hide: true
filters:
hide: true
metadataGlobs:
'docs/**':
redocly_category: Documentation
'blog/**':
redocly_category: Blog
'resources/**':
redocly_category: Resources
'community/**':
redocly_category: Community
seo:
siteUrl: https://xrpl.org/
rbac:
@@ -62,8 +77,6 @@ links:
rel: stylesheet
- href: https://www.unpkg.com/@xrpl/ai-css/xrplai.css
rel: stylesheet
- href: https://cdn.jsdelivr.net/npm/@docsearch/css@3
rel: stylesheet
logo:
srcSet: ./static/img/XRPLedger_DevPortal-black.svg light ./static/img/XRPLedger_DevPortal-white.svg dark

View File

@@ -409,6 +409,23 @@
}
]
},
{
"group": "MPT Methods",
"methods": [
{
"name": "mpt_holders",
"description": "Return all holders of an MPT and their balance (Clio only).",
"link": "/docs/references/http-websocket-apis/public-api-methods/clio-methods/mpt_holders",
"clio_only": true,
"body": {
"id": "example_mpt_holders",
"command": "mpt_holders",
"mpt_issuance_id": "0024D204E07DDDFBCD83B1649C07FE27FD536A3A32E6FDD8",
"ledger_index": "validated"
}
}
]
},
{
"group": "NFT Methods",
"methods": [

View File

@@ -53,7 +53,7 @@
"id": "connection-devnet-clio",
"ws_url": "wss://clio.devnet.rippletest.net:51233/",
"jsonrpc-url": "https://clio.devnet.rippletest.net:51234/",
"shortname": "devnet-clio",
"shortname": "Devnet-clio",
"longname": "clio.devnet.rippletest.net (Devnet Public Cluster with Clio)"
}
]

View File

@@ -12,7 +12,7 @@ labels:
The following is a comprehensive list of all known [amendments](../docs/concepts/networks-and-servers/amendments.md) and their status on the production XRP Ledger:
{% admonition type="success" name="Tip" %}
This list is updated manually. For a live view of amendment voting, see the Amendments Dashboards: [XRPScan](https://xrpscan.com/amendments), [XRPLExplorer](https://xrplexplorer.com/amendments).
This list is updated manually. For a live view of amendment voting, see the Amendments Dashboards: [XRPScan](https://xrpscan.com/amendments), [Bithomp](https://bithomp.com/en/amendments).
{% /admonition %}
| Name | Introduced | Status |

View File

@@ -212,6 +212,9 @@
- page: docs/tutorials/javascript/build-apps/build-a-browser-wallet-in-javascript.md
- page: docs/tutorials/javascript/build-apps/build-a-desktop-wallet-in-javascript.md
- page: docs/tutorials/javascript/build-apps/credential-issuing-service.md
- page: docs/tutorials/javascript/compliance/index.md
items:
- page: docs/tutorials/javascript/compliance/verify-credential.md
- page: docs/tutorials/python/index.md
expanded: false
items:

File diff suppressed because one or more lines are too long

View File

@@ -30,6 +30,13 @@
z-index: 1100 !important;
}
.xrp-ledger-dev-portal {
.DocSearch-Modal {
top: 85px;
background-color: #232325;
box-shadow: none;
}
}
// Algolia Search results --------------------------------------------------
html {
// The extra specificity makes this override the default Algolia styles.

View File

@@ -6,7 +6,7 @@
background: #32E685 !important;
padding: 7px 35px;
font-family: "Space Grotesk";
z-index: 9999;
z-index: 10;
cursor: pointer;
&:hover {

View File

@@ -144,6 +144,7 @@ $banner-height: 52px; // Apex 2025 banner is 52px. 0 for no pencil banner.
}
#topnav-search {
flex-grow: 1;
.input-group {
flex-grow: 1;
flex-wrap: nowrap; // Fix search bar splitting into two lines on iPhone 5
@@ -315,7 +316,7 @@ $banner-height: 52px; // Apex 2025 banner is 52px. 0 for no pencil banner.
@include media-breakpoint-up(xl) {
#topnav-search {
margin-left: auto;
margin-left: 3.5rem;
margin-right: 0.5rem;
}
#topnav-language {

View File

@@ -27,3 +27,6 @@ html.light .MarkpromptContentDialog h3:not(.chip) {
.MarkpromptOverlay {
z-index: 20;
}
.MarkpromptContentDialog{
z-index: 10000;
}

View File

@@ -1340,7 +1340,7 @@ main article .card-grid {
}
}
/* Search styles */
/* Algolia Search styles
.algolia-autocomplete .ds-dropdown-menu::before {
background-color: $gray-100;
@@ -1352,3 +1352,4 @@ main article .card-grid {
.DocSearch-Modal {
box-shadow: $light-box-shadow;
}
*/