Skip to content

Commit

Permalink
fix: make docs not output confusing information in xrpl client (#2337)
Browse files Browse the repository at this point in the history
* fix: make docs not output confusing information in xrpl client

---------

Co-authored-by: Jackson Mills <[email protected]>
Co-authored-by: Caleb Kniffen <[email protected]>
  • Loading branch information
3 people committed Aug 15, 2023
1 parent 5f6923e commit 528b17e
Show file tree
Hide file tree
Showing 29 changed files with 1,622 additions and 844 deletions.
14 changes: 14 additions & 0 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

2 changes: 2 additions & 0 deletions packages/xrpl/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -33,13 +33,15 @@
"xrpl-secret-numbers": "^0.3.3"
},
"devDependencies": {

"@types/node": "^16.18.38",
"https-proxy-agent": "^7.0.1",
"karma": "^6.4.1",
"karma-chrome-launcher": "^3.1.1",
"karma-jasmine": "^5.1.0",
"karma-webpack": "^5.0.0",
"lodash": "^4.17.4",
"run-s": "^0.0.0",
"react": "^18.2.0",
"typedoc": "^0.24.8"
},
Expand Down
8 changes: 4 additions & 4 deletions packages/xrpl/snippets/src/getTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
import { Client, LedgerResponse, TxResponse } from '../../src'
import { Client } from '../../src'

const client = new Client('wss://s2.ripple.com:51233')

async function getTransaction(): Promise<void> {
await client.connect()
const ledger: LedgerResponse = await client.request({
const ledger = await client.request({
command: 'ledger',
transactions: true,
ledger_index: 'validated',
})
console.log(ledger)

const transactions = ledger.result.ledger.transactions
if (transactions) {
const tx: TxResponse = await client.request({
if (transactions && transactions.length > 0) {
const tx = await client.request({
command: 'tx',
transaction: transactions[0],
})
Expand Down
9 changes: 4 additions & 5 deletions packages/xrpl/snippets/src/paths.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Client, Payment, RipplePathFindResponse } from '../../src'
import { Client, Payment } from '../../src'

const client = new Client('wss://s.altnet.rippletest.net:51233')

Expand All @@ -15,7 +15,8 @@ async function createTxWithPaths(): Promise<void> {
issuer: 'rVnYNK9yuxBz4uP8zC8LEFokM2nqH3poc',
}

const request = {
const resp = await client.request({
// TOOD: Replace with path_find - https://github.com/XRPLF/xrpl.js/issues/2385
command: 'ripple_path_find',
source_account: wallet.classicAddress,
source_currencies: [
Expand All @@ -25,9 +26,7 @@ async function createTxWithPaths(): Promise<void> {
],
destination_account,
destination_amount,
}

const resp: RipplePathFindResponse = await client.request(request)
})
console.log(resp)

const paths = resp.result.alternatives[0].paths_computed
Expand Down
164 changes: 76 additions & 88 deletions packages/xrpl/src/Wallet/fundWallet.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import fetch from 'cross-fetch'
import { isValidClassicAddress } from 'ripple-address-codec'

import type { Client } from '../client'
import { RippledError, XRPLFaucetError } from '../errors'
import { XRPLFaucetError } from '../errors'

import {
FaucetWallet,
Expand Down Expand Up @@ -45,107 +45,97 @@ export interface FundingOptions {
usageContext?: string
}

interface FaucetRequestBody {
/**
* Parameters to pass into a faucet request to fund an XRP account.
*/
export interface FaucetRequestBody {
/**
* The address to fund. If no address is provided the faucet will fund a random account.
*/
destination?: string
/**
* The total amount of XRP to fund the account with.
*/
xrpAmount?: string
/**
* An optional field to indicate the use case context of the faucet transaction
* Ex: integration test, code snippets.
*/
usageContext?: string
/**
* Information about the context of where the faucet is being called from.
* Ex: xrpl.js or xrpl-py
*/
userAgent: string
}

/**
* The fundWallet() method is used to send an amount of XRP (usually 1000) to a new (randomly generated)
* or existing XRP Ledger wallet.
*
* @example
*
* Example 1: Fund a randomly generated wallet
* const { Client, Wallet } = require('xrpl')
*
* const client = new Client('wss://s.altnet.rippletest.net:51233')
* await client.connect()
* const { balance, wallet } = await client.fundWallet()
*
* Under the hood, this will use `Wallet.generate()` to create a new random wallet, then ask a testnet faucet
* To send it XRP on ledger to make it a real account. If successful, this will return the new account balance in XRP
* Along with the Wallet object to track the keys for that account. If you'd like, you can also re-fill an existing
* Account by passing in a Wallet you already have.
* ```ts
* const api = new xrpl.Client("wss://s.altnet.rippletest.net:51233")
* await api.connect()
* const { wallet, balance } = await api.fundWallet()
* ```
*
* Example 2: Fund wallet using a custom faucet host and known wallet address
* Generate a new wallet to fund if no existing wallet is provided or its address is invalid.
*
* `fundWallet` will try to infer the url of a faucet API from the network your client is connected to.
* There are hardcoded default faucets for popular test networks like testnet and devnet.
* However, if you're working with a newer or more obscure network, you may have to specify the faucetHost
* And faucetPath so `fundWallet` can ask that faucet to fund your wallet.
*
* ```ts
* const newWallet = Wallet.generate()
* const { balance, wallet } = await client.fundWallet(newWallet, {
* amount: '10',
* faucetHost: 'https://custom-faucet.example.com',
* faucetPath: '/accounts'
* })
* console.log(`Sent 10 XRP to wallet: ${address} from the given faucet. Resulting balance: ${balance} XRP`)
* } catch (error) {
* console.error(`Failed to fund wallet: ${error}`)
* }
* }
* ```
*
* @param this - Client.
* @param wallet - An existing XRPL Wallet to fund. If undefined or null, a new Wallet will be created.
* @param options - FundingOptions
* @returns A Wallet on the Testnet or Devnet that contains some amount of XRP,
* and that wallet's balance in XRP.
* @throws When either Client isn't connected or unable to fund wallet address.
* @param wallet - Optional existing wallet.
* @returns The wallet to fund.
*/
async function fundWallet(
this: Client,
wallet?: Wallet | null,
options: FundingOptions = {},
): Promise<{
wallet: Wallet
balance: number
}> {
if (!this.isConnected()) {
throw new RippledError('Client not connected, cannot call faucet')
}
const existingWallet = Boolean(wallet)

// Generate a new Wallet if no existing Wallet is provided or its address is invalid to fund
const walletToFund =
wallet && isValidClassicAddress(wallet.classicAddress)
? wallet
: Wallet.generate()

// Create the POST request body
const postBody: FaucetRequestBody = {
destination: walletToFund.classicAddress,
xrpAmount: options.amount,
usageContext: options.usageContext,
userAgent: 'xrpl.js',
export function generateWalletToFund(wallet?: Wallet | null): Wallet {
if (wallet && isValidClassicAddress(wallet.classicAddress)) {
return wallet
}
return Wallet.generate()
}

/**
* Get the starting balance of the wallet.
*
* @param client - The client object.
* @param classicAddress - The classic address of the wallet.
* @returns The starting balance.
*/
export async function getStartingBalance(
client: Client,
classicAddress: string,
): Promise<number> {
let startingBalance = 0
if (existingWallet) {
try {
startingBalance = Number(
await this.getXrpBalance(walletToFund.classicAddress),
)
} catch {
/* startingBalance remains what it was previously */
}
try {
startingBalance = Number(await client.getXrpBalance(classicAddress))
} catch {
// startingBalance remains '0'
}
return startingBalance
}

return requestFunding(options, this, startingBalance, walletToFund, postBody)
export interface FundWalletOptions {
faucetHost?: string
faucetPath?: string
amount?: string
usageContext?: string
}

/**
*
* Helper function to request funding from a faucet. Should not be called directly from outside the xrpl.js library.
*
* @param options - See below
* @param options.faucetHost - A custom host for a faucet server. On devnet,
* testnet, AMM devnet, and HooksV3 testnet, `fundWallet` will
* attempt to determine the correct server automatically. In other environments,
* or if you would like to customize the faucet host in devnet or testnet,
* you should provide the host using this option.
* @param options.faucetPath - A custom path for a faucet server. On devnet,
* testnet, AMM devnet, and HooksV3 testnet, `fundWallet` will
* attempt to determine the correct path automatically. In other environments,
* or if you would like to customize the faucet path in devnet or testnet,
* you should provide the path using this option.
* Ex: client.fundWallet(null,{'faucet.altnet.rippletest.net', '/accounts'})
* specifies a request to 'faucet.altnet.rippletest.net/accounts' to fund a new wallet.
* @param options.amount - A custom amount to fund, if undefined or null, the default amount will be 1000.
* @param client - A connection to the XRPL to send requests and transactions.
* @param startingBalance - The amount of XRP in the given walletToFund on ledger already.
* @param walletToFund - An existing XRPL Wallet to fund.
* @param postBody - The content to send the faucet to indicate which address to fund, how much to fund it, and
* where the request is coming from.
* @returns A promise that resolves to a funded wallet and the balance within it.
*/
// eslint-disable-next-line max-params -- Helper function created for organizational purposes
async function requestFunding(
export async function requestFunding(
options: FundingOptions,
client: Client,
startingBalance: number,
Expand Down Expand Up @@ -291,5 +281,3 @@ async function getUpdatedBalance(
}, INTERVAL_SECONDS * 1000)
})
}

export default fundWallet
54 changes: 40 additions & 14 deletions packages/xrpl/src/client/RequestManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,9 +4,15 @@ import {
TimeoutError,
XrplError,
} from '../errors'
import { Response } from '../models/methods'
import { Response, RequestResponseMap } from '../models/methods'
import { BaseRequest, ErrorResponse } from '../models/methods/baseMethod'

interface PromiseEntry<T> {
resolve: (value: T | PromiseLike<T>) => void
reject: (value: Error) => void
timer: ReturnType<typeof setTimeout>
}

/**
* Manage all the requests made to the websocket, and their async responses
* that come in from the WebSocket. Responses come in over the WS connection
Expand All @@ -17,13 +23,31 @@ export default class RequestManager {
private nextId = 0
private readonly promisesAwaitingResponse = new Map<
string | number,
{
resolve: (value: Response | PromiseLike<Response>) => void
reject: (value: Error) => void
timer: ReturnType<typeof setTimeout>
}
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Necessary and typed wrapper in addPromise method
PromiseEntry<any>
>()

/**
* Adds a promise to the collection of promises awaiting response. Handles typing with generics.
*
* @template T The generic type parameter representing the resolved value type.
* @param newId - The identifier for the new promise.
* @param timer - The timer associated with the promise.
* @returns A promise that resolves to the specified generic type.
*/
public async addPromise<R extends BaseRequest, T = RequestResponseMap<R>>(
newId: string | number,
timer: ReturnType<typeof setTimeout>,
): Promise<T> {
return new Promise<T>((resolve, reject) => {
this.promisesAwaitingResponse.set(newId, {
resolve,
reject,
timer,
})
})
}

/**
* Successfully resolves a request.
*
Expand Down Expand Up @@ -87,10 +111,10 @@ export default class RequestManager {
* @returns Request ID, new request form, and the promise for resolving the request.
* @throws XrplError if request with the same ID is already pending.
*/
public createRequest<T extends BaseRequest>(
request: T,
public createRequest<R extends BaseRequest, T = RequestResponseMap<R>>(
request: R,
timeout: number,
): [string | number, string, Promise<Response>] {
): [string | number, string, Promise<T>] {
let newId: string | number
if (request.id == null) {
newId = this.nextId
Expand Down Expand Up @@ -129,11 +153,13 @@ export default class RequestManager {
request,
)
}
const newPromise = new Promise<Response>(
(resolve: (value: Response | PromiseLike<Response>) => void, reject) => {
this.promisesAwaitingResponse.set(newId, { resolve, reject, timer })
},
)
const newPromise = new Promise<T>((resolve, reject) => {
this.promisesAwaitingResponse.set(newId, {
resolve,
reject,
timer,
})
})

return [newId, newRequest, newPromise]
}
Expand Down
Loading

0 comments on commit 528b17e

Please sign in to comment.