Compare commits

..

9 Commits

Author SHA1 Message Date
tequ
b43f5a4d8e Merge branch 'main-xahau' into workflow-publish 2026-05-24 12:10:07 +09:00
tequ
8e371d5fb7 Merge branch 'main-xahau' into workflow-publish 2026-05-20 13:26:14 +09:00
tequ
d1670c5f3b Merge branch 'main-xahau' into workflow-publish 2026-05-20 12:52:15 +09:00
tequ
e4569e492a Fix build command 2026-05-20 11:31:53 +09:00
tequ
6eb09d8606 Merge branch 'main-xahau' into workflow-publish 2026-05-18 20:30:40 +09:00
tequ
bd0e5e6537 add ref: ${{ github.event.release.tag_name }} 2026-05-18 18:59:00 +09:00
tequ
355bb3fcce Publish npm packages by GitHub release 2026-05-18 18:47:07 +09:00
tequ
40c07a68b9 add workflow_dispatch 2026-05-18 16:16:32 +09:00
tequ
b2046efe85 Add npm trusted publishing workflow
Add GitHub Actions workflow for npm trusted publishing via OIDC.

The workflow validates package version bumps on PRs with npm publish dry-runs, publishes changed workspace packages from main-xahau, and creates matching GitHub releases. Shared npm publish logic is implemented as a local composite action used by both dry-run and publish jobs.
2026-05-13 12:01:45 +09:00
41 changed files with 2875 additions and 5462 deletions

View File

@@ -101,105 +101,69 @@ r.ripple.com 51235
# If you need the version of rippled to be more up to date, you may need to make a comment on this repo: https://github.com/WietseWind/docker-rippled
[features]
fixGuardDepth32
NamedHooks
IOURewardClaim
fixIOULockedBalanceInvariant
fixImportIssuer
HookAPISerializedType240
# PermissionedDomains Supported::no
# DynamicNFT Supported::no
# Credentials Supported::no
AMMClawback
# MPTokensV1 Supported::no
# InvariantsV1_1 Supported::no
fixNFTokenPageLinks
fixEnforceNFTokenTrustline
fixReducedOffersV2
# NFTokenMintOffer Supported::no
fixPreviousTxnID
PriceOracle
fixInnerObjTemplate
fixNFTokenReserve
fixFillOrKill
# DID Supported::no
fixDisallowIncomingV1
# XChainBridge Supported::no
AMM
fixReducedOffersV1
HooksUpdate2
HookOnV2
fixHookAPI20251128
fixCronStacking
ExtendedHookState
fixInvalidTxFlags
Cron
IOUIssuerWeakTSH
DeepFreeze
fixProvisionalDoubleThreading
Clawback
fixRewardClaimFlags
HookCanEmit
fix20250131
fixXahauV3
fixReduceImport
Touch
Remarks
fixFloatDivide
fix240911
fixPageCap
fix240819
fixNSDelete
ZeroB2M
Remit
fixXahauV2
fixXahauV1
HooksUpdate1
XahauGenesis
Import
URIToken
PaychanAndEscrowForTokens
BalanceRewards
Hooks
fixNFTokenRemint
fixNonFungibleTokensV1_2
fixUniversalNumber
XRPFees
DisallowIncoming
ImmediateOfferKilled
# Amendments
NegativeUNL
fixRemoveNFTokenAutoTrustLine
fixTrustLinesToSelf
NonFungibleTokensV1_1
ExpandedSignerList
NonFungibleTokensV1
CheckCashMakesTrustLine
fixRmSmallIncreasedQOffers
fixSTAmountCanonicalize
FlowSortStrands
TicketBatch
NegativeUNL
fixAmendmentMajorityCalc
HardenedValidations
fix1781
RequireFullyCanonicalSig
fixQualityUpperBound
DeletableAccounts
fixPayChanRecipientOwnerDir
fixCheckThreading
fixMasterKeyAsRegularKey
fixTakerDryOfferRemoval
MultiSignReserve
fix1578
fix1515
DepositPreauth
fix1623
fix1543
fix1571
Checks
DepositAuth
fix1513
FlowCross
HardenedValidations
DepositPreauth
MultiSignReserve
fix1623
fix1513
RequireFullyCanonicalSig
fix1543
fix1781
fixCheckThreading
fix1515
CryptoConditionsSuite
fixPayChanRecipientOwnerDir
fix1578
fix1571
fixAmendmentMajorityCalc
fixTakerDryOfferRemoval
fixMasterKeyAsRegularKey
Flow
# OwnerPaysFee Supported::no
DeletableAccounts
DepositAuth
Checks
NonFungibleTokensV1_1
DisallowIncoming
fixNonFungibleTokensV1_2
fixUniversalNumber
ImmediateOfferKilled
XRPFees
ExpandedSignerList
fixNFTokenRemint
# Additional Amendments
BalanceRewards
Hooks
HooksUpdate1
Import
Remit
URIToken
XahauGenesis
ZeroB2M
fix240819
fix240911
fixFloatDivide
fixNFTokenDirV1
fixNFTokenNegOffer
fixNSDelete
fixPageCap
fixReduceImport
fixXahauV1
fixXahauV2
fixXahauV3
PaychanAndEscrowForTokens
DeepFreeze
Clawback
[network_id]
21337

View File

@@ -4,7 +4,7 @@
name: Node.js CI
env:
XAHAUD_VERSION: 2026.6.16-dev+3330
XAHAUD_VERSION: 2025.12.1-release+2609
on:
push:

View File

@@ -43,7 +43,7 @@ jobs:
- uses: actions/setup-node@v6
with:
node-version: "24"
node-version: "20"
registry-url: https://registry.npmjs.org
package-manager-cache: false
@@ -51,7 +51,7 @@ jobs:
run: npm ci
- name: Build package
run: npm run build
run: npm run build "${{ steps.release.outputs.package-path }}"
- id: package
name: Read package metadata
@@ -61,18 +61,11 @@ jobs:
name="$(jq -r .name "$package_json")"
version="$(jq -r .version "$package_json")"
tag="$name@$version"
if [[ "$version" == *"-"* ]]; then
release_tag="${version#*-}"
release_tag="${release_tag%%.*}"
else
release_tag="latest"
fi
{
echo "name=$name"
echo "version=$version"
echo "tag=$tag"
echo "release_tag=$release_tag"
} >> "$GITHUB_OUTPUT"
- name: Check release tag matches package version
@@ -106,4 +99,4 @@ jobs:
exit 1
- name: Publish to npm
run: npm publish --workspace "${{ steps.release.outputs.package-path }}" --registry https://registry.npmjs.org --tag "${{ steps.package.outputs.release_tag }}"
run: npm publish --workspace "${{ steps.release.outputs.package-path }}" --registry https://registry.npmjs.org

14
package-lock.json generated
View File

@@ -2724,6 +2724,9 @@
"arm64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2738,6 +2741,9 @@
"arm64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2752,6 +2758,9 @@
"x64"
],
"dev": true,
"libc": [
"glibc"
],
"license": "MIT",
"optional": true,
"os": [
@@ -2766,6 +2775,9 @@
"x64"
],
"dev": true,
"libc": [
"musl"
],
"license": "MIT",
"optional": true,
"os": [
@@ -16048,7 +16060,7 @@
}
},
"packages/xahau": {
"version": "4.0.4",
"version": "4.0.3",
"license": "ISC",
"dependencies": {
"@scure/bip32": "^1.3.1",

View File

@@ -2,6 +2,8 @@
## Unreleased
## 2.1.0 (2024-06-03)
### Added
* Support for the Price Oracles amendment (XLS-47).

File diff suppressed because it is too large Load Diff

View File

@@ -4449,36 +4449,6 @@
"Flags": 0,
"Sequence": 62
}
},
{
"binary": "12003C2FFFFFFFFF2033000004D2750B6469645F6578616D706C65701D0863757272656E6379701E0870726F7669646572811401476926B590BA3245F63C829116A0A3AF7F382DF018E020301700000000000001E2041003011A0000000000000000000000000000000000000000021A0000000000000000000000005553440000000000E1F1",
"json": {
"TransactionType": "OracleSet",
"Account": "rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8",
"OracleDocumentID": 1234,
"LastUpdateTime": 4294967295,
"PriceDataSeries": [
{
"PriceData": {
"BaseAsset": "XAH",
"QuoteAsset": "USD",
"AssetPrice": "00000000000001E2",
"Scale": 3
}
}
],
"Provider": "70726F7669646572",
"URI": "6469645F6578616D706C65",
"AssetClass": "63757272656E6379"
}
},
{
"binary": "12003D2033000004D2811401476926B590BA3245F63C829116A0A3AF7F382D",
"json": {
"TransactionType": "OracleDelete",
"Account": "rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8",
"OracleDocumentID": 1234
}
}
],
"ledgerData": [{

View File

@@ -73,9 +73,7 @@ describe('Signing data', function () {
const customPaymentDefinitions = JSON.parse(
JSON.stringify(normalDefinitions),
)
// custom number would need to updated in case it has been used by an existing transaction type
customPaymentDefinitions.TRANSACTION_TYPES.Payment = 200
customPaymentDefinitions.TRANSACTION_TYPES.Payment = 31
const newDefs = new XrplDefinitions(customPaymentDefinitions)
const actual = encodeForSigning(tx_json, newDefs)
@@ -84,7 +82,7 @@ describe('Signing data', function () {
'53545800', // signingPrefix
// TransactionType
'12',
'00C8',
'001F',
// Flags
'22',
'80000000',
@@ -178,9 +176,7 @@ describe('Signing data', function () {
const customPaymentDefinitions = JSON.parse(
JSON.stringify(normalDefinitions),
)
// custom number would need to updated in case it has been used by an existing transaction type
customPaymentDefinitions.TRANSACTION_TYPES.Payment = 200
customPaymentDefinitions.TRANSACTION_TYPES.Payment = 31
const newDefs = new XrplDefinitions(customPaymentDefinitions)
const signingAccount = 'rJZdUusLDtY9NEsGea7ijqhVrXv98rYBYN'
@@ -191,7 +187,7 @@ describe('Signing data', function () {
'534D5400', // signingPrefix
// TransactionType
'12',
'00C8',
'001F',
// Flags
'22',
'80000000',

View File

@@ -4,23 +4,6 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
## Unreleased Changes
### Added
* Support for HookOnV2 Amendment
* Support for IOUClaimReward Amendment
* Support for NamedHooks
* Support for the `simulate` RPC ([XLS-69](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069-simulate))
* Support for the Price Oracles amendment (XLS-47).
## 4.0.4 (2026-05-27)
### Added
* Improve HookStateScale validation
### Fixed
* Add lsfTshCollect flag in AccountRoot
* Refactor amount assignment in partialPayment.ts
* Fix setTransactionFlagsToNumber for Xahau transactions
## 4.0.3 (2025-11-18)
### Added
@@ -68,9 +51,6 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
### Added
* Support for the Price Oracles amendment (XLS-47).
### Added
* Support for the Price Oracles amendment (XLS-47).
### Fixed
* Typo in `Channel` type `source_tab` -> `source_tag`
* Fix `client.requestAll` to handle filters better

View File

@@ -1,6 +1,6 @@
{
"name": "xahau",
"version": "4.0.4",
"version": "4.0.3",
"license": "ISC",
"description": "A TypeScript/JavaScript API for interacting with the Xahau Network in Node.js and the browser",
"files": [

View File

@@ -40,13 +40,8 @@ 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,
@@ -739,41 +734,6 @@ 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).

View File

@@ -86,14 +86,6 @@ export interface Hook {
* The transactions that triggers the hook. Represented as a 256Hash
*/
HookOn?: string
/**
* The incoming transactions that triggers to the hook. Represented as a 256Hash
*/
HookOnIncoming?: string
/**
* The outgoing transactions that triggers from the hook. Represented as a 256Hash
*/
HookOnOutgoing?: string
/**
* The transactions that can emit from the hook. Represented as a 256Hash
*/
@@ -114,10 +106,6 @@ export interface Hook {
* The grants of the hook.
*/
HookGrants?: HookGrant[]
/**
* The name of the hook.
*/
HookName?: string
}
}

View File

@@ -27,16 +27,6 @@ export default interface HookDefintion
*/
HookOn?: string
/**
* The incoming transactions that triggers to the hook. Represented as a 256Hash
*/
HookOnIncoming?: string
/**
* The outgoing transactions that triggers from the hook. Represented as a 256Hash
*/
HookOnOutgoing?: string
/**
* The namespace of the hook.
*/

View File

@@ -14,7 +14,6 @@ import ImportVLSequence from './ImportVLSequence'
import LedgerHashes from './LedgerHashes'
import NegativeUNL from './NegativeUNL'
import Offer from './Offer'
import Oracle from './Oracle'
import PayChannel from './PayChannel'
import RippleState from './RippleState'
import SignerList from './SignerList'
@@ -39,7 +38,6 @@ type LedgerEntry =
| LedgerHashes
| NegativeUNL
| Offer
| Oracle
| PayChannel
| RippleState
| SignerList
@@ -62,7 +60,6 @@ type LedgerEntryFilter =
| 'import_vl_sequence'
| 'hashes'
| 'offer'
| 'oracle'
| 'payment_channel'
| 'signer_list'
| 'state'

View File

@@ -1,43 +0,0 @@
import { PriceData } from '../common'
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
/**
* The Oracle object type describes a single Price Oracle instance.
*
* @category Ledger Entries
*/
export default interface Oracle extends BaseLedgerEntry, HasPreviousTxnID {
LedgerEntryType: 'Oracle'
/**
* The time the data was last updated, represented as a unix timestamp in seconds.
*/
LastUpdateTime: number
/**
* The XRPL account with update and delete privileges for the oracle.
*/
Owner: string
/**
* Describes the type of asset, such as "currency", "commodity", or "index".
*/
AssetClass: string
/**
* The oracle provider, such as Chainlink, Band, or DIA.
*/
Provider: string
/**
* An array of up to 10 PriceData objects.
*/
PriceDataSeries: PriceData[]
/**
* A bit-map of boolean flags. No flags are defined for the Oracle object
* type, so this value is always 0.
*/
Flags: 0
}

View File

@@ -2,13 +2,6 @@ import { IssuedCurrencyAmount } from '../common'
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
export interface RippleStateReward {
RewardLgrFirst: number
RewardLgrLast: number
RewardTime: number
TrustLineRewardAccumulator: IssuedCurrencyAmount
}
/**
* The RippleState object type connects two accounts in a single currency.
*
@@ -68,8 +61,6 @@ export default interface RippleState extends BaseLedgerEntry, HasPreviousTxnID {
* equivalent to 1 billion, or face value.
*/
HighQualityOut?: number
HighReward?: RippleStateReward
LowReward?: RippleStateReward
}
export enum RippleStateFlags {

View File

@@ -23,7 +23,6 @@ import { LedgerEntry, LedgerEntryFilter } from './LedgerEntry'
import LedgerHashes from './LedgerHashes'
import NegativeUNL, { NEGATIVE_UNL_ID } from './NegativeUNL'
import Offer, { OfferFlags } from './Offer'
import Oracle from './Oracle'
import PayChannel from './PayChannel'
import RippleState, { RippleStateFlags } from './RippleState'
import SignerList, { SignerListFlags } from './SignerList'
@@ -61,7 +60,6 @@ export {
NegativeUNL,
Offer,
OfferFlags,
Oracle,
PayChannel,
RippleState,
RippleStateFlags,

View File

@@ -1,119 +0,0 @@
import { BaseRequest, BaseResponse } from './baseMethod'
/**
* The `get_aggregate_price` method retrieves the aggregate price of specified Oracle objects,
* returning three price statistics: mean, median, and trimmed mean.
* Returns an {@link GetAggregatePriceResponse}.
*
* @category Requests
*/
export interface GetAggregatePriceRequest extends BaseRequest {
command: 'get_aggregate_price'
/**
* The currency code of the asset to be priced.
*/
base_asset: string
/**
* The currency code of the asset to quote the price of the base asset.
*/
quote_asset: string
/**
* The oracle identifier.
*/
oracles: Array<{
/**
* The XRPL account that controls the Oracle object.
*/
account: string
/**
* A unique identifier of the price oracle for the Account
*/
oracle_document_id: string | number
}>
/**
* The percentage of outliers to trim. Valid trim range is 1-25. If included, the API returns statistics for the trimmed mean.
*/
trim?: number
/**
* Defines a time range in seconds for filtering out older price data. Default value is 0, which doesn't filter any data.
*/
trim_threshold?: number
}
/**
* Response expected from an {@link GetAggregatePriceRequest}.
*
* @category Responses
*/
export interface GetAggregatePriceResponse extends BaseResponse {
result: {
/**
* The statistics from the collected oracle prices.
*/
entire_set: {
/**
* The simple mean.
*/
mean: string
/**
* The size of the data set to calculate the mean.
*/
size: number
/**
* The standard deviation.
*/
standard_deviation: string
}
/**
* The trimmed statistics from the collected oracle prices. Only appears if the trim field was specified in the request.
*/
trimmed_set?: {
/**
* The simple mean of the trimmed data.
*/
mean: string
/**
* The size of the data to calculate the trimmed mean.
*/
size: number
/**
* The standard deviation of the trimmed data.
*/
standard_deviation: string
}
/**
* The median of the collected oracle prices.
*/
median: string
/**
* The most recent timestamp out of all LastUpdateTime values.
*/
time: number
/**
* The ledger index of the ledger version that was used to generate this
* response.
*/
ledger_current_index: number
/**
* If included and set to true, the information in this response comes from
* a validated ledger version. Otherwise, the information is subject to
* change.
*/
validated: boolean
}
}

View File

@@ -78,10 +78,6 @@ import {
GatewayBalancesRequest,
GatewayBalancesResponse,
} from './gatewayBalances'
import {
GetAggregatePriceRequest,
GetAggregatePriceResponse,
} from './getAggregatePrice'
import {
LedgerBinary,
LedgerModifiedOfferCreateTransaction,
@@ -137,14 +133,6 @@ 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,
@@ -200,7 +188,6 @@ type Request =
| LedgerDataRequest
| LedgerEntryRequest
// transaction methods
| SimulateRequest
| SubmitRequest
| SubmitMultisignedRequest
| TransactionEntryRequest
@@ -225,8 +212,6 @@ type Request =
// utility methods
| PingRequest
| RandomRequest
// Price Oracle methods
| GetAggregatePriceRequest
/**
* @category Responses
@@ -250,7 +235,6 @@ type Response<Version extends APIVersion = typeof DEFAULT_API_VERSION> =
| LedgerDataResponse
| LedgerEntryResponse
// transaction methods
| SimulateResponse
| SubmitResponse
| SubmitMultisignedVersionResponseMap<Version>
| TransactionEntryResponse
@@ -275,8 +259,6 @@ type Response<Version extends APIVersion = typeof DEFAULT_API_VERSION> =
// utility methods
| PingResponse
| RandomResponse
// Price Oracle methods
| GetAggregatePriceResponse
export type RequestResponseMap<
T,
@@ -297,8 +279,6 @@ export type RequestResponseMap<
? AccountTxVersionResponseMap<Version>
: T extends GatewayBalancesRequest
? GatewayBalancesResponse
: T extends GetAggregatePriceRequest
? GetAggregatePriceResponse
: T extends NoRippleCheckRequest
? NoRippleCheckResponse
: // NOTE: The order of these LedgerRequest types is important
@@ -375,12 +355,6 @@ 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
@@ -491,8 +465,6 @@ export {
GatewayBalance,
GatewayBalancesRequest,
GatewayBalancesResponse,
GetAggregatePriceRequest,
GetAggregatePriceResponse,
NoRippleCheckRequest,
NoRippleCheckResponse,
// ledger methods
@@ -514,8 +486,6 @@ export {
LedgerEntryRequest,
LedgerEntryResponse,
// transaction methods with types
SimulateRequest,
SimulateResponse,
SubmitRequest,
SubmitResponse,
SubmitMultisignedRequest,

View File

@@ -203,13 +203,13 @@ export interface LedgerQueueData {
}
export interface LedgerBinary
extends Omit<Ledger, 'transactions' | 'accountState'> {
extends Omit<Omit<Ledger, 'transactions'>, 'accountState'> {
accountState?: string[]
transactions?: string[]
}
export interface LedgerBinaryV1
extends Omit<LedgerV1, 'transactions' | 'accountState'> {
extends Omit<Omit<LedgerV1, 'transactions'>, 'accountState'> {
accountState?: string[]
transactions?: string[]
}

View File

@@ -1,88 +0,0 @@
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>
}
}

View File

@@ -1,5 +1,4 @@
import { ValidationError } from '../../errors'
import { Currency } from '../common'
import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common'
/**
@@ -57,7 +56,6 @@ export interface ClaimReward extends BaseTransaction {
Flags?: number | ClaimRewardFlagsInterface
/** The unique address of the issuer where the reward.c hook is installed. */
Issuer?: string
ClaimCurrency?: Currency
}
/**

View File

@@ -275,10 +275,6 @@ export interface BaseTransaction {
* The hook parameters of the transaction.
*/
HookParameters?: HookParameter[]
/**
* The name of the hooks triggered by the transaction.
*/
HookName?: string
/**
* The hook parameters of the transaction.
*/

View File

@@ -32,8 +32,6 @@ export {
OfferCreateFlagsInterface,
OfferCreate,
} from './offerCreate'
export { OracleDelete } from './oracleDelete'
export { OracleSet } from './oracleSet'
export { PaymentFlags, PaymentFlagsInterface, Payment } from './payment'
export {
PaymentChannelClaimFlags,

View File

@@ -1,32 +0,0 @@
import {
BaseTransaction,
isNumber,
validateBaseTransaction,
validateRequiredField,
} from './common'
/**
* Delete an Oracle ledger entry.
*
* @category Transaction Models
*/
export interface OracleDelete extends BaseTransaction {
TransactionType: 'OracleDelete'
/**
* A unique identifier of the price oracle for the Account.
*/
OracleDocumentID: number
}
/**
* Verify the form and type of a OracleDelete at runtime.
*
* @param tx - A OracleDelete Transaction.
* @throws When the OracleDelete is malformed.
*/
export function validateOracleDelete(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
validateRequiredField(tx, 'OracleDocumentID', isNumber)
}

View File

@@ -1,198 +0,0 @@
import { ValidationError } from '../../errors'
import { PriceData } from '../common'
import { isHex } from '../utils'
import {
BaseTransaction,
isNumber,
isString,
validateBaseTransaction,
validateOptionalField,
validateRequiredField,
} from './common'
const PRICE_DATA_SERIES_MAX_LENGTH = 10
const SCALE_MAX = 10
const MINIMUM_ASSET_PRICE_LENGTH = 1
const MAXIMUM_ASSET_PRICE_LENGTH = 16
/**
* Creates a new Oracle ledger entry or updates the fields of an existing one, using the Oracle ID.
*
* The oracle provider must complete these steps before submitting this transaction:
* 1. Create or own the XRPL account in the Owner field and have enough XRP to meet the reserve and transaction fee requirements.
* 2. Publish the XRPL account public key, so it can be used for verification by dApps.
* 3. Publish a registry of available price oracles with their unique OracleDocumentID.
*
* @category Transaction Models
*/
export interface OracleSet extends BaseTransaction {
TransactionType: 'OracleSet'
/**
* A unique identifier of the price oracle for the Account.
*/
OracleDocumentID: number
/**
* The time the data was last updated, represented as a unix timestamp in seconds.
*/
LastUpdateTime: number
/**
* An array of up to 10 PriceData objects, each representing the price information
* for a token pair. More than five PriceData objects require two owner reserves.
*/
PriceDataSeries: PriceData[]
/**
* An arbitrary value that identifies an oracle provider, such as Chainlink, Band,
* or DIA. This field is a string, up to 256 ASCII hex encoded characters (0x20-0x7E).
* This field is required when creating a new Oracle ledger entry, but is optional for updates.
*/
Provider?: string
/**
* An optional Universal Resource Identifier to reference price data off-chain. This field is limited to 256 bytes.
*/
URI?: string
/**
* Describes the type of asset, such as "currency", "commodity", or "index". This field is a string, up to 16 ASCII
* hex encoded characters (0x20-0x7E). This field is required when creating a new Oracle ledger entry, but is optional
* for updates.
*/
AssetClass?: string
}
/**
* Verify the form and type of a OracleSet at runtime.
*
* @param tx - A OracleSet Transaction.
* @throws When the OracleSet is malformed.
*/
// eslint-disable-next-line max-lines-per-function -- necessary to validate many fields
export function validateOracleSet(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
validateRequiredField(tx, 'OracleDocumentID', isNumber)
validateRequiredField(tx, 'LastUpdateTime', isNumber)
validateOptionalField(tx, 'Provider', isString)
validateOptionalField(tx, 'URI', isString)
validateOptionalField(tx, 'AssetClass', isString)
/* eslint-disable max-statements, max-lines-per-function -- necessary to validate many fields */
validateRequiredField(tx, 'PriceDataSeries', (value) => {
if (!Array.isArray(value)) {
throw new ValidationError('OracleSet: PriceDataSeries must be an array')
}
if (value.length > PRICE_DATA_SERIES_MAX_LENGTH) {
throw new ValidationError(
`OracleSet: PriceDataSeries must have at most ${PRICE_DATA_SERIES_MAX_LENGTH} PriceData objects`,
)
}
// TODO: add support for handling inner objects easier (similar to validateRequiredField/validateOptionalField)
for (const priceData of value) {
if (typeof priceData !== 'object') {
throw new ValidationError(
'OracleSet: PriceDataSeries must be an array of objects',
)
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
if (priceData.PriceData == null) {
throw new ValidationError(
'OracleSet: PriceDataSeries must have a `PriceData` object',
)
}
// check if priceData only has PriceData
if (Object.keys(priceData).length !== 1) {
throw new ValidationError(
'OracleSet: PriceDataSeries must only have a single PriceData object',
)
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
if (typeof priceData.PriceData.BaseAsset !== 'string') {
throw new ValidationError(
'OracleSet: PriceDataSeries must have a `BaseAsset` string',
)
}
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
if (typeof priceData.PriceData.QuoteAsset !== 'string') {
throw new ValidationError(
'OracleSet: PriceDataSeries must have a `QuoteAsset` string',
)
}
// Either AssetPrice and Scale are both present or both excluded
if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
(priceData.PriceData.AssetPrice == null) !==
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
(priceData.PriceData.Scale == null)
) {
throw new ValidationError(
'OracleSet: PriceDataSeries must have both `AssetPrice` and `Scale` if any are present',
)
}
/* eslint-disable @typescript-eslint/no-unsafe-member-access, max-depth --
we need to validate priceData.PriceData.AssetPrice value */
if ('AssetPrice' in priceData.PriceData) {
if (!isNumber(priceData.PriceData.AssetPrice)) {
if (typeof priceData.PriceData.AssetPrice !== 'string') {
throw new ValidationError(
'OracleSet: Field AssetPrice must be a string or a number',
)
}
if (!isHex(priceData.PriceData.AssetPrice)) {
throw new ValidationError(
'OracleSet: Field AssetPrice must be a valid hex string',
)
}
if (
priceData.PriceData.AssetPrice.length <
MINIMUM_ASSET_PRICE_LENGTH ||
priceData.PriceData.AssetPrice.length > MAXIMUM_ASSET_PRICE_LENGTH
) {
throw new ValidationError(
`OracleSet: Length of AssetPrice field must be between ${MINIMUM_ASSET_PRICE_LENGTH} and ${MAXIMUM_ASSET_PRICE_LENGTH} characters long`,
)
}
}
}
/* eslint-enable @typescript-eslint/no-unsafe-member-access, max-depth */
if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
'Scale' in priceData.PriceData &&
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
!isNumber(priceData.PriceData.Scale)
) {
throw new ValidationError('OracleSet: invalid field Scale')
}
if (
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
priceData.PriceData.Scale < 0 ||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- we are validating the type
priceData.PriceData.Scale > SCALE_MAX
) {
throw new ValidationError(
`OracleSet: Scale must be in range 0-${SCALE_MAX}`,
)
}
}
return true
})
/* eslint-enable max-statements, max-lines-per-function */
}

View File

@@ -18,10 +18,6 @@ export interface SetHook extends BaseTransaction {
const MAX_HOOKS = 10
const HEX_REGEX = /^[0-9A-Fa-f]{64}$/u
/**
* 4-16 bytes in hex
*/
const HOOKNAME_REGEX = /^[0-9A-Fa-f]{8,32}$/u
/**
* Verify the form and type of an SetHook at runtime.
@@ -29,7 +25,6 @@ const HOOKNAME_REGEX = /^[0-9A-Fa-f]{8,32}$/u
* @param tx - An SetHook Transaction.
* @throws When the SetHook is Malformed.
*/
// eslint-disable-next-line max-lines-per-function -- okay for this method
export function validateSetHook(tx: Record<string, unknown>): void {
validateBaseTransaction(tx)
@@ -46,29 +41,12 @@ export function validateSetHook(tx: Record<string, unknown>): void {
for (const hook of tx.Hooks) {
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- Should be a Hook
const hookObject = hook as Hook
const {
HookOn,
HookOnIncoming,
HookOnOutgoing,
HookCanEmit,
HookNamespace,
HookName,
} = hookObject.Hook
const { HookOn, HookCanEmit, HookNamespace } = hookObject.Hook
if (HookOn !== undefined && !HEX_REGEX.test(HookOn)) {
throw new ValidationError(
`SetHook: HookOn in Hook must be a 256-bit (32-byte) hexadecimal value`,
)
}
if (HookOnIncoming !== undefined && !HEX_REGEX.test(HookOnIncoming)) {
throw new ValidationError(
`SetHook: HookOnIncoming in Hook must be a 256-bit (32-byte) hexadecimal value`,
)
}
if (HookOnOutgoing !== undefined && !HEX_REGEX.test(HookOnOutgoing)) {
throw new ValidationError(
`SetHook: HookOnOutgoing in Hook must be a 256-bit (32-byte) hexadecimal value`,
)
}
if (HookCanEmit !== undefined && !HEX_REGEX.test(HookCanEmit)) {
throw new ValidationError(
`SetHook: HookCanEmit in Hook must be a 256-bit (32-byte) hexadecimal value`,
@@ -79,10 +57,5 @@ export function validateSetHook(tx: Record<string, unknown>): void {
`SetHook: HookNamespace in Hook must be a 256-bit (32-byte) hexadecimal value`,
)
}
if (HookName !== undefined && !HOOKNAME_REGEX.test(HookName)) {
throw new ValidationError(
`SetHook: HookName in Hook must be a hex string of 8-32 hex characters`,
)
}
}
}

View File

@@ -24,8 +24,6 @@ import { Invoke, validateInvoke } from './invoke'
import { TransactionMetadata } from './metadata'
import { OfferCancel, validateOfferCancel } from './offerCancel'
import { OfferCreate, validateOfferCreate } from './offerCreate'
import { OracleDelete, validateOracleDelete } from './oracleDelete'
import { OracleSet, validateOracleSet } from './oracleSet'
import { Payment, validatePayment } from './payment'
import {
PaymentChannelClaim,
@@ -81,8 +79,6 @@ export type SubmittableTransaction =
| Invoke
| OfferCancel
| OfferCreate
| OracleDelete
| OracleSet
| Payment
| PaymentChannelClaim
| PaymentChannelCreate
@@ -253,14 +249,6 @@ export function validate(transaction: Record<string, unknown>): void {
validateOfferCreate(tx)
break
case 'OracleDelete':
validateOracleDelete(tx)
break
case 'OracleSet':
validateOracleSet(tx)
break
case 'Payment':
validatePayment(tx)
break
@@ -327,7 +315,6 @@ export function validate(transaction: Record<string, unknown>): void {
default:
throw new ValidationError(
// eslint-disable-next-line max-lines -- allowed here
`Invalid field TransactionType: ${tx.TransactionType}`,
)
}

View File

@@ -1,3 +1,5 @@
import { decode, encode } from 'xahau-binary-codec'
import type {
Client,
SubmitRequest,
@@ -10,7 +12,6 @@ import { ValidationError, XahlError } 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
@@ -51,7 +52,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 =

View File

@@ -1,78 +0,0 @@
import { stringToHex } from '@xrplf/isomorphic/utils'
import { assert } from 'chai'
import { OracleSet } from '../../../src'
import serverUrl from '../serverUrl'
import {
setupClient,
teardownClient,
type XrplIntegrationTestContext,
} from '../setup'
import { testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('get_aggregate_price', function () {
let testContext: XrplIntegrationTestContext
beforeEach(async () => {
testContext = await setupClient(serverUrl)
})
afterEach(async () => teardownClient(testContext))
it(
'base',
async () => {
const tx: OracleSet = {
TransactionType: 'OracleSet',
Account: testContext.wallet.classicAddress,
OracleDocumentID: 1234,
LastUpdateTime: Math.floor(Date.now() / 1000),
PriceDataSeries: [
{
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'USD',
AssetPrice: 740,
Scale: 3,
},
},
],
Provider: stringToHex('chainlink'),
URI: '6469645F6578616D706C65',
AssetClass: stringToHex('currency'),
}
await testTransaction(testContext.client, tx, testContext.wallet)
// confirm that the Oracle was actually created
const getAggregatePriceResponse = await testContext.client.request({
command: 'get_aggregate_price',
account: testContext.wallet.classicAddress,
base_asset: 'XRP',
quote_asset: 'USD',
trim: 20,
oracles: [
{
account: testContext.wallet.classicAddress,
oracle_document_id: 1234,
},
],
})
assert.deepEqual(getAggregatePriceResponse.result.entire_set, {
mean: '0.74',
size: 1,
standard_deviation: '0',
})
assert.deepEqual(getAggregatePriceResponse.result.trimmed_set, {
mean: '0.74',
size: 1,
standard_deviation: '0',
})
assert.equal(getAggregatePriceResponse.result.median, '0.74')
assert.equal(getAggregatePriceResponse.result.time, tx.LastUpdateTime)
},
TIMEOUT,
)
})

View File

@@ -128,7 +128,6 @@ describe('server_info (xahaud)', function () {
'node_size',
'initial_sync_duration_us',
'ports',
'git',
]
assert.deepEqual(
omit(response.result.info, removeKeys),

View File

@@ -118,7 +118,6 @@ describe('server_state', function () {
'node_size',
'initial_sync_duration_us',
'ports',
'git',
]
assert.deepEqual(
omit(response.result.state, removeKeys),

View File

@@ -1,85 +0,0 @@
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,
)
})

View File

@@ -1,66 +1,24 @@
import { assert } from 'chai'
import {
ClaimReward,
ClaimRewardFlags,
SetHook,
TrustSet,
Wallet,
} from '../../../src'
import { RippleState } from '../../../src/models/ledger'
import { ClaimReward, ClaimRewardFlags } from '../../../src'
import serverUrl from '../serverUrl'
import {
setupClient,
teardownClient,
type XrplIntegrationTestContext,
} from '../setup'
import { generateFundedWallet, testTransaction } from '../utils'
import { testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
const acceptHook =
'0061736D0100000001130360027F7F017F60037F7F7E017E60017F017E02170203656E76025F67000003656E760661636365707400010302010205030100020621057F01418088040B7F004180080B7F004180080B7F00418088040B7F004180080B07080104686F6F6B00020A9D80000199800000410141011080808080001A4100410042001081808080000B'
describe('XAH ClaimReward', function () {
describe('ClaimReward', function () {
let testContext: XrplIntegrationTestContext
let genesisWallet: Wallet
beforeAll(async () => {
genesisWallet = new Wallet(
'0330E7FC9D56BB25D6893BA3F317AE5BCF33B3291BD63DB32654A313222F7FD020',
'001ACAAEDECE405B2A958212629E16F2EB46B153EEE94CDD350FDEFF52795525B7',
)
beforeEach(async () => {
testContext = await setupClient(serverUrl)
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: genesisWallet.classicAddress,
Hooks: [
{
Hook: {
CreateCode: acceptHook,
HookApiVersion: 0,
HookOn: '00'.repeat(32),
HookNamespace: '00'.repeat(32),
},
},
],
}
await testTransaction(testContext.client, setHookTx, genesisWallet)
})
afterAll(async () => {
// reset Hook
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: genesisWallet.classicAddress,
Hooks: [{ Hook: { CreateCode: '', Flags: { hsfOverride: true } } }],
}
await testTransaction(testContext.client, setHookTx, genesisWallet)
await teardownClient(testContext)
})
afterEach(async () => teardownClient(testContext))
it(
'opt in',
@@ -68,7 +26,7 @@ describe('XAH ClaimReward', function () {
const tx: ClaimReward = {
TransactionType: 'ClaimReward',
Account: testContext.wallet.classicAddress,
Issuer: genesisWallet.address,
Issuer: 'rHb9CJAWyB4rj91VRWn96DkukG4bwdtyTh',
}
await testTransaction(testContext.client, tx, testContext.wallet)
@@ -84,7 +42,6 @@ describe('XAH ClaimReward', function () {
},
TIMEOUT,
)
it(
'opt out',
async () => {
@@ -110,135 +67,3 @@ describe('XAH ClaimReward', function () {
TIMEOUT,
)
})
describe('IOU ClaimReward', function () {
let testContext: XrplIntegrationTestContext
let issuerWallet: Wallet
let hookWallet: Wallet
beforeAll(async () => {
testContext = await setupClient(serverUrl)
issuerWallet = await generateFundedWallet(testContext.client)
hookWallet = await generateFundedWallet(testContext.client)
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: hookWallet.classicAddress,
Hooks: [
{
Hook: {
CreateCode: acceptHook,
HookApiVersion: 0,
HookOn: '00'.repeat(32),
HookNamespace: '00'.repeat(32),
},
},
],
}
await testTransaction(testContext.client, setHookTx, hookWallet)
const trustSetTx: TrustSet = {
TransactionType: 'TrustSet',
Account: testContext.wallet.classicAddress,
LimitAmount: {
currency: 'USD',
issuer: issuerWallet.address,
value: '10000000',
},
}
await testTransaction(testContext.client, trustSetTx, testContext.wallet)
})
afterAll(async () => {
// reset Hook
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: hookWallet.classicAddress,
Hooks: [{ Hook: { CreateCode: '', Flags: { hsfOverride: true } } }],
}
await testTransaction(testContext.client, setHookTx, hookWallet)
await teardownClient(testContext)
})
it(
'opt in',
async () => {
const tx: ClaimReward = {
TransactionType: 'ClaimReward',
Account: testContext.wallet.classicAddress,
Issuer: hookWallet.classicAddress,
ClaimCurrency: {
currency: 'USD',
issuer: issuerWallet.address,
},
}
await testTransaction(testContext.client, tx, testContext.wallet)
const rippleStateResponse = await testContext.client.request({
command: 'ledger_entry',
ripple_state: {
currency: 'USD',
accounts: [
testContext.wallet.classicAddress,
issuerWallet.classicAddress,
],
},
})
const node = rippleStateResponse.result.node as RippleState
assert.exists(node)
// Either LowReward or HighReward must exist
expect(Boolean(node.LowReward) || Boolean(node.HighReward))
if (node.LowReward) {
assert.exists(node.LowReward.TrustLineRewardAccumulator)
assert.exists(node.LowReward.RewardLgrFirst)
assert.exists(node.LowReward.RewardLgrLast)
assert.exists(node.LowReward.RewardTime)
}
if (node.HighReward) {
assert.exists(node.HighReward.TrustLineRewardAccumulator)
assert.exists(node.HighReward.RewardLgrFirst)
assert.exists(node.HighReward.RewardLgrLast)
assert.exists(node.HighReward.RewardTime)
}
},
TIMEOUT,
)
it(
'opt out',
async () => {
const tx: ClaimReward = {
TransactionType: 'ClaimReward',
Account: testContext.wallet.classicAddress,
Flags: ClaimRewardFlags.tfOptOut,
ClaimCurrency: {
currency: 'USD',
issuer: issuerWallet.address,
},
}
await testTransaction(testContext.client, tx, testContext.wallet)
const rippleStateResponse = await testContext.client.request({
command: 'ledger_entry',
ripple_state: {
currency: 'USD',
accounts: [
testContext.wallet.classicAddress,
issuerWallet.classicAddress,
],
},
})
const node = rippleStateResponse.result.node as RippleState
assert.exists(node)
assert.notExists(node.LowReward)
assert.notExists(node.HighReward)
},
TIMEOUT,
)
})

View File

@@ -1,76 +0,0 @@
import { stringToHex } from '@xrplf/isomorphic/utils'
import { assert } from 'chai'
import { OracleSet, OracleDelete } from '../../../src'
import serverUrl from '../serverUrl'
import {
setupClient,
teardownClient,
type XrplIntegrationTestContext,
} from '../setup'
import { testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('OracleDelete', function () {
let testContext: XrplIntegrationTestContext
beforeEach(async () => {
testContext = await setupClient(serverUrl)
})
afterEach(async () => teardownClient(testContext))
it(
'base',
async () => {
const setTx: OracleSet = {
TransactionType: 'OracleSet',
Account: testContext.wallet.classicAddress,
OracleDocumentID: 1234,
LastUpdateTime: Math.floor(Date.now() / 1000),
PriceDataSeries: [
{
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'USD',
AssetPrice: 740,
Scale: 3,
},
},
],
Provider: stringToHex('chainlink'),
URI: '6469645F6578616D706C65',
AssetClass: stringToHex('currency'),
}
await testTransaction(testContext.client, setTx, testContext.wallet)
const aoResult = await testContext.client.request({
command: 'account_objects',
account: testContext.wallet.classicAddress,
type: 'oracle',
})
// confirm that the Oracle was created
assert.equal(aoResult.result.account_objects.length, 1)
const deleteTx: OracleDelete = {
TransactionType: 'OracleDelete',
Account: testContext.wallet.classicAddress,
OracleDocumentID: 1234,
}
await testTransaction(testContext.client, deleteTx, testContext.wallet)
const aoResult2 = await testContext.client.request({
command: 'account_objects',
account: testContext.wallet.classicAddress,
})
// confirm that the Oracle was actually deleted
assert.equal(aoResult2.result.account_objects.length, 0)
},
TIMEOUT,
)
})

View File

@@ -1,91 +0,0 @@
import { stringToHex } from '@xrplf/isomorphic/utils'
import { assert } from 'chai'
import { OracleSet } from '../../../src'
import { Oracle } from '../../../src/models/ledger'
import serverUrl from '../serverUrl'
import {
setupClient,
teardownClient,
type XrplIntegrationTestContext,
} from '../setup'
import { testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
describe('OracleSet', function () {
let testContext: XrplIntegrationTestContext
beforeEach(async () => {
testContext = await setupClient(serverUrl)
})
afterEach(async () => teardownClient(testContext))
it(
'base',
async () => {
const tx: OracleSet = {
TransactionType: 'OracleSet',
Account: testContext.wallet.classicAddress,
OracleDocumentID: 1234,
LastUpdateTime: Math.floor(Date.now() / 1000),
PriceDataSeries: [
{
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'USD',
AssetPrice: 740,
Scale: 3,
},
},
{
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'INR',
// Upper bound admissible value for AssetPrice field
// large numeric values necessarily have to use str type in Javascript
// number type uses double-precision floating point representation, hence represents a smaller range of values
AssetPrice: 'ffffffffffffffff',
Scale: 3,
},
},
],
Provider: stringToHex('chainlink'),
URI: '6469645F6578616D706C65',
AssetClass: stringToHex('currency'),
}
await testTransaction(testContext.client, tx, testContext.wallet)
const result = await testContext.client.request({
command: 'account_objects',
account: testContext.wallet.classicAddress,
type: 'oracle',
})
// confirm that the Oracle was actually created
assert.equal(result.result.account_objects.length, 1)
// confirm details of Oracle ledger entry object
const oracle = result.result.account_objects[0] as Oracle
assert.equal(oracle.LastUpdateTime, tx.LastUpdateTime)
assert.equal(oracle.Owner, testContext.wallet.classicAddress)
assert.equal(oracle.AssetClass, tx.AssetClass)
assert.equal(oracle.Provider, tx.Provider)
assert.equal(oracle.PriceDataSeries.length, 2)
assert.equal(oracle.PriceDataSeries[1].PriceData.BaseAsset, 'XRP')
assert.equal(oracle.PriceDataSeries[1].PriceData.QuoteAsset, 'USD')
assert.equal(oracle.PriceDataSeries[1].PriceData.AssetPrice, '2e4')
assert.equal(oracle.PriceDataSeries[1].PriceData.Scale, 3)
assert.equal(oracle.Flags, 0)
// validate the serialization of large AssetPrice values
assert.equal(
oracle.PriceDataSeries[0].PriceData.AssetPrice,
'ffffffffffffffff',
)
},
TIMEOUT,
)
})

View File

@@ -1,138 +0,0 @@
import { SetHook, Wallet } from '../../../src'
import { Hook, HookDefinition } from '../../../src/models/ledger'
import serverUrl from '../serverUrl'
import {
setupClient,
teardownClient,
type XrplIntegrationTestContext,
} from '../setup'
import { generateFundedWallet, testTransaction } from '../utils'
// how long before each test case times out
const TIMEOUT = 20000
const acceptHook =
'0061736D0100000001130360027F7F017F60037F7F7E017E60017F017E02170203656E76025F67000003656E760661636365707400010302010205030100020621057F01418088040B7F004180080B7F004180080B7F00418088040B7F004180080B07080104686F6F6B00020A9D80000199800000410141011080808080001A4100410042001081808080000B'
describe('SetHook', function () {
let testContext: XrplIntegrationTestContext
let wallet: Wallet
beforeEach(async () => {
testContext = await setupClient(serverUrl)
wallet = await generateFundedWallet(testContext.client)
})
afterEach(async () => {
// reset Hook
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: wallet.classicAddress,
Hooks: [{ Hook: { CreateCode: '', Flags: { hsfOverride: true } } }],
}
await testTransaction(testContext.client, setHookTx, wallet)
await teardownClient(testContext)
})
it(
'base',
async () => {
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: wallet.classicAddress,
Hooks: [
{
Hook: {
CreateCode: acceptHook,
HookApiVersion: 0,
HookOn: '00'.repeat(32),
HookCanEmit: '00'.repeat(32),
HookName: '484F4F4B',
HookParameters: [
{
HookParameter: {
HookParameterName: 'DEADBEEF',
HookParameterValue: 'DEADBEEF',
},
},
],
HookNamespace: '00'.repeat(32),
},
},
],
}
await testTransaction(testContext.client, setHookTx, wallet)
const ledgerEntryResponse = await testContext.client.request({
command: 'ledger_entry',
hook: { account: wallet.classicAddress },
})
const node = ledgerEntryResponse.result.node as Hook
expect(node.Hooks.length).toEqual(1)
const hook = node.Hooks[0].Hook
expect(Object.keys(hook).length).toEqual(2)
expect(hook.HookHash).toBeDefined()
expect(hook.HookName).toBeDefined()
const hookHash = hook.HookHash!
const hookDefinitionResponse = await testContext.client.request({
command: 'ledger_entry',
hook_definition: hookHash,
})
const hookDefinitionNode = hookDefinitionResponse.result
.node as HookDefinition
expect(hookDefinitionNode.HookHash).toEqual(hookHash)
expect(hookDefinitionNode.CreateCode).toEqual(acceptHook)
expect(hookDefinitionNode.HookApiVersion).toEqual(0)
expect(hookDefinitionNode.HookOn).toEqual('00'.repeat(32))
expect(hookDefinitionNode.HookNamespace).toEqual('00'.repeat(32))
// @ts-expect-error - HookName is not defined in HookDefinition
expect(hookDefinitionNode.HookName).toBeUndefined()
expect(hookDefinitionNode.HookParameters?.length).toEqual(1)
const parameter = hookDefinitionNode.HookParameters![0].HookParameter
expect(parameter.HookParameterName).toEqual('DEADBEEF')
expect(parameter.HookParameterValue).toEqual('DEADBEEF')
},
TIMEOUT,
)
it(
'hook on incoming/outgoing',
async () => {
const setHookTx: SetHook = {
TransactionType: 'SetHook',
Account: wallet.classicAddress,
Hooks: [
{
Hook: {
CreateCode: acceptHook,
HookApiVersion: 0,
HookOnIncoming: '00'.repeat(32),
// eslint-disable-next-line no-inline-comments -- for readability
HookOnOutgoing: `01${'00'.repeat(31)}`, // should be different from HookOnIncoming
HookNamespace: '00'.repeat(32),
},
},
],
}
await testTransaction(testContext.client, setHookTx, wallet)
const ledgerEntryResponse = await testContext.client.request({
command: 'ledger_entry',
hook: { account: wallet.classicAddress },
})
const node = ledgerEntryResponse.result.node as Hook
const hook = node.Hooks[0].Hook
const hookHash = hook.HookHash!
const hookDefinitionResponse = await testContext.client.request({
command: 'ledger_entry',
hook_definition: hookHash,
})
const hookDefinitionNode = hookDefinitionResponse.result
.node as HookDefinition
expect(hookDefinitionNode.HookOn).toBeUndefined()
expect(hookDefinitionNode.HookOnIncoming).toBeDefined()
expect(hookDefinitionNode.HookOnOutgoing).toBeDefined()
},
TIMEOUT,
)
})

View File

@@ -13,8 +13,6 @@ import {
ECDSA,
AccountLinesRequest,
IssuedCurrency,
XAHAUD_API_V2,
TxResponse,
} from '../../src'
import {
Payment,
@@ -186,22 +184,20 @@ export async function verifySubmittedTransaction(
hashTx?: string,
): Promise<void> {
const hash = hashTx ?? hashSignedTx(tx)
const data: TxResponse = await client.request({
const data = await client.request({
command: 'tx',
transaction: hash,
// The current default version is v1, but we'll be using v2 for this test.
api_version: XAHAUD_API_V2,
})
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- TODO: handle this API change for 2.0.0
const decodedTx: any = typeof tx === 'string' ? decode(tx) : tx
if (decodedTx.TransactionType === 'Payment') {
if (decodedTx.TransactionType === 'Payment' && client.apiVersion !== 1) {
decodedTx.DeliverMax = decodedTx.Amount
delete decodedTx.Amount
}
assert(data.result)
assert.deepEqual(
omit(data.result.tx_json, [
omit(data.result, [
'ctid',
'date',
'hash',

View File

@@ -1,40 +0,0 @@
import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
import { validateOracleDelete } from '../../src/models/transactions/oracleDelete'
/**
* OracleDelete Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('OracleDelete', function () {
let tx
beforeEach(function () {
tx = {
TransactionType: 'OracleDelete',
Account: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
OracleDocumentID: 1234,
} as any
})
it('verifies valid OracleDelete', function () {
assert.doesNotThrow(() => validateOracleDelete(tx))
assert.doesNotThrow(() => validate(tx))
})
it(`throws w/ missing field OracleDocumentID`, function () {
delete tx.OracleDocumentID
const errorMessage = 'OracleDelete: missing field OracleDocumentID'
assert.throws(() => validateOracleDelete(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid OracleDocumentID`, function () {
tx.OracleDocumentID = '1234'
const errorMessage = 'OracleDelete: invalid field OracleDocumentID'
assert.throws(() => validateOracleDelete(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
})

View File

@@ -1,212 +0,0 @@
import { stringToHex } from '@xrplf/isomorphic/dist/utils'
import { assert } from 'chai'
import { validate, ValidationError } from '../../src'
import { validateOracleSet } from '../../src/models/transactions/oracleSet'
/**
* OracleSet Transaction Verification Testing.
*
* Providing runtime verification testing for each specific transaction type.
*/
describe('OracleSet', function () {
let tx
beforeEach(function () {
tx = {
TransactionType: 'OracleSet',
Account: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
OracleDocumentID: 1234,
LastUpdateTime: 768062172,
PriceDataSeries: [
{
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'USD',
AssetPrice: 740,
Scale: 3,
},
},
],
Provider: stringToHex('chainlink'),
URI: '6469645F6578616D706C65',
AssetClass: stringToHex('currency'),
} as any
})
it('verifies valid OracleSet', function () {
assert.doesNotThrow(() => validateOracleSet(tx))
assert.doesNotThrow(() => validate(tx))
})
it(`throws w/ missing field OracleDocumentID`, function () {
delete tx.OracleDocumentID
const errorMessage = 'OracleSet: missing field OracleDocumentID'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid OracleDocumentID`, function () {
tx.OracleDocumentID = '1234'
const errorMessage = 'OracleSet: invalid field OracleDocumentID'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing field LastUpdateTime`, function () {
delete tx.LastUpdateTime
const errorMessage = 'OracleSet: missing field LastUpdateTime'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid LastUpdateTime`, function () {
tx.LastUpdateTime = '768062172'
const errorMessage = 'OracleSet: invalid field LastUpdateTime'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing invalid Provider`, function () {
tx.Provider = 1234
const errorMessage = 'OracleSet: invalid field Provider'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing invalid URI`, function () {
tx.URI = 1234
const errorMessage = 'OracleSet: invalid field URI'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing invalid AssetClass`, function () {
tx.AssetClass = 1234
const errorMessage = 'OracleSet: invalid field AssetClass'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid PriceDataSeries must be an array`, function () {
tx.PriceDataSeries = 1234
const errorMessage = 'OracleSet: PriceDataSeries must be an array'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid PriceDataSeries must be an array of objects`, function () {
tx.PriceDataSeries = [1234]
const errorMessage =
'OracleSet: PriceDataSeries must be an array of objects'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ PriceDataSeries must have at most 10 PriceData objects`, function () {
tx.PriceDataSeries = new Array(11).fill({
PriceData: {
BaseAsset: 'XRP',
QuoteAsset: 'USD',
AssetPrice: 740,
Scale: 3,
},
})
const errorMessage =
'OracleSet: PriceDataSeries must have at most 10 PriceData objects'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ PriceDataSeries must have a PriceData object`, function () {
delete tx.PriceDataSeries[0].PriceData
const errorMessage =
'OracleSet: PriceDataSeries must have a `PriceData` object'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ PriceDataSeries must only have a single PriceData object`, function () {
tx.PriceDataSeries[0].ExtraProp = 'extraprop'
const errorMessage =
'OracleSet: PriceDataSeries must only have a single PriceData object'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing BaseAsset of PriceDataSeries`, function () {
delete tx.PriceDataSeries[0].PriceData.BaseAsset
const errorMessage =
'OracleSet: PriceDataSeries must have a `BaseAsset` string'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing QuoteAsset of PriceDataSeries`, function () {
delete tx.PriceDataSeries[0].PriceData.QuoteAsset
const errorMessage =
'OracleSet: PriceDataSeries must have a `QuoteAsset` string'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing AssetPrice with Scale present of PriceDataSeries`, function () {
delete tx.PriceDataSeries[0].PriceData.AssetPrice
const errorMessage =
'OracleSet: PriceDataSeries must have both `AssetPrice` and `Scale` if any are present'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ missing Scale with AssetPrice present of PriceDataSeries`, function () {
delete tx.PriceDataSeries[0].PriceData.Scale
const errorMessage =
'OracleSet: PriceDataSeries must have both `AssetPrice` and `Scale` if any are present'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid AssetPrice of PriceDataSeries`, function () {
// value cannot be parsed as hexadecimal number
tx.PriceDataSeries[0].PriceData.AssetPrice = 'ghij'
const errorMessage =
'OracleSet: Field AssetPrice must be a valid hex string'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`verifies valid AssetPrice of PriceDataSeries`, function () {
// valid string which can be parsed as hexadecimal number
tx.PriceDataSeries[0].PriceData.AssetPrice = 'ab15'
assert.doesNotThrow(() => validate(tx))
})
it(`throws w/ invalid AssetPrice type in PriceDataSeries`, function () {
tx.PriceDataSeries[0].PriceData.AssetPrice = ['sample', 'invalid', 'type']
const errorMessage =
'OracleSet: Field AssetPrice must be a string or a number'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ invalid Scale of PriceDataSeries`, function () {
tx.PriceDataSeries[0].PriceData.Scale = '1234'
const errorMessage = 'OracleSet: invalid field Scale'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ Scale must be in range 0-10 when above max`, function () {
tx.PriceDataSeries[0].PriceData.Scale = 11
const errorMessage = 'OracleSet: Scale must be in range 0-10'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
it(`throws w/ Scale must be in range 0-10 when below min`, function () {
tx.PriceDataSeries[0].PriceData.Scale = -1
const errorMessage = 'OracleSet: Scale must be in range 0-10'
assert.throws(() => validateOracleSet(tx), ValidationError, errorMessage)
assert.throws(() => validate(tx), ValidationError, errorMessage)
})
})

View File

@@ -32,7 +32,6 @@ describe('SetHook', function () {
HookApiVersion: 0,
HookNamespace:
'4FF9961269BF7630D32E15276569C94470174A5DA79FA567C0F62251AA9A36B9',
HookName: 'DEADBEEF',
},
},
],
@@ -67,10 +66,6 @@ describe('SetHook', function () {
'0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22',
HookOn:
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7',
HookOnIncoming:
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7',
HookOnOutgoing:
'FFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFFBFFFF7',
Flags: 1,
HookApiVersion: 0,
HookNamespace:
@@ -98,31 +93,30 @@ describe('SetHook', function () {
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
})
it.each(['HookOn', 'HookOnIncoming', 'HookOnOutgoing'])(
`throws w/ invalid %s in Hooks`,
function (field: string) {
setHookTx.Hooks = [
{
Hook: {
CreateCode:
'0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22',
[field]: '',
Flags: 1,
HookApiVersion: 0,
HookNamespace:
'4FF9961269BF7630D32E15276569C94470174A5DA79FA567C0F62251AA9A36B9',
},
it(`throws w/ invalid HookOn in Hooks`, function () {
setHookTx.SignerQuorum = 2
setHookTx.Hooks = [
{
Hook: {
CreateCode:
'0061736D01000000011C0460057F7F7F7F7F017E60037F7F7E017E60027F7F017F60017F017E02230303656E76057472616365000003656E7606616363657074000103656E76025F670002030201030503010002062B077F0141B088040B7F004180080B7F0041A6080B7F004180080B7F0041B088040B7F0041000B7F0041010B07080104686F6F6B00030AC4800001C0800001017F230041106B220124002001200036020C41920841134180084112410010001A410022002000420010011A41012200200010021A200141106A240042000B0B2C01004180080B254163636570742E633A2043616C6C65642E00224163636570742E633A2043616C6C65642E22',
HookOn: '',
Flags: 1,
HookApiVersion: 0,
HookNamespace:
'4FF9961269BF7630D32E15276569C94470174A5DA79FA567C0F62251AA9A36B9',
},
]
const errorMessage = `SetHook: ${field} in Hook must be a 256-bit (32-byte) hexadecimal value`
assert.throws(
() => validateSetHook(setHookTx),
ValidationError,
errorMessage,
)
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
},
)
},
]
const errorMessage =
'SetHook: HookOn in Hook must be a 256-bit (32-byte) hexadecimal value'
assert.throws(
() => validateSetHook(setHookTx),
ValidationError,
errorMessage,
)
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
})
it(`throws w/ invalid HookCanEmit in Hooks`, function () {
setHookTx.Hooks = [
@@ -174,25 +168,4 @@ describe('SetHook', function () {
)
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
})
it.each(['', '0'.repeat(7), '0'.repeat(33), 'ZZZZZZZZ'])(
`throws w/ invalid HookName in Hooks: %s`,
function (value: string) {
setHookTx.Hooks = [
{
Hook: {
HookName: value,
},
},
]
const errorMessage =
'SetHook: HookName in Hook must be a hex string of 8-32 hex characters'
assert.throws(
() => validateSetHook(setHookTx),
ValidationError,
errorMessage,
)
assert.throws(() => validate(setHookTx), ValidationError, errorMessage)
},
)
})