Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: add support for the simulate RPC (XLS-69d) #2867

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions packages/xrpl/HISTORY.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr

## Unreleased Changes

### Added
* Support for the `simulate` RPC

## 4.1.0 (2024-12-23)

### Added
Expand Down
68 changes: 67 additions & 1 deletion packages/xrpl/src/client/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ import type {
MarkerRequest,
MarkerResponse,
SubmitResponse,
SimulateResponse,
SimulateRequest,
} from '../models/methods'
import type { BookOffer, BookOfferCurrency } from '../models/methods/bookOffers'
import type {
Expand Down Expand Up @@ -74,7 +76,7 @@ import {
separateBuySellOrders,
sortAndLimitOffers,
} from '../sugar/getOrderbook'
import { dropsToXrp, hashes, isValidClassicAddress } from '../utils'
import { decode, dropsToXrp, hashes, isValidClassicAddress } from '../utils'
import { Wallet } from '../Wallet'
import {
type FaucetRequestBody,
Expand Down Expand Up @@ -764,6 +766,70 @@ class Client extends EventEmitter<EventTypes> {
return submitRequest(this, signedTx, opts?.failHard)
}

/**
* Simulates an unsigned transaction.
* Steps performed on a transaction:
* 1. Autofill.
* 2. Sign & Encode.
* 3. Submit.
*
* @category Core
*
* @param transaction - A transaction to autofill, sign & encode, and submit.
* @param opts - (Optional) Options used to sign and submit a transaction.
* @param opts.binary - If true, return the metadata in a binary encoding.
*
* @returns A promise that contains SimulateResponse.
* @throws RippledError if the simulate request fails.
*/
public async simulate(
transaction: SubmittableTransaction | string,
opts?: {
// If true, return the binary-encoded representation of the results.
binary?: boolean
},
): Promise<SimulateResponse> {
// verify that the transaction isn't signed
const decodedTx =
typeof transaction === 'string'
? // eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- needed for typing
(decode(transaction) as unknown as SubmittableTransaction)
: transaction
if (typeof decodedTx === 'string') {
throw new ValidationError(
'Provided transaction is not a valid transaction.',
)
}
if (decodedTx.TxnSignature !== '' && decodedTx.TxnSignature != null) {
throw new ValidationError('Transaction must not be signed.')
}
if (
decodedTx.Signers?.some(
(signer) =>
signer.Signer.TxnSignature !== '' &&
// eslint-disable-next-line @typescript-eslint/no-unnecessary-condition -- needed for JS
signer.Signer.TxnSignature != null,
)
) {
throw new ValidationError('Transaction must not be signed.')
}

setValidAddresses(decodedTx)
setTransactionFlagsToNumber(decodedTx)

if (decodedTx.NetworkID == null) {
decodedTx.NetworkID = txNeedsNetworkID(this) ? this.networkID : undefined
}

// send request
const binary = opts?.binary ?? false
const request: SimulateRequest =
typeof transaction === 'string'
? { command: 'simulate', tx_blob: transaction, binary }
: { command: 'simulate', tx_json: transaction, binary }
return this.request(request)
}

/**
* Asynchronously submits a transaction and verifies that it has been included in a
* validated ledger (or has errored/will not be included for some reason).
Expand Down
16 changes: 16 additions & 0 deletions packages/xrpl/src/models/methods/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,14 @@ import {
StateAccountingFinal,
} from './serverInfo'
import { ServerStateRequest, ServerStateResponse } from './serverState'
import {
SimulateBinaryRequest,
SimulateBinaryResponse,
SimulateJsonRequest,
SimulateJsonResponse,
SimulateRequest,
SimulateResponse,
} from './simulate'
import { SubmitRequest, SubmitResponse } from './submit'
import {
SubmitMultisignedRequest,
Expand Down Expand Up @@ -203,6 +211,7 @@ type Request =
| LedgerDataRequest
| LedgerEntryRequest
// transaction methods
| SimulateRequest
| SubmitRequest
| SubmitMultisignedRequest
| TransactionEntryRequest
Expand Down Expand Up @@ -261,6 +270,7 @@ type Response<Version extends APIVersion = typeof DEFAULT_API_VERSION> =
| LedgerDataResponse
| LedgerEntryResponse
// transaction methods
| SimulateResponse
| SubmitResponse
| SubmitMultisignedVersionResponseMap<Version>
| TransactionEntryResponse
Expand Down Expand Up @@ -398,6 +408,10 @@ export type RequestResponseMap<
? LedgerDataResponse
: T extends LedgerEntryRequest
? LedgerEntryResponse
: T extends SimulateBinaryRequest
? SimulateBinaryResponse
: T extends SimulateJsonRequest
? SimulateJsonResponse
: T extends SubmitRequest
? SubmitResponse
: T extends SubmitMultisignedRequest
Expand Down Expand Up @@ -544,6 +558,8 @@ export {
LedgerEntryRequest,
LedgerEntryResponse,
// transaction methods with types
SimulateRequest,
SimulateResponse,
SubmitRequest,
SubmitResponse,
SubmitMultisignedRequest,
Expand Down
76 changes: 76 additions & 0 deletions packages/xrpl/src/models/methods/simulate.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
import {
BaseTransaction,
Transaction,
TransactionMetadata,
} from '../transactions'

import { BaseRequest, BaseResponse } from './baseMethod'

/**
* The `simulate` method simulates a transaction without submitting it to the network.
* Returns a {@link SimulateResponse}.
*
* @category Requests
*/
export type SimulateRequest = SimulateRequestBase &
(
| {
tx_blob: string
}
| {
tx_json: Transaction
}
)

export interface SimulateRequestBase extends BaseRequest {
command: 'simulate'

binary?: boolean
}

export type SimulateBinaryRequest = SimulateRequest & {
binary: true
}

export type SimulateJsonRequest = SimulateRequest & {
binary?: false
}

export type SimulateResponse = SimulateBinaryResponse | SimulateJsonResponse

export interface SimulateBinaryResponse extends BaseResponse {
result: {
tx_blob: string

meta_blob: string

/**
* The ledger index of the ledger version that was used to generate this
* response.
*/
ledger_index: number
}
}

export interface SimulateJsonResponse<T extends BaseTransaction = Transaction>
extends BaseResponse {
result: {
applied: false

engine_result: string

engine_result_code: number

engine_result_message: string

/**
* The ledger index of the ledger version that was used to generate this
* response.
*/
ledger_index: number

tx_json: T

meta?: TransactionMetadata<T>
}
}
5 changes: 2 additions & 3 deletions packages/xrpl/src/sugar/submit.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,3 @@
import { decode, encode } from 'ripple-binary-codec'

import type {
Client,
SubmitRequest,
Expand All @@ -12,6 +10,7 @@ import { ValidationError, XrplError } from '../errors'
import { Signer } from '../models/common'
import { TxResponse } from '../models/methods'
import { BaseTransaction } from '../models/transactions/common'
import { decode, encode } from '../utils'

/** Approximate time for a ledger to close, in milliseconds */
const LEDGER_CLOSE_TIME = 1000
Expand Down Expand Up @@ -52,7 +51,7 @@ export async function submitRequest(
failHard = false,
): Promise<SubmitResponse> {
if (!isSigned(signedTransaction)) {
throw new ValidationError('Transaction must be signed')
throw new ValidationError('Transaction must be signed.')
}

const signedTxEncoded =
Expand Down
Loading