Migrate toml checker tool

Add baseline html

Add script tags

WIP fetchWallet

Make basic page with account step 1 work

Add decodeHex and helpers to update logs

Add fetchFile to access toml from domain

Copy over code & comment out migrated pieces

Add toml parsing

WIP: Add types and uncomment new code

Update the parseToml function to share code

Mostly migrate the validateDomain function

Fix bug by using for instead of foreach

Clean up code part 1

Refactor into separate files

Translate everything

Componentize the buttons

Split out code into separate files

Update package-lock

Fix spacing and uncomment code

Fix indentation

Fix direct import of xrpl

Fix import

cleaned up log entry handling to not build an array of elements

moved to resource folder and update css.

Move shared components and fix small errors

Move file and update sidebars

Fix slow load of long list of addresses

toml checker - sidebar/width fixes
This commit is contained in:
JST5000
2023-11-21 16:47:10 -08:00
committed by mDuo13
parent 9ca9a69ab2
commit 0f53f35a2c
14 changed files with 916 additions and 38 deletions

View File

@@ -0,0 +1,77 @@
import * as React from 'react';
import { useTranslate } from '@portal/hooks';
import { clsx } from 'clsx'
export const CLASS_GOOD = "badge badge-success"
export const CLASS_BAD = "badge badge-danger"
export interface LogEntryStatus {
icon?: {
label: string,
type: "SUCCESS" | "ERROR"
check?: boolean
}
followUpMessage?: JSX.Element
}
export interface LogEntryItem {
message: string
id: string
status?: LogEntryStatus
}
/**
* Add entry to the end of the value that setLogEntries modifies.
*
* @param setLogEntries - A setter to modify a list of LogEntries
* @param entry - Data for a new LogEntry
*/
export function addNewLogEntry(
setLogEntries: React.Dispatch<React.SetStateAction<LogEntryItem[]>>,
entry: LogEntryItem)
{
setLogEntries((prev) => {
return [...prev, entry]
})
}
/**
* Looks up an existing log entry from the previous value within setLogEntries which has
* the same id as entry.id. Then it updates that value to equal entry.
*
* Primarily used to update the "status" after verifying a field.
*
* @param setLogEntries - A setter to modify a list of LogEntries.
* @param entryToUpdate - Updated data for an existing LogEntry.
*/
export function updateLogEntry(
setLogEntries: React.Dispatch<React.SetStateAction<LogEntryItem[]>>,
entryToUpdate: LogEntryItem) {
setLogEntries((prev) => {
const index = prev.findIndex((entry)=> entryToUpdate.id === entry.id)
prev.splice(index, 1, entryToUpdate)
return [...prev]
})
}
export function LogEntry({
message,
id,
status
}: LogEntryItem)
{
const {translate} = useTranslate()
let icon = undefined
if(!!(status?.icon)) {
icon = <span className={
clsx(status.icon?.type === "SUCCESS" && CLASS_GOOD,
status.icon?.type === "ERROR" && CLASS_BAD)}>
{status.icon?.label}
{status.icon?.check && <i className="fa fa-check-circle"/>}
</span>
}
return (
<li id={id}>{translate(`${message} `)}{icon}{status?.followUpMessage}</li>
)
}

View File

@@ -0,0 +1,77 @@
import * as React from 'react';
import { useState } from 'react'
import { useTranslate } from '@portal/hooks';
import { LogEntry, LogEntryItem } from './LogEntry';
/**
* A button that allows a single field to be submitted & logs displayed underneath.
*/
export interface TextLookupFormProps {
/**
* The big header above the button.
*/
title: string
/**
* Main description for what the button will do. Usually wrapped in <p> with <a>'s inside.
* All translation must be done before passing in the description.
*/
description: React.JSX.Element,
/**
* 2-3 words that appear on the button itself.
*/
buttonDescription: string
/*
* Triggered when users click the button to submit the form.
* setLogEntries is internally used to display logs to the user as handleSubmit executes.
* fieldValue represents the value they submitted with the form.
*/
handleSubmit: (
setLogEntries: React.Dispatch<React.SetStateAction<LogEntryItem[]>>,
event: React.FormEvent<HTMLFormElement>,
fieldValue: string) => void
/**
* Optionally include this as an example in the form to hint to users what they should type in.
*/
formPlaceholder?: string
}
/**
* A form to look up a single text field and display logs to the user.
*
* @param props Text fields for the form / button and a handler when the button is clicked.
* @returns A single-entry form which displays logs after submitting.
*/
export function TextLookupForm(props: TextLookupFormProps) {
const { translate } = useTranslate()
const { title, description, buttonDescription, formPlaceholder, handleSubmit } = props
const [logEntries, setLogEntries] = useState<LogEntryItem[]>([])
const [fieldValue, setFieldValue] = useState("")
return (
<div className="p-3 pb-5">
<form onSubmit={(event) => handleSubmit(setLogEntries, event, fieldValue)}>
<h4>{translate(title)}</h4>
{description}
<div className="input-group">
<input type="text" className="form-control" required
placeholder={translate(formPlaceholder)}
onChange={(event) => setFieldValue(event.target.value)}
/>
<br />
<button className="btn btn-primary form-control">{translate(buttonDescription)}</button>
</div>
</form>
<br/>
<br/>
{logEntries?.length > 0 && <div>
<h5 className="result-title">{translate(`Result`)}</h5>
<ul id="log">
{logEntries.map((log) => {
return <LogEntry message={log.message} id={log.id} key={log.id} status={log.status} />
})}
</ul>
</div>}
</div>)
}

View File

@@ -0,0 +1,163 @@
import { clsx } from 'clsx'
import { Client } from 'xrpl'
import React = require("react");
import { CLASS_GOOD } from "../components/LogEntry";
import { AccountFields } from "./XrplToml";
// Decode a hexadecimal string into a regular string, assuming 8-bit characters.
// Not proper unicode decoding, but it'll work for domains which are supposed
// to be a subset of ASCII anyway.
function decodeHex(hex) {
let str = '';
for (let i = 0; i < hex.length; i += 2) {
str += String.fromCharCode(parseInt(hex.substr(i, 2), 16))
}
return str
}
function getWsUrlForNetwork(net: string) {
let wsNetworkUrl: string
if (net === "main") {
wsNetworkUrl = 'wss://s1.ripple.com:51233'
} else if (net == "testnet") {
wsNetworkUrl = 'wss://s.altnet.rippletest.net:51233'
} else if (net === "devnet") {
wsNetworkUrl = 'wss://s.devnet.rippletest.net:51233/'
} else if (net === "xahau") {
wsNetworkUrl = 'wss://xahau-test.net:51233'
} else {
wsNetworkUrl = undefined
}
return wsNetworkUrl
}
async function validateAddressDomainOnNet(addressToVerify: string, domain: string, net: string) {
if (!domain) { return undefined } // Can't validate an empty domain value
const wsNetworkUrl = getWsUrlForNetwork(net)
if(!wsNetworkUrl) {
console.error(`The XRPL TOML Checker does not currently support verifying addresses on ${net}.
Please open an issue to add support for this network.`)
return undefined
}
const api = new Client(wsNetworkUrl)
await api.connect()
let accountInfoResponse
try {
accountInfoResponse = await api.request({
"command": "account_info",
"account": addressToVerify
})
} catch(e) {
console.warn(`failed to look up address ${addressToVerify} on ${net} network"`, e)
return undefined
} finally {
await api.disconnect()
}
if (accountInfoResponse.result.account_data.Domain === undefined) {
console.info(`Address ${addressToVerify} has no Domain defined on-ledger`)
return undefined
}
let decodedDomain
try {
decodedDomain = decodeHex(accountInfoResponse.result.account_data.Domain)
} catch(e) {
console.warn("error decoding domain value", accountInfoResponse.result.account_data.Domain, e)
return undefined
}
if(decodedDomain) {
const doesDomainMatch = decodedDomain === domain
if(!doesDomainMatch) {
console.debug(addressToVerify, ": Domain mismatch ("+decodedDomain+" vs. "+domain+")")
}
return doesDomainMatch
} else {
console.debug(addressToVerify, ": Domain is undefined in settings")
return undefined
}
}
/**
* A formatted list item displaying content from a single field of a toml file.
*
* @param props Field info to display
* @returns A formatted list item
*/
function FieldListItem(props: { fieldName: string, fieldValue: string}) {
return (
<li key={props.fieldName}>
<strong>{props.fieldName}: </strong>
<span className={`fieldName`}>
{props.fieldValue}
</span>
</li>)
}
/**
* Get an array of HTML lists that can be used to display toml data.
* If no data exists or none matches the filter it will return an empty array instead.
*
* @param fields An array of objects to parse into bullet points
* @param filter Optional function to filter displayed fields to only ones which return true.
*/
export async function getListEntries(fields: Object[], filter?: Function, domainToVerify?: string) {
const formattedEntries: JSX.Element[] = []
for(let i = 0; i < fields.length; i++) {
const entry = fields[i]
if(!filter || filter(entry)) {
const fieldNames = Object.keys(entry)
const displayedFields: JSX.Element[] = []
fieldNames.forEach((fieldName) => {
if(entry[fieldName] && Array.isArray(entry[fieldName])) {
const internalList = []
entry[fieldName].forEach((value) => {
internalList.push(
<FieldListItem key={value} fieldName={fieldName} fieldValue={value}/>
)
})
displayedFields.push(<ol key={`ol-${displayedFields.length}`}>{internalList}</ol>)
} else {
displayedFields.push(
<FieldListItem key={fieldName} fieldName={fieldName} fieldValue={entry[fieldName]}/>
)
}
})
const key = `entry-${formattedEntries.length}`
const promises = []
if(domainToVerify) {
const accountEntry = entry as AccountFields
if(accountEntry.address) {
const net = accountEntry.network ?? "main"
const domainIsValid = validateAddressDomainOnNet(accountEntry.address, domainToVerify, net)
domainIsValid.then((wasValidated) => {
if(wasValidated) {
displayedFields.push(
<li className={CLASS_GOOD} key={`${key}-result`}>DOMAIN VALIDATED <i className="fa fa-check-circle"/></li>
)
}
})
promises.push(domainIsValid)
}
}
await Promise.all(promises)
formattedEntries.push((<li key={key}>
<ul className={clsx(domainToVerify && 'mb-3')} key={key + "-ul"}>{displayedFields}</ul>
</li>))
}
}
return formattedEntries
}

View File

@@ -0,0 +1,427 @@
import * as React from 'react';
import { useTranslate } from '@portal/hooks';
import axios, { AxiosError } from "axios";
import { parse } from "smol-toml";
import { getListEntries } from "./ListTomlFields";
import { addNewLogEntry, updateLogEntry, LogEntryItem, LogEntryStatus } from "../components/LogEntry";
import { MetadataField, XrplToml, AccountFields, TOML_PATH } from "./XrplToml";
/**
* Helper to log a list of fields from a toml file or display a relevant error message.
* Will return true if successfully displays at least one field from fields without erroring.
*
* @param setLogEntries A setter to update the logs with the new fields.
* @param headerText The initial message to include as a header for the list.
* @param fields A set of fields to parse and display. May be undefined, but if so,
* this function will simply return false. Simplifies typing.
* @param domainToVerify The domain to check
* @param filterDisplayedFieldsTo Limits the displayed fields to ones which match the predicate.
* @returns True if displayed any fields (after applying any given filters)
*/
async function validateAndDisplayFields(
setLogEntries: React.Dispatch<React.SetStateAction<LogEntryItem[]>>,
headerText: string,
fields?: Object[],
domainToVerify?: string,
filterDisplayedFieldsTo?: Function): Promise<boolean> {
const { translate } = useTranslate()
// If there's no data, do nothing
if(!fields) {
return false
}
// Otherwise display all relevant data in the toml file for these field
if(Array.isArray(fields)) {
let icon = undefined;
const formattedEntries = await getListEntries(fields, filterDisplayedFieldsTo, domainToVerify)
const relevantTomlFieldsExist = formattedEntries.length > 0
if(relevantTomlFieldsExist) {
addNewLogEntry(setLogEntries,
{
message: headerText,
id: headerText,
status: {
followUpMessage: (
<ol>
{formattedEntries}
</ol>
),
icon: icon
}
})
}
return relevantTomlFieldsExist
} else {
// Invalid toml data
addNewLogEntry(setLogEntries, {
message: headerText,
id: headerText,
status: {
icon: {
label: translate("WRONG TYPE - SHOULD BE TABLE-ARRAY"),
type: "ERROR",
}
}
})
return false
}
}
/**
* Check whether a metadata field on a toml file is valid, then display logs with the results.
*
* @param setLogEntries - A setter to update the logs
* @param metadata - Metadata from a toml file being verified
*/
function validateAndDisplayMetadata(
setLogEntries: React.Dispatch<React.SetStateAction<LogEntryItem[]>>,
metadata?: MetadataField) {
const { translate } = useTranslate()
if (metadata) {
const metadataId = 'metadata-log'
const metadataLogEntry = {
message: translate("Metadata section: "),
id: metadataId
}
addNewLogEntry(setLogEntries, metadataLogEntry)
// Uniquely checks if array, instead of if not array
if (Array.isArray(metadata)) {
updateLogEntry(setLogEntries, {...metadataLogEntry, status: {
icon: {
label: translate("WRONG TYPE - SHOULD BE TABLE"),
type: "ERROR",
},
}})
} else {
updateLogEntry(setLogEntries, {...metadataLogEntry, status: {
icon: {
label: translate("FOUND"),
type: "SUCCESS",
},
}})
if (metadata.modified) {
const modifiedLogId = 'modified-date-log'
const modifiedLogEntry = {
message: translate("Modified date: "),
id: modifiedLogId
}
addNewLogEntry(setLogEntries, modifiedLogEntry)
try {
updateLogEntry(setLogEntries, { ...modifiedLogEntry, status: {
icon: {
label: metadata.modified.toISOString(),
type: "SUCCESS",
},
}})
} catch(e) {
updateLogEntry(setLogEntries, { ...modifiedLogEntry, status: {
icon: {
label: translate("INVALID"),
type: "ERROR",
},
}})
}
}
}
}
}
/**
* Read in a toml file and verify it has the proper fields, then display those fields in the logs.
* This is the 3rd step for verifying a wallet, and the 2nd step for verifying a toml file itself.
*
* @param setLogEntries A setter to update the logs.
* @param tomlData Toml data to parse.
* @param addressToVerify The address we're actively looking to verify matches with this toml file.
* @param domain A website to look up further information about the toml file.
* @returns Nothing.
*/
async function parseXRPLToml(
setLogEntries: React.Dispatch<React.SetStateAction<LogEntryItem[]>>,
tomlData,
addressToVerify?: string,
domain?: string) {
const { translate } = useTranslate()
const parsingTomlLogEntry: LogEntryItem = {
message: translate("Parsing TOML data..."),
id: 'parsing-toml-data-log',
}
addNewLogEntry(setLogEntries, parsingTomlLogEntry)
let parsed: XrplToml
try {
parsed = parse(tomlData)
updateLogEntry(setLogEntries, {...parsingTomlLogEntry, status: {
icon: {
label: translate("SUCCESS"),
type: "SUCCESS",
},
}})
} catch(e) {
updateLogEntry(setLogEntries, {...parsingTomlLogEntry, status: {
icon: {
label: e,
type: "ERROR",
},
}})
return
}
validateAndDisplayMetadata(setLogEntries, parsed.METADATA)
const accountHeader = translate("Accounts:")
if(addressToVerify) {
const filterToSpecificAccount = (entry: AccountFields) => entry.address === addressToVerify
const accountFound = await validateAndDisplayFields(
setLogEntries,
accountHeader,
parsed.ACCOUNTS,
undefined,
filterToSpecificAccount)
const statusLogId = 'account-found-status-log'
if(accountFound) {
// Then share whether the domain / account pair as a whole has been validated
addNewLogEntry(setLogEntries, {
message: translate('Account has been found in TOML file and validated.'),
id: statusLogId,
status: {
icon: {
label: translate("DOMAIN VALIDATED"),
type: "SUCCESS",
check: true,
}
}
})
} else {
// We failed to find any entries which match the account we're looking for
addNewLogEntry(setLogEntries, {
message: translate("Account:"),
id: 'toml-account-entry-log',
status: {
icon: {
label: translate("NOT FOUND"),
type: "ERROR"
}
}
})
addNewLogEntry(setLogEntries, {
message: translate("Account not found in TOML file. Domain can not be verified."),
id: statusLogId,
status: {
icon: {
label: translate("VALIDATION FAILED"),
type: "ERROR",
}
}
})
}
} else {
// The final validation message is displayed under the validated account since in this case we're
// verifying a wallet address, not the toml file itself.
await validateAndDisplayFields(setLogEntries, translate(accountHeader), parsed.ACCOUNTS, domain)
// We then display the rest of the toml as additional information
await validateAndDisplayFields(setLogEntries, translate("Validators:"), parsed.VALIDATORS)
await validateAndDisplayFields(setLogEntries, translate("Principals:"), parsed.PRINCIPALS)
await validateAndDisplayFields(setLogEntries, translate("Servers:"), parsed.SERVERS)
await validateAndDisplayFields(setLogEntries, translate("Currencies:"), parsed.CURRENCIES)
}
}
/**
* Convert HTML error odes to status messages to display.
*
* @param status - HTML Error code
* @returns A human readable explanation for the HTML based on error code categories.
*/
function getHttpErrorCode(status?: number) {
let errCode;
if(status === 408) {
errCode = 'TIMEOUT'
} else if(status >= 400 && status < 500) {
errCode = 'CLIENT ERROR'
} else if (status >= 500 && status < 600) {
errCode = 'SERVER ERROR'
} else {
errCode = 'UNKNOWN'
}
return errCode
}
/**
* Extract and parse a toml file from a url derived via domain. If accountToVerify is
* passed in, this specifically verifies that address is in the toml file.
* For verifying a wallet, this is the 2nd step. For verifying a toml file itself, this is the 1st step.
*
* @param setLogEntries - A setter to update the log files.
* @param domain = The main section of a url - ex. validator.xrpl-labs.com
* @param accountToVerify - A wallet to optionally specifically check for.
*/
export async function fetchFile(
setLogEntries: React.Dispatch<React.SetStateAction<LogEntryItem[]>>,
domain: string,
accountToVerify?: string) {
const { translate } = useTranslate()
const url = "https://" + domain + TOML_PATH
const checkUrlId = `check-url-log`
const logEntry = {
message: translate(`Checking ${url} ...`),
id: checkUrlId,
}
addNewLogEntry(setLogEntries, logEntry)
try {
const response = await axios.get(url)
const data: string = response.data
updateLogEntry(setLogEntries, {...logEntry, status: {
icon: {
label: translate("FOUND"),
type: "SUCCESS",
},
}})
// Continue to the next step of verification
parseXRPLToml(setLogEntries, data, accountToVerify, domain)
} catch (e) {
const errorUpdate: LogEntryItem = {...logEntry, status: {
icon: {
label: translate(getHttpErrorCode((e as AxiosError)?.status)),
type: "ERROR",
},
followUpMessage: (<p>
{translate("Check if the file is actually hosted at the URL above, ")
+ translate("check your server's HTTPS settings and certificate, and make sure your server provides the required ")}
<a href="xrp-ledger-toml.html#cors-setup">{translate("CORS header.")}</a>
</p>)
}}
updateLogEntry(setLogEntries, errorUpdate)
}
}
/**
* Helper to display the result of trying to decode the domain decoding.
*
* @param setAccountLogEntries - A setter to update the displayed logs.
*/
function displayDecodedWalletLog(
setAccountLogEntries: React.Dispatch<React.SetStateAction<LogEntryItem[]>>,) {
const { translate } = useTranslate()
const logId = 'decoding-domain-hex'
addNewLogEntry(setAccountLogEntries, {
message: translate('Decoding domain hex'),
id: logId,
status: {
icon: {
label: translate('SUCCESS'),
type: 'SUCCESS',
},
}
})
}
/**
* Decode ascii hex into a string.
*
* @param hex - a hex string encoded in ascii.
* @returns The decoded string
*/
function decodeHexWallet(hex: string): string {
let decodedDomain = '';
for (let i = 0; i < hex.length; i += 2) {
decodedDomain += String.fromCharCode(parseInt(hex.substring(i, i + 2), 16))
}
return decodedDomain
}
/**
* The first step to verify an XRPL Wallet is verified with a toml file.
* Looks up the domain associated with the given accountToVerify and the status on success or failure.
*
* @param accountToVerify
* @param setAccountLogEntries
* @param socket
*/
export function fetchWallet(
setAccountLogEntries: React.Dispatch<React.SetStateAction<LogEntryItem[]>>,
accountToVerify: string,
socket?: WebSocket)
{
const {translate} = useTranslate()
// Reset the logs
setAccountLogEntries([])
const walletLogEntry = {
message: translate(`Checking domain of account`),
id: 'check-domain-account',
}
addNewLogEntry(setAccountLogEntries, walletLogEntry)
const url = "wss://xrplcluster.com"
if (typeof socket !== "undefined" && socket.readyState < 2) {
socket.close()
}
const data = {
"command": "account_info",
"account": accountToVerify,
}
socket = new WebSocket(url)
socket.addEventListener('message', (event) => {
let data;
// Defaults to error to simplify logic later on
let response: LogEntryStatus = {
icon: {
label: translate(`ERROR`),
type: `ERROR`,
},
};
try {
data = JSON.parse(event.data)
if (data.status === 'success') {
if (data.result.account_data.Domain) {
try {
response = {
icon: {
label: translate('SUCCESS'),
type: 'SUCCESS',
},
}
// Continue to the next step of validation
const decodedDomain = decodeHexWallet(data.result.account_data.Domain)
displayDecodedWalletLog(setAccountLogEntries)
fetchFile(setAccountLogEntries, decodedDomain, accountToVerify)
} catch(e) {
console.log(e)
response.followUpMessage = <p>{translate(`Error decoding domain field: ${data.result.account_data.Domain}`)}</p>
}
} else {
response.followUpMessage = <p>{translate("Make sure the account has the Domain field set.")}</p>
}
} else {
response.followUpMessage = <p>{translate("Make sure you are entering a valid XRP Ledger address.")}</p>
}
updateLogEntry(setAccountLogEntries, { ...walletLogEntry, status: response })
} catch {
socket.close()
return false
}
})
socket.addEventListener('open', () => {
socket.send(JSON.stringify(data))
})
}

View File

@@ -0,0 +1,49 @@
export const TOML_PATH = "/.well-known/xrp-ledger.toml"
export interface AccountFields {
address: string,
network: string,
desc: string
}
export interface ValidatorFields {
public_key: string,
network: string,
owner_country: string,
server_country: string,
unl: string
}
export interface PrincipalFields {
name: string,
email: string,
}
export interface ServerFields {
json_rpc: string,
ws: string,
peer: string,
network: string,
}
export interface CurrencyFields {
code: string,
display_decimals: string,
issuer: string,
network: string,
symbol: string
}
export interface MetadataField {
// TODO: There could be other fields here, but this is all the existing code used
modified: Date
}
export interface XrplToml {
ACCOUNTS?: AccountFields[],
VALIDATORS?: ValidatorFields[],
PRINCIPALS?: PrincipalFields[],
SERVERS?: ServerFields[],
CURRENCIES?: CurrencyFields[],
METADATA?: MetadataField
}

View File

@@ -0,0 +1,79 @@
import * as React from 'react';
import { useTranslate } from '@portal/hooks';
import { TextLookupForm, type TextLookupFormProps } from './components/TextLookupForm';
import { fetchFile, fetchWallet } from './toml-checker/ValidateTomlSteps';
import { LogEntryItem } from './components/LogEntry';
/**
* Example data to test the tool with
*
* Domains:
* - Valid: validator.xrpl-labs.com
* - Not valid: sologenic.com
*
* Addresses:
* - Valid: rSTAYKxF2K77ZLZ8GoAwTqPGaphAqMyXV
* - No toml: rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz
* - No domain: rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh
*/
function handleSubmitWallet(
setAccountLogEntries: React.Dispatch<React.SetStateAction<LogEntryItem[]>>,
event: React.FormEvent<HTMLFormElement>,
addressToVerify: string) {
event.preventDefault()
setAccountLogEntries([])
fetchWallet(setAccountLogEntries, addressToVerify)
}
function handleSubmitDomain(
setDomainLogEntries: React.Dispatch<React.SetStateAction<LogEntryItem[]>>,
event: React.FormEvent<HTMLFormElement>,
domainAddress: string) {
event.preventDefault();
setDomainLogEntries([])
fetchFile(setDomainLogEntries, domainAddress)
}
export default function TomlChecker() {
const { translate } = useTranslate();
const domainButtonProps: TextLookupFormProps = {
title: `Look Up By Domain`,
description: <p>{translate(`This tool allows you to verify that your `)}<code>{translate(`xrp-ledger.toml`)}</code>
{translate(` file is syntactically correct and deployed properly.`)}</p>,
buttonDescription: `Check toml file`,
formPlaceholder: "example.com (Domain name to check)",
handleSubmit: handleSubmitDomain,
}
const addressButtonProps: TextLookupFormProps = {
title: `Look Up By Account`,
description: <p>{translate(`Enter an XRP Ledger address to see if that account is claimed by the domain it says owns it.`)}</p>,
buttonDescription: `Check account`,
formPlaceholder: `r... (${translate("Wallet Address to check")})`,
handleSubmit: handleSubmitWallet
}
return (
<div className="toml-checker row">
{/* This aside is empty but it keeps the formatting similar to other pages */}
<aside className="right-sidebar col-lg-3 order-lg-4" role="complementary"/>
<main className="main col-lg-9" role="main" id="main_content_body">
<section className="container-fluid">
<div className="p-3">
<h1>{translate(`xrp-ledger.toml Checker`)}</h1>
<p>{translate(`If you run an XRP Ledger validator or use the XRP Ledger for your business,
you can provide information about your usage of the XRP Ledger to the world in a machine-readable `)}
<a href="https://xrpl.org/xrp-ledger-toml.html"><code>{translate(`xrp-ledger.toml`)}</code>{translate(` file`)}</a>.</p>
</div>
<TextLookupForm {...domainButtonProps} />
<TextLookupForm {...addressButtonProps} />
</section>
</main>
</div>
)
}

View File

@@ -635,6 +635,7 @@
- label: WebSocket API Tool
- label: ripple.txt Validator
- label: xrp-ledger.toml Checker
page: resources/dev-tools/xrp-ledger-toml-checker.page.tsx
- label: Domain Verification Checker
- label: XRP Faucets
page: resources/dev-tools/xrp-faucets.page.tsx

File diff suppressed because one or more lines are too long

View File

@@ -16,6 +16,7 @@ const ACCOUNT_FIELDS = [
const WORKS = 'rSTAYKxF2K77ZLZ8GoAwTqPGaphAqMyXV'
const NOTOML = 'rsoLo2S1kiGeCcn6hCUXVrCpGMWLrRrLZz'
const NODOMAIN = 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh'
// validator.xrpl-labs.com (Works for the domain side)
let socket;

View File

@@ -4,6 +4,7 @@ import { FC } from 'react'
import { useEffect, useState } from 'react'
import React = require('react');
import XRPLoader from '../components/XRPLoader';
import * as xrpl from 'xrpl'
export const MIN_LOADER_MS = 1250
export const DEFAULT_TIMEOUT = 1000
@@ -65,7 +66,6 @@ export const XRPLGuard: FC<{ testCheck?: () => boolean, children }> = ({
const { translate } = useTranslate();
const isXRPLLoaded = useThrottledCheck(
// @ts-expect-error - xrpl is added via a script tag (TODO: Directly import when xrpl.js 3.0 is released)
testCheck ?? (() => typeof xrpl === 'object'),
MIN_LOADER_MS,
)

65
package-lock.json generated
View File

@@ -15,12 +15,14 @@
"@redocly/realm": "0.69.4",
"@uiw/codemirror-themes": "4.21.21",
"@uiw/react-codemirror": "^4.21.21",
"axios": "^1.6.2",
"clsx": "^2.0.0",
"lottie-react": "^2.4.0",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-alert": "^7.0.3",
"react18-json-view": "^0.2.6",
"smol-toml": "^1.1.3",
"xrpl": "^3.0.0-beta.1"
},
"devDependencies": {
@@ -2994,29 +2996,6 @@
"@swagger-api/apidom-parser-adapter-yaml-1-2": "^0.87.0"
}
},
"node_modules/@swagger-api/apidom-reference/node_modules/axios": {
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
"dependencies": {
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/@swagger-api/apidom-reference/node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/@tsconfig/node10": {
"version": "1.0.9",
"resolved": "https://registry.npmjs.org/@tsconfig/node10/-/node10-1.0.9.tgz",
@@ -3570,11 +3549,26 @@
"dev": true
},
"node_modules/axios": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"version": "1.6.2",
"resolved": "https://registry.npmjs.org/axios/-/axios-1.6.2.tgz",
"integrity": "sha512-7i24Ri4pmDRfJTR7LDBhsOTtcm+9kjX5WiY1X3wIisx6G9So3pfMkEiU7emUBe46oceVImccTEM3k6C5dbVW8A==",
"dependencies": {
"follow-redirects": "^1.14.8"
"follow-redirects": "^1.15.0",
"form-data": "^4.0.0",
"proxy-from-env": "^1.1.0"
}
},
"node_modules/axios/node_modules/form-data": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
"integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
"dependencies": {
"asynckit": "^0.4.0",
"combined-stream": "^1.0.8",
"mime-types": "^2.1.12"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/babel-jest": {
@@ -10148,6 +10142,15 @@
"node": ">=8.0.0"
}
},
"node_modules/smol-toml": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/smol-toml/-/smol-toml-1.1.3.tgz",
"integrity": "sha512-qTyy6Owjho1ISBmxj4HdrFWB2kMQ5RczU6J04OqslSfdSH656OIHuomHS4ZDvhwm37nig/uXyiTMJxlC9zIVfw==",
"engines": {
"node": ">= 18",
"pnpm": ">= 8"
}
},
"node_modules/source-map": {
"version": "0.5.7",
"resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz",
@@ -10903,6 +10906,14 @@
"@babel/runtime": "^7.17.2"
}
},
"node_modules/typesense/node_modules/axios": {
"version": "0.26.1",
"resolved": "https://registry.npmjs.org/axios/-/axios-0.26.1.tgz",
"integrity": "sha512-fPwcX4EvnSHuInCMItEhAGnaSEXRBjtzh9fOtsE6E1G6p7vl7edEeZe11QHf18+6+9gR5PbKV/sGKNaD8YaMeA==",
"dependencies": {
"follow-redirects": "^1.14.8"
}
},
"node_modules/uc.micro": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/uc.micro/-/uc.micro-1.0.6.tgz",

View File

@@ -18,12 +18,14 @@
"@redocly/realm": "0.69.4",
"@uiw/codemirror-themes": "4.21.21",
"@uiw/react-codemirror": "^4.21.21",
"axios": "^1.6.2",
"clsx": "^2.0.0",
"lottie-react": "^2.4.0",
"moment": "^2.29.4",
"react": "^18.2.0",
"react-alert": "^7.0.3",
"react18-json-view": "^0.2.6",
"smol-toml": "^1.1.3",
"xrpl": "^3.0.0-beta.1"
},
"overrides": {

View File

@@ -1,8 +0,0 @@
.toml-checker {
#result {
display: none;
}
#verify-domain-result {
display: none;
}
}

View File

@@ -69,7 +69,6 @@ $line-height-base: 1.5;
@import "_video.scss";
@import "_contribute.scss";
// @import "_top-banner.scss";
@import "_toml-checker.scss";
@import "_tutorials.scss";
@import "_docs-landing.scss";
@import "_xrplai.scss";