-
Notifications
You must be signed in to change notification settings - Fork 1.1k
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
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
- Loading branch information
Showing
9 changed files
with
363 additions
and
2 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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." | ||
} | ||
] | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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>); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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') |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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...")}/> | ||
} | ||
</> | ||
) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters