mirror of
https://github.com/Xahau/xahau.js.git
synced 2026-04-29 15:37:50 +00:00
feat: add support for the simulate RPC (XLS-69d) (#2867)
This commit is contained in:
@@ -10,6 +10,9 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
|
||||
### Changed
|
||||
* Deprecated `setTransactionFlagsToNumber`. Start using convertTxFlagsToNumber instead
|
||||
|
||||
### Added
|
||||
* Support for the `simulate` RPC ([XLS-69](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069-simulate))
|
||||
|
||||
## 4.1.0 (2024-12-23)
|
||||
|
||||
### Added
|
||||
|
||||
@@ -40,8 +40,13 @@ import type {
|
||||
MarkerRequest,
|
||||
MarkerResponse,
|
||||
SubmitResponse,
|
||||
SimulateRequest,
|
||||
} from '../models/methods'
|
||||
import type { BookOffer, BookOfferCurrency } from '../models/methods/bookOffers'
|
||||
import {
|
||||
SimulateBinaryResponse,
|
||||
SimulateJsonResponse,
|
||||
} from '../models/methods/simulate'
|
||||
import type {
|
||||
EventTypes,
|
||||
OnEventToListenerMap,
|
||||
@@ -764,6 +769,41 @@ 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<Binary extends boolean = false>(
|
||||
transaction: SubmittableTransaction | string,
|
||||
opts?: {
|
||||
// If true, return the binary-encoded representation of the results.
|
||||
binary?: Binary
|
||||
},
|
||||
): Promise<
|
||||
Binary extends true ? SimulateBinaryResponse : SimulateJsonResponse
|
||||
> {
|
||||
// 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).
|
||||
|
||||
@@ -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,
|
||||
@@ -203,6 +211,7 @@ type Request =
|
||||
| LedgerDataRequest
|
||||
| LedgerEntryRequest
|
||||
// transaction methods
|
||||
| SimulateRequest
|
||||
| SubmitRequest
|
||||
| SubmitMultisignedRequest
|
||||
| TransactionEntryRequest
|
||||
@@ -261,6 +270,7 @@ type Response<Version extends APIVersion = typeof DEFAULT_API_VERSION> =
|
||||
| LedgerDataResponse
|
||||
| LedgerEntryResponse
|
||||
// transaction methods
|
||||
| SimulateResponse
|
||||
| SubmitResponse
|
||||
| SubmitMultisignedVersionResponseMap<Version>
|
||||
| TransactionEntryResponse
|
||||
@@ -398,6 +408,12 @@ export type RequestResponseMap<
|
||||
? LedgerDataResponse
|
||||
: T extends LedgerEntryRequest
|
||||
? LedgerEntryResponse
|
||||
: T extends SimulateBinaryRequest
|
||||
? SimulateBinaryResponse
|
||||
: T extends SimulateJsonRequest
|
||||
? SimulateJsonResponse
|
||||
: T extends SimulateRequest
|
||||
? SimulateJsonResponse
|
||||
: T extends SubmitRequest
|
||||
? SubmitResponse
|
||||
: T extends SubmitMultisignedRequest
|
||||
@@ -544,6 +560,8 @@ export {
|
||||
LedgerEntryRequest,
|
||||
LedgerEntryResponse,
|
||||
// transaction methods with types
|
||||
SimulateRequest,
|
||||
SimulateResponse,
|
||||
SubmitRequest,
|
||||
SubmitResponse,
|
||||
SubmitMultisignedRequest,
|
||||
|
||||
@@ -203,13 +203,13 @@ export interface LedgerQueueData {
|
||||
}
|
||||
|
||||
export interface LedgerBinary
|
||||
extends Omit<Omit<Ledger, 'transactions'>, 'accountState'> {
|
||||
extends Omit<Ledger, 'transactions' | 'accountState'> {
|
||||
accountState?: string[]
|
||||
transactions?: string[]
|
||||
}
|
||||
|
||||
export interface LedgerBinaryV1
|
||||
extends Omit<Omit<LedgerV1, 'transactions'>, 'accountState'> {
|
||||
extends Omit<LedgerV1, 'transactions' | 'accountState'> {
|
||||
accountState?: string[]
|
||||
transactions?: string[]
|
||||
}
|
||||
|
||||
88
packages/xrpl/src/models/methods/simulate.ts
Normal file
88
packages/xrpl/src/models/methods/simulate.ts
Normal file
@@ -0,0 +1,88 @@
|
||||
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 = BaseRequest & {
|
||||
command: 'simulate'
|
||||
|
||||
binary?: boolean
|
||||
} & (
|
||||
| {
|
||||
tx_blob: string
|
||||
tx_json?: never
|
||||
}
|
||||
| {
|
||||
tx_json: Transaction
|
||||
tx_blob?: never
|
||||
}
|
||||
)
|
||||
|
||||
export type SimulateBinaryRequest = SimulateRequest & {
|
||||
binary: true
|
||||
}
|
||||
|
||||
export type SimulateJsonRequest = SimulateRequest & {
|
||||
binary?: false
|
||||
}
|
||||
|
||||
/**
|
||||
* Response expected from an {@link SimulateRequest}.
|
||||
*
|
||||
* @category Responses
|
||||
*/
|
||||
export type SimulateResponse = SimulateJsonResponse | SimulateBinaryResponse
|
||||
|
||||
export interface SimulateBinaryResponse extends BaseResponse {
|
||||
result: {
|
||||
applied: false
|
||||
|
||||
engine_result: string
|
||||
|
||||
engine_result_code: number
|
||||
|
||||
engine_result_message: string
|
||||
|
||||
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>
|
||||
}
|
||||
}
|
||||
@@ -1,5 +1,3 @@
|
||||
import { decode, encode } from 'ripple-binary-codec'
|
||||
|
||||
import type {
|
||||
Client,
|
||||
SubmitRequest,
|
||||
@@ -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
|
||||
@@ -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 =
|
||||
|
||||
@@ -125,6 +125,7 @@ describe('server_info (rippled)', function () {
|
||||
'build_version',
|
||||
'node_size',
|
||||
'initial_sync_duration_us',
|
||||
'git',
|
||||
]
|
||||
assert.deepEqual(
|
||||
omit(response.result.info, removeKeys),
|
||||
|
||||
@@ -116,6 +116,7 @@ describe('server_state', function () {
|
||||
'node_size',
|
||||
'initial_sync_duration_us',
|
||||
'ports',
|
||||
'git',
|
||||
]
|
||||
assert.deepEqual(
|
||||
omit(response.result.state, removeKeys),
|
||||
|
||||
85
packages/xrpl/test/integration/requests/simulate.test.ts
Normal file
85
packages/xrpl/test/integration/requests/simulate.test.ts
Normal file
@@ -0,0 +1,85 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { AccountSet, SimulateRequest } from '../../../src'
|
||||
import { SimulateBinaryRequest } from '../../../src/models/methods/simulate'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
teardownClient,
|
||||
type XrplIntegrationTestContext,
|
||||
} from '../setup'
|
||||
|
||||
// how long before each test case times out
|
||||
const TIMEOUT = 20000
|
||||
|
||||
describe('simulate', function () {
|
||||
let testContext: XrplIntegrationTestContext
|
||||
|
||||
beforeEach(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
})
|
||||
afterEach(async () => teardownClient(testContext))
|
||||
|
||||
it(
|
||||
'json',
|
||||
async () => {
|
||||
const simulateRequest: SimulateRequest = {
|
||||
command: 'simulate',
|
||||
tx_json: {
|
||||
TransactionType: 'AccountSet',
|
||||
Account: testContext.wallet.address,
|
||||
NFTokenMinter: testContext.wallet.address,
|
||||
},
|
||||
}
|
||||
const simulateResponse = await testContext.client.request(simulateRequest)
|
||||
|
||||
assert.equal(simulateResponse.type, 'response')
|
||||
assert.typeOf(simulateResponse.result.meta, 'object')
|
||||
assert.typeOf(simulateResponse.result.tx_json, 'object')
|
||||
assert.equal(simulateResponse.result.engine_result, 'tesSUCCESS')
|
||||
assert.isFalse(simulateResponse.result.applied)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
|
||||
it(
|
||||
'binary',
|
||||
async () => {
|
||||
const simulateRequest: SimulateBinaryRequest = {
|
||||
command: 'simulate',
|
||||
tx_json: {
|
||||
TransactionType: 'AccountSet',
|
||||
Account: testContext.wallet.address,
|
||||
},
|
||||
binary: true,
|
||||
}
|
||||
const simulateResponse = await testContext.client.request(simulateRequest)
|
||||
|
||||
assert.equal(simulateResponse.type, 'response')
|
||||
assert.typeOf(simulateResponse.result.meta_blob, 'string')
|
||||
assert.typeOf(simulateResponse.result.tx_blob, 'string')
|
||||
assert.equal(simulateResponse.result.engine_result, 'tesSUCCESS')
|
||||
assert.isFalse(simulateResponse.result.applied)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
|
||||
it(
|
||||
'sugar',
|
||||
async () => {
|
||||
const tx: AccountSet = {
|
||||
TransactionType: 'AccountSet',
|
||||
Account: testContext.wallet.address,
|
||||
NFTokenMinter: testContext.wallet.address,
|
||||
}
|
||||
const simulateResponse = await testContext.client.simulate(tx)
|
||||
|
||||
assert.equal(simulateResponse.type, 'response')
|
||||
assert.typeOf(simulateResponse.result.meta, 'object')
|
||||
assert.typeOf(simulateResponse.result.tx_json, 'object')
|
||||
assert.equal(simulateResponse.result.engine_result, 'tesSUCCESS')
|
||||
assert.isFalse(simulateResponse.result.applied)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
Reference in New Issue
Block a user