mirror of
https://github.com/Xahau/xahau.js.git
synced 2025-11-27 23:55:49 +00:00
Credentials (#2829)
* create credentials obj, modify depositpreauth * structrure of transaction models * initial validation methods and modify transactions affected by deposit auth * cleanup and add new transactions to list * binarycodec and add amendments to config * methods account for credentials * binary codec update * add amendments to config * error validation for credentials actions * core logic of error validation completed * type checking in error validation * init test files and field type validations * basic tests for crud transactions * cred delete tests * cred accept unit tests * cred create and accept unit tests * cred delete unit tests * depositPreauth unit tests * generic checks for payment, paymentchannelclaim, escrowfinish credential list * ledger entry update * lint errors * cleanup and use helper methods * fix lint bug * init integration tests for new transactions * fix build error, integration test docker update * unit test fixes -- all pass now * integration test layout complete * integration command * integration tests run * cicd command edit * lint and cleanup * modified history markdown * deposit preauth integration update * update docs with new docker command * fix validation for string id credential arrays * exports * add flag * lint * fix typo in contributing doc * docstring typos * readable string * fix test' * review comment fixes * txn duplicate fix * Apply suggestions from code review Co-authored-by: Omar Khan <khancodegt@gmail.com> Co-authored-by: Mayukha Vadari <mvadari@ripple.com> * Apply suggestions from code review Co-authored-by: Omar Khan <khancodegt@gmail.com> * Apply suggestions from code review Co-authored-by: Omar Khan <khancodegt@gmail.com> Co-authored-by: Mayukha Vadari <mvadari@ripple.com> * typo in auto suggest * rebase * readd definitions after rebase * cleanup list val * unit tests fixed and running * lint * refactor authcred check to work * Update packages/xrpl/src/models/transactions/payment.ts Co-authored-by: Omar Khan <khancodegt@gmail.com> * typo * Update .ci-config/rippled.cfg Co-authored-by: Omar Khan <khancodegt@gmail.com> * update rippled version * optional field nits * add to response depositauthorize * Update packages/xrpl/src/models/transactions/CredentialCreate.ts Co-authored-by: Omar Khan <khancodegt@gmail.com> * Update packages/xrpl/src/models/transactions/CredentialDelete.ts Co-authored-by: Omar Khan <khancodegt@gmail.com> * Update packages/xrpl/src/models/transactions/accountDelete.ts Co-authored-by: Omar Khan <khancodegt@gmail.com> * Apply suggestions from code review Co-authored-by: Omar Khan <khancodegt@gmail.com> * cleanups * unit test fix * more escrowfinish tests * clearer error message * re add statement * undo autodeleted mandates * remove extraneous integration tests for now * lint * Update .ci-config/rippled.cfg Co-authored-by: Omar Khan <khancodegt@gmail.com> * Update packages/xrpl/src/models/transactions/common.ts Co-authored-by: Omar Khan <khancodegt@gmail.com> * added tests * typo --------- Co-authored-by: Omar Khan <khancodegt@gmail.com> Co-authored-by: Mayukha Vadari <mvadari@ripple.com>
This commit is contained in:
@@ -178,8 +178,9 @@ PriceOracle
|
||||
fixEmptyDID
|
||||
fixXChainRewardRounding
|
||||
fixPreviousTxnID
|
||||
# 2.3.0-rc1 Amendments
|
||||
fixAMMv1_1
|
||||
# 2.3.0 Amendments
|
||||
fixAMMv1_2
|
||||
Credentials
|
||||
NFTokenMintOffer
|
||||
MPTokensV1
|
||||
|
||||
4
.github/workflows/nodejs.yml
vendored
4
.github/workflows/nodejs.yml
vendored
@@ -106,7 +106,7 @@ jobs:
|
||||
|
||||
- name: Run docker in background
|
||||
run: |
|
||||
docker run --detach --rm -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/etc/opt/ripple/" --name rippled-service --health-cmd="rippled server_nfo || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true --entrypoint bash ${{ env.RIPPLED_DOCKER_IMAGE }} -c "rippled -a"
|
||||
docker run --detach --rm -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/etc/opt/ripple/" --name rippled-service --health-cmd="rippled server_info || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true --entrypoint bash ${{ env.RIPPLED_DOCKER_IMAGE }} -c "rippled -a"
|
||||
|
||||
- name: Use Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v4
|
||||
@@ -162,7 +162,7 @@ jobs:
|
||||
|
||||
- name: Run docker in background
|
||||
run: |
|
||||
docker run --detach --rm -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/etc/opt/ripple/" --name rippled-service --health-cmd="rippled server_nfo || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true --entrypoint bash ${{ env.RIPPLED_DOCKER_IMAGE }} -c "rippled -a"
|
||||
docker run --detach --rm -p 6006:6006 --volume "${{ github.workspace }}/.ci-config/":"/etc/opt/ripple/" --name rippled-service --health-cmd="rippled server_info || exit 1" --health-interval=5s --health-retries=10 --health-timeout=2s --env GITHUB_ACTIONS=true --env CI=true --entrypoint bash ${{ env.RIPPLED_DOCKER_IMAGE }} -c "rippled -a"
|
||||
|
||||
- name: Setup npm version 10
|
||||
run: |
|
||||
|
||||
@@ -64,18 +64,20 @@ 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 --interactive -t --volume $PWD/.ci-config:/opt/ripple/etc/ --platform linux/amd64 rippleci/rippled:2.0.0-b4 /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg
|
||||
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'
|
||||
npm run build
|
||||
npm run test:integration
|
||||
```
|
||||
|
||||
Breaking down the command:
|
||||
* `docker run -p 6006:6006` starts a Docker container with an open port for admin WebSocket requests.
|
||||
* `--interactive` allows you to interact with the container.
|
||||
* `-t` starts a terminal in the container for you to send commands to.
|
||||
* `--volume $PWD/.ci-config:/config/` identifies the `rippled.cfg` and `validators.txt` to import. It must be an absolute path, so we use `$PWD` instead of `./`.
|
||||
`--rm` tells docker to close the container after processes are done running.
|
||||
* `-it` allows you to interact with the container.
|
||||
`--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
|
||||
* `/opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg` starts `rippled` in standalone mode
|
||||
* `--entrypoint bash rippleci/rippled:2.3.0-rc1` manually overrides the entrypoint (for versions of rippled >= 2.3.0)
|
||||
* `-c 'rippled -a'` provides the bash command to start `rippled` in standalone mode from the manual entrypoint
|
||||
|
||||
### Browser Tests
|
||||
|
||||
@@ -90,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 --interactive -t --volume $PWD/.ci-config:/opt/ripple/etc/ --platform linux/amd64 rippleci/rippled:2.2.0-b3 /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg
|
||||
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'
|
||||
npm run test:browser
|
||||
```
|
||||
|
||||
|
||||
@@ -910,6 +910,26 @@
|
||||
"type": "UInt64"
|
||||
}
|
||||
],
|
||||
[
|
||||
"IssuerNode",
|
||||
{
|
||||
"nth": 27,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "UInt64"
|
||||
}
|
||||
],
|
||||
[
|
||||
"SubjectNode",
|
||||
{
|
||||
"nth": 28,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "UInt64"
|
||||
}
|
||||
],
|
||||
[
|
||||
"EmailHash",
|
||||
{
|
||||
@@ -1810,6 +1830,16 @@
|
||||
"type": "Blob"
|
||||
}
|
||||
],
|
||||
[
|
||||
"CredentialType",
|
||||
{
|
||||
"nth": 31,
|
||||
"isVLEncoded": true,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "Blob"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Account",
|
||||
{
|
||||
@@ -1980,6 +2010,16 @@
|
||||
"type": "AccountID"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Subject",
|
||||
{
|
||||
"nth": 24,
|
||||
"isVLEncoded": true,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "AccountID"
|
||||
}
|
||||
],
|
||||
[
|
||||
"TransactionMetaData",
|
||||
{
|
||||
@@ -2270,6 +2310,16 @@
|
||||
"type": "STObject"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Credential",
|
||||
{
|
||||
"nth": 33,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "STObject"
|
||||
}
|
||||
],
|
||||
[
|
||||
"Signers",
|
||||
{
|
||||
@@ -2460,6 +2510,26 @@
|
||||
"type": "STArray"
|
||||
}
|
||||
],
|
||||
[
|
||||
"AuthorizeCredentials",
|
||||
{
|
||||
"nth": 26,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "STArray"
|
||||
}
|
||||
],
|
||||
[
|
||||
"UnauthorizeCredentials",
|
||||
{
|
||||
"nth": 27,
|
||||
"isVLEncoded": false,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "STArray"
|
||||
}
|
||||
],
|
||||
[
|
||||
"CloseResolution",
|
||||
{
|
||||
@@ -2640,6 +2710,16 @@
|
||||
"type": "Vector256"
|
||||
}
|
||||
],
|
||||
[
|
||||
"CredentialIDs",
|
||||
{
|
||||
"nth": 5,
|
||||
"isVLEncoded": true,
|
||||
"isSerialized": true,
|
||||
"isSigningField": true,
|
||||
"type": "Vector256"
|
||||
}
|
||||
],
|
||||
[
|
||||
"MPTokenIssuanceID",
|
||||
{
|
||||
@@ -2781,6 +2861,7 @@
|
||||
"NegativeUNL": 78,
|
||||
"Offer": 111,
|
||||
"Oracle": 128,
|
||||
"Credential": 129,
|
||||
"PayChannel": 120,
|
||||
"RippleState": 114,
|
||||
"SignerList": 83,
|
||||
@@ -2797,6 +2878,7 @@
|
||||
"tecAMM_NOT_EMPTY": 167,
|
||||
"tecARRAY_EMPTY": 190,
|
||||
"tecARRAY_TOO_LARGE": 191,
|
||||
"tecBAD_CREDENTIALS": 193,
|
||||
"tecCANT_ACCEPT_OWN_NFTOKEN_OFFER": 158,
|
||||
"tecCLAIM": 100,
|
||||
"tecCRYPTOCONDITION_ERROR": 146,
|
||||
@@ -2982,6 +3064,9 @@
|
||||
"CheckCash": 17,
|
||||
"CheckCreate": 16,
|
||||
"Clawback": 30,
|
||||
"CredentialCreate": 58,
|
||||
"CredentialAccept": 59,
|
||||
"CredentialDelete": 60,
|
||||
"DIDDelete": 50,
|
||||
"DIDSet": 49,
|
||||
"DepositPreauth": 19,
|
||||
|
||||
@@ -5,9 +5,10 @@ Subscribe to [the **xrpl-announce** mailing list](https://groups.google.com/g/xr
|
||||
## Unreleased Changes
|
||||
|
||||
### Added
|
||||
* parseTransactionFlags as a utility function in the xrpl package to streamline transactions flags-to-map conversion
|
||||
* Added new MPT transaction definitions (XLS-33)
|
||||
* 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)
|
||||
|
||||
### Fixed
|
||||
* `TransactionStream` model supports APIv2
|
||||
|
||||
@@ -162,6 +162,16 @@ export interface AuthAccount {
|
||||
}
|
||||
}
|
||||
|
||||
export interface AuthorizeCredential {
|
||||
Credential: {
|
||||
/** The issuer of the credential. */
|
||||
Issuer: string
|
||||
|
||||
/** A hex-encoded value to identify the type of credential from the issuer. */
|
||||
CredentialType: string
|
||||
}
|
||||
}
|
||||
|
||||
export interface XChainBridge {
|
||||
LockingChainDoor: string
|
||||
LockingChainIssue: Currency
|
||||
|
||||
47
packages/xrpl/src/models/ledger/Credential.ts
Normal file
47
packages/xrpl/src/models/ledger/Credential.ts
Normal file
@@ -0,0 +1,47 @@
|
||||
import { GlobalFlags } from '../transactions/common'
|
||||
|
||||
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
|
||||
|
||||
export interface CredentialFlags extends GlobalFlags {
|
||||
lsfAccepted?: boolean
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* A Credential object describes a credential, similar to a passport, which is an issuable identity verifier
|
||||
* that can be used as a prerequisite for other transactions
|
||||
*
|
||||
* @category Ledger Entries
|
||||
*/
|
||||
export default interface Credential extends BaseLedgerEntry, HasPreviousTxnID {
|
||||
LedgerEntryType: 'Credential'
|
||||
/**
|
||||
* A bit-map of boolean flags
|
||||
*/
|
||||
Flags: number | CredentialFlags
|
||||
|
||||
/** The account that the credential is for. */
|
||||
Subject: string
|
||||
|
||||
/** The issuer of the credential. */
|
||||
Issuer: string
|
||||
|
||||
/** A hex-encoded value to identify the type of credential from the issuer. */
|
||||
CredentialType: string
|
||||
|
||||
/** A hint indicating which page of the subject's owner directory links to this object,
|
||||
* in case the directory consists of multiple pages.
|
||||
*/
|
||||
SubjectNode: string
|
||||
|
||||
/** A hint indicating which page of the issuer's owner directory links to this object,
|
||||
* in case the directory consists of multiple pages.
|
||||
*/
|
||||
IssuerNode: string
|
||||
|
||||
/** Credential expiration. */
|
||||
Expiration?: number
|
||||
|
||||
/** Additional data about the credential (such as a link to the VC document). */
|
||||
URI?: string
|
||||
}
|
||||
@@ -1,3 +1,5 @@
|
||||
import { AuthorizeCredential } from '../common'
|
||||
|
||||
import { BaseLedgerEntry, HasPreviousTxnID } from './BaseLedgerEntry'
|
||||
|
||||
/**
|
||||
@@ -12,8 +14,6 @@ export default interface DepositPreauth
|
||||
LedgerEntryType: 'DepositPreauth'
|
||||
/** The account that granted the preauthorization. */
|
||||
Account: string
|
||||
/** The account that received the preauthorization. */
|
||||
Authorize: string
|
||||
/**
|
||||
* A bit-map of boolean flags. No flags are defined for DepositPreauth
|
||||
* objects, so this value is always 0.
|
||||
@@ -24,4 +24,8 @@ export default interface DepositPreauth
|
||||
* object, in case the directory consists of multiple pages.
|
||||
*/
|
||||
OwnerNode: string
|
||||
/** The account that received the preauthorization. */
|
||||
Authorize?: string
|
||||
/** The credential(s) that received the preauthorization. */
|
||||
AuthorizeCredentials?: AuthorizeCredential[]
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ import Amendments from './Amendments'
|
||||
import AMM from './AMM'
|
||||
import Bridge from './Bridge'
|
||||
import Check from './Check'
|
||||
import Credential from './Credential'
|
||||
import DepositPreauth from './DepositPreauth'
|
||||
import DirectoryNode from './DirectoryNode'
|
||||
import Escrow from './Escrow'
|
||||
@@ -24,6 +25,7 @@ type LedgerEntry =
|
||||
| AMM
|
||||
| Bridge
|
||||
| Check
|
||||
| Credential
|
||||
| DepositPreauth
|
||||
| DirectoryNode
|
||||
| Escrow
|
||||
@@ -45,6 +47,7 @@ type LedgerEntryFilter =
|
||||
| 'amm'
|
||||
| 'bridge'
|
||||
| 'check'
|
||||
| 'credential'
|
||||
| 'deposit_preauth'
|
||||
| 'did'
|
||||
| 'directory'
|
||||
|
||||
@@ -6,6 +6,7 @@ import Amendments, { Majority, AMENDMENTS_ID } from './Amendments'
|
||||
import AMM, { VoteSlot } from './AMM'
|
||||
import Bridge from './Bridge'
|
||||
import Check from './Check'
|
||||
import Credential from './Credential'
|
||||
import DepositPreauth from './DepositPreauth'
|
||||
import DID from './DID'
|
||||
import DirectoryNode from './DirectoryNode'
|
||||
@@ -41,6 +42,7 @@ export {
|
||||
AMM,
|
||||
Bridge,
|
||||
Check,
|
||||
Credential,
|
||||
DepositPreauth,
|
||||
DirectoryNode,
|
||||
DID,
|
||||
|
||||
@@ -15,6 +15,12 @@ export interface DepositAuthorizedRequest
|
||||
source_account: string
|
||||
/** The recipient of a possible payment. */
|
||||
destination_account: string
|
||||
/**
|
||||
* The object IDs of Credential objects. If this field is included, then the
|
||||
* credential will be taken into account when analyzing whether the sender can send
|
||||
* funds to the destination.
|
||||
*/
|
||||
credentials?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -52,5 +58,9 @@ export interface DepositAuthorizedResponse extends BaseResponse {
|
||||
source_account: string
|
||||
/** If true, the information comes from a validated ledger version. */
|
||||
validated?: boolean
|
||||
/** The object IDs of `Credential` objects. If this field is included,
|
||||
* then the credential will be taken into account when analyzing whether
|
||||
* the sender can send funds to the destination. */
|
||||
credentials?: string[]
|
||||
}
|
||||
}
|
||||
|
||||
@@ -83,6 +83,23 @@ export interface LedgerEntryRequest extends BaseRequest, LookupByLedgerRequest {
|
||||
/** The object ID of a Check object to retrieve. */
|
||||
check?: string
|
||||
|
||||
/* Specify the Credential to retrieve. If a string, must be the ledger entry ID of
|
||||
* the entry, as hexadecimal. If an object, requires subject, issuer, and
|
||||
* credential_type sub-fields.
|
||||
*/
|
||||
credential?:
|
||||
| {
|
||||
/** The account that is the subject of the credential. */
|
||||
subject: string
|
||||
|
||||
/** The account that issued the credential. */
|
||||
issuer: string
|
||||
|
||||
/** The type of the credential, as issued. */
|
||||
credentialType: string
|
||||
}
|
||||
| string
|
||||
|
||||
/**
|
||||
* Specify a DepositPreauth object to retrieve. If a string, must be the
|
||||
* object ID of the DepositPreauth object, as hexadecimal. If an object,
|
||||
|
||||
44
packages/xrpl/src/models/transactions/CredentialAccept.ts
Normal file
44
packages/xrpl/src/models/transactions/CredentialAccept.ts
Normal file
@@ -0,0 +1,44 @@
|
||||
import {
|
||||
BaseTransaction,
|
||||
isString,
|
||||
validateBaseTransaction,
|
||||
validateCredentialType,
|
||||
validateRequiredField,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
* Accepts a credential issued to the Account (i.e. the Account is the Subject of the Credential object).
|
||||
* Credentials are represented in hex. Whilst they are allowed a maximum length of 64
|
||||
* bytes, every byte requires 2 hex characters for representation.
|
||||
* The credential is not considered valid until it has been transferred/accepted.
|
||||
*
|
||||
* @category Transaction Models
|
||||
* */
|
||||
export interface CredentialAccept extends BaseTransaction {
|
||||
TransactionType: 'CredentialAccept'
|
||||
|
||||
/** The subject of the credential. */
|
||||
Account: string
|
||||
|
||||
/** The issuer of the credential. */
|
||||
Issuer: string
|
||||
|
||||
/** A hex-encoded value to identify the type of credential from the issuer. */
|
||||
CredentialType: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of a CredentialAccept at runtime.
|
||||
*
|
||||
* @param tx - A CredentialAccept Transaction.
|
||||
* @throws When the CredentialAccept is Malformed.
|
||||
*/
|
||||
export function validateCredentialAccept(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
validateRequiredField(tx, 'Account', isString)
|
||||
|
||||
validateRequiredField(tx, 'Issuer', isString)
|
||||
|
||||
validateCredentialType(tx)
|
||||
}
|
||||
81
packages/xrpl/src/models/transactions/CredentialCreate.ts
Normal file
81
packages/xrpl/src/models/transactions/CredentialCreate.ts
Normal file
@@ -0,0 +1,81 @@
|
||||
import { HEX_REGEX } from '@xrplf/isomorphic/utils'
|
||||
|
||||
import { ValidationError } from '../../errors'
|
||||
|
||||
import {
|
||||
BaseTransaction,
|
||||
isNumber,
|
||||
isString,
|
||||
validateBaseTransaction,
|
||||
validateCredentialType,
|
||||
validateOptionalField,
|
||||
validateRequiredField,
|
||||
} from './common'
|
||||
|
||||
const MAX_URI_LENGTH = 256
|
||||
|
||||
/**
|
||||
* Creates a Credential object. It must be sent by the issuer.
|
||||
*
|
||||
* @category Transaction Models
|
||||
* */
|
||||
export interface CredentialCreate extends BaseTransaction {
|
||||
TransactionType: 'CredentialCreate'
|
||||
|
||||
/** The issuer of the credential. */
|
||||
Account: string
|
||||
|
||||
/** The subject of the credential. */
|
||||
Subject: string
|
||||
|
||||
/** A hex-encoded value to identify the type of credential from the issuer. */
|
||||
CredentialType: string
|
||||
|
||||
/** Credential expiration. */
|
||||
Expiration?: number
|
||||
|
||||
/** Additional data about the credential (such as a link to the VC document). */
|
||||
URI?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of a CredentialCreate at runtime.
|
||||
*
|
||||
* @param tx - A CredentialCreate Transaction.
|
||||
* @throws When the CredentialCreate is Malformed.
|
||||
*/
|
||||
export function validateCredentialCreate(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
validateRequiredField(tx, 'Account', isString)
|
||||
|
||||
validateRequiredField(tx, 'Subject', isString)
|
||||
|
||||
validateCredentialType(tx)
|
||||
|
||||
validateOptionalField(tx, 'Expiration', isNumber)
|
||||
|
||||
validateURI(tx.URI)
|
||||
}
|
||||
|
||||
function validateURI(URI: unknown): void {
|
||||
if (URI === undefined) {
|
||||
return
|
||||
}
|
||||
|
||||
if (typeof URI !== 'string') {
|
||||
throw new ValidationError('CredentialCreate: invalid field URI')
|
||||
}
|
||||
|
||||
if (URI.length === 0) {
|
||||
throw new ValidationError('CredentialCreate: URI cannot be an empty string')
|
||||
} else if (URI.length > MAX_URI_LENGTH) {
|
||||
throw new ValidationError(
|
||||
`CredentialCreate: URI length must be <= ${MAX_URI_LENGTH}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (!HEX_REGEX.test(URI)) {
|
||||
throw new ValidationError('CredentialCreate: URI must be encoded in hex')
|
||||
}
|
||||
}
|
||||
55
packages/xrpl/src/models/transactions/CredentialDelete.ts
Normal file
55
packages/xrpl/src/models/transactions/CredentialDelete.ts
Normal file
@@ -0,0 +1,55 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
|
||||
import {
|
||||
BaseTransaction,
|
||||
isString,
|
||||
validateBaseTransaction,
|
||||
validateCredentialType,
|
||||
validateOptionalField,
|
||||
validateRequiredField,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
* Deletes a Credential object.
|
||||
*
|
||||
* @category Transaction Models
|
||||
* */
|
||||
export interface CredentialDelete extends BaseTransaction {
|
||||
TransactionType: 'CredentialDelete'
|
||||
|
||||
/** The transaction submitter. */
|
||||
Account: string
|
||||
|
||||
/** A hex-encoded value to identify the type of credential from the issuer. */
|
||||
CredentialType: string
|
||||
|
||||
/** The person that the credential is for. If omitted, Account is assumed to be the subject. */
|
||||
Subject?: string
|
||||
|
||||
/** The issuer of the credential. If omitted, Account is assumed to be the issuer. */
|
||||
Issuer?: string
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of a CredentialDelete at runtime.
|
||||
*
|
||||
* @param tx - A CredentialDelete Transaction.
|
||||
* @throws When the CredentialDelete is Malformed.
|
||||
*/
|
||||
export function validateCredentialDelete(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
if (!tx.Subject && !tx.Issuer) {
|
||||
throw new ValidationError(
|
||||
'CredentialDelete: either `Issuer` or `Subject` must be provided',
|
||||
)
|
||||
}
|
||||
|
||||
validateRequiredField(tx, 'Account', isString)
|
||||
|
||||
validateCredentialType(tx)
|
||||
|
||||
validateOptionalField(tx, 'Subject', isString)
|
||||
|
||||
validateOptionalField(tx, 'Issuer', isString)
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import {
|
||||
isAccount,
|
||||
isNumber,
|
||||
validateBaseTransaction,
|
||||
validateCredentialsList,
|
||||
validateOptionalField,
|
||||
validateRequiredField,
|
||||
} from './common'
|
||||
@@ -28,6 +29,12 @@ export interface AccountDelete extends BaseTransaction {
|
||||
* information for the recipient of the deleted account's leftover XRP.
|
||||
*/
|
||||
DestinationTag?: number
|
||||
/**
|
||||
* Credentials associated with sender of this transaction. The credentials included
|
||||
* must not be expired. The list must not be empty when specified and cannot contain
|
||||
* more than 8 credentials.
|
||||
*/
|
||||
CredentialIDs?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -41,4 +48,11 @@ export function validateAccountDelete(tx: Record<string, unknown>): void {
|
||||
|
||||
validateRequiredField(tx, 'Destination', isAccount)
|
||||
validateOptionalField(tx, 'DestinationTag', isNumber)
|
||||
|
||||
validateCredentialsList(
|
||||
tx.CredentialIDs,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
|
||||
tx.TransactionType as string,
|
||||
true,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,9 +1,12 @@
|
||||
/* eslint-disable max-lines -- common utility file */
|
||||
import { HEX_REGEX } from '@xrplf/isomorphic/utils'
|
||||
import { isValidClassicAddress, isValidXAddress } from 'ripple-address-codec'
|
||||
import { TRANSACTION_TYPES } from 'ripple-binary-codec'
|
||||
|
||||
import { ValidationError } from '../../errors'
|
||||
import {
|
||||
Amount,
|
||||
AuthorizeCredential,
|
||||
Currency,
|
||||
IssuedCurrencyAmount,
|
||||
Memo,
|
||||
@@ -14,6 +17,9 @@ import {
|
||||
import { onlyHasFields } from '../utils'
|
||||
|
||||
const MEMO_SIZE = 3
|
||||
const MAX_CREDENTIALS_LIST_LENGTH = 8
|
||||
const MAX_CREDENTIAL_BYTE_LENGTH = 64
|
||||
const MAX_CREDENTIAL_TYPE_LENGTH = MAX_CREDENTIAL_BYTE_LENGTH * 2
|
||||
|
||||
function isMemo(obj: { Memo?: unknown }): boolean {
|
||||
if (obj.Memo == null) {
|
||||
@@ -61,6 +67,7 @@ const ISSUE_SIZE = 2
|
||||
const ISSUED_CURRENCY_SIZE = 3
|
||||
const XCHAIN_BRIDGE_SIZE = 4
|
||||
const MPTOKEN_SIZE = 2
|
||||
const AUTHORIZE_CREDENTIAL_SIZE = 1
|
||||
|
||||
function isRecord(value: unknown): value is Record<string, unknown> {
|
||||
return value !== null && typeof value === 'object'
|
||||
@@ -121,6 +128,22 @@ export function isIssuedCurrency(
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of an AuthorizeCredential at runtime
|
||||
*
|
||||
* @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 {
|
||||
return (
|
||||
isRecord(input) &&
|
||||
isRecord(input.Credential) &&
|
||||
Object.keys(input).length === AUTHORIZE_CREDENTIAL_SIZE &&
|
||||
typeof input.Credential.CredentialType === 'string' &&
|
||||
typeof input.Credential.Issuer === 'string'
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of an MPT at runtime.
|
||||
*
|
||||
@@ -387,3 +410,97 @@ export function parseAmountValue(amount: unknown): number {
|
||||
}
|
||||
return parseFloat(amount.value)
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the form and type of a CredentialType at runtime.
|
||||
*
|
||||
* @param tx A CredentialType Transaction.
|
||||
* @throws when the CredentialType is malformed.
|
||||
*/
|
||||
export function validateCredentialType(tx: Record<string, unknown>): void {
|
||||
if (typeof tx.TransactionType !== 'string') {
|
||||
throw new ValidationError('Invalid TransactionType')
|
||||
}
|
||||
if (tx.CredentialType === undefined) {
|
||||
throw new ValidationError(
|
||||
`${tx.TransactionType}: missing field CredentialType`,
|
||||
)
|
||||
}
|
||||
|
||||
if (!isString(tx.CredentialType)) {
|
||||
throw new ValidationError(
|
||||
`${tx.TransactionType}: CredentialType must be a string`,
|
||||
)
|
||||
}
|
||||
if (tx.CredentialType.length === 0) {
|
||||
throw new ValidationError(
|
||||
`${tx.TransactionType}: CredentialType cannot be an empty string`,
|
||||
)
|
||||
} else if (tx.CredentialType.length > MAX_CREDENTIAL_TYPE_LENGTH) {
|
||||
throw new ValidationError(
|
||||
`${tx.TransactionType}: CredentialType length cannot be > ${MAX_CREDENTIAL_TYPE_LENGTH}`,
|
||||
)
|
||||
}
|
||||
|
||||
if (!HEX_REGEX.test(tx.CredentialType)) {
|
||||
throw new ValidationError(
|
||||
`${tx.TransactionType}: CredentialType must be encoded in hex`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check a CredentialAuthorize array for parameter errors
|
||||
*
|
||||
* @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
|
||||
* @throws Validation Error if the formatting is incorrect
|
||||
*/
|
||||
// eslint-disable-next-line max-lines-per-function -- separating logic further will add unnecessary complexity
|
||||
export function validateCredentialsList(
|
||||
credentials: unknown,
|
||||
transactionType: string,
|
||||
isStringID: boolean,
|
||||
): void {
|
||||
if (credentials == null) {
|
||||
return
|
||||
}
|
||||
if (!Array.isArray(credentials)) {
|
||||
throw new ValidationError(
|
||||
`${transactionType}: Credentials must be an array`,
|
||||
)
|
||||
}
|
||||
if (credentials.length > MAX_CREDENTIALS_LIST_LENGTH) {
|
||||
throw new ValidationError(
|
||||
`${transactionType}: Credentials length cannot exceed ${MAX_CREDENTIALS_LIST_LENGTH} elements`,
|
||||
)
|
||||
} else if (credentials.length === 0) {
|
||||
throw new ValidationError(
|
||||
`${transactionType}: Credentials cannot be an empty array`,
|
||||
)
|
||||
}
|
||||
credentials.forEach((credential) => {
|
||||
if (isStringID) {
|
||||
if (!isString(credential)) {
|
||||
throw new ValidationError(
|
||||
`${transactionType}: Invalid Credentials ID list format`,
|
||||
)
|
||||
}
|
||||
} else if (!isAuthorizeCredential(credential)) {
|
||||
throw new ValidationError(
|
||||
`${transactionType}: Invalid Credentials format`,
|
||||
)
|
||||
}
|
||||
})
|
||||
if (containsDuplicates(credentials)) {
|
||||
throw new ValidationError(
|
||||
`${transactionType}: Credentials cannot contain duplicate elements`,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
function containsDuplicates(objectList: object[]): boolean {
|
||||
const objSet = new Set(objectList.map((obj) => JSON.stringify(obj)))
|
||||
return objSet.size !== objectList.length
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
import { AuthorizeCredential } from '../common'
|
||||
|
||||
import { BaseTransaction, validateBaseTransaction } from './common'
|
||||
import {
|
||||
BaseTransaction,
|
||||
validateBaseTransaction,
|
||||
validateCredentialsList,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
* A DepositPreauth transaction gives another account pre-approval to deliver
|
||||
@@ -18,6 +23,16 @@ export interface DepositPreauth extends BaseTransaction {
|
||||
* revoked.
|
||||
*/
|
||||
Unauthorize?: string
|
||||
|
||||
/**
|
||||
* The credential(s) to preauthorize.
|
||||
*/
|
||||
AuthorizeCredentials?: AuthorizeCredential[]
|
||||
|
||||
/**
|
||||
* The credential(s) whose preauthorization should be revoked.
|
||||
*/
|
||||
UnauthorizeCredentials?: AuthorizeCredential[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -29,17 +44,7 @@ export interface DepositPreauth extends BaseTransaction {
|
||||
export function validateDepositPreauth(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
if (tx.Authorize !== undefined && tx.Unauthorize !== undefined) {
|
||||
throw new ValidationError(
|
||||
"DepositPreauth: can't provide both Authorize and Unauthorize fields",
|
||||
)
|
||||
}
|
||||
|
||||
if (tx.Authorize === undefined && tx.Unauthorize === undefined) {
|
||||
throw new ValidationError(
|
||||
'DepositPreauth: must provide either Authorize or Unauthorize field',
|
||||
)
|
||||
}
|
||||
validateSingleAuthorizationFieldProvided(tx)
|
||||
|
||||
if (tx.Authorize !== undefined) {
|
||||
if (typeof tx.Authorize !== 'string') {
|
||||
@@ -51,9 +56,7 @@ export function validateDepositPreauth(tx: Record<string, unknown>): void {
|
||||
"DepositPreauth: Account can't preauthorize its own address",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (tx.Unauthorize !== undefined) {
|
||||
} else if (tx.Unauthorize !== undefined) {
|
||||
if (typeof tx.Unauthorize !== 'string') {
|
||||
throw new ValidationError('DepositPreauth: Unauthorize must be a string')
|
||||
}
|
||||
@@ -63,5 +66,38 @@ export function validateDepositPreauth(tx: Record<string, unknown>): void {
|
||||
"DepositPreauth: Account can't unauthorize its own address",
|
||||
)
|
||||
}
|
||||
} else if (tx.AuthorizeCredentials !== undefined) {
|
||||
validateCredentialsList(
|
||||
tx.AuthorizeCredentials,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- confirmed in base transaction check
|
||||
tx.TransactionType as string,
|
||||
false,
|
||||
)
|
||||
} else if (tx.UnauthorizeCredentials !== undefined) {
|
||||
validateCredentialsList(
|
||||
tx.UnauthorizeCredentials,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- confirmed in base transaction check
|
||||
tx.TransactionType as string,
|
||||
false,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
// Boolean logic to ensure exactly one of 4 inputs was provided
|
||||
function validateSingleAuthorizationFieldProvided(
|
||||
tx: Record<string, unknown>,
|
||||
): void {
|
||||
const fields = [
|
||||
'Authorize',
|
||||
'Unauthorize',
|
||||
'AuthorizeCredentials',
|
||||
'UnauthorizeCredentials',
|
||||
]
|
||||
const countProvided = fields.filter((key) => tx[key] !== undefined).length
|
||||
|
||||
if (countProvided !== 1) {
|
||||
throw new ValidationError(
|
||||
'DepositPreauth: Requires exactly one field of the following: Authorize, Unauthorize, AuthorizeCredentials, UnauthorizeCredentials.',
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,6 +5,7 @@ import {
|
||||
BaseTransaction,
|
||||
isAccount,
|
||||
validateBaseTransaction,
|
||||
validateCredentialsList,
|
||||
validateRequiredField,
|
||||
} from './common'
|
||||
|
||||
@@ -32,6 +33,10 @@ export interface EscrowFinish extends BaseTransaction {
|
||||
* the held payment's Condition.
|
||||
*/
|
||||
Fulfillment?: string
|
||||
/** Credentials associated with the sender of this transaction.
|
||||
* The credentials included must not be expired.
|
||||
*/
|
||||
CredentialIDs?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -45,6 +50,13 @@ export function validateEscrowFinish(tx: Record<string, unknown>): void {
|
||||
|
||||
validateRequiredField(tx, 'Owner', isAccount)
|
||||
|
||||
validateCredentialsList(
|
||||
tx.CredentialIDs,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
|
||||
tx.TransactionType as string,
|
||||
true,
|
||||
)
|
||||
|
||||
if (tx.OfferSequence == null) {
|
||||
throw new ValidationError('EscrowFinish: missing field OfferSequence')
|
||||
}
|
||||
|
||||
@@ -32,6 +32,9 @@ export { CheckCancel } from './checkCancel'
|
||||
export { CheckCash } from './checkCash'
|
||||
export { CheckCreate } from './checkCreate'
|
||||
export { Clawback } from './clawback'
|
||||
export { CredentialAccept } from './CredentialAccept'
|
||||
export { CredentialCreate } from './CredentialCreate'
|
||||
export { CredentialDelete } from './CredentialDelete'
|
||||
export { DIDDelete } from './DIDDelete'
|
||||
export { DIDSet } from './DIDSet'
|
||||
export { DepositPreauth } from './depositPreauth'
|
||||
|
||||
@@ -12,6 +12,7 @@ import {
|
||||
validateOptionalField,
|
||||
isNumber,
|
||||
Account,
|
||||
validateCredentialsList,
|
||||
} from './common'
|
||||
import type { TransactionMetadataBase } from './metadata'
|
||||
|
||||
@@ -149,6 +150,11 @@ export interface Payment extends BaseTransaction {
|
||||
* field names are lower-case.
|
||||
*/
|
||||
DeliverMin?: Amount | MPTAmount
|
||||
/**
|
||||
* Credentials associated with the sender of this transaction.
|
||||
* The credentials included must not be expired.
|
||||
*/
|
||||
CredentialIDs?: string[]
|
||||
Flags?: number | PaymentFlagsInterface
|
||||
}
|
||||
|
||||
@@ -177,6 +183,13 @@ export function validatePayment(tx: Record<string, unknown>): void {
|
||||
validateRequiredField(tx, 'Destination', isAccount)
|
||||
validateOptionalField(tx, 'DestinationTag', isNumber)
|
||||
|
||||
validateCredentialsList(
|
||||
tx.CredentialIDs,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
|
||||
tx.TransactionType as string,
|
||||
true,
|
||||
)
|
||||
|
||||
if (tx.InvoiceID !== undefined && typeof tx.InvoiceID !== 'string') {
|
||||
throw new ValidationError('PaymentTransaction: InvoiceID must be a string')
|
||||
}
|
||||
|
||||
@@ -1,6 +1,11 @@
|
||||
import { ValidationError } from '../../errors'
|
||||
|
||||
import { BaseTransaction, GlobalFlags, validateBaseTransaction } from './common'
|
||||
import {
|
||||
BaseTransaction,
|
||||
GlobalFlags,
|
||||
validateBaseTransaction,
|
||||
validateCredentialsList,
|
||||
} from './common'
|
||||
|
||||
/**
|
||||
* Enum representing values for PaymentChannelClaim transaction flags.
|
||||
@@ -127,6 +132,11 @@ export interface PaymentChannelClaim extends BaseTransaction {
|
||||
* field is omitted.
|
||||
*/
|
||||
PublicKey?: string
|
||||
/**
|
||||
* Credentials associated with the sender of this transaction.
|
||||
* The credentials included must not be expired.
|
||||
*/
|
||||
CredentialIDs?: string[]
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -138,6 +148,13 @@ export interface PaymentChannelClaim extends BaseTransaction {
|
||||
export function validatePaymentChannelClaim(tx: Record<string, unknown>): void {
|
||||
validateBaseTransaction(tx)
|
||||
|
||||
validateCredentialsList(
|
||||
tx.CredentialIDs,
|
||||
// eslint-disable-next-line @typescript-eslint/consistent-type-assertions -- known from base check
|
||||
tx.TransactionType as string,
|
||||
true,
|
||||
)
|
||||
|
||||
if (tx.Channel === undefined) {
|
||||
throw new ValidationError('PaymentChannelClaim: missing Channel')
|
||||
}
|
||||
|
||||
@@ -19,6 +19,9 @@ import { CheckCash, validateCheckCash } from './checkCash'
|
||||
import { CheckCreate, validateCheckCreate } from './checkCreate'
|
||||
import { Clawback, validateClawback } from './clawback'
|
||||
import { BaseTransaction, isIssuedCurrency } from './common'
|
||||
import { CredentialAccept, validateCredentialAccept } from './CredentialAccept'
|
||||
import { CredentialCreate, validateCredentialCreate } from './CredentialCreate'
|
||||
import { CredentialDelete, validateCredentialDelete } from './CredentialDelete'
|
||||
import { DepositPreauth, validateDepositPreauth } from './depositPreauth'
|
||||
import { DIDDelete, validateDIDDelete } from './DIDDelete'
|
||||
import { DIDSet, validateDIDSet } from './DIDSet'
|
||||
@@ -122,6 +125,9 @@ export type SubmittableTransaction =
|
||||
| CheckCash
|
||||
| CheckCreate
|
||||
| Clawback
|
||||
| CredentialAccept
|
||||
| CredentialCreate
|
||||
| CredentialDelete
|
||||
| DIDDelete
|
||||
| DIDSet
|
||||
| DepositPreauth
|
||||
@@ -299,6 +305,18 @@ export function validate(transaction: Record<string, unknown>): void {
|
||||
validateClawback(tx)
|
||||
break
|
||||
|
||||
case 'CredentialAccept':
|
||||
validateCredentialAccept(tx)
|
||||
break
|
||||
|
||||
case 'CredentialCreate':
|
||||
validateCredentialCreate(tx)
|
||||
break
|
||||
|
||||
case 'CredentialDelete':
|
||||
validateCredentialDelete(tx)
|
||||
break
|
||||
|
||||
case 'DIDDelete':
|
||||
validateDIDDelete(tx)
|
||||
break
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
To run integration tests:
|
||||
1. Run rippled in standalone node, either in a docker container (preferred) or by installing rippled.
|
||||
* Go to the top-level of the `xrpl.js` repo, just above the `packages` folder.
|
||||
* With docker, run `docker run -p 6006:6006 --interactive -t --volume $PWD/.ci-config:/opt/ripple/etc/ --platform linux/amd64 rippleci/rippled:2.2.0-b3 /opt/ripple/bin/rippled -a --conf /opt/ripple/etc/rippled.cfg`
|
||||
* With docker, run `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'`
|
||||
* Or [download and build rippled](https://xrpl.org/install-rippled.html) and run `./rippled -a --start`
|
||||
* If you'd like to use the latest rippled amendments, you should modify your `rippled.cfg` file to enable amendments in the `[amendments]` section. You can view `.ci-config/rippled.cfg` in the top level folder as an example of this.
|
||||
2. Run `npm run test:integration` or `npm run test:browser`
|
||||
|
||||
@@ -0,0 +1,62 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import {
|
||||
AccountObjectsResponse,
|
||||
CredentialAccept,
|
||||
CredentialCreate,
|
||||
} from '../../../src'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
teardownClient,
|
||||
type XrplIntegrationTestContext,
|
||||
} from '../setup'
|
||||
import { generateFundedWallet, testTransaction } from '../utils'
|
||||
|
||||
describe('CredentialAccept', function () {
|
||||
// testContext wallet acts as issuer in this test
|
||||
let testContext: XrplIntegrationTestContext
|
||||
|
||||
beforeAll(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
})
|
||||
afterAll(async () => teardownClient(testContext))
|
||||
|
||||
it('base', async function () {
|
||||
const subjectWallet = await generateFundedWallet(testContext.client)
|
||||
|
||||
const credentialCreateTx: CredentialCreate = {
|
||||
TransactionType: 'CredentialCreate',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
Subject: subjectWallet.classicAddress,
|
||||
CredentialType: stringToHex('Test Credential Type'),
|
||||
}
|
||||
|
||||
await testTransaction(
|
||||
testContext.client,
|
||||
credentialCreateTx,
|
||||
testContext.wallet,
|
||||
)
|
||||
|
||||
const credentialAcceptTx: CredentialAccept = {
|
||||
TransactionType: 'CredentialAccept',
|
||||
Account: subjectWallet.classicAddress,
|
||||
Issuer: testContext.wallet.classicAddress,
|
||||
CredentialType: stringToHex('Test Credential Type'),
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, credentialAcceptTx, subjectWallet)
|
||||
|
||||
// Credential is now an object in recipient's wallet after accept
|
||||
const accountObjectsResponse: AccountObjectsResponse =
|
||||
await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: subjectWallet.classicAddress,
|
||||
type: 'credential',
|
||||
})
|
||||
const { account_objects } = accountObjectsResponse.result
|
||||
|
||||
assert.equal(account_objects.length, 1)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,49 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { AccountObjectsResponse, CredentialCreate } from '../../../src'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
teardownClient,
|
||||
type XrplIntegrationTestContext,
|
||||
} from '../setup'
|
||||
import { generateFundedWallet, testTransaction } from '../utils'
|
||||
|
||||
describe('CredentialCreate', function () {
|
||||
// testContext wallet acts as issuer in this test
|
||||
let testContext: XrplIntegrationTestContext
|
||||
|
||||
beforeAll(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
})
|
||||
afterAll(async () => teardownClient(testContext))
|
||||
|
||||
it('base', async function () {
|
||||
const subjectWallet = await generateFundedWallet(testContext.client)
|
||||
|
||||
const credentialCreateTx: CredentialCreate = {
|
||||
TransactionType: 'CredentialCreate',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
Subject: subjectWallet.classicAddress,
|
||||
CredentialType: stringToHex('Test Credential Type'),
|
||||
}
|
||||
|
||||
await testTransaction(
|
||||
testContext.client,
|
||||
credentialCreateTx,
|
||||
testContext.wallet,
|
||||
)
|
||||
|
||||
// Unaccepted credential still belongs to issuer's account
|
||||
const accountObjectsResponse: AccountObjectsResponse =
|
||||
await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: testContext.wallet.classicAddress,
|
||||
type: 'credential',
|
||||
})
|
||||
const { account_objects } = accountObjectsResponse.result
|
||||
|
||||
assert.equal(account_objects.length, 1)
|
||||
})
|
||||
})
|
||||
@@ -0,0 +1,105 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import {
|
||||
AccountObjectsResponse,
|
||||
CredentialAccept,
|
||||
CredentialCreate,
|
||||
} from '../../../src'
|
||||
import { CredentialDelete } from '../../../src/models/transactions/CredentialDelete'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
teardownClient,
|
||||
type XrplIntegrationTestContext,
|
||||
} from '../setup'
|
||||
import { generateFundedWallet, testTransaction } from '../utils'
|
||||
|
||||
describe('CredentialDelete', function () {
|
||||
// testContext wallet acts as issuer in this test
|
||||
let testContext: XrplIntegrationTestContext
|
||||
|
||||
beforeAll(async () => {
|
||||
testContext = await setupClient(serverUrl)
|
||||
})
|
||||
afterAll(async () => teardownClient(testContext))
|
||||
|
||||
it('base', async function () {
|
||||
const subjectWallet = await generateFundedWallet(testContext.client)
|
||||
|
||||
const credentialCreateTx: CredentialCreate = {
|
||||
TransactionType: 'CredentialCreate',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
Subject: subjectWallet.classicAddress,
|
||||
CredentialType: stringToHex('Test Credential Type'),
|
||||
}
|
||||
|
||||
await testTransaction(
|
||||
testContext.client,
|
||||
credentialCreateTx,
|
||||
testContext.wallet,
|
||||
)
|
||||
|
||||
const createAccountObjectsResponse: AccountObjectsResponse =
|
||||
await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: testContext.wallet.classicAddress,
|
||||
type: 'credential',
|
||||
})
|
||||
|
||||
assert.equal(createAccountObjectsResponse.result.account_objects.length, 1)
|
||||
|
||||
const credentialAcceptTx: CredentialAccept = {
|
||||
TransactionType: 'CredentialAccept',
|
||||
Account: subjectWallet.classicAddress,
|
||||
Issuer: testContext.wallet.classicAddress,
|
||||
CredentialType: stringToHex('Test Credential Type'),
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, credentialAcceptTx, subjectWallet)
|
||||
|
||||
// Credential is now an object in recipient's wallet after accept
|
||||
const acceptAccountObjectsResponse: AccountObjectsResponse =
|
||||
await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: subjectWallet.classicAddress,
|
||||
type: 'credential',
|
||||
})
|
||||
|
||||
assert.equal(acceptAccountObjectsResponse.result.account_objects.length, 1)
|
||||
|
||||
const credentialDeleteTx: CredentialDelete = {
|
||||
TransactionType: 'CredentialDelete',
|
||||
Account: subjectWallet.classicAddress,
|
||||
Issuer: testContext.wallet.classicAddress,
|
||||
CredentialType: stringToHex('Test Credential Type'),
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, credentialDeleteTx, subjectWallet)
|
||||
|
||||
// Check both issuer and subject no longer have a credential tied to the account
|
||||
const SubjectAccountObjectsDeleteResponse: AccountObjectsResponse =
|
||||
await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: subjectWallet.classicAddress,
|
||||
type: 'credential',
|
||||
})
|
||||
|
||||
assert.equal(
|
||||
SubjectAccountObjectsDeleteResponse.result.account_objects.length,
|
||||
0,
|
||||
)
|
||||
|
||||
const IssuerAccountObjectsDeleteResponse: AccountObjectsResponse =
|
||||
await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
account: testContext.wallet.classicAddress,
|
||||
type: 'credential',
|
||||
})
|
||||
|
||||
assert.equal(
|
||||
IssuerAccountObjectsDeleteResponse.result.account_objects.length,
|
||||
0,
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -1,11 +1,19 @@
|
||||
import { DepositPreauth, Wallet } from '../../../src'
|
||||
import { stringToHex } from '@xrplf/isomorphic/utils'
|
||||
|
||||
import {
|
||||
AuthorizeCredential,
|
||||
CredentialAccept,
|
||||
CredentialCreate,
|
||||
DepositPreauth,
|
||||
Wallet,
|
||||
} from '../../../src'
|
||||
import serverUrl from '../serverUrl'
|
||||
import {
|
||||
setupClient,
|
||||
teardownClient,
|
||||
type XrplIntegrationTestContext,
|
||||
} from '../setup'
|
||||
import { fundAccount, testTransaction } from '../utils'
|
||||
import { fundAccount, generateFundedWallet, testTransaction } from '../utils'
|
||||
|
||||
// how long before each test case times out
|
||||
const TIMEOUT = 20000
|
||||
@@ -32,4 +40,119 @@ describe('DepositPreauth', function () {
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
|
||||
it(
|
||||
'AuthorizeCredential base case',
|
||||
async () => {
|
||||
const subjectWallet = await generateFundedWallet(testContext.client)
|
||||
|
||||
const credentialCreateTx: CredentialCreate = {
|
||||
TransactionType: 'CredentialCreate',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
Subject: subjectWallet.classicAddress,
|
||||
CredentialType: stringToHex('Test Credential Type'),
|
||||
}
|
||||
|
||||
await testTransaction(
|
||||
testContext.client,
|
||||
credentialCreateTx,
|
||||
testContext.wallet,
|
||||
)
|
||||
|
||||
const credentialAcceptTx: CredentialAccept = {
|
||||
TransactionType: 'CredentialAccept',
|
||||
Account: subjectWallet.classicAddress,
|
||||
Issuer: testContext.wallet.classicAddress,
|
||||
CredentialType: stringToHex('Test Credential Type'),
|
||||
}
|
||||
|
||||
await testTransaction(
|
||||
testContext.client,
|
||||
credentialAcceptTx,
|
||||
subjectWallet,
|
||||
)
|
||||
|
||||
const authorizeCredentialObj: AuthorizeCredential = {
|
||||
Credential: {
|
||||
Issuer: testContext.wallet.classicAddress,
|
||||
CredentialType: stringToHex('Test Credential Type'),
|
||||
},
|
||||
}
|
||||
|
||||
const wallet2 = Wallet.generate()
|
||||
await fundAccount(testContext.client, wallet2)
|
||||
const tx: DepositPreauth = {
|
||||
TransactionType: 'DepositPreauth',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
AuthorizeCredentials: [authorizeCredentialObj],
|
||||
}
|
||||
await testTransaction(testContext.client, tx, testContext.wallet)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
|
||||
it(
|
||||
'UnauthorizeCredential base case',
|
||||
async () => {
|
||||
const subjectWallet = await generateFundedWallet(testContext.client)
|
||||
|
||||
const credentialCreateTx: CredentialCreate = {
|
||||
TransactionType: 'CredentialCreate',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
Subject: subjectWallet.classicAddress,
|
||||
CredentialType: stringToHex('Test Credential Type'),
|
||||
}
|
||||
|
||||
await testTransaction(
|
||||
testContext.client,
|
||||
credentialCreateTx,
|
||||
testContext.wallet,
|
||||
)
|
||||
|
||||
const credentialAcceptTx: CredentialAccept = {
|
||||
TransactionType: 'CredentialAccept',
|
||||
Account: subjectWallet.classicAddress,
|
||||
Issuer: testContext.wallet.classicAddress,
|
||||
CredentialType: stringToHex('Test Credential Type'),
|
||||
}
|
||||
|
||||
await testTransaction(
|
||||
testContext.client,
|
||||
credentialAcceptTx,
|
||||
subjectWallet,
|
||||
)
|
||||
|
||||
const authorizeCredentialObj: AuthorizeCredential = {
|
||||
Credential: {
|
||||
Issuer: testContext.wallet.classicAddress,
|
||||
CredentialType: stringToHex('Test Credential Type'),
|
||||
},
|
||||
}
|
||||
|
||||
const wallet2 = Wallet.generate()
|
||||
await fundAccount(testContext.client, wallet2)
|
||||
const authCredDepositPreauthTx: DepositPreauth = {
|
||||
TransactionType: 'DepositPreauth',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
AuthorizeCredentials: [authorizeCredentialObj],
|
||||
}
|
||||
await testTransaction(
|
||||
testContext.client,
|
||||
authCredDepositPreauthTx,
|
||||
testContext.wallet,
|
||||
)
|
||||
|
||||
const UnauthCredDepositPreauthTx: DepositPreauth = {
|
||||
TransactionType: 'DepositPreauth',
|
||||
Account: testContext.wallet.classicAddress,
|
||||
UnauthorizeCredentials: [authorizeCredentialObj],
|
||||
}
|
||||
await testTransaction(
|
||||
testContext.client,
|
||||
UnauthCredDepositPreauthTx,
|
||||
testContext.wallet,
|
||||
)
|
||||
},
|
||||
TIMEOUT,
|
||||
)
|
||||
})
|
||||
|
||||
@@ -135,7 +135,7 @@ describe('Payment', function () {
|
||||
const meta = txResponse.result
|
||||
.meta as TransactionMetadata<MPTokenIssuanceCreate>
|
||||
|
||||
const mptID = meta.mpt_issuance_id
|
||||
const mptID = meta.mpt_issuance_id!
|
||||
|
||||
let accountObjectsResponse = await testContext.client.request({
|
||||
command: 'account_objects',
|
||||
@@ -151,7 +151,7 @@ describe('Payment', function () {
|
||||
const authTx: MPTokenAuthorize = {
|
||||
TransactionType: 'MPTokenAuthorize',
|
||||
Account: wallet2.classicAddress,
|
||||
MPTokenIssuanceID: mptID!,
|
||||
MPTokenIssuanceID: mptID,
|
||||
}
|
||||
|
||||
await testTransaction(testContext.client, authTx, wallet2)
|
||||
@@ -173,7 +173,7 @@ describe('Payment', function () {
|
||||
Account: testContext.wallet.classicAddress,
|
||||
Destination: wallet2.classicAddress,
|
||||
Amount: {
|
||||
mpt_issuance_id: mptID!,
|
||||
mpt_issuance_id: mptID,
|
||||
value: '100',
|
||||
},
|
||||
}
|
||||
|
||||
153
packages/xrpl/test/models/CredentialAccept.test.ts
Normal file
153
packages/xrpl/test/models/CredentialAccept.test.ts
Normal file
@@ -0,0 +1,153 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/dist/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { validate, ValidationError } from '../../src'
|
||||
import { validateCredentialAccept } from '../../src/models/transactions/CredentialAccept'
|
||||
|
||||
/**
|
||||
* CredentialAccept Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('CredentialAccept', function () {
|
||||
let credentialAccept
|
||||
|
||||
beforeEach(function () {
|
||||
credentialAccept = {
|
||||
TransactionType: 'CredentialAccept',
|
||||
Issuer: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
Account: 'rNdY9XDnQ4Dr1EgefwU3CBRuAjt3sAutGg',
|
||||
CredentialType: stringToHex('Passport'),
|
||||
Sequence: 1337,
|
||||
Flags: 0,
|
||||
} as any
|
||||
})
|
||||
|
||||
it(`verifies valid CredentialAccept`, function () {
|
||||
assert.doesNotThrow(() => validateCredentialAccept(credentialAccept))
|
||||
assert.doesNotThrow(() => validate(credentialAccept))
|
||||
})
|
||||
|
||||
it(`throws w/ missing field Account`, function () {
|
||||
credentialAccept.Account = undefined
|
||||
const errorMessage = 'CredentialAccept: missing field Account'
|
||||
assert.throws(
|
||||
() => validateCredentialAccept(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ Account not a string`, function () {
|
||||
credentialAccept.Account = 123
|
||||
const errorMessage = 'CredentialAccept: invalid field Account'
|
||||
assert.throws(
|
||||
() => validateCredentialAccept(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ missing field Issuer`, function () {
|
||||
credentialAccept.Issuer = undefined
|
||||
const errorMessage = 'CredentialAccept: missing field Issuer'
|
||||
assert.throws(
|
||||
() => validateCredentialAccept(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ Issuer not a string`, function () {
|
||||
credentialAccept.Issuer = 123
|
||||
const errorMessage = 'CredentialAccept: invalid field Issuer'
|
||||
assert.throws(
|
||||
() => validateCredentialAccept(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ missing field CredentialType`, function () {
|
||||
credentialAccept.CredentialType = undefined
|
||||
const errorMessage = 'CredentialAccept: missing field CredentialType'
|
||||
assert.throws(
|
||||
() => validateCredentialAccept(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ credentialType field too long`, function () {
|
||||
credentialAccept.CredentialType = stringToHex('A'.repeat(129))
|
||||
const errorMessage =
|
||||
'CredentialAccept: CredentialType length cannot be > 128'
|
||||
assert.throws(
|
||||
() => validateCredentialAccept(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ credentialType field empty`, function () {
|
||||
credentialAccept.CredentialType = ''
|
||||
const errorMessage =
|
||||
'CredentialAccept: CredentialType cannot be an empty string'
|
||||
assert.throws(
|
||||
() => validateCredentialAccept(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ credentialType field not hex`, function () {
|
||||
credentialAccept.CredentialType = 'this is not hex'
|
||||
const errorMessage =
|
||||
'CredentialAccept: CredentialType must be encoded in hex'
|
||||
assert.throws(
|
||||
() => validateCredentialAccept(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialAccept),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
})
|
||||
230
packages/xrpl/test/models/CredentialCreate.test.ts
Normal file
230
packages/xrpl/test/models/CredentialCreate.test.ts
Normal file
@@ -0,0 +1,230 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/dist/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { validate, ValidationError } from '../../src'
|
||||
import { validateCredentialCreate } from '../../src/models/transactions/CredentialCreate'
|
||||
|
||||
/**
|
||||
* CredentialCreate Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('credentialCreate', function () {
|
||||
let credentialCreate
|
||||
|
||||
beforeEach(function () {
|
||||
credentialCreate = {
|
||||
TransactionType: 'CredentialCreate',
|
||||
Account: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
Subject: 'rNdY9XDnQ4Dr1EgefwU3CBRuAjt3sAutGg',
|
||||
CredentialType: stringToHex('Passport'),
|
||||
Expiration: 1212025,
|
||||
URI: stringToHex('TestURI'),
|
||||
Sequence: 1337,
|
||||
Flags: 0,
|
||||
} as any
|
||||
})
|
||||
|
||||
it(`verifies valid credentialCreate`, function () {
|
||||
assert.doesNotThrow(() => validateCredentialCreate(credentialCreate))
|
||||
assert.doesNotThrow(() => validate(credentialCreate))
|
||||
})
|
||||
|
||||
it(`throws w/ missing field Account`, function () {
|
||||
credentialCreate.Account = undefined
|
||||
const errorMessage = 'CredentialCreate: missing field Account'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ Account not string`, function () {
|
||||
credentialCreate.Account = 123
|
||||
const errorMessage = 'CredentialCreate: invalid field Account'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ missing field Subject`, function () {
|
||||
credentialCreate.Subject = undefined
|
||||
const errorMessage = 'CredentialCreate: missing field Subject'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ Subject not string`, function () {
|
||||
credentialCreate.Subject = 123
|
||||
const errorMessage = 'CredentialCreate: invalid field Subject'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ missing field credentialType`, function () {
|
||||
credentialCreate.CredentialType = undefined
|
||||
const errorMessage = 'CredentialCreate: missing field CredentialType'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ credentialType field too long`, function () {
|
||||
credentialCreate.CredentialType = stringToHex('A'.repeat(129))
|
||||
const errorMessage =
|
||||
'CredentialCreate: CredentialType length cannot be > 128'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ credentialType field empty`, function () {
|
||||
credentialCreate.CredentialType = ''
|
||||
const errorMessage =
|
||||
'CredentialCreate: CredentialType cannot be an empty string'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ credentialType field not hex`, function () {
|
||||
credentialCreate.CredentialType = 'this is not hex'
|
||||
const errorMessage =
|
||||
'CredentialCreate: CredentialType must be encoded in hex'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ Expiration field not number`, function () {
|
||||
credentialCreate.Expiration = 'this is not a number'
|
||||
const errorMessage = 'CredentialCreate: invalid field Expiration'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ URI field not a string`, function () {
|
||||
credentialCreate.URI = 123
|
||||
const errorMessage = 'CredentialCreate: invalid field URI'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ URI field empty`, function () {
|
||||
credentialCreate.URI = ''
|
||||
const errorMessage = 'CredentialCreate: URI cannot be an empty string'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ URI field too long`, function () {
|
||||
credentialCreate.URI = stringToHex('A'.repeat(129))
|
||||
const errorMessage = 'CredentialCreate: URI length must be <= 256'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ URI field not hex`, function () {
|
||||
credentialCreate.URI = 'this is not hex'
|
||||
const errorMessage = 'CredentialCreate: URI must be encoded in hex'
|
||||
assert.throws(
|
||||
() => validateCredentialCreate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialCreate),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
})
|
||||
171
packages/xrpl/test/models/CredentialDelete.test.ts
Normal file
171
packages/xrpl/test/models/CredentialDelete.test.ts
Normal file
@@ -0,0 +1,171 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/dist/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { validate, ValidationError } from '../../src'
|
||||
import { validateCredentialDelete } from '../../src/models/transactions/CredentialDelete'
|
||||
|
||||
/**
|
||||
* CredentialDelete Transaction Verification Testing.
|
||||
*
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('CredentialDelete', function () {
|
||||
let credentialDelete
|
||||
|
||||
beforeEach(function () {
|
||||
credentialDelete = {
|
||||
TransactionType: 'CredentialDelete',
|
||||
Issuer: 'r9LqNeG6qHxjeUocjvVki2XR35weJ9mZgQ',
|
||||
Subject: 'rNdY9XDnQ4Dr1EgefwU3CBRuAjt3sAutGg',
|
||||
Account: 'rNdY9XDnQ4Dr1EgefwU3CBRuAjt3sAutGg',
|
||||
CredentialType: stringToHex('Passport'),
|
||||
Sequence: 1337,
|
||||
Flags: 0,
|
||||
} as any
|
||||
})
|
||||
|
||||
it(`verifies valid credentialDelete`, function () {
|
||||
assert.doesNotThrow(() => validateCredentialDelete(credentialDelete))
|
||||
assert.doesNotThrow(() => validate(credentialDelete))
|
||||
})
|
||||
|
||||
it(`throws w/ missing field Account`, function () {
|
||||
credentialDelete.Account = undefined
|
||||
const errorMessage = 'CredentialDelete: missing field Account'
|
||||
assert.throws(
|
||||
() => validateCredentialDelete(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ Account not string`, function () {
|
||||
credentialDelete.Account = 123
|
||||
const errorMessage = 'CredentialDelete: invalid field Account'
|
||||
assert.throws(
|
||||
() => validateCredentialDelete(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ Subject not string`, function () {
|
||||
credentialDelete.Subject = 123
|
||||
const errorMessage = 'CredentialDelete: invalid field Subject'
|
||||
assert.throws(
|
||||
() => validateCredentialDelete(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ Issuer not string`, function () {
|
||||
credentialDelete.Issuer = 123
|
||||
const errorMessage = 'CredentialDelete: invalid field Issuer'
|
||||
assert.throws(
|
||||
() => validateCredentialDelete(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ missing field Subject and Issuer`, function () {
|
||||
credentialDelete.Subject = undefined
|
||||
credentialDelete.Issuer = undefined
|
||||
const errorMessage =
|
||||
'CredentialDelete: either `Issuer` or `Subject` must be provided'
|
||||
assert.throws(
|
||||
() => validateCredentialDelete(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ missing field credentialType`, function () {
|
||||
credentialDelete.CredentialType = undefined
|
||||
const errorMessage = 'CredentialDelete: missing field CredentialType'
|
||||
assert.throws(
|
||||
() => validateCredentialDelete(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ credentialType field too long`, function () {
|
||||
credentialDelete.CredentialType = stringToHex('A'.repeat(129))
|
||||
const errorMessage =
|
||||
'CredentialDelete: CredentialType length cannot be > 128'
|
||||
assert.throws(
|
||||
() => validateCredentialDelete(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ credentialType field empty`, function () {
|
||||
credentialDelete.CredentialType = ''
|
||||
const errorMessage =
|
||||
'CredentialDelete: CredentialType cannot be an empty string'
|
||||
assert.throws(
|
||||
() => validateCredentialDelete(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ credentialType field not hex`, function () {
|
||||
credentialDelete.CredentialType = 'this is not hex'
|
||||
const errorMessage =
|
||||
'CredentialDelete: CredentialType must be encoded in hex'
|
||||
assert.throws(
|
||||
() => validateCredentialDelete(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(credentialDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
})
|
||||
@@ -9,8 +9,10 @@ import { validateAccountDelete } from '../../src/models/transactions/accountDele
|
||||
* Providing runtime verification testing for each specific transaction type.
|
||||
*/
|
||||
describe('AccountDelete', function () {
|
||||
it(`verifies valid AccountDelete`, function () {
|
||||
const validAccountDelete = {
|
||||
let validAccountDelete
|
||||
|
||||
beforeEach(() => {
|
||||
validAccountDelete = {
|
||||
TransactionType: 'AccountDelete',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Destination: 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe',
|
||||
@@ -18,76 +20,166 @@ describe('AccountDelete', function () {
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
CredentialIDs: [
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A',
|
||||
],
|
||||
} as any
|
||||
|
||||
})
|
||||
it(`verifies valid AccountDelete`, function () {
|
||||
assert.doesNotThrow(() => validateAccountDelete(validAccountDelete))
|
||||
})
|
||||
|
||||
it(`throws w/ missing Destination`, function () {
|
||||
const invalidDestination = {
|
||||
TransactionType: 'AccountDelete',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
validAccountDelete.Destination = undefined
|
||||
const errorMessage = 'AccountDelete: missing field Destination'
|
||||
|
||||
assert.throws(
|
||||
() => validateAccountDelete(invalidDestination),
|
||||
() => validateAccountDelete(validAccountDelete),
|
||||
ValidationError,
|
||||
'AccountDelete: missing field Destination',
|
||||
errorMessage,
|
||||
)
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalidDestination),
|
||||
() => validate(validAccountDelete),
|
||||
ValidationError,
|
||||
'AccountDelete: missing field Destination',
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid Destination`, function () {
|
||||
const invalidDestination = {
|
||||
TransactionType: 'AccountDelete',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Destination: 65478965,
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
validAccountDelete.Destination = 65478965
|
||||
const errorMessage = 'AccountDelete: invalid field Destination'
|
||||
|
||||
assert.throws(
|
||||
() => validateAccountDelete(invalidDestination),
|
||||
() => validateAccountDelete(validAccountDelete),
|
||||
ValidationError,
|
||||
'AccountDelete: invalid field Destination',
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(invalidDestination),
|
||||
() => validate(validAccountDelete),
|
||||
ValidationError,
|
||||
'AccountDelete: invalid field Destination',
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ invalid DestinationTag`, function () {
|
||||
const invalidDestinationTag = {
|
||||
TransactionType: 'AccountDelete',
|
||||
Account: 'rWYkbWkCeg8dP6rXALnjgZSjjLyih5NXm',
|
||||
Destination: 'rPT1Sjq2YGrBMTttX4GZHjKu9dyfzbpAYe',
|
||||
DestinationTag: 'gvftyujnbv',
|
||||
Fee: '5000000',
|
||||
Sequence: 2470665,
|
||||
Flags: 2147483648,
|
||||
} as any
|
||||
validAccountDelete.DestinationTag = 'gvftyujnbv'
|
||||
const errorMessage = 'AccountDelete: invalid field DestinationTag'
|
||||
|
||||
assert.throws(
|
||||
() => validateAccountDelete(invalidDestinationTag),
|
||||
() => validateAccountDelete(validAccountDelete),
|
||||
ValidationError,
|
||||
'AccountDelete: invalid field DestinationTag',
|
||||
errorMessage,
|
||||
)
|
||||
|
||||
assert.throws(
|
||||
() => validate(invalidDestinationTag),
|
||||
() => validate(validAccountDelete),
|
||||
ValidationError,
|
||||
'AccountDelete: invalid field DestinationTag',
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ non-array CredentialIDs`, function () {
|
||||
validAccountDelete.CredentialIDs =
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A'
|
||||
|
||||
const errorMessage = 'AccountDelete: Credentials must be an array'
|
||||
|
||||
assert.throws(
|
||||
() => validateAccountDelete(validAccountDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(validAccountDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws CredentialIDs length exceeds max length`, function () {
|
||||
validAccountDelete.CredentialIDs = [
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66B',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66C',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66D',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66E',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66F',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F660',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F661',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662',
|
||||
]
|
||||
|
||||
const errorMessage =
|
||||
'AccountDelete: Credentials length cannot exceed 8 elements'
|
||||
|
||||
assert.throws(
|
||||
() => validateAccountDelete(validAccountDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(validAccountDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ empty CredentialIDs`, function () {
|
||||
validAccountDelete.CredentialIDs = []
|
||||
|
||||
const errorMessage = 'AccountDelete: Credentials cannot be an empty array'
|
||||
|
||||
assert.throws(
|
||||
() => validateAccountDelete(validAccountDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(validAccountDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ non-string CredentialIDs`, function () {
|
||||
validAccountDelete.CredentialIDs = [
|
||||
123123,
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662',
|
||||
]
|
||||
|
||||
const errorMessage = 'AccountDelete: Invalid Credentials ID list format'
|
||||
|
||||
assert.throws(
|
||||
() => validateAccountDelete(validAccountDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(validAccountDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ duplicate CredentialIDs`, function () {
|
||||
validAccountDelete.CredentialIDs = [
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662',
|
||||
]
|
||||
|
||||
const errorMessage =
|
||||
'AccountDelete: Credentials cannot contain duplicate elements'
|
||||
|
||||
assert.throws(
|
||||
() => validateAccountDelete(validAccountDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(validAccountDelete),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
import { stringToHex } from '@xrplf/isomorphic/dist/utils'
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { validate, ValidationError } from '../../src'
|
||||
import { AuthorizeCredential, validate, ValidationError } from '../../src'
|
||||
import { validateDepositPreauth } from '../../src/models/transactions/depositPreauth'
|
||||
|
||||
/**
|
||||
@@ -11,6 +12,13 @@ import { validateDepositPreauth } from '../../src/models/transactions/depositPre
|
||||
describe('DepositPreauth', function () {
|
||||
let depositPreauth
|
||||
|
||||
const validCredential = {
|
||||
Credential: {
|
||||
Issuer: 'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW',
|
||||
CredentialType: stringToHex('Passport'),
|
||||
},
|
||||
}
|
||||
|
||||
beforeEach(function () {
|
||||
depositPreauth = {
|
||||
TransactionType: 'DepositPreauth',
|
||||
@@ -30,32 +38,73 @@ describe('DepositPreauth', function () {
|
||||
assert.doesNotThrow(() => validate(depositPreauth))
|
||||
})
|
||||
|
||||
it('throws when both Authorize and Unauthorize are provided', function () {
|
||||
it('verifies valid DepositPreauth when only AuthorizeCredentials is provided', function () {
|
||||
depositPreauth.AuthorizeCredentials = [validCredential]
|
||||
assert.doesNotThrow(() => validateDepositPreauth(depositPreauth))
|
||||
assert.doesNotThrow(() => validate(depositPreauth))
|
||||
})
|
||||
|
||||
it('verifies valid DepositPreauth when only UnauthorizeCredentials is provided', function () {
|
||||
depositPreauth.UnauthorizeCredentials = [validCredential]
|
||||
assert.doesNotThrow(() => validateDepositPreauth(depositPreauth))
|
||||
assert.doesNotThrow(() => validate(depositPreauth))
|
||||
})
|
||||
|
||||
it('throws when multiple of Authorize, Unauthorize, AuthorizeCredentials, UnauthorizeCredentials are provided', function () {
|
||||
const errorMessage =
|
||||
'DepositPreauth: Requires exactly one field of the following: Authorize, Unauthorize, AuthorizeCredentials, UnauthorizeCredentials.'
|
||||
|
||||
depositPreauth.Authorize = 'rsA2LpzuawewSBQXkiju3YQTMzW13pAAdW'
|
||||
depositPreauth.UnauthorizeCredentials = [validCredential]
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
|
||||
depositPreauth.Unauthorize = 'raKEEVSGnKSD9Zyvxu4z6Pqpm4ABH8FS6n'
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
"DepositPreauth: can't provide both Authorize and Unauthorize fields",
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(depositPreauth),
|
||||
ValidationError,
|
||||
"DepositPreauth: can't provide both Authorize and Unauthorize fields",
|
||||
)
|
||||
})
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
|
||||
it('throws when neither Authorize nor Unauthorize are provided', function () {
|
||||
depositPreauth.AuthorizeCredentials = [validCredential]
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
'DepositPreauth: must provide either Authorize or Unauthorize field',
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
|
||||
depositPreauth.Authorize = undefined
|
||||
assert.throws(
|
||||
() => validate(depositPreauth),
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
'DepositPreauth: must provide either Authorize or Unauthorize field',
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
|
||||
depositPreauth.UnauthorizeCredentials = undefined
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it('throws when none of Authorize, Unauthorize, AuthorizeCredentials, UnauthorizeCredentials are provided', function () {
|
||||
const errorMessage =
|
||||
'DepositPreauth: Requires exactly one field of the following: Authorize, Unauthorize, AuthorizeCredentials, UnauthorizeCredentials.'
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it('throws when Authorize is not a string', function () {
|
||||
@@ -108,4 +157,154 @@ describe('DepositPreauth', function () {
|
||||
"DepositPreauth: Account can't unauthorize its own address",
|
||||
)
|
||||
})
|
||||
|
||||
it('throws when AuthorizeCredentials is not an array', function () {
|
||||
const errorMessage = 'DepositPreauth: Credentials must be an array'
|
||||
depositPreauth.AuthorizeCredentials = validCredential
|
||||
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it('throws when UnauthorizeCredentials is not an array', function () {
|
||||
const errorMessage = 'DepositPreauth: Credentials must be an array'
|
||||
depositPreauth.UnauthorizeCredentials = validCredential
|
||||
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it('throws when AuthorizeCredentials is empty array', function () {
|
||||
const errorMessage = 'DepositPreauth: Credentials cannot be an empty array'
|
||||
depositPreauth.AuthorizeCredentials = []
|
||||
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it('throws when UnauthorizeCredentials is empty array', function () {
|
||||
const errorMessage = 'DepositPreauth: Credentials cannot be an empty array'
|
||||
depositPreauth.UnauthorizeCredentials = []
|
||||
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it('throws when AuthorizeCredentials is too long', function () {
|
||||
const sampleCredentials: AuthorizeCredential[] = []
|
||||
const errorMessage =
|
||||
'DepositPreauth: Credentials length cannot exceed 8 elements'
|
||||
for (let index = 0; index < 9; index++) {
|
||||
sampleCredentials.push({
|
||||
Credential: {
|
||||
Issuer: `SampleIssuer${index}`,
|
||||
CredentialType: stringToHex('Passport'),
|
||||
},
|
||||
})
|
||||
}
|
||||
depositPreauth.AuthorizeCredentials = sampleCredentials
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it('throws when UnauthorizeCredentials is too long', function () {
|
||||
const sampleCredentials: AuthorizeCredential[] = []
|
||||
const errorMessage =
|
||||
'DepositPreauth: Credentials length cannot exceed 8 elements'
|
||||
for (let index = 0; index < 9; index++) {
|
||||
sampleCredentials.push({
|
||||
Credential: {
|
||||
Issuer: `SampleIssuer${index}`,
|
||||
CredentialType: stringToHex('Passport'),
|
||||
},
|
||||
})
|
||||
}
|
||||
depositPreauth.UnauthorizeCredentials = sampleCredentials
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it('throws when AuthorizeCredentials is invalid shape', function () {
|
||||
const invalidCredentials = [
|
||||
{ Credential: 'Invalid Shape' },
|
||||
{ Credential: 'Another Invalid Shape' },
|
||||
]
|
||||
const errorMessage = 'DepositPreauth: Invalid Credentials format'
|
||||
|
||||
depositPreauth.AuthorizeCredentials = invalidCredentials
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it('throws when UnauthorizeCredentials is invalid shape', function () {
|
||||
const invalidCredentials = [
|
||||
{ Credential: 'Invalid Shape' },
|
||||
{ Credential: 'Another Invalid Shape' },
|
||||
]
|
||||
const errorMessage = 'DepositPreauth: Invalid Credentials format'
|
||||
|
||||
depositPreauth.UnauthorizeCredentials = invalidCredentials
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it('throws when AuthorizeCredentials has duplicates', function () {
|
||||
const invalidCredentials = [validCredential, validCredential]
|
||||
const errorMessage =
|
||||
'DepositPreauth: Credentials cannot contain duplicate elements'
|
||||
|
||||
depositPreauth.AuthorizeCredentials = invalidCredentials
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it('throws when UnauthorizeCredentials has duplicates', function () {
|
||||
const invalidCredentials = [validCredential, validCredential]
|
||||
const errorMessage =
|
||||
'DepositPreauth: Credentials cannot contain duplicate elements'
|
||||
|
||||
depositPreauth.UnauthorizeCredentials = invalidCredentials
|
||||
assert.throws(
|
||||
() => validateDepositPreauth(depositPreauth),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(depositPreauth), ValidationError, errorMessage)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -20,6 +20,9 @@ describe('EscrowFinish', function () {
|
||||
Condition:
|
||||
'A0258020E3B0C44298FC1C149AFBF4C8996FB92427AE41E4649B934CA495991B7852B855810100',
|
||||
Fulfillment: 'A0028000',
|
||||
CredentialIDs: [
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A',
|
||||
],
|
||||
}
|
||||
})
|
||||
it(`verifies valid EscrowFinish`, function () {
|
||||
@@ -28,8 +31,9 @@ describe('EscrowFinish', function () {
|
||||
})
|
||||
|
||||
it(`verifies valid EscrowFinish w/o optional`, function () {
|
||||
delete escrow.Condition
|
||||
delete escrow.Fulfillment
|
||||
escrow.Condition = undefined
|
||||
escrow.Fulfillment = undefined
|
||||
escrow.CredentialIDs = undefined
|
||||
|
||||
assert.doesNotThrow(() => validateEscrowFinish(escrow))
|
||||
assert.doesNotThrow(() => validate(escrow))
|
||||
@@ -101,4 +105,88 @@ describe('EscrowFinish', function () {
|
||||
'EscrowFinish: Fulfillment must be a string',
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ non-array CredentialIDs`, function () {
|
||||
escrow.CredentialIDs =
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A'
|
||||
|
||||
const errorMessage = 'EscrowFinish: Credentials must be an array'
|
||||
|
||||
assert.throws(
|
||||
() => validateEscrowFinish(escrow),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(escrow), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws CredentialIDs length exceeds max length`, function () {
|
||||
escrow.CredentialIDs = [
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66B',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66C',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66D',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66E',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66F',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F660',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F661',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662',
|
||||
]
|
||||
|
||||
const errorMessage =
|
||||
'EscrowFinish: Credentials length cannot exceed 8 elements'
|
||||
|
||||
assert.throws(
|
||||
() => validateEscrowFinish(escrow),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(escrow), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ empty CredentialIDs`, function () {
|
||||
escrow.CredentialIDs = []
|
||||
|
||||
const errorMessage = 'EscrowFinish: Credentials cannot be an empty array'
|
||||
|
||||
assert.throws(
|
||||
() => validateEscrowFinish(escrow),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(escrow), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ non-string CredentialIDs`, function () {
|
||||
escrow.CredentialIDs = [
|
||||
123123,
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662',
|
||||
]
|
||||
|
||||
const errorMessage = 'EscrowFinish: Invalid Credentials ID list format'
|
||||
|
||||
assert.throws(
|
||||
() => validateEscrowFinish(escrow),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(escrow), ValidationError, errorMessage)
|
||||
})
|
||||
|
||||
it(`throws w/ duplicate CredentialIDs`, function () {
|
||||
escrow.CredentialIDs = [
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662',
|
||||
]
|
||||
|
||||
const errorMessage =
|
||||
'EscrowFinish: Credentials cannot contain duplicate elements'
|
||||
|
||||
assert.throws(
|
||||
() => validateEscrowFinish(escrow),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(() => validate(escrow), ValidationError, errorMessage)
|
||||
})
|
||||
})
|
||||
|
||||
@@ -1,3 +1,4 @@
|
||||
/* eslint-disable max-statements -- need additional tests for optional fields */
|
||||
import { assert } from 'chai'
|
||||
|
||||
import { validate, PaymentFlags, ValidationError } from '../../src'
|
||||
@@ -272,4 +273,107 @@ describe('Payment', function () {
|
||||
assert.doesNotThrow(() => validatePayment(mptPaymentTransaction))
|
||||
assert.doesNotThrow(() => validate(mptPaymentTransaction))
|
||||
})
|
||||
|
||||
it(`throws w/ non-array CredentialIDs`, function () {
|
||||
paymentTransaction.CredentialIDs =
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A'
|
||||
|
||||
const errorMessage = 'Payment: Credentials must be an array'
|
||||
|
||||
assert.throws(
|
||||
() => validatePayment(paymentTransaction),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(paymentTransaction),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws CredentialIDs length exceeds max length`, function () {
|
||||
paymentTransaction.CredentialIDs = [
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66A',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66B',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66C',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66D',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66E',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F66F',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F660',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F661',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662',
|
||||
]
|
||||
|
||||
const errorMessage = 'Payment: Credentials length cannot exceed 8 elements'
|
||||
|
||||
assert.throws(
|
||||
() => validatePayment(paymentTransaction),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(paymentTransaction),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ empty CredentialIDs`, function () {
|
||||
paymentTransaction.CredentialIDs = []
|
||||
|
||||
const errorMessage = 'Payment: Credentials cannot be an empty array'
|
||||
|
||||
assert.throws(
|
||||
() => validatePayment(paymentTransaction),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(paymentTransaction),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ non-string CredentialIDs`, function () {
|
||||
paymentTransaction.CredentialIDs = [
|
||||
123123,
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662',
|
||||
]
|
||||
|
||||
const errorMessage = 'Payment: Invalid Credentials ID list format'
|
||||
|
||||
assert.throws(
|
||||
() => validatePayment(paymentTransaction),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(paymentTransaction),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
|
||||
it(`throws w/ duplicate CredentialIDs`, function () {
|
||||
paymentTransaction.CredentialIDs = [
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662',
|
||||
'EA85602C1B41F6F1F5E83C0E6B87142FB8957BD209469E4CC347BA2D0C26F662',
|
||||
]
|
||||
|
||||
const errorMessage =
|
||||
'Payment: Credentials cannot contain duplicate elements'
|
||||
|
||||
assert.throws(
|
||||
() => validatePayment(paymentTransaction),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
assert.throws(
|
||||
() => validate(paymentTransaction),
|
||||
ValidationError,
|
||||
errorMessage,
|
||||
)
|
||||
})
|
||||
})
|
||||
|
||||
Reference in New Issue
Block a user