diff --git a/README.md b/README.md index d86ed5d62..3a2928312 100644 --- a/README.md +++ b/README.md @@ -160,6 +160,7 @@ Validator | Description **isVariableWidth(str)** | check if the string contains a mixture of full and half-width chars. **isVAT(str, countryCode)** | checks that the string is a [valid VAT number](https://en.wikipedia.org/wiki/VAT_identification_number) if validation is available for the given country code matching [ISO 3166-1 alpha-2](https://en.wikipedia.org/wiki/ISO_3166-1_alpha-2).

Available country codes: `[ 'GB', 'IT' ]`. **isWhitelisted(str, chars)** | checks characters if they appear in the whitelist. +**isXRPAddress(str, [, options])** | check if string is valid XRP address.

options object is optional. With options object, we can specify format of xrp address (classic and/or xAddress). In case of xAddress, user must also specify if we should check if address is valid on test/dev network, or on main network, because format of xAddress depends on network.

Example: `{ classicAddress: true, xAddress: { test: false } }`

Note: This does not verify checksum of address. This checks only the format of address. **matches(str, pattern [, modifiers])** | check if string matches the pattern.

Either `matches('foo', /foo/i)` or `matches('foo', 'foo', 'i')`. ## Sanitizers diff --git a/src/index.js b/src/index.js index e91c582da..e99aebb39 100644 --- a/src/index.js +++ b/src/index.js @@ -102,6 +102,8 @@ import isMimeType from './lib/isMimeType'; import isLatLong from './lib/isLatLong'; import isPostalCode, { locales as isPostalCodeLocales } from './lib/isPostalCode'; +import isXRPAddress from './lib/isXRPAddress'; + import ltrim from './lib/ltrim'; import rtrim from './lib/rtrim'; import trim from './lib/trim'; @@ -222,6 +224,7 @@ const validator = { isDate, isLicensePlate, isVAT, + isXRPAddress, }; export default validator; diff --git a/src/lib/isXRPAddress.js b/src/lib/isXRPAddress.js new file mode 100644 index 000000000..3e295f69b --- /dev/null +++ b/src/lib/isXRPAddress.js @@ -0,0 +1,30 @@ +import assertString from './util/assertString'; + +const classicAddressRegex = /^r[A-HJ-NP-Za-km-z1-9]{24,35}$/; +const xAddressMainnetRegex = /^X[A-HJ-NP-Za-km-z1-9]{46,}$/; +const xAddressNonMainnetRegex = /^T[A-HJ-NP-Za-km-z1-9]{46,}$/; + +export default function isXRPAddress(address, options) { + assertString(address); + + if (typeof (options) !== 'object') { + // only check for classic address + return classicAddressRegex.test(address); + } + + if (typeof (options.xAddress) !== 'object') { + // just passthrough if user does not want to check anything + return options.classicAddress ? classicAddressRegex.test(address) : true; + } + + let xAddressRegex = xAddressMainnetRegex; + if (options.xAddress.test) { + xAddressRegex = xAddressNonMainnetRegex; + } + + if (options.classicAddress) { + return xAddressRegex.test(address) || classicAddressRegex.test(address); + } + + return xAddressRegex.test(address); +} diff --git a/test/validators.js b/test/validators.js index 4a4180a8e..3a3b7ac85 100644 --- a/test/validators.js +++ b/test/validators.js @@ -1,6 +1,6 @@ import assert from 'assert'; -import fs from 'fs'; import { format } from 'util'; +import fs from 'fs'; import vm from 'vm'; import validator from '../src/index'; @@ -8589,6 +8589,162 @@ describe('Validators', () => { }); }); + it('should validate XRP addresses', () => { + test({ + validator: 'isXRPAddress', + valid: [ + 'rAAAAAAAAAAAAAAAAAAAAAAAAA', + 'rUfkLWGb7UwFgpci24kBpu7eCR', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oTnpLxUG', + ], + invalid: [ + 'r4zkbgzeQriVB1iFeZj8rTT1', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oInpLxUG', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oOnpLxUG', + 'r4zkbgzeQriVB1iFeZj8rTT1q3olnpLxUG', + 'r4zkbgzeQriVB1iFeZj8rTT1q3o+npLxUG', + 'r4zkbgzeQriVB1iFeZj8rTT1q3o/npLxUG', + '4zkbgzeQriVB1iFeZj8rTT1q3oTnpLxUGr', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oTnpLxUGGGG', + ], + }); + + test({ + validator: 'isXRPAddress', + args: [ + { + classicAddress: false, + }, + ], + valid: [ + 'a', + 'xyz', + ], + }); + + test({ + validator: 'isXRPAddress', + args: [ + { + classicAddress: true, + }, + ], + valid: [ + 'rAAAAAAAAAAAAAAAAAAAAAAAAA', + 'rUfkLWGb7UwFgpci24kBpu7eCR', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oTnpLxUG', + ], + invalid: [ + 'r4zkbgzeQriVB1iFeZj8rTT1', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oInpLxUG', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oOnpLxUG', + 'r4zkbgzeQriVB1iFeZj8rTT1q3olnpLxUG', + 'r4zkbgzeQriVB1iFeZj8rTT1q3o+npLxUG', + 'r4zkbgzeQriVB1iFeZj8rTT1q3o/npLxUG', + '4zkbgzeQriVB1iFeZj8rTT1q3oTnpLxUGr', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oTnpLxUGGGG', + ], + }); + + test({ + validator: 'isXRPAddress', + args: [ + { + xAddress: { + test: false, + }, + }, + ], + valid: [ + 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ', + 'XV5sbjUmgPpvXv4ixFWZ5ptAYZ6PD2gYsjNFQLKYW33DzBm', + 'XV5sbjUmgPpvXv4ixFWZ5ptAYZ6PD28Sq49uo34VyjnmK5H', + ], + invalid: [ + 'rAAAAAAAAAAAAAAAAAAAAAAAAA', + 'rUfkLWGb7UwFgpci24kBpu7eCR', + 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oTnpLxUG', + 'T7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ', + ], + }); + + test({ + validator: 'isXRPAddress', + args: [ + { + xAddress: { + test: true, + }, + }, + ], + valid: [ + 'T7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ', + ], + invalid: [ + 'rAAAAAAAAAAAAAAAAAAAAAAAAA', + 'rUfkLWGb7UwFgpci24kBpu7eCR', + 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oTnpLxUG', + 'T7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cq', + 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ', + ], + }); + + test({ + validator: 'isXRPAddress', + args: [ + { + classicAddress: true, + xAddress: { + test: true, + }, + }, + ], + valid: [ + 'rAAAAAAAAAAAAAAAAAAAAAAAAA', + 'rUfkLWGb7UwFgpci24kBpu7eCR', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oTnpLxUG', + 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', + 'T7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ', + ], + invalid: [ + 'T7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cq', + 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ', + ], + }); + + test({ + validator: 'isXRPAddress', + args: [ + { + classicAddress: true, + xAddress: { + test: false, + }, + }, + ], + valid: [ + 'rAAAAAAAAAAAAAAAAAAAAAAAAA', + 'rUfkLWGb7UwFgpci24kBpu7eCR', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oTnpLxUG', + 'r9cZA1mLK5R5Am25ArfXFmqgNwjZgnfk59', + 'X7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cqZ', + ], + invalid: [ + 'r4zkbgzeQriVB1iFeZj8rTT1', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oInpLxUG', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oOnpLxUG', + 'r4zkbgzeQriVB1iFeZj8rTT1q3olnpLxUG', + 'r4zkbgzeQriVB1iFeZj8rTT1q3o+npLxUG', + 'r4zkbgzeQriVB1iFeZj8rTT1q3o/npLxUG', + '4zkbgzeQriVB1iFeZj8rTT1q3oTnpLxUGr', + 'r4zkbgzeQriVB1iFeZj8rTT1q3oTnpLxUGGGG', + 'T7AcgcsBL6XDcUb289X4mJ8djcdyKaB5hJDWMArnXr61cq', + ], + }); + }); + it('should validate booleans', () => { test({ validator: 'isBoolean',