mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-12-06 17:27:59 +00:00
Compare commits
13 Commits
v4.1.0
...
ripple-bin
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
5b16d0cfe3 | ||
|
|
35e40d9d71 | ||
|
|
ea9e3dcc98 | ||
|
|
61da4c567a | ||
|
|
189abc1a26 | ||
|
|
ce5ca316ca | ||
|
|
991a1d29a4 | ||
|
|
23d26c8c2e | ||
|
|
abdb192c69 | ||
|
|
84943ae0b6 | ||
|
|
d8126807a4 | ||
|
|
7a2fa3fcaa | ||
|
|
76c3355858 |
@@ -63,17 +63,10 @@ online_delete=256
|
||||
[debug_logfile]
|
||||
/var/log/rippled/debug.log
|
||||
|
||||
[sntp_servers]
|
||||
time.windows.com
|
||||
time.apple.com
|
||||
time.nist.gov
|
||||
pool.ntp.org
|
||||
|
||||
[ips]
|
||||
r.ripple.com 51235
|
||||
|
||||
[validators_file]
|
||||
validators.txt
|
||||
|
||||
[rpc_startup]
|
||||
{ "command": "log_level", "severity": "info" }
|
||||
@@ -180,6 +173,7 @@ fixXChainRewardRounding
|
||||
fixPreviousTxnID
|
||||
fixAMMv1_1
|
||||
# 2.3.0 Amendments
|
||||
AMMClawback
|
||||
fixAMMv1_2
|
||||
Credentials
|
||||
NFTokenMintOffer
|
||||
@@ -188,3 +182,6 @@ fixNFTokenPageLinks
|
||||
fixInnerObjTemplate2
|
||||
fixEnforceNFTokenTrustline
|
||||
fixReducedOffersV2
|
||||
DeepFreeze
|
||||
DynamicNFT
|
||||
PermissionedDomains
|
||||
|
||||
2
.github/workflows/nodejs.yml
vendored
2
.github/workflows/nodejs.yml
vendored
@@ -4,7 +4,7 @@
|
||||
name: Node.js CI
|
||||
|
||||
env:
|
||||
RIPPLED_DOCKER_IMAGE: rippleci/rippled:2.3.0-rc1
|
||||
RIPPLED_DOCKER_IMAGE: rippleci/rippled:develop
|
||||
|
||||
on:
|
||||
push:
|
||||
|
||||
@@ -64,7 +64,7 @@ From the top-level xrpl.js folder (one level above `packages`), run the followin
|
||||
```bash
|
||||
npm install
|
||||
# sets up the rippled standalone Docker container - you can skip this step if you already have it set up
|
||||
docker run -p 6006:6006 --rm -it --name rippled_standalone --volume $PWD/.ci-config:/etc/opt/ripple/ --entrypoint bash rippleci/rippled:2.3.0-rc1 -c 'rippled -a'
|
||||
docker run -p 6006:6006 --rm -it --name rippled_standalone --volume $PWD/.ci-config:/etc/opt/ripple/ --entrypoint bash rippleci/rippled:develop -c 'rippled -a'
|
||||
npm run build
|
||||
npm run test:integration
|
||||
```
|
||||
@@ -76,7 +76,7 @@ Breaking down the command:
|
||||
`--name rippled_standalone` is an instance name for clarity
|
||||
* `--volume $PWD/.ci-config:/etc/opt/ripple/` identifies the `rippled.cfg` and `validators.txt` to import. It must be an absolute path, so we use `$PWD` instead of `./`.
|
||||
* `rippleci/rippled` is an image that is regularly updated with the latest `rippled` releases
|
||||
* `--entrypoint bash rippleci/rippled:2.3.0-rc1` manually overrides the entrypoint (for versions of rippled >= 2.3.0)
|
||||
* `--entrypoint bash rippleci/rippled:develop` manually overrides the entrypoint (for the latest version of rippled on the `develop` branch)
|
||||
* `-c 'rippled -a'` provides the bash command to start `rippled` in standalone mode from the manual entrypoint
|
||||
|
||||
### Browser Tests
|
||||
@@ -92,7 +92,7 @@ This should be run from the `xrpl.js` top level folder (one above the `packages`
|
||||
```bash
|
||||
npm run build
|
||||
# sets up the rippled standalone Docker container - you can skip this step if you already have it set up
|
||||
docker run -p 6006:6006 --rm -it --name rippled_standalone --volume $PWD/.ci-config:/etc/opt/ripple/ --entrypoint bash rippleci/rippled:2.3.0-rc1 -c 'rippled -a'
|
||||
docker run -p 6006:6006 --rm -it --name rippled_standalone --volume $PWD/.ci-config:/etc/opt/ripple/ --entrypoint bash rippleci/rippled:develop -c 'rippled -a'
|
||||
npm run test:browser
|
||||
```
|
||||
|
||||
|
||||
817
package-lock.json
generated
817
package-lock.json
generated
File diff suppressed because it is too large
Load Diff
@@ -58,7 +58,7 @@
|
||||
"typescript": "^5.1.6",
|
||||
"webpack": "^5.81.0",
|
||||
"webpack-bundle-analyzer": "^4.1.0",
|
||||
"webpack-cli": "^5.0.1"
|
||||
"webpack-cli": "^6.0.1"
|
||||
},
|
||||
"workspaces": [
|
||||
"./packages/*"
|
||||
|
||||
@@ -2,6 +2,12 @@
|
||||
|
||||
## Unreleased
|
||||
|
||||
## 2.3.0 (2025-2-13)
|
||||
|
||||
### Added
|
||||
* Support for the AMMClawback amendment (XLS-73)
|
||||
* Support for the Permissioned Domains amendment (XLS-80).
|
||||
|
||||
## 2.2.0 (2024-12-23)
|
||||
|
||||
### Added
|
||||
@@ -11,6 +17,7 @@
|
||||
|
||||
### Added
|
||||
* Support for the Price Oracles amendment (XLS-47).
|
||||
* Support for the `DynamicNFT` amendment (XLS-46)
|
||||
|
||||
### Fixed
|
||||
* Better error handling/error messages for serialization/deserialization errors.
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "ripple-binary-codec",
|
||||
"version": "2.2.0",
|
||||
"version": "2.3.0",
|
||||
"description": "XRP Ledger binary codec",
|
||||
"files": [
|
||||
"dist/*",
|
||||
|
||||
@@ -1250,6 +1250,16 @@
|
||||
"type": "Hash256"
|
||||
}
|
||||
],
|
||||
[
|
||||
"DomainID",
|
||||
{
|
||||
"nth": 34,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "Hash256"
|
||||
}
|
||||
],
|
||||
[
|
||||
"hash",
|
||||
{
|
||||
@@ -2530,6 +2540,15 @@
|
||||
"type": "STArray"
|
||||
}
|
||||
],
|
||||
[
|
||||
"AcceptedCredentials", {
|
||||
"nth": 28,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "STArray"
|
||||
}
|
||||
],
|
||||
[
|
||||
"CloseResolution",
|
||||
{
|
||||
@@ -2863,6 +2882,7 @@
|
||||
"Oracle": 128,
|
||||
"Credential": 129,
|
||||
"PayChannel": 120,
|
||||
"PermissionedDomain": 130,
|
||||
"RippleState": 114,
|
||||
"SignerList": 83,
|
||||
"Ticket": 84,
|
||||
@@ -3053,6 +3073,7 @@
|
||||
},
|
||||
"TRANSACTION_TYPES": {
|
||||
"AMMBid": 39,
|
||||
"AMMClawback": 31,
|
||||
"AMMCreate": 35,
|
||||
"AMMDelete": 40,
|
||||
"AMMDeposit": 36,
|
||||
@@ -3085,6 +3106,7 @@
|
||||
"NFTokenCancelOffer": 28,
|
||||
"NFTokenCreateOffer": 27,
|
||||
"NFTokenMint": 25,
|
||||
"NFTokenModify": 61,
|
||||
"OfferCancel": 8,
|
||||
"OfferCreate": 7,
|
||||
"OracleDelete": 52,
|
||||
@@ -3093,6 +3115,8 @@
|
||||
"PaymentChannelClaim": 15,
|
||||
"PaymentChannelCreate": 13,
|
||||
"PaymentChannelFund": 14,
|
||||
"PermissionedDomainSet": 62,
|
||||
"PermissionedDomainDelete": 63,
|
||||
"SetFee": 101,
|
||||
"SetRegularKey": 5,
|
||||
"SignerListSet": 12,
|
||||
|
||||
@@ -2,7 +2,22 @@
|
||||
|
||||
Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xrpl-announce) for release announcements. We recommend that xrpl.js (ripple-lib) users stay up-to-date with the latest stable release.
|
||||
|
||||
## Unreleased Changes
|
||||
## Unreleased
|
||||
|
||||
## 4.2.0 (2025-2-13)
|
||||
|
||||
### Added
|
||||
* Support for the AMMClawback amendment (XLS-73)
|
||||
* Adds utility function `convertTxFlagsToNumber`
|
||||
* Support for the Permissioned Domains amendment (XLS-80).
|
||||
* Support for the `simulate` RPC ([XLS-69](https://github.com/XRPLF/XRPL-Standards/tree/master/XLS-0069-simulate))
|
||||
* Support for XLS-77d Deep-Freeze amendment
|
||||
|
||||
### Changed
|
||||
* Deprecated `setTransactionFlagsToNumber`. Start using convertTxFlagsToNumber instead
|
||||
|
||||
### Fixed
|
||||
* Include `network_id` field in the `server_state` response interface.
|
||||
|
||||
## 4.1.0 (2024-12-23)
|
||||
|
||||
@@ -11,6 +26,7 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
|
||||
* New `MPTAmount` type support for `Payment` and `Clawback` transactions
|
||||
* `parseTransactionFlags` as a utility function in the xrpl package to streamline transactions flags-to-map conversion
|
||||
* Support for XLS-70d (Credentials)
|
||||
* Support for the `DynamicNFT` amendment (XLS-46)
|
||||
|
||||
### Fixed
|
||||
* `TransactionStream` model supports APIv2
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "xrpl",
|
||||
"version": "4.1.0",
|
||||
"version": "4.2.0",
|
||||
"license": "ISC",
|
||||
"description": "A TypeScript/JavaScript API for interacting with the XRP Ledger in Node.js and the browser",
|
||||
"files": [
|
||||
@@ -29,7 +29,7 @@
|
||||
"bignumber.js": "^9.0.0",
|
||||
"eventemitter3": "^5.0.1",
|
||||
"ripple-address-codec": "^5.0.0",
|
||||
"ripple-binary-codec": "^2.2.0",
|
||||
"ripple-binary-codec": "^2.3.0",
|
||||
"ripple-keypairs": "^2.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
@@ -43,7 +43,7 @@
|
||||
"lodash": "^4.17.4",
|
||||
"react": "^19.0.0",
|
||||
"run-s": "^0.0.0",
|
||||
"typedoc": "0.26.11",
|
||||
"typedoc": "0.27.6",
|
||||
"ws": "^8.14.2"
|
||||
},
|
||||
"resolutions": {
|
||||
|
||||
@@ -40,14 +40,19 @@ 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,
|
||||
} from '../models/methods/subscribe'
|
||||
import type { SubmittableTransaction } from '../models/transactions'
|
||||
import { setTransactionFlagsToNumber } from '../models/utils/flags'
|
||||
import { convertTxFlagsToNumber } from '../models/utils/flags'
|
||||
import {
|
||||
ensureClassicAddress,
|
||||
submitRequest,
|
||||
@@ -665,7 +670,7 @@ class Client extends EventEmitter<EventTypes> {
|
||||
const tx = { ...transaction }
|
||||
|
||||
setValidAddresses(tx)
|
||||
setTransactionFlagsToNumber(tx)
|
||||
tx.Flags = convertTxFlagsToNumber(tx)
|
||||
|
||||
const promises: Array<Promise<void>> = []
|
||||
if (tx.NetworkID == null) {
|
||||
@@ -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).
|
||||
|
||||
@@ -8,8 +8,9 @@
|
||||
*/
|
||||
export * as LedgerEntry from './ledger'
|
||||
export {
|
||||
setTransactionFlagsToNumber,
|
||||
parseAccountRootFlags,
|
||||
setTransactionFlagsToNumber,
|
||||
convertTxFlagsToNumber,
|
||||
parseTransactionFlags,
|
||||
} from './utils/flags'
|
||||
export * from './methods'
|
||||
|
||||
@@ -13,6 +13,7 @@ import NegativeUNL from './NegativeUNL'
|
||||
import Offer from './Offer'
|
||||
import Oracle from './Oracle'
|
||||
import PayChannel from './PayChannel'
|
||||
import PermissionedDomain from './PermissionedDomain'
|
||||
import RippleState from './RippleState'
|
||||
import SignerList from './SignerList'
|
||||
import Ticket from './Ticket'
|
||||
@@ -35,6 +36,7 @@ type LedgerEntry =
|
||||
| Offer
|
||||
| Oracle
|
||||
| PayChannel
|
||||
| PermissionedDomain
|
||||
| RippleState
|
||||
| SignerList
|
||||
| Ticket
|
||||
@@ -61,6 +63,7 @@ type LedgerEntryFilter =
|
||||
| 'offer'
|
||||
| 'oracle'
|
||||
| 'payment_channel'
|
||||
| 'permissioned_domain'
|
||||
| 'signer_list'
|
||||
| 'state'
|
||||
| 'ticket'
|
||||
|
||||
29
packages/xrpl/src/models/ledger/PermissionedDomain.ts
Normal file
29
packages/xrpl/src/models/ledger/PermissionedDomain.ts
Normal file
@@ -0,0 +1,29 @@
|
||||
import { AuthorizeCredential } from '../common'
|
||||
|
||||
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
|
||||
|
||||
export default interface PermissionedDomain
|
||||
extends BaseLedgerEntry,
|
||||
HasPreviousTxnID {
|
||||
/* The ledger object's type (PermissionedDomain). */
|
||||
LedgerEntryType: 'PermissionedDomain'
|
||||
|
||||
/* The account that controls the settings of the domain. */
|
||||
Owner: string
|
||||
|
||||
/* The credentials that are accepted by the domain.
|
||||
Ownership of one of these credentials automatically
|
||||
makes you a member of the domain. */
|
||||
AcceptedCredentials: AuthorizeCredential[]
|
||||
|
||||
/* Flag values associated with this object. */
|
||||
Flags: 0
|
||||
|
||||
/* Owner account's directory page containing the PermissionedDomain object. */
|
||||
OwnerNode: string
|
||||
|
||||
/* The Sequence value of the PermissionedDomainSet
|
||||
transaction that created this domain. Used in combination
|
||||
with the Account to identify this domain. */
|
||||
Sequence: number
|
||||
}
|
||||
@@ -77,4 +77,8 @@ export enum RippleStateFlags {
|
||||
lsfHighFreeze = 0x00800000,
|
||||
// True, trust line to AMM. Used by client apps to identify payments via AMM.
|
||||
lsfAMMNode = 0x01000000,
|
||||
// True, low side has set deep freeze flag
|
||||
lsfLowDeepFreeze = 0x02000000,
|
||||
// True, high side has set deep freeze flag
|
||||
lsfHighDeepFreeze = 0x04000000,
|
||||
}
|
||||
|
||||
@@ -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[]
|
||||
}
|
||||
|
||||
@@ -51,6 +51,7 @@ export interface ServerStateResponse extends BaseResponse {
|
||||
load_factor_fee_queue?: number
|
||||
load_factor_fee_reference?: number
|
||||
load_factor_server?: number
|
||||
network_id: number
|
||||
peer_disconnects?: string
|
||||
peer_disconnects_resources?: string
|
||||
peers: number
|
||||
|
||||
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>
|
||||
}
|
||||
}
|
||||
120
packages/xrpl/src/models/transactions/AMMClawback.ts
Normal file
120
packages/xrpl/src/models/transactions/AMMClawback.ts
Normal file
@@ -0,0 +1,120 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
import { Currency, IssuedCurrency, IssuedCurrencyAmount } from '../common'
|
||||
|
||||
import {
|
||||
Account,
|
||||
BaseTransaction,
|
||||
GlobalFlags,
|
||||
isAccount,
|
||||
isAmount,
|
||||
isCurrency,
|
||||
validateBaseTransaction,
|
||||
validateOptionalField,
|
||||
validateRequiredField,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
* Enum representing values for AMMClawback Transaction Flags.
|
||||
*
|
||||
* @category Transaction Flags
|
||||
*/
|
||||
export enum AMMClawbackFlags {
|
||||
tfClawTwoAssets = 0x00000001,
|
||||
}
|
||||
|
||||
/**
|
||||
* Map of flags to boolean values representing {@link AMMClawback} transaction
|
||||
* flags.
|
||||
*
|
||||
* @category Transaction Flags
|
||||
*/
|
||||
export interface AMMClawbackFlagsInterface extends GlobalFlags {
|
||||
tfClawTwoAssets?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
* Claw back tokens from a holder that has deposited your issued tokens into an AMM pool.
|
||||
*
|
||||
* Clawback is disabled by default. To use clawback, you must send an AccountSet transaction to enable the
|
||||
* Allow Trust Line Clawback setting. An issuer with any existing tokens cannot enable clawback. You can
|
||||
* only enable Allow Trust Line Clawback if you have a completely empty owner directory, meaning you must
|
||||
* do so before you set up any trust lines, offers, escrows, payment channels, checks, or signer lists.
|
||||
* After you enable clawback, it cannot reverted: the account permanently gains the ability to claw back
|
||||
* issued assets on trust lines.
|
||||
*/
|
||||
export interface AMMClawback extends BaseTransaction {
|
||||
TransactionType: 'AMMClawback'
|
||||
|
||||
/**
|
||||
* The account holding the asset to be clawed back.
|
||||
*/
|
||||
Holder: Account
|
||||
|
||||
/**
|
||||
* Specifies the asset that the issuer wants to claw back from the AMM pool.
|
||||
* In JSON, this is an object with currency and issuer fields. The issuer field must match with Account.
|
||||
*/
|
||||
Asset: IssuedCurrency
|
||||
|
||||
/**
|
||||
* Specifies the other asset in the AMM's pool. In JSON, this is an object with currency and
|
||||
* issuer fields (omit issuer for XRP).
|
||||
*/
|
||||
Asset2: Currency
|
||||
|
||||
/**
|
||||
* The maximum amount to claw back from the AMM account. The currency and issuer subfields should match
|
||||
* the Asset subfields. If this field isn't specified, or the value subfield exceeds the holder's available
|
||||
* tokens in the AMM, all of the holder's tokens will be clawed back.
|
||||
*/
|
||||
Amount?: IssuedCurrencyAmount
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of an AMMClawback at runtime.
|
||||
*
|
||||
* @param tx - An AMMClawback Transaction.
|
||||
* @throws {ValidationError} When the transaction is malformed.
|
||||
*/
|
||||
export function validateAMMClawback(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
validateRequiredField(tx, 'Holder', isAccount)
|
||||
|
||||
validateRequiredField(tx, 'Asset', isCurrency)
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- required
|
||||
const asset = tx.Asset as IssuedCurrency
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- required
|
||||
const amount = tx.Amount as IssuedCurrencyAmount
|
||||
|
||||
if (tx.Holder === asset.issuer) {
|
||||
throw new ValidationError(
|
||||
'AMMClawback: Holder and Asset.issuer must be distinct',
|
||||
)
|
||||
}
|
||||
|
||||
if (tx.Account !== asset.issuer) {
|
||||
throw new ValidationError(
|
||||
'AMMClawback: Account must be the same as Asset.issuer',
|
||||
)
|
||||
}
|
||||
|
||||
validateRequiredField(tx, 'Asset2', isCurrency)
|
||||
|
||||
validateOptionalField(tx, 'Amount', isAmount)
|
||||
|
||||
if (tx.Amount != null) {
|
||||
if (amount.currency !== asset.currency) {
|
||||
throw new ValidationError(
|
||||
'AMMClawback: Amount.currency must match Asset.currency',
|
||||
)
|
||||
}
|
||||
|
||||
if (amount.issuer !== asset.issuer) {
|
||||
throw new ValidationError(
|
||||
'AMMClawback: Amount.issuer must match Amount.issuer',
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,10 @@ export enum NFTokenMintFlags {
|
||||
* issuer.
|
||||
*/
|
||||
tfTransferable = 0x00000008,
|
||||
/**
|
||||
* If set, indicates that this NFT's URI can be modified.
|
||||
*/
|
||||
tfMutable = 0x00000010,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -51,6 +55,7 @@ export interface NFTokenMintFlagsInterface extends GlobalFlags {
|
||||
tfOnlyXRP?: boolean
|
||||
tfTrustLine?: boolean
|
||||
tfTransferable?: boolean
|
||||
tfMutable?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
67
packages/xrpl/src/models/transactions/NFTokenModify.ts
Normal file
67
packages/xrpl/src/models/transactions/NFTokenModify.ts
Normal file
@@ -0,0 +1,67 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
import { isHex } from '../utils'
|
||||
|
||||
import {
|
||||
BaseTransaction,
|
||||
validateBaseTransaction,
|
||||
isAccount,
|
||||
isString,
|
||||
validateOptionalField,
|
||||
Account,
|
||||
validateRequiredField,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
* The NFTokenModify transaction modifies an NFToken's URI
|
||||
* if its tfMutable is set to true.
|
||||
*/
|
||||
export interface NFTokenModify extends BaseTransaction {
|
||||
TransactionType: 'NFTokenModify'
|
||||
/**
|
||||
* Identifies the NFTokenID of the NFToken object that the
|
||||
* offer references.
|
||||
*/
|
||||
NFTokenID: string
|
||||
/**
|
||||
* Indicates the AccountID of the account that owns the corresponding NFToken.
|
||||
* Can be omitted if the owner is the account submitting this transaction
|
||||
*/
|
||||
Owner?: Account
|
||||
/**
|
||||
* URI that points to the data and/or metadata associated with the NFT.
|
||||
* This field need not be an HTTP or HTTPS URL; it could be an IPFS URI, a
|
||||
* magnet link, immediate data encoded as an RFC2379 "data" URL, or even an
|
||||
* opaque issuer-specific encoding. The URI is NOT checked for validity, but
|
||||
* the field is limited to a maximum length of 256 bytes.
|
||||
*
|
||||
* This field must be hex-encoded. You can use `convertStringToHex` to
|
||||
* convert this field to the proper encoding.
|
||||
*
|
||||
* This field must not be an empty string. Omit it from the transaction or
|
||||
* set to `null` if you do not use it.
|
||||
*/
|
||||
URI?: string | null
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of an NFTokenModify at runtime.
|
||||
*
|
||||
* @param tx - An NFTokenModify Transaction.
|
||||
* @throws When the NFTokenModify is Malformed.
|
||||
*/
|
||||
export function validateNFTokenModify(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
validateRequiredField(tx, 'NFTokenID', isString)
|
||||
validateOptionalField(tx, 'Owner', isAccount)
|
||||
validateOptionalField(tx, 'URI', isString)
|
||||
|
||||
if (tx.URI !== undefined && typeof tx.URI === 'string') {
|
||||
if (tx.URI === '') {
|
||||
throw new ValidationError('NFTokenModify: URI must not be empty string')
|
||||
}
|
||||
if (!isHex(tx.URI)) {
|
||||
throw new ValidationError('NFTokenModify: URI must be in hex format')
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
validateCredentialsList,
|
||||
validateOptionalField,
|
||||
validateRequiredField,
|
||||
MAX_AUTHORIZED_CREDENTIALS,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
@@ -54,5 +55,6 @@ export function validateAccountDelete(tx: Record<string, unknown>): void {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
|
||||
tx.TransactionType as string,
|
||||
true,
|
||||
MAX_AUTHORIZED_CREDENTIALS,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -9,15 +9,15 @@ import {
|
||||
AuthorizeCredential,
|
||||
Currency,
|
||||
IssuedCurrencyAmount,
|
||||
MPTAmount,
|
||||
Memo,
|
||||
Signer,
|
||||
XChainBridge,
|
||||
MPTAmount,
|
||||
} from '../common'
|
||||
import { onlyHasFields } from '../utils'
|
||||
|
||||
const MEMO_SIZE = 3
|
||||
const MAX_CREDENTIALS_LIST_LENGTH = 8
|
||||
export const MAX_AUTHORIZED_CREDENTIALS = 8
|
||||
const MAX_CREDENTIAL_BYTE_LENGTH = 64
|
||||
const MAX_CREDENTIAL_TYPE_LENGTH = MAX_CREDENTIAL_BYTE_LENGTH * 2
|
||||
|
||||
@@ -134,7 +134,9 @@ export function isIssuedCurrency(
|
||||
* @param input - The input to check the form and type of
|
||||
* @returns Whether the AuthorizeCredential is properly formed
|
||||
*/
|
||||
function isAuthorizeCredential(input: unknown): input is AuthorizeCredential {
|
||||
export function isAuthorizeCredential(
|
||||
input: unknown,
|
||||
): input is AuthorizeCredential {
|
||||
return (
|
||||
isRecord(input) &&
|
||||
isRecord(input.Credential) &&
|
||||
@@ -455,13 +457,16 @@ export function validateCredentialType(tx: Record<string, unknown>): void {
|
||||
* @param credentials An array of credential IDs to check for errors
|
||||
* @param transactionType The transaction type to include in error messages
|
||||
* @param isStringID Toggle for if array contains IDs instead of AuthorizeCredential objects
|
||||
* @param maxCredentials The maximum length of the credentials array.
|
||||
* PermissionedDomainSet transaction uses 10, other transactions use 8.
|
||||
* @throws Validation Error if the formatting is incorrect
|
||||
*/
|
||||
// eslint-disable-next-line max-lines-per-function -- separating logic further will add unnecessary complexity
|
||||
// eslint-disable-next-line max-lines-per-function, max-params -- separating logic further will add unnecessary complexity
|
||||
export function validateCredentialsList(
|
||||
credentials: unknown,
|
||||
transactionType: string,
|
||||
isStringID: boolean,
|
||||
maxCredentials: number,
|
||||
): void {
|
||||
if (credentials == null) {
|
||||
return
|
||||
@@ -471,9 +476,9 @@ export function validateCredentialsList(
|
||||
`${transactionType}: Credentials must be an array`,
|
||||
)
|
||||
}
|
||||
if (credentials.length > MAX_CREDENTIALS_LIST_LENGTH) {
|
||||
if (credentials.length > maxCredentials) {
|
||||
throw new ValidationError(
|
||||
`${transactionType}: Credentials length cannot exceed ${MAX_CREDENTIALS_LIST_LENGTH} elements`,
|
||||
`${transactionType}: Credentials length cannot exceed ${maxCredentials} elements`,
|
||||
)
|
||||
} else if (credentials.length === 0) {
|
||||
throw new ValidationError(
|
||||
@@ -500,7 +505,42 @@ export function validateCredentialsList(
|
||||
}
|
||||
}
|
||||
|
||||
function containsDuplicates(objectList: object[]): boolean {
|
||||
const objSet = new Set(objectList.map((obj) => JSON.stringify(obj)))
|
||||
return objSet.size !== objectList.length
|
||||
// Type guard to ensure we're working with AuthorizeCredential[]
|
||||
// Note: This is not a rigorous type-guard. A more thorough solution would be to iterate over the array and check each item.
|
||||
function isAuthorizeCredentialArray(
|
||||
list: AuthorizeCredential[] | string[],
|
||||
): list is AuthorizeCredential[] {
|
||||
return typeof list[0] !== 'string'
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if an array of objects contains any duplicates.
|
||||
*
|
||||
* @param objectList - Array of objects to check for duplicates
|
||||
* @returns True if duplicates exist, false otherwise
|
||||
*/
|
||||
export function containsDuplicates(
|
||||
objectList: AuthorizeCredential[] | string[],
|
||||
): boolean {
|
||||
// Case-1: Process a list of string-IDs
|
||||
if (typeof objectList[0] === 'string') {
|
||||
const objSet = new Set(objectList.map((obj) => JSON.stringify(obj)))
|
||||
return objSet.size !== objectList.length
|
||||
}
|
||||
|
||||
// Case-2: Process a list of nested objects
|
||||
const seen = new Set<string>()
|
||||
|
||||
if (isAuthorizeCredentialArray(objectList)) {
|
||||
for (const item of objectList) {
|
||||
const key = `${item.Credential.Issuer}-${item.Credential.CredentialType}`
|
||||
// eslint-disable-next-line max-depth -- necessary to check for type-guards
|
||||
if (seen.has(key)) {
|
||||
return true
|
||||
}
|
||||
seen.add(key)
|
||||
}
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
BaseTransaction,
|
||||
validateBaseTransaction,
|
||||
validateCredentialsList,
|
||||
MAX_AUTHORIZED_CREDENTIALS,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
@@ -72,6 +73,7 @@ export function validateDepositPreauth(tx: Record<string, unknown>): void {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- confirmed in base transaction check
|
||||
tx.TransactionType as string,
|
||||
false,
|
||||
MAX_AUTHORIZED_CREDENTIALS,
|
||||
)
|
||||
} else if (tx.UnauthorizeCredentials !== undefined) {
|
||||
validateCredentialsList(
|
||||
@@ -79,6 +81,7 @@ export function validateDepositPreauth(tx: Record<string, unknown>): void {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- confirmed in base transaction check
|
||||
tx.TransactionType as string,
|
||||
false,
|
||||
MAX_AUTHORIZED_CREDENTIALS,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import {
|
||||
validateBaseTransaction,
|
||||
validateCredentialsList,
|
||||
validateRequiredField,
|
||||
MAX_AUTHORIZED_CREDENTIALS,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
@@ -55,6 +56,7 @@ export function validateEscrowFinish(tx: Record<string, unknown>): void {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
|
||||
tx.TransactionType as string,
|
||||
true,
|
||||
MAX_AUTHORIZED_CREDENTIALS,
|
||||
)
|
||||
|
||||
if (tx.OfferSequence == null) {
|
||||
|
||||
@@ -15,13 +15,18 @@ export {
|
||||
} from './accountSet'
|
||||
export { AccountDelete } from './accountDelete'
|
||||
export { AMMBid } from './AMMBid'
|
||||
export {
|
||||
AMMClawbackFlags,
|
||||
AMMClawbackFlagsInterface,
|
||||
AMMClawback,
|
||||
} from './AMMClawback'
|
||||
export { AMMCreate } from './AMMCreate'
|
||||
export { AMMDelete } from './AMMDelete'
|
||||
export {
|
||||
AMMDepositFlags,
|
||||
AMMDepositFlagsInterface,
|
||||
AMMDeposit,
|
||||
} from './AMMDeposit'
|
||||
export { AMMCreate } from './AMMCreate'
|
||||
export { AMMVote } from './AMMVote'
|
||||
export {
|
||||
AMMWithdrawFlags,
|
||||
@@ -71,6 +76,7 @@ export {
|
||||
NFTokenMintFlags,
|
||||
NFTokenMintFlagsInterface,
|
||||
} from './NFTokenMint'
|
||||
export { NFTokenModify, validateNFTokenModify } from './NFTokenModify'
|
||||
export { OfferCancel } from './offerCancel'
|
||||
export {
|
||||
OfferCreateFlags,
|
||||
@@ -105,3 +111,6 @@ export {
|
||||
XChainModifyBridgeFlags,
|
||||
XChainModifyBridgeFlagsInterface,
|
||||
} from './XChainModifyBridge'
|
||||
|
||||
export { PermissionedDomainSet } from './permissionedDomainSet'
|
||||
export { PermissionedDomainDelete } from './permissionedDomainDelete'
|
||||
|
||||
@@ -13,6 +13,7 @@ import {
|
||||
isNumber,
|
||||
Account,
|
||||
validateCredentialsList,
|
||||
MAX_AUTHORIZED_CREDENTIALS,
|
||||
} from './common'
|
||||
import type { TransactionMetadataBase } from './metadata'
|
||||
|
||||
@@ -188,6 +189,7 @@ export function validatePayment(tx: Record<string, unknown>): void {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
|
||||
tx.TransactionType as string,
|
||||
true,
|
||||
MAX_AUTHORIZED_CREDENTIALS,
|
||||
)
|
||||
|
||||
if (tx.InvoiceID !== undefined && typeof tx.InvoiceID !== 'string') {
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
GlobalFlags,
|
||||
validateBaseTransaction,
|
||||
validateCredentialsList,
|
||||
MAX_AUTHORIZED_CREDENTIALS,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
@@ -153,6 +154,7 @@ export function validatePaymentChannelClaim(tx: Record<string, unknown>): void {
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
|
||||
tx.TransactionType as string,
|
||||
true,
|
||||
MAX_AUTHORIZED_CREDENTIALS,
|
||||
)
|
||||
|
||||
if (tx.Channel === undefined) {
|
||||
|
||||
@@ -0,0 +1,28 @@
|
||||
import {
|
||||
BaseTransaction,
|
||||
isString,
|
||||
validateBaseTransaction,
|
||||
validateRequiredField,
|
||||
} from './common'
|
||||
|
||||
export interface PermissionedDomainDelete extends BaseTransaction {
|
||||
/* The transaction type (PermissionedDomainDelete). */
|
||||
TransactionType: 'PermissionedDomainDelete'
|
||||
|
||||
/* The domain to delete. */
|
||||
DomainID: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of a PermissionedDomainDelete transaction.
|
||||
*
|
||||
* @param tx - The transaction to verify.
|
||||
* @throws When the transaction is malformed.
|
||||
*/
|
||||
export function validatePermissionedDomainDelete(
|
||||
tx: Record<string, unknown>,
|
||||
): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
validateRequiredField(tx, 'DomainID', isString)
|
||||
}
|
||||
@@ -0,0 +1,54 @@
|
||||
import { AuthorizeCredential } from '../common'
|
||||
|
||||
import {
|
||||
BaseTransaction,
|
||||
isString,
|
||||
validateBaseTransaction,
|
||||
validateOptionalField,
|
||||
validateRequiredField,
|
||||
validateCredentialsList,
|
||||
} from './common'
|
||||
|
||||
const MAX_ACCEPTED_CREDENTIALS = 10
|
||||
|
||||
export interface PermissionedDomainSet extends BaseTransaction {
|
||||
/* The transaction type (PermissionedDomainSet). */
|
||||
TransactionType: 'PermissionedDomainSet'
|
||||
|
||||
/* The domain to modify. Must be included if modifying an existing domain. */
|
||||
DomainID?: string
|
||||
|
||||
/* The credentials that are accepted by the domain. Ownership of one
|
||||
of these credentials automatically makes you a member of the domain.
|
||||
An empty array means deleting the field. */
|
||||
AcceptedCredentials: AuthorizeCredential[]
|
||||
}
|
||||
|
||||
/**
|
||||
* Validate a PermissionedDomainSet transaction.
|
||||
*
|
||||
* @param tx - The transaction to validate.
|
||||
* @throws {ValidationError} When the transaction is invalid.
|
||||
*/
|
||||
export function validatePermissionedDomainSet(
|
||||
tx: Record<string, unknown>,
|
||||
): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
validateOptionalField(tx, 'DomainID', isString)
|
||||
validateRequiredField(
|
||||
tx,
|
||||
'AcceptedCredentials',
|
||||
() => tx.AcceptedCredentials instanceof Array,
|
||||
)
|
||||
|
||||
validateCredentialsList(
|
||||
tx.AcceptedCredentials,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
|
||||
tx.TransactionType as string,
|
||||
// PermissionedDomainSet uses AuthorizeCredential nested objects only, strings are not allowed
|
||||
false,
|
||||
// PermissionedDomainSet uses at most 10 accepted credentials. This is different from Credential-feature transactions.
|
||||
MAX_ACCEPTED_CREDENTIALS,
|
||||
)
|
||||
}
|
||||
@@ -4,11 +4,12 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
import { IssuedCurrencyAmount, Memo } from '../common'
|
||||
import { isHex } from '../utils'
|
||||
import { setTransactionFlagsToNumber } from '../utils/flags'
|
||||
import { convertTxFlagsToNumber } from '../utils/flags'
|
||||
|
||||
import { AccountDelete, validateAccountDelete } from './accountDelete'
|
||||
import { AccountSet, validateAccountSet } from './accountSet'
|
||||
import { AMMBid, validateAMMBid } from './AMMBid'
|
||||
import { AMMClawback, validateAMMClawback } from './AMMClawback'
|
||||
import { AMMCreate, validateAMMCreate } from './AMMCreate'
|
||||
import { AMMDelete, validateAMMDelete } from './AMMDelete'
|
||||
import { AMMDeposit, validateAMMDeposit } from './AMMDeposit'
|
||||
@@ -57,6 +58,7 @@ import {
|
||||
validateNFTokenCreateOffer,
|
||||
} from './NFTokenCreateOffer'
|
||||
import { NFTokenMint, validateNFTokenMint } from './NFTokenMint'
|
||||
import { NFTokenModify, validateNFTokenModify } from './NFTokenModify'
|
||||
import { OfferCancel, validateOfferCancel } from './offerCancel'
|
||||
import { OfferCreate, validateOfferCreate } from './offerCreate'
|
||||
import { OracleDelete, validateOracleDelete } from './oracleDelete'
|
||||
@@ -74,6 +76,14 @@ import {
|
||||
PaymentChannelFund,
|
||||
validatePaymentChannelFund,
|
||||
} from './paymentChannelFund'
|
||||
import {
|
||||
PermissionedDomainDelete,
|
||||
validatePermissionedDomainDelete,
|
||||
} from './permissionedDomainDelete'
|
||||
import {
|
||||
PermissionedDomainSet,
|
||||
validatePermissionedDomainSet,
|
||||
} from './permissionedDomainSet'
|
||||
import { SetFee } from './setFee'
|
||||
import { SetRegularKey, validateSetRegularKey } from './setRegularKey'
|
||||
import { SignerListSet, validateSignerListSet } from './signerListSet'
|
||||
@@ -114,6 +124,7 @@ import {
|
||||
*/
|
||||
export type SubmittableTransaction =
|
||||
| AMMBid
|
||||
| AMMClawback
|
||||
| AMMCreate
|
||||
| AMMDelete
|
||||
| AMMDeposit
|
||||
@@ -143,6 +154,7 @@ export type SubmittableTransaction =
|
||||
| NFTokenCancelOffer
|
||||
| NFTokenCreateOffer
|
||||
| NFTokenMint
|
||||
| NFTokenModify
|
||||
| OfferCancel
|
||||
| OfferCreate
|
||||
| OracleDelete
|
||||
@@ -151,6 +163,8 @@ export type SubmittableTransaction =
|
||||
| PaymentChannelClaim
|
||||
| PaymentChannelCreate
|
||||
| PaymentChannelFund
|
||||
| PermissionedDomainSet
|
||||
| PermissionedDomainDelete
|
||||
| SetRegularKey
|
||||
| SignerListSet
|
||||
| TicketCreate
|
||||
@@ -255,12 +269,16 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
})
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- okay here
|
||||
setTransactionFlagsToNumber(tx as unknown as Transaction)
|
||||
tx.Flags = convertTxFlagsToNumber(tx as unknown as Transaction)
|
||||
switch (tx.TransactionType) {
|
||||
case 'AMMBid':
|
||||
validateAMMBid(tx)
|
||||
break
|
||||
|
||||
case 'AMMClawback':
|
||||
validateAMMClawback(tx)
|
||||
break
|
||||
|
||||
case 'AMMCreate':
|
||||
validateAMMCreate(tx)
|
||||
break
|
||||
@@ -377,6 +395,10 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
validateNFTokenMint(tx)
|
||||
break
|
||||
|
||||
case 'NFTokenModify':
|
||||
validateNFTokenModify(tx)
|
||||
break
|
||||
|
||||
case 'OfferCancel':
|
||||
validateOfferCancel(tx)
|
||||
break
|
||||
@@ -409,6 +431,14 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
validatePaymentChannelFund(tx)
|
||||
break
|
||||
|
||||
case 'PermissionedDomainSet':
|
||||
validatePermissionedDomainSet(tx)
|
||||
break
|
||||
|
||||
case 'PermissionedDomainDelete':
|
||||
validatePermissionedDomainDelete(tx)
|
||||
break
|
||||
|
||||
case 'SetRegularKey':
|
||||
validateSetRegularKey(tx)
|
||||
break
|
||||
|
||||
@@ -30,6 +30,11 @@ export enum TrustSetFlags {
|
||||
tfSetFreeze = 0x00100000,
|
||||
/** Unfreeze the trust line. */
|
||||
tfClearFreeze = 0x00200000,
|
||||
/** Deep-Freeze the trustline -- disallow sending and receiving the said IssuedCurrency */
|
||||
/** Allowed only if the trustline is already regularly frozen, or if tfSetFreeze is set in the same transaction. */
|
||||
tfSetDeepFreeze = 0x00400000,
|
||||
/** Clear a Deep-Frozen trustline */
|
||||
tfClearDeepFreeze = 0x00800000,
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -89,6 +94,11 @@ export interface TrustSetFlagsInterface extends GlobalFlags {
|
||||
tfSetFreeze?: boolean
|
||||
/** Unfreeze the trust line. */
|
||||
tfClearFreeze?: boolean
|
||||
/** Deep-Freeze the trustline -- disallow sending and receiving the said IssuedCurrency */
|
||||
/** Allowed only if the trustline is already regularly frozen, or if tfSetFreeze is set in the same transaction. */
|
||||
tfSetDeepFreeze?: boolean
|
||||
/** Clear a Deep-Frozen trust line */
|
||||
tfClearDeepFreeze?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
/* eslint-disable no-param-reassign -- param reassign is safe */
|
||||
/* eslint-disable no-bitwise -- flags require bitwise operations */
|
||||
import { ValidationError } from '../../errors'
|
||||
import {
|
||||
@@ -6,9 +5,9 @@ import {
|
||||
AccountRootFlags,
|
||||
} from '../ledger/AccountRoot'
|
||||
import { AccountSetTfFlags } from '../transactions/accountSet'
|
||||
import { AMMClawbackFlags } from '../transactions/AMMClawback'
|
||||
import { AMMDepositFlags } from '../transactions/AMMDeposit'
|
||||
import { AMMWithdrawFlags } from '../transactions/AMMWithdraw'
|
||||
import { GlobalFlags } from '../transactions/common'
|
||||
import { MPTokenAuthorizeFlags } from '../transactions/MPTokenAuthorize'
|
||||
import { MPTokenIssuanceCreateFlags } from '../transactions/MPTokenIssuanceCreate'
|
||||
import { MPTokenIssuanceSetFlags } from '../transactions/MPTokenIssuanceSet'
|
||||
@@ -49,6 +48,7 @@ export function parseAccountRootFlags(
|
||||
|
||||
const txToFlag = {
|
||||
AccountSet: AccountSetTfFlags,
|
||||
AMMClawback: AMMClawbackFlags,
|
||||
AMMDeposit: AMMDepositFlags,
|
||||
AMMWithdraw: AMMWithdrawFlags,
|
||||
MPTokenAuthorize: MPTokenAuthorizeFlags,
|
||||
@@ -63,37 +63,61 @@ const txToFlag = {
|
||||
XChainModifyBridge: XChainModifyBridgeFlags,
|
||||
}
|
||||
|
||||
function isTxToFlagKey(
|
||||
transactionType: string,
|
||||
): transactionType is keyof typeof txToFlag {
|
||||
return transactionType in txToFlag
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets a transaction's flags to its numeric representation.
|
||||
*
|
||||
* @deprecated
|
||||
* This utility function is deprecated.
|
||||
* Use convertTxFlagsToNumber() instead and use the returned value to modify the Transaction.Flags from the caller.
|
||||
*
|
||||
* @param tx - A transaction to set its flags to its numeric representation.
|
||||
*/
|
||||
export function setTransactionFlagsToNumber(tx: Transaction): void {
|
||||
if (tx.Flags == null) {
|
||||
tx.Flags = 0
|
||||
return
|
||||
}
|
||||
if (typeof tx.Flags === 'number') {
|
||||
return
|
||||
}
|
||||
// eslint-disable-next-line no-console -- intended deprecation warning
|
||||
console.warn(
|
||||
'This function is deprecated. Use convertTxFlagsToNumber() instead and use the returned value to modify the Transaction.Flags from the caller.',
|
||||
)
|
||||
|
||||
tx.Flags = txToFlag[tx.TransactionType]
|
||||
? convertFlagsToNumber(tx.Flags, txToFlag[tx.TransactionType])
|
||||
: 0
|
||||
if (tx.Flags) {
|
||||
// eslint-disable-next-line no-param-reassign -- intended param reassign in setter, retain old functionality for compatibility
|
||||
tx.Flags = convertTxFlagsToNumber(tx)
|
||||
}
|
||||
}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- added ValidationError check for flagEnum
|
||||
function convertFlagsToNumber(flags: GlobalFlags, flagEnum: any): number {
|
||||
return Object.keys(flags).reduce((resultFlags, flag) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
|
||||
if (flagEnum[flag] == null) {
|
||||
throw new ValidationError(
|
||||
`flag ${flag} doesn't exist in flagEnum: ${JSON.stringify(flagEnum)}`,
|
||||
)
|
||||
}
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
|
||||
return flags[flag] ? resultFlags | flagEnum[flag] : resultFlags
|
||||
}, 0)
|
||||
/**
|
||||
* Returns a Transaction's Flags as its numeric representation.
|
||||
*
|
||||
* @param tx - A Transaction to parse Flags for
|
||||
* @returns A numerical representation of a Transaction's Flags
|
||||
*/
|
||||
export function convertTxFlagsToNumber(tx: Transaction): number {
|
||||
if (!tx.Flags) {
|
||||
return 0
|
||||
}
|
||||
if (typeof tx.Flags === 'number') {
|
||||
return tx.Flags
|
||||
}
|
||||
|
||||
if (isTxToFlagKey(tx.TransactionType)) {
|
||||
const flagEnum = txToFlag[tx.TransactionType]
|
||||
return Object.keys(tx.Flags).reduce((resultFlags, flag) => {
|
||||
if (flagEnum[flag] == null) {
|
||||
throw new ValidationError(
|
||||
`Invalid flag ${flag}. Valid flags are ${JSON.stringify(flagEnum)}`,
|
||||
)
|
||||
}
|
||||
|
||||
return tx.Flags?.[flag] ? resultFlags | flagEnum[flag] : resultFlags
|
||||
}, 0)
|
||||
}
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -103,22 +127,24 @@ function convertFlagsToNumber(flags: GlobalFlags, flagEnum: any): number {
|
||||
* @returns A map with all flags as booleans.
|
||||
*/
|
||||
export function parseTransactionFlags(tx: Transaction): object {
|
||||
setTransactionFlagsToNumber(tx)
|
||||
if (typeof tx.Flags !== 'number' || !tx.Flags || tx.Flags === 0) {
|
||||
const flags = convertTxFlagsToNumber(tx)
|
||||
if (flags === 0) {
|
||||
return {}
|
||||
}
|
||||
|
||||
const flags = tx.Flags
|
||||
const flagsMap = {}
|
||||
const booleanFlagMap = {}
|
||||
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- safe member access
|
||||
const flagEnum = txToFlag[tx.TransactionType]
|
||||
Object.values(flagEnum).forEach((flag) => {
|
||||
// eslint-disable-next-line @typescript-eslint/no-unsafe-member-access -- safe member access
|
||||
if (typeof flag === 'string' && isFlagEnabled(flags, flagEnum[flag])) {
|
||||
flagsMap[flag] = true
|
||||
}
|
||||
})
|
||||
if (isTxToFlagKey(tx.TransactionType)) {
|
||||
const transactionTypeFlags = txToFlag[tx.TransactionType]
|
||||
Object.values(transactionTypeFlags).forEach((flag) => {
|
||||
if (
|
||||
typeof flag === 'string' &&
|
||||
isFlagEnabled(flags, transactionTypeFlags[flag])
|
||||
) {
|
||||
booleanFlagMap[flag] = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
return flagsMap
|
||||
return booleanFlagMap
|
||||
}
|
||||
|
||||
@@ -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,8 @@ describe('server_info (rippled)', function () {
|
||||
'build_version',
|
||||
'node_size',
|
||||
'initial_sync_duration_us',
|
||||
'network_id',
|
||||
'git',
|
||||
]
|
||||
assert.deepEqual(
|
||||
omit(response.result.info, removeKeys),
|
||||
|
||||
@@ -60,6 +60,7 @@ describe('server_state', function () {
|
||||
load_factor_fee_queue: 256,
|
||||
load_factor_fee_reference: 256,
|
||||
load_factor_server: 256,
|
||||
network_id: 63456,
|
||||
peer_disconnects: '0',
|
||||
peer_disconnects_resources: '0',
|
||||
peers: 0,
|
||||
@@ -116,6 +117,8 @@ describe('server_state', function () {
|
||||
'node_size',
|
||||
'initial_sync_duration_us',
|
||||
'ports',
|
||||
'git',
|
||||
'network_id',
|
||||
]
|
||||
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,
|
||||
)
|
||||
})
|
||||
@@ -0,0 +1,57 @@
|
||||
import { AMMClawback, AMMDeposit, AMMDepositFlags, XRP } from 'xrpl'
|
||||
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
teardownClient,
|
||||
type XrplIntegrationTestContext,
|
||||
} from '../setup'
|
||||
import { createAMMPool, testTransaction } from '../utils'
|
||||
|
||||
describe('AMMClawback', function () {
|
||||
let testContext: XrplIntegrationTestContext
|
||||
|
||||
beforeAll(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
})
|
||||
afterAll(async () => teardownClient(testContext))
|
||||
|
||||
it('base', async function () {
|
||||
const ammPool = await createAMMPool(testContext.client, true)
|
||||
const { issuerWallet } = ammPool
|
||||
const holderWallet = ammPool.lpWallet
|
||||
|
||||
const asset = {
|
||||
currency: 'USD',
|
||||
issuer: issuerWallet.classicAddress,
|
||||
}
|
||||
const asset2 = {
|
||||
currency: 'XRP',
|
||||
} as XRP
|
||||
|
||||
const ammDepositTx: AMMDeposit = {
|
||||
TransactionType: 'AMMDeposit',
|
||||
Account: holderWallet.classicAddress,
|
||||
Asset: asset,
|
||||
Asset2: asset2,
|
||||
Amount: {
|
||||
currency: 'USD',
|
||||
issuer: issuerWallet.address,
|
||||
value: '10',
|
||||
},
|
||||
Flags: AMMDepositFlags.tfSingleAsset,
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, ammDepositTx, holderWallet)
|
||||
|
||||
const ammClawback: AMMClawback = {
|
||||
TransactionType: 'AMMClawback',
|
||||
Account: issuerWallet.address,
|
||||
Holder: holderWallet.address,
|
||||
Asset: asset,
|
||||
Asset2: asset2,
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, ammClawback, issuerWallet)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,104 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { NFTokenModify } from '../../../dist/npm'
|
||||
import { NFTokenMintFlags } from '../../../dist/npm/src'
|
||||
import {
|
||||
convertStringToHex,
|
||||
getNFTokenID,
|
||||
NFTokenMint,
|
||||
TransactionMetadata,
|
||||
TxRequest,
|
||||
} from '../../../src'
|
||||
import { hashSignedTx } from '../../../src/utils/hashes'
|
||||
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('NFTokenModify', function () {
|
||||
let testContext: XrplIntegrationTestContext
|
||||
|
||||
beforeEach(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
})
|
||||
afterEach(async () => teardownClient(testContext))
|
||||
|
||||
// Mint an NFToken with tfMutable flag and modify URI later
|
||||
it(
|
||||
'modify NFToken URI',
|
||||
async function () {
|
||||
const oldUri = convertStringToHex('https://www.google.com')
|
||||
const newUri = convertStringToHex('https://www.youtube.com')
|
||||
|
||||
const mutableMint: NFTokenMint = {
|
||||
TransactionType: 'NFTokenMint',
|
||||
Account: testContext.wallet.address,
|
||||
Flags: NFTokenMintFlags.tfMutable,
|
||||
URI: oldUri,
|
||||
NFTokenTaxon: 0,
|
||||
}
|
||||
const response = await testTransaction(
|
||||
testContext.client,
|
||||
mutableMint,
|
||||
testContext.wallet,
|
||||
)
|
||||
assert.equal(response.type, 'response')
|
||||
|
||||
const mutableTx: TxRequest = {
|
||||
command: 'tx',
|
||||
transaction: hashSignedTx(response.result.tx_blob),
|
||||
}
|
||||
const mutableTxResponse = await testContext.client.request(mutableTx)
|
||||
|
||||
const mutableNFTokenID =
|
||||
getNFTokenID(
|
||||
mutableTxResponse.result.meta as TransactionMetadata<NFTokenMint>,
|
||||
) ?? 'undefined'
|
||||
|
||||
const accountNFTs = await testContext.client.request({
|
||||
command: 'account_nfts',
|
||||
account: testContext.wallet.address,
|
||||
})
|
||||
|
||||
assert.equal(
|
||||
accountNFTs.result.account_nfts.find(
|
||||
(nft) => nft.NFTokenID === mutableNFTokenID,
|
||||
)?.URI,
|
||||
oldUri,
|
||||
)
|
||||
|
||||
const modifyTx: NFTokenModify = {
|
||||
TransactionType: 'NFTokenModify',
|
||||
Account: testContext.wallet.address,
|
||||
NFTokenID: mutableNFTokenID,
|
||||
URI: newUri,
|
||||
}
|
||||
|
||||
const modifyResponse = await testTransaction(
|
||||
testContext.client,
|
||||
modifyTx,
|
||||
testContext.wallet,
|
||||
)
|
||||
assert.equal(modifyResponse.type, 'response')
|
||||
|
||||
const nfts = await testContext.client.request({
|
||||
command: 'account_nfts',
|
||||
account: testContext.wallet.address,
|
||||
})
|
||||
|
||||
assert.equal(
|
||||
nfts.result.account_nfts.find(
|
||||
(nft) => nft.NFTokenID === mutableNFTokenID,
|
||||
)?.URI,
|
||||
newUri,
|
||||
)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
@@ -1,24 +1,36 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { OfferCreate } from '../../../src'
|
||||
import { OfferCreate, TrustSet, Wallet } from '../../../src'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
teardownClient,
|
||||
type XrplIntegrationTestContext,
|
||||
} from '../setup'
|
||||
import { testTransaction } from '../utils'
|
||||
import {
|
||||
testTransaction,
|
||||
generateFundedWallet,
|
||||
submitTransaction,
|
||||
} from '../utils'
|
||||
|
||||
// how long before each test case times out
|
||||
const TIMEOUT = 20000
|
||||
|
||||
describe('OfferCreate', function () {
|
||||
let testContext: XrplIntegrationTestContext
|
||||
let wallet_deep_freeze_trustline: Wallet | undefined
|
||||
|
||||
beforeEach(async () => {
|
||||
beforeAll(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
if (!wallet_deep_freeze_trustline) {
|
||||
// eslint-disable-next-line require-atomic-updates -- race condition doesn't really matter
|
||||
wallet_deep_freeze_trustline = await generateFundedWallet(
|
||||
testContext.client,
|
||||
)
|
||||
}
|
||||
})
|
||||
afterEach(async () => teardownClient(testContext))
|
||||
|
||||
afterAll(async () => teardownClient(testContext))
|
||||
|
||||
it(
|
||||
'base',
|
||||
@@ -49,4 +61,52 @@ describe('OfferCreate', function () {
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
|
||||
it(
|
||||
'OfferCreate with Deep-Frozen trustline must fail',
|
||||
async () => {
|
||||
assert(wallet_deep_freeze_trustline != null)
|
||||
|
||||
// deep-freeze the trust line
|
||||
const trust_set_tx: TrustSet = {
|
||||
TransactionType: 'TrustSet',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
LimitAmount: {
|
||||
currency: 'USD',
|
||||
issuer: wallet_deep_freeze_trustline.classicAddress,
|
||||
value: '10',
|
||||
},
|
||||
Flags: {
|
||||
tfSetFreeze: true,
|
||||
tfSetDeepFreeze: true,
|
||||
},
|
||||
}
|
||||
|
||||
await testTransaction(
|
||||
testContext.client,
|
||||
trust_set_tx,
|
||||
testContext.wallet,
|
||||
)
|
||||
|
||||
const offer_create_tx: OfferCreate = {
|
||||
TransactionType: 'OfferCreate',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
TakerGets: '13100000',
|
||||
TakerPays: {
|
||||
currency: 'USD',
|
||||
issuer: wallet_deep_freeze_trustline.classicAddress,
|
||||
value: '10',
|
||||
},
|
||||
}
|
||||
|
||||
const response = await submitTransaction({
|
||||
client: testContext.client,
|
||||
transaction: offer_create_tx,
|
||||
wallet: testContext.wallet,
|
||||
})
|
||||
|
||||
assert.equal(response.result.engine_result, 'tecFROZEN')
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -0,0 +1,86 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import {
|
||||
LedgerEntryRequest,
|
||||
PermissionedDomainDelete,
|
||||
PermissionedDomainSet,
|
||||
AuthorizeCredential,
|
||||
} from '../../../src'
|
||||
import PermissionedDomain from '../../../src/models/ledger/PermissionedDomain'
|
||||
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('PermissionedDomainSet', function () {
|
||||
let testContext: XrplIntegrationTestContext
|
||||
|
||||
beforeEach(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
})
|
||||
afterEach(async () => teardownClient(testContext))
|
||||
|
||||
it(
|
||||
'Lifecycle of PermissionedDomain ledger object',
|
||||
async () => {
|
||||
const sampleCredential: AuthorizeCredential = {
|
||||
Credential: {
|
||||
CredentialType: stringToHex('Passport'),
|
||||
Issuer: testContext.wallet.classicAddress,
|
||||
},
|
||||
}
|
||||
|
||||
// Step-1: Test the PermissionedDomainSet transaction
|
||||
const pdSet: PermissionedDomainSet = {
|
||||
TransactionType: 'PermissionedDomainSet',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
AcceptedCredentials: [sampleCredential],
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, pdSet, testContext.wallet)
|
||||
|
||||
// Step-2: Validate the ledger_entry, account_objects RPC methods
|
||||
// validate the account_objects RPC
|
||||
const result = await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: testContext.wallet.classicAddress,
|
||||
type: 'permissioned_domain',
|
||||
})
|
||||
|
||||
assert.equal(result.result.account_objects.length, 1)
|
||||
const pd = result.result.account_objects[0] as PermissionedDomain
|
||||
|
||||
assert.equal(pd.Flags, 0)
|
||||
expect(pd.AcceptedCredentials).toEqual([sampleCredential])
|
||||
|
||||
// validate the ledger_entry RPC
|
||||
const ledgerEntryRequest: LedgerEntryRequest = {
|
||||
command: 'ledger_entry',
|
||||
// fetch the PD `index` from the previous account_objects RPC response
|
||||
index: pd.index,
|
||||
}
|
||||
const ledgerEntryResult = await testContext.client.request(
|
||||
ledgerEntryRequest,
|
||||
)
|
||||
assert.deepEqual(pd, ledgerEntryResult.result.node)
|
||||
|
||||
// Step-3: Test the PDDelete transaction
|
||||
const pdDelete: PermissionedDomainDelete = {
|
||||
TransactionType: 'PermissionedDomainDelete',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
// fetch the PD `index` from the previous account_objects RPC response
|
||||
DomainID: pd.index,
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, pdDelete, testContext.wallet)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
@@ -1,6 +1,8 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { TrustSet, percentToQuality, Wallet } from '../../../src'
|
||||
import { RippleState } from '../../../src/models/ledger/index'
|
||||
import { RippleStateFlags } from '../../../src/models/ledger/RippleState'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
@@ -85,4 +87,60 @@ describe('TrustSet', function () {
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
|
||||
it(
|
||||
'Create a Deep-Frozen trustline',
|
||||
async () => {
|
||||
assert(wallet2 != null)
|
||||
// deep-freeze a trustline with the specified counter-party/currency-code
|
||||
const tx: TrustSet = {
|
||||
TransactionType: 'TrustSet',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
LimitAmount: {
|
||||
currency: 'USD',
|
||||
issuer: wallet2.classicAddress,
|
||||
value: '10',
|
||||
},
|
||||
Flags: {
|
||||
tfSetFreeze: true,
|
||||
tfSetDeepFreeze: true,
|
||||
},
|
||||
}
|
||||
|
||||
const response = await testTransaction(
|
||||
testContext.client,
|
||||
tx,
|
||||
testContext.wallet,
|
||||
)
|
||||
assert.equal(response.result.engine_result, 'tesSUCCESS')
|
||||
|
||||
// assert that the trustline is frozen
|
||||
const trustLine = await testContext.client.request({
|
||||
command: 'account_lines',
|
||||
account: testContext.wallet.classicAddress,
|
||||
})
|
||||
assert.equal(trustLine.result.lines[0].freeze, true)
|
||||
|
||||
// verify that the trust-line is deep-frozen
|
||||
// this operation cannot be done with the account_lines RPC
|
||||
const account_objects = await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: testContext.wallet.classicAddress,
|
||||
})
|
||||
|
||||
const rippleState = account_objects.result
|
||||
.account_objects[0] as RippleState
|
||||
|
||||
// Depending on the pseudo-random generation of accounts,
|
||||
// either of the below leger-object flags must be set
|
||||
|
||||
const hasDeepFreeze =
|
||||
// eslint-disable-next-line no-bitwise -- required to validate flag
|
||||
(rippleState.Flags & RippleStateFlags.lsfHighDeepFreeze) |
|
||||
// eslint-disable-next-line no-bitwise -- required to validate flag
|
||||
(rippleState.Flags & RippleStateFlags.lsfLowDeepFreeze)
|
||||
assert.isTrue(hasDeepFreeze !== 0)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -373,7 +373,10 @@ export async function getIOUBalance(
|
||||
return (await client.request(request)).result.lines[0].balance
|
||||
}
|
||||
|
||||
export async function createAMMPool(client: Client): Promise<{
|
||||
export async function createAMMPool(
|
||||
client: Client,
|
||||
enableAMMClawback = false,
|
||||
): Promise<{
|
||||
issuerWallet: Wallet
|
||||
lpWallet: Wallet
|
||||
asset: Currency
|
||||
@@ -391,6 +394,16 @@ export async function createAMMPool(client: Client): Promise<{
|
||||
|
||||
await testTransaction(client, accountSetTx, issuerWallet)
|
||||
|
||||
if (enableAMMClawback) {
|
||||
const accountSetTx2: AccountSet = {
|
||||
TransactionType: 'AccountSet',
|
||||
Account: issuerWallet.classicAddress,
|
||||
SetFlag: AccountSetAsfFlags.asfAllowTrustLineClawback,
|
||||
}
|
||||
|
||||
await testTransaction(client, accountSetTx2, issuerWallet)
|
||||
}
|
||||
|
||||
const trustSetTx: TrustSet = {
|
||||
TransactionType: 'TrustSet',
|
||||
Flags: TrustSetFlags.tfClearNoRipple,
|
||||
|
||||
176
packages/xrpl/test/models/AMMClawback.test.ts
Normal file
176
packages/xrpl/test/models/AMMClawback.test.ts
Normal file
@@ -0,0 +1,176 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { validate, ValidationError } from '../../src'
|
||||
import {
|
||||
AMMClawbackFlags,
|
||||
validateAMMClawback,
|
||||
} from '../../src/models/transactions/AMMClawback'
|
||||
|
||||
/**
|
||||
* AMMClawback Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('AMMClawback', function () {
|
||||
let ammClawback
|
||||
|
||||
beforeEach(function () {
|
||||
ammClawback = {
|
||||
TransactionType: 'AMMClawback',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Holder: 'rPyfep3gcLzkosKC9XiE77Y8DZWG6iWDT9',
|
||||
Asset: {
|
||||
currency: 'USD',
|
||||
issuer: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
},
|
||||
Asset2: {
|
||||
currency: 'XRP',
|
||||
},
|
||||
Amount: {
|
||||
currency: 'USD',
|
||||
issuer: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
value: '1000',
|
||||
},
|
||||
Sequence: 1337,
|
||||
} as any
|
||||
})
|
||||
|
||||
it(`verifies valid AMMClawback`, function () {
|
||||
assert.doesNotThrow(() => validateAMMClawback(ammClawback))
|
||||
assert.doesNotThrow(() => validate(ammClawback))
|
||||
})
|
||||
|
||||
it(`verifies valid AMMClawback without Amount`, function () {
|
||||
delete ammClawback.Amount
|
||||
assert.doesNotThrow(() => validateAMMClawback(ammClawback))
|
||||
assert.doesNotThrow(() => validate(ammClawback))
|
||||
})
|
||||
|
||||
it(`verifies valid AMMClawback with tfClawTwoAssets`, function () {
|
||||
ammClawback.flags = AMMClawbackFlags.tfClawTwoAssets
|
||||
assert.doesNotThrow(() => validateAMMClawback(ammClawback))
|
||||
assert.doesNotThrow(() => validate(ammClawback))
|
||||
})
|
||||
|
||||
it(`throws w/ missing Holder`, function () {
|
||||
delete ammClawback.Holder
|
||||
const errorMessage = 'AMMClawback: missing field Holder'
|
||||
assert.throws(
|
||||
() => validateAMMClawback(ammClawback),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid field Holder`, function () {
|
||||
ammClawback.Holder = 1234
|
||||
const errorMessage = 'AMMClawback: invalid field Holder'
|
||||
assert.throws(
|
||||
() => validateAMMClawback(ammClawback),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ Holder and Asset.issuer must be distinct`, function () {
|
||||
ammClawback.Holder = ammClawback.Asset.issuer
|
||||
const errorMessage = 'AMMClawback: Holder and Asset.issuer must be distinct'
|
||||
assert.throws(
|
||||
() => validateAMMClawback(ammClawback),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ missing Asset`, function () {
|
||||
delete ammClawback.Asset
|
||||
const errorMessage = 'AMMClawback: missing field Asset'
|
||||
assert.throws(
|
||||
() => validateAMMClawback(ammClawback),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid field Asset`, function () {
|
||||
ammClawback.Asset = '1000'
|
||||
const errorMessage = 'AMMClawback: invalid field Asset'
|
||||
assert.throws(
|
||||
() => validateAMMClawback(ammClawback),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ Account must be the same as Asset.issuer`, function () {
|
||||
ammClawback.Account = 'rf1BiGeXwwQoi8Z2ueFYTEXSwuJYfV2Jpn'
|
||||
const errorMessage = 'AMMClawback: Account must be the same as Asset.issuer'
|
||||
assert.throws(
|
||||
() => validateAMMClawback(ammClawback),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ missing Asset2`, function () {
|
||||
delete ammClawback.Asset2
|
||||
const errorMessage = 'AMMClawback: missing field Asset2'
|
||||
assert.throws(
|
||||
() => validateAMMClawback(ammClawback),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid field Asset2`, function () {
|
||||
ammClawback.Asset2 = '1000'
|
||||
const errorMessage = 'AMMClawback: invalid field Asset2'
|
||||
assert.throws(
|
||||
() => validateAMMClawback(ammClawback),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid field Amount`, function () {
|
||||
ammClawback.Amount = 1000
|
||||
const errorMessage = 'AMMClawback: invalid field Amount'
|
||||
assert.throws(
|
||||
() => validateAMMClawback(ammClawback),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ Amount.currency must match Asset.currency`, function () {
|
||||
ammClawback.Amount.currency = 'ETH'
|
||||
const errorMessage =
|
||||
'AMMClawback: Amount.currency must match Asset.currency'
|
||||
assert.throws(
|
||||
() => validateAMMClawback(ammClawback),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ Amount.issuer must match Amount.issuer`, function () {
|
||||
ammClawback.Amount.issuer = 'rnYgaEtpqpNRt3wxE39demVpDAA817rQEY'
|
||||
const errorMessage = 'AMMClawback: Amount.issuer must match Amount.issuer'
|
||||
assert.throws(
|
||||
() => validateAMMClawback(ammClawback),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(ammClawback), ValidationError, errorMessage)
|
||||
})
|
||||
})
|
||||
75
packages/xrpl/test/models/NFTokenModify.test.ts
Normal file
75
packages/xrpl/test/models/NFTokenModify.test.ts
Normal file
@@ -0,0 +1,75 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { convertStringToHex, validate, ValidationError } from '../../src'
|
||||
|
||||
const TOKEN_ID =
|
||||
'00090032B5F762798A53D543A014CAF8B297CFF8F2F937E844B17C9E00000003'
|
||||
|
||||
/**
|
||||
* NFTokenModify Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('NFTokenModify', function () {
|
||||
it(`verifies valid NFTokenModify`, function () {
|
||||
const validNFTokenModify = {
|
||||
TransactionType: 'NFTokenModify',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
NFTokenID: TOKEN_ID,
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
URI: convertStringToHex('http://xrpl.org'),
|
||||
} as any
|
||||
|
||||
assert.doesNotThrow(() => validate(validNFTokenModify))
|
||||
})
|
||||
|
||||
it(`throws w/ missing NFTokenID`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenModify',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenModify: missing field NFTokenID',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ URI being an empty string`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenModify',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
NFTokenID: TOKEN_ID,
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
URI: '',
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenModify: URI must not be empty string',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ URI not in hex format`, function () {
|
||||
const invalid = {
|
||||
TransactionType: 'NFTokenModify',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
NFTokenID: TOKEN_ID,
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
URI: '--',
|
||||
} as any
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalid),
|
||||
ValidationError,
|
||||
'NFTokenModify: URI must be in hex format',
|
||||
)
|
||||
})
|
||||
})
|
||||
49
packages/xrpl/test/models/permissionedDomainDelete.test.ts
Normal file
49
packages/xrpl/test/models/permissionedDomainDelete.test.ts
Normal file
@@ -0,0 +1,49 @@
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { validate, ValidationError } from '../../src'
|
||||
import { validatePermissionedDomainDelete } from '../../src/models/transactions/permissionedDomainDelete'
|
||||
|
||||
/**
|
||||
* PermissionedDomainDelete Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('PermissionedDomainDelete', function () {
|
||||
let tx
|
||||
|
||||
beforeEach(function () {
|
||||
tx = {
|
||||
TransactionType: 'PermissionedDomainDelete',
|
||||
Account: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
|
||||
DomainID:
|
||||
'D88930B33C2B6831660BFD006D91FF100011AD4E67CBB78B460AF0A215103737',
|
||||
} as any
|
||||
})
|
||||
|
||||
it('verifies valid PermissionedDomainDelete', function () {
|
||||
assert.doesNotThrow(() => validatePermissionedDomainDelete(tx))
|
||||
assert.doesNotThrow(() => validate(tx))
|
||||
})
|
||||
|
||||
it(`throws w/ missing field DomainID`, function () {
|
||||
delete tx.DomainID
|
||||
const errorMessage = 'PermissionedDomainDelete: missing field DomainID'
|
||||
assert.throws(
|
||||
() => validatePermissionedDomainDelete(tx),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid DomainID`, function () {
|
||||
tx.DomainID = 1234
|
||||
const errorMessage = 'PermissionedDomainDelete: invalid field DomainID'
|
||||
assert.throws(
|
||||
() => validatePermissionedDomainDelete(tx),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
})
|
||||
92
packages/xrpl/test/models/permissionedDomainSet.test.ts
Normal file
92
packages/xrpl/test/models/permissionedDomainSet.test.ts
Normal file
@@ -0,0 +1,92 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/dist/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { AuthorizeCredential, validate, ValidationError } from '../../src'
|
||||
|
||||
/**
|
||||
* PermissionedDomainSet Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('PermissionedDomainSet', function () {
|
||||
let tx
|
||||
const sampleCredential: AuthorizeCredential = {
|
||||
Credential: {
|
||||
CredentialType: stringToHex('Passport'),
|
||||
Issuer: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
|
||||
},
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
tx = {
|
||||
TransactionType: 'PermissionedDomainSet',
|
||||
Account: 'rfmDuhDyLGgx94qiwf3YF8BUV5j6KSvE8',
|
||||
DomainID:
|
||||
'D88930B33C2B6831660BFD006D91FF100011AD4E67CBB78B460AF0A215103737',
|
||||
AcceptedCredentials: [sampleCredential],
|
||||
} as any
|
||||
})
|
||||
|
||||
it('verifies valid PermissionedDomainSet', function () {
|
||||
assert.doesNotThrow(() => validate(tx))
|
||||
})
|
||||
|
||||
it(`throws with invalid field DomainID`, function () {
|
||||
// DomainID is expected to be a string
|
||||
tx.DomainID = 1234
|
||||
const errorMessage = 'PermissionedDomainSet: invalid field DomainID'
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ missing field AcceptedCredentials`, function () {
|
||||
delete tx.AcceptedCredentials
|
||||
const errorMessage =
|
||||
'PermissionedDomainSet: missing field AcceptedCredentials'
|
||||
assert.throws(() => validate(tx), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it('throws when AcceptedCredentials exceeds maximum length', function () {
|
||||
tx.AcceptedCredentials = Array(11).fill(sampleCredential)
|
||||
assert.throws(
|
||||
() => validate(tx),
|
||||
ValidationError,
|
||||
'PermissionedDomainSet: Credentials length cannot exceed 10 elements',
|
||||
)
|
||||
})
|
||||
|
||||
it('throws when AcceptedCredentials is empty', function () {
|
||||
tx.AcceptedCredentials = []
|
||||
assert.throws(
|
||||
() => validate(tx),
|
||||
ValidationError,
|
||||
'PermissionedDomainSet: Credentials cannot be an empty array',
|
||||
)
|
||||
})
|
||||
|
||||
it('throws when AcceptedCredentials is not an array type', function () {
|
||||
tx.AcceptedCredentials = 'AcceptedCredentials is not an array'
|
||||
assert.throws(
|
||||
() => validate(tx),
|
||||
ValidationError,
|
||||
'PermissionedDomainSet: invalid field AcceptedCredentials',
|
||||
)
|
||||
})
|
||||
|
||||
it('throws when AcceptedCredentials contains duplicates', function () {
|
||||
tx.AcceptedCredentials = [sampleCredential, sampleCredential]
|
||||
assert.throws(
|
||||
() => validate(tx),
|
||||
ValidationError,
|
||||
'PermissionedDomainSet: Credentials cannot contain duplicate elements',
|
||||
)
|
||||
})
|
||||
|
||||
it('throws when AcceptedCredentials contains invalid format', function () {
|
||||
tx.AcceptedCredentials = [{ Field1: 'Value1', Field2: 'Value2' }]
|
||||
assert.throws(
|
||||
() => validate(tx),
|
||||
ValidationError,
|
||||
'PermissionedDomainSet: Invalid Credentials format',
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -22,6 +22,11 @@ describe('TrustSet', function () {
|
||||
},
|
||||
QualityIn: 1234,
|
||||
QualityOut: 4321,
|
||||
// an example of deep-frozen trustline
|
||||
Flags: {
|
||||
tfSetFreeze: true,
|
||||
tfSetDeepFreeze: true,
|
||||
},
|
||||
} as any
|
||||
})
|
||||
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable import/no-deprecated -- using deprecated setTransactionFlagsToNumbers to ensure no breaking changes */
|
||||
/* eslint-disable no-bitwise -- flags require bitwise operations */
|
||||
import { assert } from 'chai'
|
||||
|
||||
@@ -12,10 +13,13 @@ import {
|
||||
TrustSet,
|
||||
TrustSetFlags,
|
||||
} from '../../src'
|
||||
import { AuthorizeCredential } from '../../src/models/common'
|
||||
import { AccountRootFlags } from '../../src/models/ledger'
|
||||
import { containsDuplicates } from '../../src/models/transactions/common'
|
||||
import { isFlagEnabled } from '../../src/models/utils'
|
||||
import {
|
||||
setTransactionFlagsToNumber,
|
||||
convertTxFlagsToNumber,
|
||||
parseAccountRootFlags,
|
||||
parseTransactionFlags,
|
||||
} from '../../src/models/utils/flags'
|
||||
@@ -26,6 +30,43 @@ import {
|
||||
* Provides tests for utils used in models.
|
||||
*/
|
||||
describe('Models Utils', function () {
|
||||
describe('validate containsDuplicates utility method', function () {
|
||||
it(`use nested-objects for input parameters, list contains duplicates`, function () {
|
||||
// change the order of the inner-objects in the list
|
||||
const list_with_duplicates: AuthorizeCredential[] = [
|
||||
{ Credential: { Issuer: 'alice', CredentialType: 'Passport' } },
|
||||
{ Credential: { CredentialType: 'Passport', Issuer: 'alice' } },
|
||||
]
|
||||
|
||||
assert.isTrue(containsDuplicates(list_with_duplicates))
|
||||
})
|
||||
|
||||
it(`use nested-objects for input parameters, no duplicates`, function () {
|
||||
const list_without_dups: AuthorizeCredential[] = [
|
||||
{ Credential: { Issuer: 'alice', CredentialType: 'Passport' } },
|
||||
{ Credential: { CredentialType: 'DMV_license', Issuer: 'bob' } },
|
||||
]
|
||||
|
||||
assert.isFalse(containsDuplicates(list_without_dups))
|
||||
})
|
||||
|
||||
it(`use string-IDs for input parameters`, function () {
|
||||
const list_without_dups: string[] = [
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A',
|
||||
'F9F89FBB1426210D58D6A06E5EEF1783D6A90EE403B79AEDF0FED36A6DE238D2',
|
||||
'5328F2D1D6EBBC6093DC10F1EA3DD630666F5B2491EB9BDD7DF9A6C45AC12C46',
|
||||
]
|
||||
|
||||
const list_with_duplicates: string[] = [
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A',
|
||||
]
|
||||
|
||||
assert.isFalse(containsDuplicates(list_without_dups))
|
||||
assert.isTrue(containsDuplicates(list_with_duplicates))
|
||||
})
|
||||
})
|
||||
|
||||
describe('isFlagEnabled', function () {
|
||||
let flags: number
|
||||
const flag1 = 0x00010000
|
||||
@@ -46,6 +87,65 @@ describe('Models Utils', function () {
|
||||
})
|
||||
})
|
||||
|
||||
describe('parseAccountRootFlags', function () {
|
||||
// eslint-disable-next-line complexity -- Simpler to list them all out at once.
|
||||
it('all flags enabled', function () {
|
||||
const accountRootFlags =
|
||||
AccountRootFlags.lsfDefaultRipple |
|
||||
AccountRootFlags.lsfDepositAuth |
|
||||
AccountRootFlags.lsfDisableMaster |
|
||||
AccountRootFlags.lsfDisallowXRP |
|
||||
AccountRootFlags.lsfGlobalFreeze |
|
||||
AccountRootFlags.lsfNoFreeze |
|
||||
AccountRootFlags.lsfPasswordSpent |
|
||||
AccountRootFlags.lsfRequireAuth |
|
||||
AccountRootFlags.lsfRequireDestTag |
|
||||
AccountRootFlags.lsfDisallowIncomingNFTokenOffer |
|
||||
AccountRootFlags.lsfDisallowIncomingCheck |
|
||||
AccountRootFlags.lsfDisallowIncomingPayChan |
|
||||
AccountRootFlags.lsfDisallowIncomingTrustline |
|
||||
AccountRootFlags.lsfAllowTrustLineClawback
|
||||
|
||||
const parsed = parseAccountRootFlags(accountRootFlags)
|
||||
|
||||
assert.isTrue(
|
||||
parsed.lsfDefaultRipple &&
|
||||
parsed.lsfDepositAuth &&
|
||||
parsed.lsfDisableMaster &&
|
||||
parsed.lsfDisallowXRP &&
|
||||
parsed.lsfGlobalFreeze &&
|
||||
parsed.lsfNoFreeze &&
|
||||
parsed.lsfPasswordSpent &&
|
||||
parsed.lsfRequireAuth &&
|
||||
parsed.lsfRequireDestTag &&
|
||||
parsed.lsfDisallowIncomingNFTokenOffer &&
|
||||
parsed.lsfDisallowIncomingCheck &&
|
||||
parsed.lsfDisallowIncomingPayChan &&
|
||||
parsed.lsfDisallowIncomingTrustline &&
|
||||
parsed.lsfAllowTrustLineClawback,
|
||||
)
|
||||
})
|
||||
|
||||
it('no flags set', function () {
|
||||
const parsed = parseAccountRootFlags(0)
|
||||
|
||||
assert.isUndefined(parsed.lsfDefaultRipple)
|
||||
assert.isUndefined(parsed.lsfDepositAuth)
|
||||
assert.isUndefined(parsed.lsfDisableMaster)
|
||||
assert.isUndefined(parsed.lsfDisallowXRP)
|
||||
assert.isUndefined(parsed.lsfGlobalFreeze)
|
||||
assert.isUndefined(parsed.lsfNoFreeze)
|
||||
assert.isUndefined(parsed.lsfPasswordSpent)
|
||||
assert.isUndefined(parsed.lsfRequireAuth)
|
||||
assert.isUndefined(parsed.lsfRequireDestTag)
|
||||
assert.isUndefined(parsed.lsfDisallowIncomingNFTokenOffer)
|
||||
assert.isUndefined(parsed.lsfDisallowIncomingCheck)
|
||||
assert.isUndefined(parsed.lsfDisallowIncomingPayChan)
|
||||
assert.isUndefined(parsed.lsfDisallowIncomingTrustline)
|
||||
assert.isUndefined(parsed.lsfAllowTrustLineClawback)
|
||||
})
|
||||
})
|
||||
|
||||
describe('setTransactionFlagsToNumber', function () {
|
||||
it('sets OfferCreateFlags to its numeric value', function () {
|
||||
const tx: OfferCreate = {
|
||||
@@ -151,64 +251,9 @@ describe('Models Utils', function () {
|
||||
setTransactionFlagsToNumber(tx)
|
||||
assert.strictEqual(tx.Flags, 0)
|
||||
})
|
||||
})
|
||||
|
||||
// eslint-disable-next-line complexity -- Simpler to list them all out at once.
|
||||
it('parseAccountRootFlags all enabled', function () {
|
||||
const accountRootFlags =
|
||||
AccountRootFlags.lsfDefaultRipple |
|
||||
AccountRootFlags.lsfDepositAuth |
|
||||
AccountRootFlags.lsfDisableMaster |
|
||||
AccountRootFlags.lsfDisallowXRP |
|
||||
AccountRootFlags.lsfGlobalFreeze |
|
||||
AccountRootFlags.lsfNoFreeze |
|
||||
AccountRootFlags.lsfPasswordSpent |
|
||||
AccountRootFlags.lsfRequireAuth |
|
||||
AccountRootFlags.lsfRequireDestTag |
|
||||
AccountRootFlags.lsfDisallowIncomingNFTokenOffer |
|
||||
AccountRootFlags.lsfDisallowIncomingCheck |
|
||||
AccountRootFlags.lsfDisallowIncomingPayChan |
|
||||
AccountRootFlags.lsfDisallowIncomingTrustline |
|
||||
AccountRootFlags.lsfAllowTrustLineClawback
|
||||
|
||||
const parsed = parseAccountRootFlags(accountRootFlags)
|
||||
|
||||
assert.isTrue(
|
||||
parsed.lsfDefaultRipple &&
|
||||
parsed.lsfDepositAuth &&
|
||||
parsed.lsfDisableMaster &&
|
||||
parsed.lsfDisallowXRP &&
|
||||
parsed.lsfGlobalFreeze &&
|
||||
parsed.lsfNoFreeze &&
|
||||
parsed.lsfPasswordSpent &&
|
||||
parsed.lsfRequireAuth &&
|
||||
parsed.lsfRequireDestTag &&
|
||||
parsed.lsfDisallowIncomingNFTokenOffer &&
|
||||
parsed.lsfDisallowIncomingCheck &&
|
||||
parsed.lsfDisallowIncomingPayChan &&
|
||||
parsed.lsfDisallowIncomingTrustline &&
|
||||
parsed.lsfAllowTrustLineClawback,
|
||||
)
|
||||
})
|
||||
|
||||
it('parseAccountFlags all false', function () {
|
||||
const parsed = parseAccountRootFlags(0)
|
||||
|
||||
assert.isUndefined(parsed.lsfDefaultRipple)
|
||||
assert.isUndefined(parsed.lsfDepositAuth)
|
||||
assert.isUndefined(parsed.lsfDisableMaster)
|
||||
assert.isUndefined(parsed.lsfDisallowXRP)
|
||||
assert.isUndefined(parsed.lsfGlobalFreeze)
|
||||
assert.isUndefined(parsed.lsfNoFreeze)
|
||||
assert.isUndefined(parsed.lsfPasswordSpent)
|
||||
assert.isUndefined(parsed.lsfRequireAuth)
|
||||
assert.isUndefined(parsed.lsfRequireDestTag)
|
||||
assert.isUndefined(parsed.lsfDisallowIncomingNFTokenOffer)
|
||||
assert.isUndefined(parsed.lsfDisallowIncomingCheck)
|
||||
assert.isUndefined(parsed.lsfDisallowIncomingPayChan)
|
||||
assert.isUndefined(parsed.lsfDisallowIncomingTrustline)
|
||||
assert.isUndefined(parsed.lsfAllowTrustLineClawback)
|
||||
})
|
||||
|
||||
describe('parseTransactionFlags', function () {
|
||||
it('parseTransactionFlags all enabled', function () {
|
||||
const tx: PaymentChannelClaim = {
|
||||
Account: 'r...',
|
||||
@@ -264,4 +309,111 @@ describe('Models Utils', function () {
|
||||
assert.notStrictEqual(flagsMap, expected)
|
||||
})
|
||||
})
|
||||
|
||||
describe('convertTxFlagsToNumber', function () {
|
||||
it('converts OfferCreateFlags to its numeric value', function () {
|
||||
const tx: OfferCreate = {
|
||||
Account: 'r3rhWeE31Jt5sWmi4QiGLMZnY3ENgqw96W',
|
||||
Fee: '10',
|
||||
TakerGets: {
|
||||
currency: 'DSH',
|
||||
issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX',
|
||||
value: '43.11584856965009',
|
||||
},
|
||||
TakerPays: '12928290425',
|
||||
TransactionType: 'OfferCreate',
|
||||
TxnSignature:
|
||||
'3045022100D874CDDD6BB24ED66E83B1D3574D3ECAC753A78F26DB7EBA89EAB8E7D72B95F802207C8CCD6CEA64E4AE2014E59EE9654E02CA8F03FE7FCE0539E958EAE182234D91',
|
||||
Flags: {
|
||||
tfPassive: true,
|
||||
tfImmediateOrCancel: false,
|
||||
tfFillOrKill: true,
|
||||
tfSell: false,
|
||||
},
|
||||
}
|
||||
|
||||
const { tfPassive, tfFillOrKill } = OfferCreateFlags
|
||||
const expected: number = tfPassive | tfFillOrKill
|
||||
|
||||
const result = convertTxFlagsToNumber(tx)
|
||||
assert.strictEqual(result, expected)
|
||||
})
|
||||
|
||||
it('converts PaymentChannelClaimFlags to its numeric value', function () {
|
||||
const tx: PaymentChannelClaim = {
|
||||
Account: 'r...',
|
||||
TransactionType: 'PaymentChannelClaim',
|
||||
Channel:
|
||||
'C1AE6DDDEEC05CF2978C0BAD6FE302948E9533691DC749DCDD3B9E5992CA6198',
|
||||
Flags: {
|
||||
tfRenew: true,
|
||||
tfClose: false,
|
||||
},
|
||||
}
|
||||
|
||||
const { tfRenew } = PaymentChannelClaimFlags
|
||||
const expected: number = tfRenew
|
||||
|
||||
const result = convertTxFlagsToNumber(tx)
|
||||
assert.strictEqual(result, expected)
|
||||
})
|
||||
|
||||
it('converts PaymentTransactionFlags to its numeric value', function () {
|
||||
const tx: Payment = {
|
||||
TransactionType: 'Payment',
|
||||
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
|
||||
Amount: '1234',
|
||||
Destination: 'rfkE1aSy9G8Upk4JssnwBxhEv5p4mn2KTy',
|
||||
Flags: {
|
||||
tfNoRippleDirect: false,
|
||||
tfPartialPayment: true,
|
||||
tfLimitQuality: true,
|
||||
},
|
||||
}
|
||||
|
||||
const { tfPartialPayment, tfLimitQuality } = PaymentFlags
|
||||
const expected: number = tfPartialPayment | tfLimitQuality
|
||||
|
||||
const result = convertTxFlagsToNumber(tx)
|
||||
assert.strictEqual(result, expected)
|
||||
})
|
||||
|
||||
it('converts TrustSetFlags to its numeric value', function () {
|
||||
const tx: TrustSet = {
|
||||
TransactionType: 'TrustSet',
|
||||
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
|
||||
LimitAmount: {
|
||||
currency: 'XRP',
|
||||
issuer: 'rcXY84C4g14iFp6taFXjjQGVeHqSCh9RX',
|
||||
value: '4329.23',
|
||||
},
|
||||
QualityIn: 1234,
|
||||
QualityOut: 4321,
|
||||
Flags: {
|
||||
tfSetfAuth: true,
|
||||
tfSetNoRipple: false,
|
||||
tfClearNoRipple: true,
|
||||
tfSetFreeze: false,
|
||||
tfClearFreeze: true,
|
||||
},
|
||||
}
|
||||
|
||||
const { tfSetfAuth, tfClearNoRipple, tfClearFreeze } = TrustSetFlags
|
||||
const expected: number = tfSetfAuth | tfClearNoRipple | tfClearFreeze
|
||||
|
||||
const result = convertTxFlagsToNumber(tx)
|
||||
assert.strictEqual(result, expected)
|
||||
})
|
||||
|
||||
it('converts other transaction types flags to its numeric value', function () {
|
||||
const tx: DepositPreauth = {
|
||||
TransactionType: 'DepositPreauth',
|
||||
Account: 'rUn84CUYbNjRoTQ6mSW7BVJPSVJNLb1QLo',
|
||||
Flags: {},
|
||||
}
|
||||
|
||||
const result = convertTxFlagsToNumber(tx)
|
||||
assert.strictEqual(result, 0)
|
||||
})
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user