mirror of
https://github.com/XRPLF/xrpl-dev-portal.git
synced 2025-11-28 23:55:49 +00:00
Migrate the faucet page to Redocly (#2243)
* Get basic HTML loading for faucet page * Add xrpl.js implementation * Add sidebar and fix throbber * Add translates * Try to format sidebar * Fix formatting * Support xrpl.js * Fix links * Comment out XRPLGuard for now * Make AMM Devnet faucet work * Improve readability * Update all instances of link + fix topnav * Remove unnecessary file * Use a more current version of xrpl * Add missing loader while keys are generating * Type with xrpl and remove unnecessary script * Use string interpolation instead of multiple trans * Move faucets into a json file * Remove the old faucet code * Use xrpl-beta directly * Use dropsToXRP * Support hooks natively * Remove AMM-Devnet * Revert changes to link path * Revert link changes pt 2 * Revert pt 3 * Use XRPLoader for loading icon * Fix small mistakes * Remove unnecessary changes
This commit is contained in:
28
content/dev-tools/faucets.json
Normal file
28
content/dev-tools/faucets.json
Normal file
@@ -0,0 +1,28 @@
|
||||
{
|
||||
"knownFaucets": [
|
||||
{
|
||||
"id": "faucet-select-testnet",
|
||||
"wsUrl": "wss://s.altnet.rippletest.net:51233/",
|
||||
"jsonRpcUrl": "https://s.altnet.rippletest.net:51234/",
|
||||
"faucetUrl": "faucet.altnet.rippletest.net",
|
||||
"shortName": "Testnet",
|
||||
"desc": "Mainnet-like network for testing applications."
|
||||
},
|
||||
{
|
||||
"id": "faucet-select-devnet",
|
||||
"wsUrl": "wss://s.devnet.rippletest.net:51233/",
|
||||
"jsonRpcUrl": "https://s.devnet.rippletest.net:51234/",
|
||||
"faucetUrl": "faucet.devnet.rippletest.net",
|
||||
"shortName": "Devnet",
|
||||
"desc": "Preview of upcoming amendments."
|
||||
},
|
||||
{
|
||||
"id": "faucet-select-xahau",
|
||||
"wsUrl": "wss://xahau-test.net/",
|
||||
"jsonRpcUrl": "https://xahau-test.net/",
|
||||
"faucetUrl": "xahau-test.net",
|
||||
"shortName": "Xahau-Testnet",
|
||||
"desc": "Hooks (L1 smart contracts) enabled Xahau testnet."
|
||||
}
|
||||
]
|
||||
}
|
||||
203
content/dev-tools/xrp-faucets.page.tsx
Normal file
203
content/dev-tools/xrp-faucets.page.tsx
Normal file
@@ -0,0 +1,203 @@
|
||||
import * as React from 'react';
|
||||
import { useTranslate } from '@portal/hooks';
|
||||
import { useState } from 'react';
|
||||
import { Client, dropsToXrp, Wallet } from 'xrpl';
|
||||
import * as faucetData from './faucets.json'
|
||||
import XRPLoader from 'content/static/components/XRPLoader';
|
||||
|
||||
interface FaucetInfo {
|
||||
id: string,
|
||||
wsUrl: string,
|
||||
jsonRpcUrl: string,
|
||||
faucetUrl: string,
|
||||
shortName: string,
|
||||
desc: string,
|
||||
}
|
||||
|
||||
async function waitForSequence(client: Client, address: string):
|
||||
Promise<{ sequence: string, balance: string }>
|
||||
{
|
||||
let response;
|
||||
while (true) {
|
||||
try {
|
||||
response = await client.request({
|
||||
command: "account_info",
|
||||
account: address,
|
||||
ledger_index: "validated"
|
||||
})
|
||||
break
|
||||
} catch(e) {
|
||||
await new Promise(resolve => setTimeout(resolve, 1000))
|
||||
}
|
||||
}
|
||||
console.log(response)
|
||||
|
||||
return { sequence: response.result.account_data.Sequence, balance: response.result.account_data.Balance}
|
||||
}
|
||||
|
||||
function FaucetEndpoints({ faucet, givenKey } : { faucet: FaucetInfo, givenKey: string}) {
|
||||
const { translate } = useTranslate();
|
||||
|
||||
return (<div key={givenKey}>
|
||||
<h4>{translate(`${faucet.shortName} Servers`)}</h4>
|
||||
<pre>
|
||||
<code>
|
||||
// WebSocket<br/>
|
||||
{faucet.wsUrl}<br/>
|
||||
<br/>
|
||||
// JSON-RPC<br/>
|
||||
{faucet.jsonRpcUrl}
|
||||
</code>
|
||||
</pre>
|
||||
</div>)
|
||||
}
|
||||
|
||||
function FaucetSidebar({ faucets }: { faucets: FaucetInfo[] }): React.JSX.Element {
|
||||
return (<aside className="right-sidebar col-lg-6 order-lg-4" role="complementary">
|
||||
{faucets.map(
|
||||
(faucet) => <FaucetEndpoints faucet={faucet} key={faucet.shortName + " Endpoints"} givenKey={faucet.shortName + " Endpoints"}/>
|
||||
)}
|
||||
</aside>)
|
||||
}
|
||||
|
||||
export default function XRPFaucets(): React.JSX.Element {
|
||||
const { translate } = useTranslate();
|
||||
|
||||
const faucets: FaucetInfo[] = faucetData.knownFaucets
|
||||
|
||||
const [selectedFaucet, setSelectedFaucet] = useState(faucets[0])
|
||||
|
||||
return (
|
||||
<div className="container-fluid" role="document" id="main_content_wrapper">
|
||||
<div className="row">
|
||||
<FaucetSidebar faucets={faucets}/>
|
||||
<main className="main col-md-7 col-lg-6 order-md-3" role="main" id="main_content_body">
|
||||
<section className="container-fluid pt-3 p-md-3">
|
||||
<h1>{translate("XRP Faucets")}</h1>
|
||||
<div className="content">
|
||||
<p>{translate("These ")}<a href="parallel-networks.html">{translate("parallel XRP Ledger test networks")}</a> {translate("provide platforms for testing changes to the XRP Ledger and software built on it, without using real funds.")}</p>
|
||||
<p>{translate("These funds are intended for")} <strong>{translate("testing")}</strong> {translate("only. Test networks' ledger history and balances are reset as necessary. Devnets may be reset without warning.")}</p>
|
||||
<p>{translate("All balances and XRP on these networks are separate from Mainnet. As a precaution, do not use the Testnet or Devnet credentials on the Mainnet.")}</p>
|
||||
|
||||
<h3>{translate("Choose Network:")}</h3>
|
||||
{ faucets.map((net) => (
|
||||
<div className="form-check" key={"network-" + net.shortName}>
|
||||
<input onChange={() => setSelectedFaucet(net)} className="form-check-input" type="radio"
|
||||
name="faucet-selector" id={net.id} checked={selectedFaucet.shortName == net.shortName} />
|
||||
<label className="form-check-label" htmlFor={net.id}>
|
||||
<strong>{translate(net.shortName)}</strong>: {translate(net.desc)}
|
||||
</label>
|
||||
</div>
|
||||
)) }
|
||||
|
||||
<br/>
|
||||
<TestCredentials selectedFaucet={selectedFaucet}/>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
|
||||
async function generateFaucetCredentialsAndUpdateUI(
|
||||
selectedFaucet: FaucetInfo,
|
||||
setButtonClicked: React.Dispatch<React.SetStateAction<boolean>>,
|
||||
setGeneratedCredentialsFaucet: React.Dispatch<React.SetStateAction<string>>,
|
||||
setAddress: React.Dispatch<React.SetStateAction<string>>,
|
||||
setSecret: React.Dispatch<React.SetStateAction<string>>,
|
||||
setBalance: React.Dispatch<React.SetStateAction<string>>,
|
||||
setSequence: React.Dispatch<React.SetStateAction<string>>): Promise<void> {
|
||||
|
||||
setButtonClicked(true)
|
||||
|
||||
// Clear existing credentials
|
||||
setGeneratedCredentialsFaucet(selectedFaucet.shortName)
|
||||
setAddress("")
|
||||
setSecret("")
|
||||
setBalance("")
|
||||
setSequence("")
|
||||
const { translate } = useTranslate();
|
||||
|
||||
|
||||
const wallet = Wallet.generate()
|
||||
|
||||
const client = new Client(selectedFaucet.wsUrl)
|
||||
await client.connect()
|
||||
|
||||
try {
|
||||
setAddress(wallet.address)
|
||||
setSecret(wallet.seed)
|
||||
|
||||
await client.fundWallet(wallet, { faucetHost: selectedFaucet.faucetUrl, usageContext: "xrpl.org-faucet" })
|
||||
|
||||
const response = await waitForSequence(client, wallet.address)
|
||||
|
||||
setSequence(response.sequence)
|
||||
setBalance(response.balance)
|
||||
|
||||
} catch (e) {
|
||||
alert(translate(`There was an error with the ${selectedFaucet.shortName} faucet. Please try again.`))
|
||||
}
|
||||
setButtonClicked(false)
|
||||
}
|
||||
|
||||
function TestCredentials({selectedFaucet}) {
|
||||
const { translate } = useTranslate();
|
||||
|
||||
const [generatedCredentialsFaucet, setGeneratedCredentialsFaucet] = useState("")
|
||||
const [address, setAddress] = useState("")
|
||||
const [secret, setSecret] = useState("")
|
||||
const [balance, setBalance] = useState("")
|
||||
const [sequence, setSequence] = useState("")
|
||||
const [buttonClicked, setButtonClicked] = useState(false)
|
||||
|
||||
return (<div>
|
||||
{/* <XRPLGuard> TODO: Re-add this once we find a good way to avoid browser/server mismatch errors */}
|
||||
<div className="btn-toolbar" role="toolbar" aria-label="Button">
|
||||
<button id="generate-creds-button" onClick={
|
||||
() => generateFaucetCredentialsAndUpdateUI(
|
||||
selectedFaucet,
|
||||
setButtonClicked,
|
||||
setGeneratedCredentialsFaucet,
|
||||
setAddress,
|
||||
setSecret,
|
||||
setBalance,
|
||||
setSequence)
|
||||
} className="btn btn-primary mr-2 mb-2">
|
||||
{translate(`Generate ${selectedFaucet.shortName} credentials`)}
|
||||
</button>
|
||||
</div>
|
||||
{/* </XRPLGuard> */}
|
||||
|
||||
|
||||
{generatedCredentialsFaucet && <div id="your-credentials">
|
||||
<h2>{translate(`Your ${generatedCredentialsFaucet} Credentials`)}</h2>
|
||||
</div>}
|
||||
|
||||
{(buttonClicked && address === "") && <XRPLoader message={translate("Generating keys..")}/>}
|
||||
|
||||
{address && <div id="address"><h3>{translate("Address")}</h3>{address}</div>}
|
||||
|
||||
{secret && <div id="secret"><h3>{translate("Secret")}</h3>{secret}</div>}
|
||||
|
||||
{(address && !balance) && (<div>
|
||||
<br/>
|
||||
<XRPLoader message={translate("Funding account...")}/>
|
||||
</div>)}
|
||||
|
||||
{balance && <div id="balance">
|
||||
<h3>{translate("Balance")}</h3>
|
||||
{dropsToXrp(balance).toLocaleString("en")} {translate("XRP")}
|
||||
</div>}
|
||||
|
||||
{sequence && <div id="sequence">
|
||||
<h3>{translate("Sequence Number")}</h3>
|
||||
{sequence}
|
||||
</div>}
|
||||
|
||||
{(secret && !sequence) && <XRPLoader message={translate("Waiting...")}/>}
|
||||
|
||||
</div>
|
||||
)
|
||||
}
|
||||
@@ -667,6 +667,8 @@
|
||||
- label: xrp-ledger.toml Checker
|
||||
- label: Domain Verification Checker
|
||||
- label: XRP Faucets
|
||||
href: /dev-tools/xrp-faucets
|
||||
page: /dev-tools/xrp-faucets.page.tsx
|
||||
- label: Transaction Sender
|
||||
- label: XRPL Learning Portal
|
||||
href: https://learn.xrpl.org/
|
||||
|
||||
13
content/static/components/XRPLoader.tsx
Normal file
13
content/static/components/XRPLoader.tsx
Normal file
@@ -0,0 +1,13 @@
|
||||
import * as React from 'react';
|
||||
|
||||
export interface XRPLoaderProps {
|
||||
message?: string
|
||||
}
|
||||
|
||||
export default function XRPLoader(props: XRPLoaderProps) {
|
||||
return (
|
||||
<div id="loader" style={{ display: "inline" }}>
|
||||
<img alt="(loading)" className="throbber" src="/img/xrp-loader-96.png" />
|
||||
{props.message}
|
||||
</div>);
|
||||
}
|
||||
27
content/static/js/type-helpers.ts
Normal file
27
content/static/js/type-helpers.ts
Normal file
@@ -0,0 +1,27 @@
|
||||
type TypeofType =
|
||||
| 'bigint'
|
||||
| 'boolean'
|
||||
| 'function'
|
||||
| 'number'
|
||||
| 'object'
|
||||
| 'string'
|
||||
| 'undefined'
|
||||
|
||||
type TypeCheckFn = (thing: unknown) => boolean
|
||||
|
||||
|
||||
/**
|
||||
* Curried function for creating typeof checker functions.
|
||||
* @param {string} type The type to check against (eg 'string', 'number')
|
||||
* @param {function} [secondaryTest] Optional additional test function to run in cases where a type match isn't always a sure indicator.
|
||||
* @returns {boolean} Whether the value matches the type
|
||||
*/
|
||||
const isTypeof =
|
||||
<T>(type: TypeofType, secondaryTest?: TypeCheckFn) =>
|
||||
(thing: unknown): thing is T => {
|
||||
const matches = typeof thing === type
|
||||
if (matches && secondaryTest) return secondaryTest(thing)
|
||||
return matches
|
||||
}
|
||||
|
||||
export const isFunction = isTypeof<(...args: unknown[]) => unknown>('function')
|
||||
85
content/static/js/xrpl-guard.tsx
Normal file
85
content/static/js/xrpl-guard.tsx
Normal file
@@ -0,0 +1,85 @@
|
||||
import { useTranslate } from '@portal/hooks';
|
||||
import { isFunction } from './type-helpers'
|
||||
import { FC } from 'react'
|
||||
import { useEffect, useState } from 'react'
|
||||
import React = require('react');
|
||||
import XRPLoader from '../components/XRPLoader';
|
||||
|
||||
export const MIN_LOADER_MS = 1250
|
||||
export const DEFAULT_TIMEOUT = 1000
|
||||
|
||||
const sleep = (delay) => new Promise((resolve) => setTimeout(resolve, delay))
|
||||
|
||||
/**
|
||||
* Evaluate a check function will eventually resolve to `true`
|
||||
*
|
||||
* If check is initially true, immediatly return `isTrue`
|
||||
* If check is initially false and becomes true, return true after `timeoutMs`
|
||||
*/
|
||||
export const useThrottledCheck = (
|
||||
check: () => boolean,
|
||||
timeoutMs = DEFAULT_TIMEOUT,
|
||||
) => {
|
||||
const [isTrue, setIsTrue] = useState(() => check())
|
||||
|
||||
useEffect(() => {
|
||||
const doCheck = async (tries = 0) => {
|
||||
const waitMs = 250,
|
||||
waitedMs = tries * waitMs
|
||||
|
||||
if (check()) {
|
||||
const debouncedDelay =
|
||||
waitedMs < timeoutMs ? timeoutMs - (waitedMs % timeoutMs) : 0
|
||||
|
||||
setTimeout(() => setIsTrue(true), debouncedDelay)
|
||||
return
|
||||
}
|
||||
|
||||
await sleep(waitMs)
|
||||
|
||||
doCheck(tries + 1)
|
||||
}
|
||||
|
||||
if (!isTrue) {
|
||||
doCheck()
|
||||
}
|
||||
}, [check, isTrue])
|
||||
|
||||
return isTrue
|
||||
}
|
||||
|
||||
/**
|
||||
* Show a loading spinner if XRPL isn't loaded yet by
|
||||
* waiting at least MIN_LOADER_MS before rendering children
|
||||
* in order to make the visual loading transition smooth
|
||||
*
|
||||
* e.g. if xrpl loads after 500ms, wait
|
||||
* another MIN_LOADER_MS - 500ms before rendering children
|
||||
*
|
||||
* @param {function} testCheck for testing only, a check function to use
|
||||
*/
|
||||
export const XRPLGuard: FC<{ testCheck?: () => boolean, children }> = ({
|
||||
testCheck,
|
||||
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,
|
||||
)
|
||||
|
||||
return (
|
||||
<>
|
||||
{isXRPLLoaded ? (
|
||||
isFunction(children) ? (
|
||||
children()
|
||||
) : (
|
||||
children
|
||||
)
|
||||
) : <XRPLoader message={translate("Loading...")}/>
|
||||
}
|
||||
</>
|
||||
)
|
||||
}
|
||||
@@ -67,7 +67,8 @@
|
||||
- label: Send XRP
|
||||
href: /tutorials/send-xrp/
|
||||
- label: XRP Faucets
|
||||
href: /docs/dev-tools/xrp-faucets/
|
||||
href: /dev-tools/xrp-faucets
|
||||
page: /dev-tools/xrp-faucets.page.tsx
|
||||
- label: XRPL Servers
|
||||
href: /infrastructure/xrpl-servers/
|
||||
- label: Dev Tools
|
||||
|
||||
Reference in New Issue
Block a user