diff --git a/packages/xrpl/HISTORY.md b/packages/xrpl/HISTORY.md index 0714983e50..acf56d238f 100644 --- a/packages/xrpl/HISTORY.md +++ b/packages/xrpl/HISTORY.md @@ -8,9 +8,10 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr * Support for `NFTokenMintOffer` (XLS-52) ### Fixed -* `OracleSet` transaction accepts hexadecimal string values for `AssetPrice` field +* Fix `OracleSet` transaction to accept hexadecimal string values for `AssetPrice` field * `TransactionStream` model includes `hash` field in APIv2 * `TransactionStream` model includes `close_time_iso` field only for APIv2 +* Better faucet support ## 4.2.0 (2025-2-13) diff --git a/packages/xrpl/src/Wallet/defaultFaucets.ts b/packages/xrpl/src/Wallet/defaultFaucets.ts index bf5c38ae51..9281a45334 100644 --- a/packages/xrpl/src/Wallet/defaultFaucets.ts +++ b/packages/xrpl/src/Wallet/defaultFaucets.ts @@ -16,34 +16,37 @@ export enum FaucetNetwork { Devnet = 'faucet.devnet.rippletest.net', } -export const FaucetNetworkPaths: Record = { +export const faucetNetworkPaths: Record = { [FaucetNetwork.Testnet]: '/accounts', [FaucetNetwork.Devnet]: '/accounts', } +export const faucetNetworkIDs: Map = new Map([ + [1, FaucetNetwork.Testnet], + [2, FaucetNetwork.Devnet], +]) + /** * Get the faucet host based on the Client connection. * * @param client - Client. * @returns A {@link FaucetNetwork}. - * @throws When the client url is not on altnet or devnet. + * @throws When there is no known faucet for the client's network ID. */ export function getFaucetHost(client: Client): FaucetNetwork | undefined { - const connectionUrl = client.url - - // 'altnet' for Ripple Testnet server and 'testnet' for XRPL Labs Testnet server - if (connectionUrl.includes('altnet') || connectionUrl.includes('testnet')) { - return FaucetNetwork.Testnet - } - - if (connectionUrl.includes('sidechain-net2')) { + if (client.networkID == null) { throw new XRPLFaucetError( - 'Cannot fund an account on an issuing chain. Accounts must be created via the bridge.', + 'Cannot create faucet URL without networkID or the faucetHost information', ) } - if (connectionUrl.includes('devnet')) { - return FaucetNetwork.Devnet + if (faucetNetworkIDs.has(client.networkID)) { + return faucetNetworkIDs.get(client.networkID) + } + + if (client.networkID === 0) { + // mainnet does not have a faucet + throw new XRPLFaucetError('Faucet is not available for mainnet.') } throw new XRPLFaucetError('Faucet URL is not defined or inferrable.') @@ -54,11 +57,11 @@ export function getFaucetHost(client: Client): FaucetNetwork | undefined { * * @param hostname - hostname. * @returns A String with the correct path for the input hostname. - * If hostname undefined or cannot find (key, value) pair in {@link FaucetNetworkPaths}, defaults to '/accounts' + * If hostname undefined or cannot find (key, value) pair in {@link faucetNetworkPaths}, defaults to '/accounts' */ -export function getDefaultFaucetPath(hostname: string | undefined): string { +export function getFaucetPath(hostname: string | undefined): string { if (hostname === undefined) { return '/accounts' } - return FaucetNetworkPaths[hostname] || '/accounts' + return faucetNetworkPaths[hostname] || '/accounts' } diff --git a/packages/xrpl/src/Wallet/fundWallet.ts b/packages/xrpl/src/Wallet/fundWallet.ts index 2a9560c064..9db1f06cc4 100644 --- a/packages/xrpl/src/Wallet/fundWallet.ts +++ b/packages/xrpl/src/Wallet/fundWallet.ts @@ -3,11 +3,7 @@ import { isValidClassicAddress } from 'ripple-address-codec' import type { Client } from '../client' import { XRPLFaucetError } from '../errors' -import { - FaucetWallet, - getFaucetHost, - getDefaultFaucetPath, -} from './defaultFaucets' +import { FaucetWallet, getFaucetHost, getFaucetPath } from './defaultFaucets' import { Wallet } from '.' @@ -148,7 +144,7 @@ export async function requestFunding( if (!hostname) { throw new XRPLFaucetError('No faucet hostname could be derived') } - const pathname = options.faucetPath ?? getDefaultFaucetPath(hostname) + const pathname = options.faucetPath ?? getFaucetPath(hostname) const response = await fetch(`https://${hostname}${pathname}`, { method: 'POST', headers: { @@ -190,31 +186,24 @@ async function processSuccessfulResponse( new XRPLFaucetError(`The faucet account is undefined`), ) } - try { - // Check at regular interval if the address is enabled on the XRPL and funded - const updatedBalance = await getUpdatedBalance( - client, - classicAddress, - startingBalance, - ) + // Check at regular interval if the address is enabled on the XRPL and funded + const updatedBalance = await getUpdatedBalance( + client, + classicAddress, + startingBalance, + ) - if (updatedBalance > startingBalance) { - return { - wallet: walletToFund, - balance: updatedBalance, - } + if (updatedBalance > startingBalance) { + return { + wallet: walletToFund, + balance: updatedBalance, } - throw new XRPLFaucetError( - `Unable to fund address with faucet after waiting ${ - INTERVAL_SECONDS * MAX_ATTEMPTS - } seconds`, - ) - } catch (err) { - if (err instanceof Error) { - throw new XRPLFaucetError(err.message) - } - throw err } + throw new XRPLFaucetError( + `Unable to fund address with faucet after waiting ${ + INTERVAL_SECONDS * MAX_ATTEMPTS + } seconds`, + ) } async function processError(response: Response, body): Promise { diff --git a/packages/xrpl/test/wallet/fundWallet.test.ts b/packages/xrpl/test/wallet/fundWallet.test.ts index 3e061faad8..777c60d13e 100644 --- a/packages/xrpl/test/wallet/fundWallet.test.ts +++ b/packages/xrpl/test/wallet/fundWallet.test.ts @@ -2,9 +2,9 @@ import { assert } from 'chai' import { FaucetNetwork, - FaucetNetworkPaths, + faucetNetworkPaths, getFaucetHost, - getDefaultFaucetPath, + getFaucetPath, } from '../../src/Wallet/defaultFaucets' import { setupClient, @@ -22,35 +22,24 @@ describe('Get Faucet host ', function () { it('returns the Devnet host', function () { const expectedFaucet = FaucetNetwork.Devnet - // @ts-expect-error Intentionally modifying private data for test - testContext.client.connection.url = FaucetNetwork.Devnet + testContext.client.networkID = 2 assert.strictEqual(getFaucetHost(testContext.client), expectedFaucet) }) it('returns the Testnet host', function () { const expectedFaucet = FaucetNetwork.Testnet - // @ts-expect-error Intentionally modifying private data for test - testContext.client.connection.url = FaucetNetwork.Testnet - - assert.strictEqual(getFaucetHost(testContext.client), expectedFaucet) - }) - - it('returns the Testnet host with the XRPL Labs server', function () { - const expectedFaucet = FaucetNetwork.Testnet - // @ts-expect-error Intentionally modifying private data for test - testContext.client.connection.url = 'wss://testnet.xrpl-labs.com' + testContext.client.networkID = 1 assert.strictEqual(getFaucetHost(testContext.client), expectedFaucet) }) it('returns the correct faucetPath for Devnet host', function () { - const expectedFaucetPath = FaucetNetworkPaths[FaucetNetwork.Devnet] - // @ts-expect-error Intentionally modifying private data for test - testContext.client.connection.url = FaucetNetwork.Devnet + const expectedFaucetPath = faucetNetworkPaths[FaucetNetwork.Devnet] + testContext.client.networkID = 2 assert.strictEqual( - getDefaultFaucetPath(getFaucetHost(testContext.client)), + getFaucetPath(getFaucetHost(testContext.client)), expectedFaucetPath, ) }) @@ -58,11 +47,16 @@ describe('Get Faucet host ', function () { it('returns the correct faucetPath for undefined host', function () { const expectedFaucetPath = '/accounts' - assert.strictEqual(getDefaultFaucetPath(undefined), expectedFaucetPath) + assert.strictEqual(getFaucetPath(undefined), expectedFaucetPath) + }) + + it('throws if connected to mainnet', function () { + testContext.client.networkID = 0 + assert.throws(() => getFaucetHost(testContext.client)) }) it('throws if not connected to a known faucet host', function () { - // Info: setupClient.setup creates a connection to 'localhost' + testContext.client.networkID = 300 assert.throws(() => getFaucetHost(testContext.client)) }) })