From ab489fa7c62b6bf72d9febdc5c5a9f6493fd8df5 Mon Sep 17 00:00:00 2001 From: Rastislav Date: Sat, 7 Dec 2024 15:42:40 +0100 Subject: [PATCH 1/4] Crypto types --- .github/workflows/publish.yml | 2 +- .github/workflows/tests.yml | 4 +- .gitignore | 4 +- README.md | 124 ++++-- ican.d.ts | 142 +++++++ ican.js | 770 +++++++++++++--------------------- package.json | 18 +- src/index.d.ts | 99 ----- src/index.js | 471 --------------------- test/index.js | 16 + test/samples.json | 16 + 11 files changed, 579 insertions(+), 1087 deletions(-) create mode 100644 ican.d.ts delete mode 100644 src/index.d.ts delete mode 100644 src/index.js diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml index ab9bb5f..3528fab 100644 --- a/.github/workflows/publish.yml +++ b/.github/workflows/publish.yml @@ -17,7 +17,7 @@ jobs: - name: Setup Node uses: actions/setup-node@v3 with: - node-version: '18.x' + node-version: '20.x' registry-url: 'https://registry.npmjs.org' - name: Install & Build 🔧 run: npm i && npm run build diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml index 22625d8..e0ef2df 100644 --- a/.github/workflows/tests.yml +++ b/.github/workflows/tests.yml @@ -14,7 +14,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: 18 + node-version: '20.x' registry-url: https://registry.npmjs.org/ - run: npm i - run: npm run unit @@ -25,7 +25,7 @@ jobs: - uses: actions/checkout@v4 - uses: actions/setup-node@v3 with: - node-version: 18 + node-version: '20.x' registry-url: https://registry.npmjs.org/ - run: npm i - run: npm run standard diff --git a/.gitignore b/.gitignore index eb2f234..d9abb7c 100644 --- a/.gitignore +++ b/.gitignore @@ -16,10 +16,12 @@ node_modules yarn.lock package-lock.json +/dist + .DS_Store .AppleDouble .LSOverride -Icon +/Icon ._* .DocumentRevisions-V100 .fseventsd diff --git a/README.md b/README.md index d9c85b7..2656ba7 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,9 @@ + # ican.js ICAN and BCAN validation, formatting, and conversion in JavaScript. -ICAN.js adheres to the [ISO 13616 IBAN Registry technical specification](https://www.swift.com/standards/data-standards/iban) and includes support for Crypto addresses. +ICAN.js adheres to the [ISO 13616 IBAN Registry technical specification](https://www.swift.com/standards/data-standards/iban) and includes support for Crypto addresses, including distinctions for mainnets, testnets, and enterprise networks. ## Usage @@ -12,71 +13,124 @@ ICAN.js is compatible with both CommonJS and AMD module definitions. You can install [@blockchainhub/ican from NPM](https://www.npmjs.com/package/@blockchainhub/ican) using Yarn, NPM, or another tool. -> Yarn +> NPM ```sh -yarn add @blockchainhub/ican +npm install @blockchainhub/ican ``` -> NPM +> Bun ```sh -npm i @blockchainhub/ican +bun add @blockchainhub/ican ``` -### In node.js +### In Node.js ```js -var ICAN = require('@blockchainhub/ican'); -ICAN.isValid('hello world'); // false -ICAN.isValid('BE68539007547034'); // true +const ICAN = require('@blockchainhub/ican'); +console.log(ICAN.isValid('hello world')); // false +console.log(ICAN.isValid('BE68539007547034')); // true ``` -### In the browser +### In the Browser -You can use a module loader (AMD or CommonJS) or access it directly through the global ```ICAN``` object: +You can use a module loader (AMD or CommonJS) or access it directly through the global `ICAN` object: ```html ``` -### With React +### With ESM -Using ICAN.js with React is easy. For example: +Using ICAN.js with React is straightforward: -```js -import Ican from '@blockchainhub/ican'; -Ican.isValid('hello world'); -Ican.isValid('BE68539007547034'); +```ts +import ICAN from '@blockchainhub/ican'; +console.log(ICAN.isValid('hello world')); // false +console.log(ICAN.isValid('BE68539007547034')); // true ``` ## API -- isValid(ican, onlyCrypto) -- toBCAN(ican, separator, onlyCrypto) -- fromBCAN(countryCode, bcan) -- isValidBCAN(countryCode, bcan, onlyCrypto) -- printFormat(ican, separator) -- electronicFormat(ican) -- shortFormat(ican, separator, frontCount, backCount) +### Methods -## Contributions +#### `isValid(ican, onlyCrypto)` -We welcome contributions in any form. Here's how you can help: +- Validates an ICAN. +- **Parameters**: + - `ican`: The ICAN to validate. + - `onlyCrypto`: (Optional) Restrict validation to crypto definitions. Possible values: + - `true`: Include all crypto networks. + - `false`: Exclude crypto networks. + - `'main'`: Mainnets. + - `'test'`: Testnets. + - `'enter'`: Enterprise networks. + +#### `toBCAN(ican, separator)` + +- Converts an ICAN to its country-specific BCAN representation. +- **Parameters**: + - `ican`: The ICAN to convert. + - `separator`: (Optional) The separator to use between BCAN blocks (default is `' '`). + +#### `fromBCAN(countryCode, bcan)` + +- Converts a BCAN to an ICAN for a specific country. +- **Parameters**: + - `countryCode`: The country code of the BCAN. + - `bcan`: The BCAN to convert. + +#### `isValidBCAN(countryCode, bcan, onlyCrypto)` -- Fork [this repository](/fork) -- Open a [pull request](/pulls) -- Support us with some Øres / ₡ores: [cb7147879011ea207df5b35a24ca6f0859dcfb145999](https://blockindex.net/address/cb7147879011ea207df5b35a24ca6f0859dcfb145999) -- Star this repository +- Validates a BCAN. +- **Parameters**: + - `countryCode`: The country code of the BCAN. + - `bcan`: The BCAN to validate. + - `onlyCrypto`: (Optional) Restrict validation to crypto definitions (same values as `isValid`). -## Epigram +#### `printFormat(ican, separator)` + +- Formats an ICAN for display with separators. +- **Parameters**: + - `ican`: The ICAN to format. + - `separator`: (Optional) The separator to use (default is `' '`). + +#### `electronicFormat(ican)` + +- Converts an ICAN to its electronic format (removing non-alphanumeric characters and uppercasing). + +#### `shortFormat(ican, separator, frontCount, backCount)` + +- Produces a shortened version of the ICAN with a custom separator. +- **Parameters**: + - `ican`: The ICAN to shorten. + - `separator`: (Optional) The separator to use (default is `'…'`). + - `frontCount`: (Optional) Number of characters to display at the start (default is `4`). + - `backCount`: (Optional) Number of characters to display at the end (default is `4`). + +### Variables + +- `ican`: An International Crypto Account Number. +- `bcan`: A country-specific Base Crypto Account Number. +- `countryCode`: The country code for the BCAN. +- `separator`: A separator to format ICAN or BCAN strings. +- `onlyCrypto`: A filter for crypto validation (values: `true`, `false`, `'main'`, `'test'`, `'enter'`). +- `frontCount` and `backCount`: Define how many characters to include in the `shortFormat` method. + +## Contributions + +We welcome contributions in any form. Here's how you can help: -> 「Cryptoni Confidimus」 +1. Fork [this repository](/fork). +2. Open a [pull request](/pulls). +3. Support us with some Øres / ₡ores: [cb7147879011ea207df5b35a24ca6f0859dcfb145999](https://blockindex.net/address/cb7147879011ea207df5b35a24ca6f0859dcfb145999). +4. Star this repository. ## License diff --git a/ican.d.ts b/ican.d.ts new file mode 100644 index 0000000..c692153 --- /dev/null +++ b/ican.d.ts @@ -0,0 +1,142 @@ +interface ICANStatic { + /** + * An object containing all the known ICAN specifications. + */ + countries: Record; + + /** + * Returns the ICAN in an electronic format. + * @param ican The ICAN to convert. + * @returns The ICAN in electronic format. + */ + electronicFormat(ican: string): string; + + /** + * Convert the passed BCAN to an ICAN for this country specification. + * @param countryCode The country of the BCAN. + * @param bcan The BCAN to convert to ICAN. + * @returns The ICAN. + */ + fromBCAN(countryCode: string, bcan: string): string; + + /** + * Check if the passed ICAN is valid according to this specification. + * @param ican The ICAN to validate. + * @param onlyCrypto Check only crypto definitions. Possible values: + * - `true` = Include all crypto networks. + * - `false` = Exclude crypto networks. + * - `'main' | 'mainnet'` = Mainnets. + * - `'test' | 'testnet'` = Testnets. + * - `'enter' | 'enterprise'` = Enterprise networks. + * @returns True if valid, false otherwise. + */ + isValid(ican: string, onlyCrypto?: boolean | 'main' | 'test' | 'enter' | 'mainnet' | 'testnet' | 'enterprise'): boolean; + + /** + * Check if the passed BCAN is valid. + * @param countryCode The country of the BCAN. + * @param bcan The BCAN to validate. + * @param onlyCrypto Check only crypto definitions. Possible values: + * - `true` = Include all crypto networks. + * - `false` = Exclude crypto networks. + * - `'main' | 'mainnet'` = Mainnets. + * - `'test' | 'testnet'` = Testnets. + * - `'enter' | 'enterprise'` = Enterprise networks. + * @returns True if valid, false otherwise. + */ + isValidBCAN(countryCode: string, bcan: string, onlyCrypto?: boolean | 'main' | 'test' | 'enter' | 'mainnet' | 'testnet' | 'enterprise'): boolean; + + /** + * Returns the ICAN in a print format. + * @param ican The ICAN to convert. + * @param [separator] The separator to use between ICAN blocks, defaults to ' '. + * @returns The formatted ICAN. + */ + printFormat(ican: string, separator?: string): string; + + /** + * Convert the passed ICAN to a country-specific BCAN. + * @param ican The ICAN to convert. + * @param [separator] The separator to use between BCAN blocks, defaults to ' '. + * @returns The BCAN + */ + toBCAN(ican: string, separator?: string): string; + + /** + * Returns the ICAN in a short format. + * @param ican The ICAN to convert. + * @param [separator] The separator to use between ICAN openings/endings, defaults to '…'. + * @param [frontCount] The number of characters to show at the beginning, defaults to 4. + * @param [backCount] The number of characters to show at the end, defaults to 4. + * @returns The shortened ICAN. + */ + shortFormat(ican: string, separator?: string, frontCount?: number, backCount?: number): string; +} + +declare var ICAN: ICANStatic; + +declare namespace ICAN { + interface Specification { + /** The code of the country. */ + readonly countryCode: string; + /** The length of the ICAN. */ + readonly length: number; + /** The structure of the underlying BCAN (for validation and formatting). */ + readonly structure: string; + /** + * The crypto property. Possible values: + * - `true` = All crypto networks. + * - `false` = Non-crypto. + * - `'main' | 'mainnet'` = Mainnets. + * - `'test' | 'testnet'` = Testnets. + * - `'enter' | 'enterprise'` = Enterprise networks. + */ + readonly crypto: boolean | 'main' | 'test' | 'enter' | 'mainnet' | 'testnet' | 'enterprise'; + /** An example valid ICAN. */ + readonly example: string; + + /** + * Check if the passed ICAN is valid according to this specification. + * @param ican The ICAN to validate. + * @param onlyCrypto Check only crypto definitions. Possible values: + * - `true` = Include all crypto networks. + * - `false` = Exclude crypto networks. + * - `'main' | 'mainnet'` = Mainnets. + * - `'test' | 'testnet'` = Testnets. + * - `'enter' | 'enterprise'` = Enterprise networks. + * @returns True if valid, false otherwise. + */ + isValid(ican: string, onlyCrypto?: boolean | 'main' | 'test' | 'enter' | 'mainnet' | 'testnet' | 'enterprise'): boolean; + + /** + * Convert the passed ICAN to a country-specific BCAN. + * @param ican The ICAN to convert. + * @param separator The separator to use between BCAN blocks. + * @returns The BCAN. + */ + toBCAN(ican: string, separator: string): string; + + /** + * Convert the passed BCAN to an ICAN for this country specification. + * @param bcan The BCAN to convert to ICAN. + * @returns The ICAN. + */ + fromBCAN(bcan: string): string; + + /** + * Check if the passed BCAN is valid. + * @param bcan The BCAN to validate. + * @param onlyCrypto Check only crypto definitions. Possible values: + * - `true` = Include all crypto networks. + * - `false` = Exclude crypto networks. + * - `'main' | 'mainnet'` = Mainnets. + * - `'test' | 'testnet'` = Testnets. + * - `'enter' | 'enterprise'` = Enterprise networks. + * @returns True if valid, false otherwise. + */ + isValidBCAN(bcan: string, onlyCrypto?: boolean | 'main' | 'test' | 'enter' | 'mainnet' | 'testnet' | 'enterprise'): boolean; + } +} + +export = ICAN; +export as namespace ICAN; diff --git a/ican.js b/ican.js index 0ed6cdc..876ca2f 100644 --- a/ican.js +++ b/ican.js @@ -1,471 +1,303 @@ (function (root, factory) { - if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef - define(['exports'], factory) // eslint-disable-line no-undef - } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') { - factory(exports) - } else { - factory(root.ICAN = {}) - } + if (typeof define === 'function' && define.amd) { + define(['exports'], factory); + } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') { + factory(exports); + } else { + factory(root.ICAN = {}); + } }(this, function (exports) { - if (!Array.prototype.map) { - Array.prototype.map = function (fun /*, thisArg */) { // eslint-disable-line no-extend-native - 'use strict' - - if (this === undefined || this === null) { throw new TypeError() } - - const t = Object(this) - const len = t.length >>> 0 - if (typeof fun !== 'function') { throw new TypeError() } - - const res = new Array(len) - const thisArg = arguments.length >= 2 ? arguments[1] : undefined - for (let i = 0; i < len; i++) { - if (i in t) { res[i] = fun.call(thisArg, t[i], i, t) } - } - - return res - } - } - - const A = 'A'.charCodeAt(0) - const Z = 'Z'.charCodeAt(0) - - /** - * Prepare an ICAN for mod 97 computation by moving the first 4 chars to the end and transforming the letters to - * numbers (A = 10, B = 11, ..., Z = 35), as specified in ISO13616. - * - * @param {string} ican the ICAN - * @returns {string} the prepared ICAN - */ - function iso13616Prepare (ican) { - ican = ican.toUpperCase() - ican = ican.slice(4) + ican.slice(0, 4); - - return ican.split('').map(function (n) { - const code = n.charCodeAt(0) - if (code >= A && code <= Z) { - // A = 10, B = 11, ... Z = 35 - return code - A + 10 - } else { - return n - } - }).join('') - } - - /** - * Calculates the MOD 97 10 of the passed ICAN as specified in ISO7064. - * - * @param ican - * @returns {number} - */ - function iso7064Mod97 (ican) { - let remainder = ican - let block - - while (remainder.length > 2) { - block = remainder.slice(0, 9) - remainder = parseInt(block, 10) % 97 + remainder.slice(block.length) - } - - return parseInt(remainder, 10) % 97 - } - - /** - * Parse the BCAN structure used to configure each ICAN Specification and returns a matching regular expression. - * A structure is composed of blocks of 3 characters (one letter and 2 digits). Each block represents - * a logical group in the typical representation of the BCAN. For each group, the letter indicates which characters - * are allowed in this group and the following 2-digits number tells the length of the group. - * - * @param {string} structure the structure to parse - * @returns {RegExp} - */ - function parseStructure (structure) { - // split in blocks of 3 chars - const regex = structure.match(/(.{3})/g).map(function (block) { - // parse each structure block (1-char + 2-digits) - let format - const pattern = block.slice(0, 1) - const repeats = parseInt(block.slice(1), 10) - - switch (pattern) { - case 'A': - format = '0-9A-Za-z' - break - case 'B': - format = '0-9A-Z' - break - case 'C': - format = 'A-Za-z' - break - case 'H': - format = '0-9A-Fa-f' - break - case 'F': - format = '0-9' - break - case 'L': - format = 'a-z' - break - case 'U': - format = 'A-Z' - break - case 'W': - format = '0-9a-z' - break - } - - return '([' + format + ']{' + repeats + '})' - }) - - return new RegExp('^' + regex.join('') + '$') - } - - /** - * - * @param ican - * @returns {string} - */ - function electronicFormat (ican) { - return ican.replace(NON_ALPHANUM, '').toUpperCase() - } - - /** - * Create a new Specification for a valid ICAN number. - * - * @param countryCode the code of the country - * @param length the length of the ICAN - * @param structure the structure of the underlying BCAN (for validation and formatting) - * @param example an example valid ICAN - * @constructor - */ - function Specification (countryCode, length, structure, crypto, example) { - this.countryCode = countryCode - this.length = length - this.structure = structure - this.crypto = crypto - this.example = example - } - - /** - * Lazy-loaded regex (parse the structure and construct the regular expression the first time we need it for validation) - */ - Specification.prototype._regex = function () { - return this._cachedRegex || (this._cachedRegex = parseStructure(this.structure)) - } - - /** - * Check if the passed ican is valid according to this specification. - * - * @param {String} ican the ican to validate - * @param {Boolean} onlyCrypto check only digital assets - * @returns {boolean} true if valid, false otherwise - */ - Specification.prototype.isValid = function (ican, onlyCrypto = false) { - return this.length === ican.length && - this.countryCode === ican.slice(0, 2) && - (!onlyCrypto || this.crypto) && - this._regex().test(ican.slice(4)) && - iso7064Mod97(iso13616Prepare(ican)) === 1 - } - - /** - * Convert the passed ICAN to a country-specific BCAN. - * - * @param ican the ICAN to convert - * @param separator the separator to use between BCAN blocks - * @returns {string} the BCAN - */ - Specification.prototype.toBCAN = function (ican, separator) { - return this._regex().exec(ican.slice(4)).slice(1).join(separator) - } - - /** - * Convert the passed BCAN to an ICAN for this country specification. - * - * @param bcan the BCAN to convert to ICAN - * @returns {string} the ICAN - */ - Specification.prototype.fromBCAN = function (bcan) { - if (!this.isValidBCAN(bcan)) { - throw new Error('Invalid BCAN') - } - - const remainder = iso7064Mod97(iso13616Prepare(this.countryCode + '00' + bcan)) - const checkDigit = ('0' + (98 - remainder)).slice(-2) - - return this.countryCode + checkDigit + bcan - } - - /** - * Check of the passed BCAN is valid. - * This function only checks the format of the BCAN (length and matching the leter/number specs) but does not - * verify the check digit. - * - * @param bcan the BCAN to validate - * @returns {boolean} true if the passed bcan is a valid BCAN according to this specification, false otherwise - */ - Specification.prototype.isValidBCAN = function (bcan, onlyCrypto = false) { - return this.length - 4 === bcan.length && - (!onlyCrypto || this.crypto) && - this._regex().test(bcan) - } - - const countries = {} - - function addSpecification (ICAN) { - countries[ICAN.countryCode] = ICAN - } - - addSpecification(new Specification('AD', 24, 'F04F04A12', false, 'AD1200012030200359100100')) - addSpecification(new Specification('AE', 23, 'F03F16', false, 'AE070331234567890123456')) - addSpecification(new Specification('AL', 28, 'F08A16', false, 'AL47212110090000000235698741')) - addSpecification(new Specification('AT', 20, 'F05F11', false, 'AT611904300234573201')) - addSpecification(new Specification('AZ', 28, 'U04A20', false, 'AZ21NABZ00000000137010001944')) - addSpecification(new Specification('BA', 20, 'F03F03F08F02', false, 'BA391290079401028494')) - addSpecification(new Specification('BE', 16, 'F03F07F02', false, 'BE68539007547034')) - addSpecification(new Specification('BG', 22, 'U04F04F02A08', false, 'BG80BNBG96611020345678')) - addSpecification(new Specification('BH', 22, 'U04A14', false, 'BH67BMAG00001299123456')) - addSpecification(new Specification('BR', 29, 'F08F05F10U01A01', false, 'BR9700360305000010009795493P1')) - addSpecification(new Specification('BY', 28, 'A04F04A16', false, 'BY13NBRB3600900000002Z00AB00')) - addSpecification(new Specification('CH', 21, 'F05A12', false, 'CH9300762011623852957')) - addSpecification(new Specification('CR', 22, 'F04F14', false, 'CR72012300000171549015')) - addSpecification(new Specification('CY', 28, 'F03F05A16', false, 'CY17002001280000001200527600')) - addSpecification(new Specification('CZ', 24, 'F04F06F10', false, 'CZ6508000000192000145399')) - addSpecification(new Specification('DE', 22, 'F08F10', false, 'DE89370400440532013000')) - addSpecification(new Specification('DK', 18, 'F04F09F01', false, 'DK5000400440116243')) - addSpecification(new Specification('DO', 28, 'U04F20', false, 'DO28BAGR00000001212453611324')) - addSpecification(new Specification('EE', 20, 'F02F02F11F01', false, 'EE382200221020145685')) - addSpecification(new Specification('EG', 29, 'F04F04F17', false, 'EG800002000156789012345180002')) - addSpecification(new Specification('ES', 24, 'F04F04F01F01F10', false, 'ES9121000418450200051332')) - addSpecification(new Specification('FI', 18, 'F06F07F01', false, 'FI2112345600000785')) - addSpecification(new Specification('FO', 18, 'F04F09F01', false, 'FO6264600001631634')) - addSpecification(new Specification('FR', 27, 'F05F05A11F02', false, 'FR1420041010050500013M02606')) - addSpecification(new Specification('GB', 22, 'U04F06F08', false, 'GB29NWBK60161331926819')) - addSpecification(new Specification('GE', 22, 'U02F16', false, 'GE29NB0000000101904917')) - addSpecification(new Specification('GI', 23, 'U04A15', false, 'GI75NWBK000000007099453')) - addSpecification(new Specification('GL', 18, 'F04F09F01', false, 'GL8964710001000206')) - addSpecification(new Specification('GR', 27, 'F03F04A16', false, 'GR1601101250000000012300695')) - addSpecification(new Specification('GT', 28, 'A04A20', false, 'GT82TRAJ01020000001210029690')) - addSpecification(new Specification('HR', 21, 'F07F10', false, 'HR1210010051863000160')) - addSpecification(new Specification('HU', 28, 'F03F04F01F15F01', false, 'HU42117730161111101800000000')) - addSpecification(new Specification('IE', 22, 'U04F06F08', false, 'IE29AIBK93115212345678')) - addSpecification(new Specification('IL', 23, 'F03F03F13', false, 'IL620108000000099999999')) - addSpecification(new Specification('IS', 26, 'F04F02F06F10', false, 'IS140159260076545510730339')) - addSpecification(new Specification('IT', 27, 'U01F05F05A12', false, 'IT60X0542811101000000123456')) - addSpecification(new Specification('IQ', 23, 'U04F03A12', false, 'IQ98NBIQ850123456789012')) - addSpecification(new Specification('JO', 30, 'A04F22', false, 'JO15AAAA1234567890123456789012')) - addSpecification(new Specification('KW', 30, 'U04A22', false, 'KW81CBKU0000000000001234560101')) - addSpecification(new Specification('KZ', 20, 'F03A13', false, 'KZ86125KZT5004100100')) - addSpecification(new Specification('LB', 28, 'F04A20', false, 'LB62099900000001001901229114')) - addSpecification(new Specification('LC', 32, 'U04F24', false, 'LC07HEMM000100010012001200013015')) - addSpecification(new Specification('LI', 21, 'F05A12', false, 'LI21088100002324013AA')) - addSpecification(new Specification('LT', 20, 'F05F11', false, 'LT121000011101001000')) - addSpecification(new Specification('LU', 20, 'F03A13', false, 'LU280019400644750000')) - addSpecification(new Specification('LV', 21, 'U04A13', false, 'LV80BANK0000435195001')) - addSpecification(new Specification('MC', 27, 'F05F05A11F02', false, 'MC5811222000010123456789030')) - addSpecification(new Specification('MD', 24, 'U02A18', false, 'MD24AG000225100013104168')) - addSpecification(new Specification('ME', 22, 'F03F13F02', false, 'ME25505000012345678951')) - addSpecification(new Specification('MK', 19, 'F03A10F02', false, 'MK07250120000058984')) - addSpecification(new Specification('MR', 27, 'F05F05F11F02', false, 'MR1300020001010000123456753')) - addSpecification(new Specification('MT', 31, 'U04F05A18', false, 'MT84MALT011000012345MTLCAST001S')) - addSpecification(new Specification('MU', 30, 'U04F02F02F12F03U03', false, 'MU17BOMM0101101030300200000MUR')) - addSpecification(new Specification('NL', 18, 'U04F10', false, 'NL91ABNA0417164300')) - addSpecification(new Specification('NO', 15, 'F04F06F01', false, 'NO9386011117947')) - addSpecification(new Specification('PK', 24, 'U04A16', false, 'PK36SCBL0000001123456702')) - addSpecification(new Specification('PL', 28, 'F08F16', false, 'PL61109010140000071219812874')) - addSpecification(new Specification('PS', 29, 'U04A21', false, 'PS92PALS000000000400123456702')) - addSpecification(new Specification('PT', 25, 'F04F04F11F02', false, 'PT50000201231234567890154')) - addSpecification(new Specification('QA', 29, 'U04A21', false, 'QA30AAAA123456789012345678901')) - addSpecification(new Specification('RO', 24, 'U04A16', false, 'RO49AAAA1B31007593840000')) - addSpecification(new Specification('RS', 22, 'F03F13F02', false, 'RS35260005601001611379')) - addSpecification(new Specification('SA', 24, 'F02A18', false, 'SA0380000000608010167519')) - addSpecification(new Specification('SC', 31, 'U04F04F16U03', false, 'SC18SSCB11010000000000001497USD')) - addSpecification(new Specification('SE', 24, 'F03F16F01', false, 'SE4550000000058398257466')) - addSpecification(new Specification('SI', 19, 'F05F08F02', false, 'SI56263300012039086')) - addSpecification(new Specification('SK', 24, 'F04F06F10', false, 'SK3112000000198742637541')) - addSpecification(new Specification('SM', 27, 'U01F05F05A12', false, 'SM86U0322509800000000270100')) - addSpecification(new Specification('ST', 25, 'F08F11F02', false, 'ST68000100010051845310112')) - addSpecification(new Specification('SV', 28, 'U04F20', false, 'SV62CENR00000000000000700025')) - addSpecification(new Specification('TL', 23, 'F03F14F02', false, 'TL380080012345678910157')) - addSpecification(new Specification('TN', 24, 'F02F03F13F02', false, 'TN5910006035183598478831')) - addSpecification(new Specification('TR', 26, 'F05F01A16', false, 'TR330006100519786457841326')) - addSpecification(new Specification('UA', 29, 'F25', false, 'UA511234567890123456789012345')) - addSpecification(new Specification('VA', 22, 'F18', false, 'VA59001123000012345678')) - addSpecification(new Specification('VG', 24, 'U04F16', false, 'VG96VPVG0000012345678901')) - addSpecification(new Specification('XK', 20, 'F04F10F02', false, 'XK051212012345678906')) - - // The following countries are not included in the official ICAN registry but use the ICAN specification - - // Angola - addSpecification(new Specification('AO', 25, 'F21', false, 'AO69123456789012345678901')) - // Burkina - addSpecification(new Specification('BF', 27, 'F23', false, 'BF2312345678901234567890123')) - // Burundi - addSpecification(new Specification('BI', 16, 'F12', false, 'BI41123456789012')) - // Benin - addSpecification(new Specification('BJ', 28, 'F24', false, 'BJ39123456789012345678901234')) - // Ivory - addSpecification(new Specification('CI', 28, 'U02F22', false, 'CI70CI1234567890123456789012')) - // Cameron - addSpecification(new Specification('CM', 27, 'F23', false, 'CM9012345678901234567890123')) - // Cape Verde - addSpecification(new Specification('CV', 25, 'F21', false, 'CV30123456789012345678901')) - // Algeria - addSpecification(new Specification('DZ', 24, 'F20', false, 'DZ8612345678901234567890')) - // Iran - addSpecification(new Specification('IR', 26, 'F22', false, 'IR861234568790123456789012')) - // Madagascar - addSpecification(new Specification('MG', 27, 'F23', false, 'MG1812345678901234567890123')) - // Mali - addSpecification(new Specification('ML', 28, 'U01F23', false, 'ML15A12345678901234567890123')) - // Mozambique - addSpecification(new Specification('MZ', 25, 'F21', false, 'MZ25123456789012345678901')) - // Senegal - addSpecification(new Specification('SN', 28, 'U01F23', false, 'SN52A12345678901234567890123')) - - // The following are regional and administrative French Republic subdivision ICAN specification (same structure as FR, only country code vary) - addSpecification(new Specification('GF', 27, 'F05F05A11F02', false, 'GF121234512345123456789AB13')) - addSpecification(new Specification('GP', 27, 'F05F05A11F02', false, 'GP791234512345123456789AB13')) - addSpecification(new Specification('MQ', 27, 'F05F05A11F02', false, 'MQ221234512345123456789AB13')) - addSpecification(new Specification('RE', 27, 'F05F05A11F02', false, 'RE131234512345123456789AB13')) - addSpecification(new Specification('PF', 27, 'F05F05A11F02', false, 'PF281234512345123456789AB13')) - addSpecification(new Specification('TF', 27, 'F05F05A11F02', false, 'TF891234512345123456789AB13')) - addSpecification(new Specification('YT', 27, 'F05F05A11F02', false, 'YT021234512345123456789AB13')) - addSpecification(new Specification('NC', 27, 'F05F05A11F02', false, 'NC551234512345123456789AB13')) - addSpecification(new Specification('BL', 27, 'F05F05A11F02', false, 'BL391234512345123456789AB13')) - addSpecification(new Specification('MF', 27, 'F05F05A11F02', false, 'MF551234512345123456789AB13')) - addSpecification(new Specification('PM', 27, 'F05F05A11F02', false, 'PM071234512345123456789AB13')) - addSpecification(new Specification('WF', 27, 'F05F05A11F02', false, 'WF621234512345123456789AB13')) - - // Digital Assets - - // Core Blockchain Mainnet - addSpecification(new Specification('CB', 44, 'H40', true, 'CB661234567890ABCDEF1234567890ABCDEF12345678')) - // Core Blockchain Testnet - Devín - addSpecification(new Specification('AB', 44, 'H40', true, 'AB841234567890ABCDEF1234567890ABCDEF12345678')) - // Core Enterprise Blockchain Enterprise - addSpecification(new Specification('CE', 44, 'H40', true, 'CE571234567890ABCDEF1234567890ABCDEF12345678')) - - const NON_ALPHANUM = /[^a-zA-Z0-9]/g - const EVERY_FOUR_CHARS = /(.{4})(?!$)/g - - /** - * Utility function to check if a variable is a String. - * - * @param v - * @returns {boolean} true if the passed variable is a String, false otherwise. - */ - function isString (v) { - return (typeof v === 'string' || v instanceof String) - } - - /** - * Check if an ICAN is valid. - * - * @param {String} ican the ICAN to validate. - * @returns {boolean} true if the passed ICAN is valid, false otherwise - */ - exports.isValid = function (ican, onlyCrypto = false) { - if (!isString(ican)) { - return false - } - ican = electronicFormat(ican) - const countryStructure = countries[ican.slice(0, 2)] - return !!countryStructure && - (!onlyCrypto || countryStructure.crypto) && - countryStructure.isValid(ican) - } - - /** - * Convert an ICAN to a BCAN. - * - * @param ican - * @param {String} [separator] the separator to use between the blocks of the BCAN, defaults to ' ' - * @returns {string} the BCAN - */ - exports.toBCAN = function (ican, separator) { - if (typeof separator === 'undefined') { - separator = ' ' - } - ican = electronicFormat(ican) - const countryStructure = countries[ican.slice(0, 2)] - if (!countryStructure) { - throw new Error('No country with code ' + ican.slice(0, 2)) - } - return countryStructure.toBCAN(ican, separator) - } - - /** - * Convert the passed BCAN to an ICAN for this country specification. - * - * @param countryCode the country of the BCAN - * @param bcan the BCAN to convert to ICAN - * @returns {string} the ICAN - */ - exports.fromBCAN = function (countryCode, bcan) { - const countryStructure = countries[countryCode] - if (!countryStructure) { - throw new Error('No country with code ' + countryCode) - } - return countryStructure.fromBCAN(electronicFormat(bcan)) - } - - /** - * Check the validity of the passed BCAN. - * - * @param countryCode the country of the BCAN - * @param bcan the BCAN to check the validity of - * @param {Boolean} onlyCrypto check only digital assets - * @returns {boolean} true if the passed BCAN is a valid BCAN for the country, false otherwise - */ - exports.isValidBCAN = function (countryCode, bcan, onlyCrypto = false) { - if (!isString(bcan)) { - return false - } - const countryStructure = countries[countryCode] - return countryStructure && - (!onlyCrypto || countryStructure.crypto) && - countryStructure.isValidBCAN(electronicFormat(bcan)) - } - - /** - * - * @param ican - * @param separator - * @returns {string} - */ - exports.printFormat = function (ican, separator) { - if (typeof separator === 'undefined') { - separator = ' ' - } - return electronicFormat(ican).replace(EVERY_FOUR_CHARS, '$1' + separator) - } - - /** - * - * @param ican - * @param separator - * @param frontCount - * @param backCount - * @returns {string} - */ - exports.shortFormat = function (ican, separator, frontCount, backCount) { - if (typeof separator === 'undefined') { - separator = '…' - } - if (typeof frontCount === 'undefined') { - frontCount = 4 - } - if (typeof backCount === 'undefined') { - backCount = 4 - } - const electronic = electronicFormat(ican) - return electronic.slice(0, frontCount) + separator + electronic.slice(-backCount) - } - - exports.electronicFormat = electronicFormat - /** - * An object containing all the known ICAN specifications. - */ - exports.countries = countries -})) + 'use strict'; + + const A = 'A'.charCodeAt(0); + const Z = 'Z'.charCodeAt(0); + const NON_ALPHANUM = /[^a-zA-Z0-9]/g; + const EVERY_FOUR_CHARS = /(.{4})(?!$)/g; + + // Polyfill for Array.prototype.map if not available + if (!Array.prototype.map) { + Array.prototype.map = function (callback, thisArg) { + if (this == null) throw new TypeError(); + if (typeof callback !== 'function') throw new TypeError(); + + const t = Object(this); + const len = t.length >>> 0; + const res = new Array(len); + for (let i = 0; i < len; i++) { + if (i in t) res[i] = callback.call(thisArg, t[i], i, t); + } + return res; + }; + } + + // Utility functions + const iso13616Prepare = (ican) => { + const rearranged = ican.slice(4) + ican.slice(0, 4); + return rearranged.toUpperCase().split('').map(char => { + const code = char.charCodeAt(0); + return (code >= A && code <= Z) ? code - A + 10 : char; + }).join(''); + }; + + const iso7064Mod97 = (ican) => { + let remainder = ican; + while (remainder.length > 2) { + const block = remainder.slice(0, 9); + remainder = (parseInt(block, 10) % 97).toString() + remainder.slice(block.length); + } + return parseInt(remainder, 10) % 97; + }; + + const parseStructure = (structure) => { + return new RegExp('^' + structure.match(/.{3}/g).map(block => { + const [pattern, repeats] = [block[0], parseInt(block.slice(1), 10)]; + const formatMap = { + 'A': '0-9A-Za-z', + 'B': '0-9A-Z', + 'C': 'A-Za-z', + 'H': '0-9A-Fa-f', + 'F': '0-9', + 'L': 'a-z', + 'U': 'A-Z', + 'W': '0-9a-z', + }; + return `([${formatMap[pattern]}]{${repeats}})`; + }).join('') + '$'); + }; + + const electronicFormat = (ican) => ican.replace(NON_ALPHANUM, '').toUpperCase(); + + // Specification class + class Specification { + constructor(countryCode, length, structure, crypto, example) { + this.countryCode = countryCode; + this.length = length; + this.structure = structure; + this.crypto = this.normalizeCrypto(crypto); + this.example = example; + } + + normalizeCrypto(crypto) { + if (crypto === 'main' || crypto === 'mainnet') return 'main'; + if (crypto === 'test' || crypto === 'testnet') return 'test'; + if (crypto === 'enter' || crypto === 'enterprise') return 'enter'; + return crypto; + } + + get regex() { + if (!this._cachedRegex) { + this._cachedRegex = parseStructure(this.structure); + } + return this._cachedRegex; + } + + isValid(ican, onlyCrypto = false) { + ican = electronicFormat(ican); + return this.length === ican.length && + this.countryCode === ican.slice(0, 2) && + (!onlyCrypto || this.isMatchingCrypto(onlyCrypto)) && + this.regex.test(ican.slice(4)) && + iso7064Mod97(iso13616Prepare(ican)) === 1; + } + + isMatchingCrypto(onlyCrypto) { + if (typeof onlyCrypto === 'string') { + if (onlyCrypto === 'mainnet') onlyCrypto = 'main'; + else if (onlyCrypto === 'testnet') onlyCrypto = 'test'; + else if (onlyCrypto === 'enterprise') onlyCrypto = 'enter'; + } + if (onlyCrypto === true) return this.crypto !== false; + return this.crypto === onlyCrypto; + } + + toBCAN(ican, separator = ' ') { + return this.regex.exec(ican.slice(4)).slice(1).join(separator); + } + + fromBCAN(bcan) { + if (!this.isValidBCAN(bcan)) throw new Error('Invalid BCAN'); + const remainder = iso7064Mod97(iso13616Prepare(this.countryCode + '00' + bcan)); + const checkDigit = ('0' + (98 - remainder)).slice(-2); + return this.countryCode + checkDigit + bcan; + } + + isValidBCAN(bcan, onlyCrypto = false) { + return this.length - 4 === bcan.length && + (!onlyCrypto || this.isMatchingCrypto(onlyCrypto)) && + this.regex.test(bcan); + } + } + + // Country specifications + const countries = {}; + + const addSpecification = (spec) => { + countries[spec.countryCode] = spec; + }; + + addSpecification(new Specification('AD', 24, 'F04F04A12', false, 'AD1200012030200359100100')) + addSpecification(new Specification('AE', 23, 'F03F16', false, 'AE070331234567890123456')) + addSpecification(new Specification('AL', 28, 'F08A16', false, 'AL47212110090000000235698741')) + addSpecification(new Specification('AT', 20, 'F05F11', false, 'AT611904300234573201')) + addSpecification(new Specification('AZ', 28, 'U04A20', false, 'AZ21NABZ00000000137010001944')) + addSpecification(new Specification('BA', 20, 'F03F03F08F02', false, 'BA391290079401028494')) + addSpecification(new Specification('BE', 16, 'F03F07F02', false, 'BE68539007547034')) + addSpecification(new Specification('BG', 22, 'U04F04F02A08', false, 'BG80BNBG96611020345678')) + addSpecification(new Specification('BH', 22, 'U04A14', false, 'BH67BMAG00001299123456')) + addSpecification(new Specification('BR', 29, 'F08F05F10U01A01', false, 'BR9700360305000010009795493P1')) + addSpecification(new Specification('BY', 28, 'A04F04A16', false, 'BY13NBRB3600900000002Z00AB00')) + addSpecification(new Specification('CH', 21, 'F05A12', false, 'CH9300762011623852957')) + addSpecification(new Specification('CR', 22, 'F04F14', false, 'CR72012300000171549015')) + addSpecification(new Specification('CY', 28, 'F03F05A16', false, 'CY17002001280000001200527600')) + addSpecification(new Specification('CZ', 24, 'F04F06F10', false, 'CZ6508000000192000145399')) + addSpecification(new Specification('DE', 22, 'F08F10', false, 'DE89370400440532013000')) + addSpecification(new Specification('DK', 18, 'F04F09F01', false, 'DK5000400440116243')) + addSpecification(new Specification('DO', 28, 'U04F20', false, 'DO28BAGR00000001212453611324')) + addSpecification(new Specification('EE', 20, 'F02F02F11F01', false, 'EE382200221020145685')) + addSpecification(new Specification('EG', 29, 'F04F04F17', false, 'EG800002000156789012345180002')) + addSpecification(new Specification('ES', 24, 'F04F04F01F01F10', false, 'ES9121000418450200051332')) + addSpecification(new Specification('FI', 18, 'F06F07F01', false, 'FI2112345600000785')) + addSpecification(new Specification('FO', 18, 'F04F09F01', false, 'FO6264600001631634')) + addSpecification(new Specification('FR', 27, 'F05F05A11F02', false, 'FR1420041010050500013M02606')) + addSpecification(new Specification('GB', 22, 'U04F06F08', false, 'GB29NWBK60161331926819')) + addSpecification(new Specification('GE', 22, 'U02F16', false, 'GE29NB0000000101904917')) + addSpecification(new Specification('GI', 23, 'U04A15', false, 'GI75NWBK000000007099453')) + addSpecification(new Specification('GL', 18, 'F04F09F01', false, 'GL8964710001000206')) + addSpecification(new Specification('GR', 27, 'F03F04A16', false, 'GR1601101250000000012300695')) + addSpecification(new Specification('GT', 28, 'A04A20', false, 'GT82TRAJ01020000001210029690')) + addSpecification(new Specification('HR', 21, 'F07F10', false, 'HR1210010051863000160')) + addSpecification(new Specification('HU', 28, 'F03F04F01F15F01', false, 'HU42117730161111101800000000')) + addSpecification(new Specification('IE', 22, 'U04F06F08', false, 'IE29AIBK93115212345678')) + addSpecification(new Specification('IL', 23, 'F03F03F13', false, 'IL620108000000099999999')) + addSpecification(new Specification('IS', 26, 'F04F02F06F10', false, 'IS140159260076545510730339')) + addSpecification(new Specification('IT', 27, 'U01F05F05A12', false, 'IT60X0542811101000000123456')) + addSpecification(new Specification('IQ', 23, 'U04F03A12', false, 'IQ98NBIQ850123456789012')) + addSpecification(new Specification('JO', 30, 'A04F22', false, 'JO15AAAA1234567890123456789012')) + addSpecification(new Specification('KW', 30, 'U04A22', false, 'KW81CBKU0000000000001234560101')) + addSpecification(new Specification('KZ', 20, 'F03A13', false, 'KZ86125KZT5004100100')) + addSpecification(new Specification('LB', 28, 'F04A20', false, 'LB62099900000001001901229114')) + addSpecification(new Specification('LC', 32, 'U04F24', false, 'LC07HEMM000100010012001200013015')) + addSpecification(new Specification('LI', 21, 'F05A12', false, 'LI21088100002324013AA')) + addSpecification(new Specification('LT', 20, 'F05F11', false, 'LT121000011101001000')) + addSpecification(new Specification('LU', 20, 'F03A13', false, 'LU280019400644750000')) + addSpecification(new Specification('LV', 21, 'U04A13', false, 'LV80BANK0000435195001')) + addSpecification(new Specification('MC', 27, 'F05F05A11F02', false, 'MC5811222000010123456789030')) + addSpecification(new Specification('MD', 24, 'U02A18', false, 'MD24AG000225100013104168')) + addSpecification(new Specification('ME', 22, 'F03F13F02', false, 'ME25505000012345678951')) + addSpecification(new Specification('MK', 19, 'F03A10F02', false, 'MK07250120000058984')) + addSpecification(new Specification('MR', 27, 'F05F05F11F02', false, 'MR1300020001010000123456753')) + addSpecification(new Specification('MT', 31, 'U04F05A18', false, 'MT84MALT011000012345MTLCAST001S')) + addSpecification(new Specification('MU', 30, 'U04F02F02F12F03U03', false, 'MU17BOMM0101101030300200000MUR')) + addSpecification(new Specification('NL', 18, 'U04F10', false, 'NL91ABNA0417164300')) + addSpecification(new Specification('NO', 15, 'F04F06F01', false, 'NO9386011117947')) + addSpecification(new Specification('PK', 24, 'U04A16', false, 'PK36SCBL0000001123456702')) + addSpecification(new Specification('PL', 28, 'F08F16', false, 'PL61109010140000071219812874')) + addSpecification(new Specification('PS', 29, 'U04A21', false, 'PS92PALS000000000400123456702')) + addSpecification(new Specification('PT', 25, 'F04F04F11F02', false, 'PT50000201231234567890154')) + addSpecification(new Specification('QA', 29, 'U04A21', false, 'QA30AAAA123456789012345678901')) + addSpecification(new Specification('RO', 24, 'U04A16', false, 'RO49AAAA1B31007593840000')) + addSpecification(new Specification('RS', 22, 'F03F13F02', false, 'RS35260005601001611379')) + addSpecification(new Specification('SA', 24, 'F02A18', false, 'SA0380000000608010167519')) + addSpecification(new Specification('SC', 31, 'U04F04F16U03', false, 'SC18SSCB11010000000000001497USD')) + addSpecification(new Specification('SE', 24, 'F03F16F01', false, 'SE4550000000058398257466')) + addSpecification(new Specification('SI', 19, 'F05F08F02', false, 'SI56263300012039086')) + addSpecification(new Specification('SK', 24, 'F04F06F10', false, 'SK3112000000198742637541')) + addSpecification(new Specification('SM', 27, 'U01F05F05A12', false, 'SM86U0322509800000000270100')) + addSpecification(new Specification('ST', 25, 'F08F11F02', false, 'ST68000100010051845310112')) + addSpecification(new Specification('SV', 28, 'U04F20', false, 'SV62CENR00000000000000700025')) + addSpecification(new Specification('TL', 23, 'F03F14F02', false, 'TL380080012345678910157')) + addSpecification(new Specification('TN', 24, 'F02F03F13F02', false, 'TN5910006035183598478831')) + addSpecification(new Specification('TR', 26, 'F05F01A16', false, 'TR330006100519786457841326')) + addSpecification(new Specification('UA', 29, 'F25', false, 'UA511234567890123456789012345')) + addSpecification(new Specification('VA', 22, 'F18', false, 'VA59001123000012345678')) + addSpecification(new Specification('VG', 24, 'U04F16', false, 'VG96VPVG0000012345678901')) + addSpecification(new Specification('XK', 20, 'F04F10F02', false, 'XK051212012345678906')) + + // The following countries are not included in the official ICAN registry but use the ICAN specification + + // Angola + addSpecification(new Specification('AO', 25, 'F21', false, 'AO69123456789012345678901')) + // Burkina + addSpecification(new Specification('BF', 27, 'F23', false, 'BF2312345678901234567890123')) + // Burundi + addSpecification(new Specification('BI', 16, 'F12', false, 'BI41123456789012')) + // Benin + addSpecification(new Specification('BJ', 28, 'F24', false, 'BJ39123456789012345678901234')) + // Ivory + addSpecification(new Specification('CI', 28, 'U02F22', false, 'CI70CI1234567890123456789012')) + // Cameron + addSpecification(new Specification('CM', 27, 'F23', false, 'CM9012345678901234567890123')) + // Cape Verde + addSpecification(new Specification('CV', 25, 'F21', false, 'CV30123456789012345678901')) + // Algeria + addSpecification(new Specification('DZ', 24, 'F20', false, 'DZ8612345678901234567890')) + // Iran + addSpecification(new Specification('IR', 26, 'F22', false, 'IR861234568790123456789012')) + // Madagascar + addSpecification(new Specification('MG', 27, 'F23', false, 'MG1812345678901234567890123')) + // Mali + addSpecification(new Specification('ML', 28, 'U01F23', false, 'ML15A12345678901234567890123')) + // Mozambique + addSpecification(new Specification('MZ', 25, 'F21', false, 'MZ25123456789012345678901')) + // Senegal + addSpecification(new Specification('SN', 28, 'U01F23', false, 'SN52A12345678901234567890123')) + + // The following are regional and administrative French Republic subdivision ICAN specification (same structure as FR, only country code vary) + addSpecification(new Specification('GF', 27, 'F05F05A11F02', false, 'GF121234512345123456789AB13')) + addSpecification(new Specification('GP', 27, 'F05F05A11F02', false, 'GP791234512345123456789AB13')) + addSpecification(new Specification('MQ', 27, 'F05F05A11F02', false, 'MQ221234512345123456789AB13')) + addSpecification(new Specification('RE', 27, 'F05F05A11F02', false, 'RE131234512345123456789AB13')) + addSpecification(new Specification('PF', 27, 'F05F05A11F02', false, 'PF281234512345123456789AB13')) + addSpecification(new Specification('TF', 27, 'F05F05A11F02', false, 'TF891234512345123456789AB13')) + addSpecification(new Specification('YT', 27, 'F05F05A11F02', false, 'YT021234512345123456789AB13')) + addSpecification(new Specification('NC', 27, 'F05F05A11F02', false, 'NC551234512345123456789AB13')) + addSpecification(new Specification('BL', 27, 'F05F05A11F02', false, 'BL391234512345123456789AB13')) + addSpecification(new Specification('MF', 27, 'F05F05A11F02', false, 'MF551234512345123456789AB13')) + addSpecification(new Specification('PM', 27, 'F05F05A11F02', false, 'PM071234512345123456789AB13')) + addSpecification(new Specification('WF', 27, 'F05F05A11F02', false, 'WF621234512345123456789AB13')) + + // Digital Assets + + // Core Blockchain - Mainnet + addSpecification(new Specification('CB', 44, 'H40', 'main', 'CB661234567890ABCDEF1234567890ABCDEF12345678')) + // Core Blockchain - Testnet [Devín] + addSpecification(new Specification('AB', 44, 'H40', 'test', 'AB841234567890ABCDEF1234567890ABCDEF12345678')) + // Core Blockchain - Enterprise [Koliba] + addSpecification(new Specification('CE', 44, 'H40', 'enter', 'CE571234567890ABCDEF1234567890ABCDEF12345678')) + + // Exports + exports.isValid = (ican, onlyCrypto = false) => { + if (typeof ican !== 'string') return false; + ican = electronicFormat(ican); + const spec = countries[ican.slice(0, 2)]; + return spec ? spec.isValid(ican, onlyCrypto) : false; + }; + + exports.toBCAN = (ican, separator) => { + ican = electronicFormat(ican); + const spec = countries[ican.slice(0, 2)]; + if (!spec) throw new Error('No country with code ' + ican.slice(0, 2)); + return spec.toBCAN(ican, separator); + }; + + exports.fromBCAN = (countryCode, bcan) => { + const spec = countries[countryCode]; + if (!spec) throw new Error('No country with code ' + countryCode); + return spec.fromBCAN(electronicFormat(bcan)); + }; + + exports.isValidBCAN = (countryCode, bcan, onlyCrypto = false) => { + const spec = countries[countryCode]; + return spec ? spec.isValidBCAN(electronicFormat(bcan), onlyCrypto) : false; + }; + + exports.printFormat = (ican, separator = ' ') => electronicFormat(ican).replace(EVERY_FOUR_CHARS, '$1' + separator); + + exports.shortFormat = (ican, separator = '…', frontCount = 4, backCount = 4) => { + const formatted = electronicFormat(ican); + return formatted.slice(0, frontCount) + separator + formatted.slice(-backCount); + }; + + exports.electronicFormat = electronicFormat; + exports.countries = countries; +})); diff --git a/package.json b/package.json index e9273ba..b25c9b7 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@blockchainhub/ican", - "version": "0.4.1", + "version": "0.4.2", "description": "A JavaScript library to validate, format, and convert International Crypto Account Numbers (ICAN) and Basic Crypto Account Numbers (BCAN).", "keywords": [ "ican", @@ -14,21 +14,21 @@ "convert" ], "devDependencies": { - "standard": "^17.1.0", - "tape": "^5.7.5" + "standard": "^17.1.2", + "tape": "^5.9.0" }, "scripts": { - "build": "cp ican.js src/index.js && standard --fix 'src/index.js'", + "build": "mkdir -p dist && cp ican.js dist/index.js && cp ican.d.ts dist/index.d.ts && standard --fix 'dist/index.js'", "gitdiff": "npm run build && git diff --exit-code", - "standard": "standard 'src/index.js'", - "test": "npm run unit && npm run standard 'src/index.js'", + "standard": "standard 'dist/index.js'", + "test": "npm run build && npm run unit && npm run standard 'dist/index.js'", "unit": "tape test/*.js" }, "files": [ - "src" + "dist" ], - "main": "src/index.js", - "types": "src/index.d.ts", + "main": "dist/index.js", + "types": "dist/index.d.ts", "repository": "https://github.com/bchainhub/ican.js", "author": "Blockchain Hub", "license": "CORE", diff --git a/src/index.d.ts b/src/index.d.ts deleted file mode 100644 index 8f0c398..0000000 --- a/src/index.d.ts +++ /dev/null @@ -1,99 +0,0 @@ -interface ICANStatic { - /** - * An object containing all the known ICAN specifications - */ - countries: Record; - - /** - * @summary Returns the ICAN in a electronic format. - * @param ican The ICAN to convert. - * @param The ICAN in electronic format. - */ - electronicFormat(ican: string): string; - - /** - * @summary Convert the passed BCAN to an ICAN for this country specification. - * @param countryCode The country of the BCAN. - * @param bcan The BCAN to convert to ICAN. - * @returns The ICAN. - */ - fromBCAN(countryCode: string, bcan: string): string; - - /** - * @summary Check if the passed ican is valid according to this specification. - * @param ican The ican to validate. - * @param onlyCrypto Check only crypto definitions. - * @returns True if valid, false otherwise. - */ - isValid(ican: string, onlyCrypto?: boolean): boolean; - - /** - * @summary Check of the passed BCAN is valid. - * @param countryCode The country of the BCAN. - * @param bcan The BCAN to validate. - * @returns True if valid, false otherwise. - */ - isValidBCAN(countryCode: string, bcan: string, onlyCrypto?: boolean): boolean; - - /** - * @summary Returns the ICAN in a print format. - * @param ican The ICAN to convert. - * @param [separator] The separator to use between ICAN blocks, defaults to ' '. - */ - printFormat(ican: string, separator?: string): string; - - /** - * @summary Convert the passed ICAN to a country-specific BCAN. - * @param ican The ICAN to convert. - * @param [separator] The separator to use between BCAN blocks, defaults to ' '. - * @returns The BCAN - */ - toBCAN(ican: string, separator?: string): string; - - /** - * @summary Returns the ICAN in a short format. - * @param ican The ICAN to convert. - * @param [separator] The separator to use between ICAN openings/endings, defaults to '…'. - * @param [frontCount] The number of characters to show at the beginning, defaults to 4. - * @param [backCount] The number of characters to show at the end, defaults to 4. - */ - shortFormat(ican: string, separator?: string, frontCount?: number, backCount?: number): string; -} - -declare var ICAN: ICANStatic; - -declare namespace ICAN { - interface Specification { - /** the code of the country */ - readonly countryCode: string; - /** the length of the ICAN */ - readonly length: number; - /*& the structure of the underlying BCAN (for validation and formatting) */ - readonly structure: string; - /** is digital asset */ - readonly crypto: boolean; - /** an example valid ICAN */ - readonly example: string; - /** Check if the passed ican is valid according to this specification. */ - isValid(ican: string, onlyCrypto?: boolean): boolean; - /** - * Convert the passed ICAN to a country-specific BCAN. - */ - toBCAN(ican: string, separator: string): string; - /** - * Convert the passed BCAN to an ICAN for this country specification. - * Please note that "generation of the ICAN shall be the exclusive responsibility of the bank/branch servicing the account". - * This method implements the preferred algorithm described in http://en.wikipedia.org/wiki/International_Bank_Account_Number#Generating_ICAN_check_digits - */ - fromBCAN(bcan: string): string; - /** - * Check of the passed BCAN is valid. - * This function only checks the format of the BCAN (length and matching the letetr/number specs) but does not - * verify the check digit. - */ - isValidBCAN(bcan: string, onlyCrypto?: boolean): boolean; - } -} - -export = ICAN; -export as namespace ICAN; diff --git a/src/index.js b/src/index.js deleted file mode 100644 index d4f37ed..0000000 --- a/src/index.js +++ /dev/null @@ -1,471 +0,0 @@ -(function (root, factory) { - if (typeof define === 'function' && define.amd) { // eslint-disable-line no-undef - define(['exports'], factory) // eslint-disable-line no-undef - } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') { - factory(exports) - } else { - factory(root.ICAN = {}) - } -}(this, function (exports) { - if (!Array.prototype.map) { - Array.prototype.map = function (fun /*, thisArg */) { // eslint-disable-line no-extend-native - 'use strict' - - if (this === undefined || this === null) { throw new TypeError() } - - const t = Object(this) - const len = t.length >>> 0 - if (typeof fun !== 'function') { throw new TypeError() } - - const res = new Array(len) - const thisArg = arguments.length >= 2 ? arguments[1] : undefined - for (let i = 0; i < len; i++) { - if (i in t) { res[i] = fun.call(thisArg, t[i], i, t) } - } - - return res - } - } - - const A = 'A'.charCodeAt(0) - const Z = 'Z'.charCodeAt(0) - - /** - * Prepare an ICAN for mod 97 computation by moving the first 4 chars to the end and transforming the letters to - * numbers (A = 10, B = 11, ..., Z = 35), as specified in ISO13616. - * - * @param {string} ican the ICAN - * @returns {string} the prepared ICAN - */ - function iso13616Prepare (ican) { - ican = ican.toUpperCase() - ican = ican.slice(4) + ican.slice(0, 4) - - return ican.split('').map(function (n) { - const code = n.charCodeAt(0) - if (code >= A && code <= Z) { - // A = 10, B = 11, ... Z = 35 - return code - A + 10 - } else { - return n - } - }).join('') - } - - /** - * Calculates the MOD 97 10 of the passed ICAN as specified in ISO7064. - * - * @param ican - * @returns {number} - */ - function iso7064Mod97 (ican) { - let remainder = ican - let block - - while (remainder.length > 2) { - block = remainder.slice(0, 9) - remainder = parseInt(block, 10) % 97 + remainder.slice(block.length) - } - - return parseInt(remainder, 10) % 97 - } - - /** - * Parse the BCAN structure used to configure each ICAN Specification and returns a matching regular expression. - * A structure is composed of blocks of 3 characters (one letter and 2 digits). Each block represents - * a logical group in the typical representation of the BCAN. For each group, the letter indicates which characters - * are allowed in this group and the following 2-digits number tells the length of the group. - * - * @param {string} structure the structure to parse - * @returns {RegExp} - */ - function parseStructure (structure) { - // split in blocks of 3 chars - const regex = structure.match(/(.{3})/g).map(function (block) { - // parse each structure block (1-char + 2-digits) - let format - const pattern = block.slice(0, 1) - const repeats = parseInt(block.slice(1), 10) - - switch (pattern) { - case 'A': - format = '0-9A-Za-z' - break - case 'B': - format = '0-9A-Z' - break - case 'C': - format = 'A-Za-z' - break - case 'H': - format = '0-9A-Fa-f' - break - case 'F': - format = '0-9' - break - case 'L': - format = 'a-z' - break - case 'U': - format = 'A-Z' - break - case 'W': - format = '0-9a-z' - break - } - - return '([' + format + ']{' + repeats + '})' - }) - - return new RegExp('^' + regex.join('') + '$') - } - - /** - * - * @param ican - * @returns {string} - */ - function electronicFormat (ican) { - return ican.replace(NON_ALPHANUM, '').toUpperCase() - } - - /** - * Create a new Specification for a valid ICAN number. - * - * @param countryCode the code of the country - * @param length the length of the ICAN - * @param structure the structure of the underlying BCAN (for validation and formatting) - * @param example an example valid ICAN - * @constructor - */ - function Specification (countryCode, length, structure, crypto, example) { - this.countryCode = countryCode - this.length = length - this.structure = structure - this.crypto = crypto - this.example = example - } - - /** - * Lazy-loaded regex (parse the structure and construct the regular expression the first time we need it for validation) - */ - Specification.prototype._regex = function () { - return this._cachedRegex || (this._cachedRegex = parseStructure(this.structure)) - } - - /** - * Check if the passed ican is valid according to this specification. - * - * @param {String} ican the ican to validate - * @param {Boolean} onlyCrypto check only digital assets - * @returns {boolean} true if valid, false otherwise - */ - Specification.prototype.isValid = function (ican, onlyCrypto = false) { - return this.length === ican.length && - this.countryCode === ican.slice(0, 2) && - (!onlyCrypto || this.crypto) && - this._regex().test(ican.slice(4)) && - iso7064Mod97(iso13616Prepare(ican)) === 1 - } - - /** - * Convert the passed ICAN to a country-specific BCAN. - * - * @param ican the ICAN to convert - * @param separator the separator to use between BCAN blocks - * @returns {string} the BCAN - */ - Specification.prototype.toBCAN = function (ican, separator) { - return this._regex().exec(ican.slice(4)).slice(1).join(separator) - } - - /** - * Convert the passed BCAN to an ICAN for this country specification. - * - * @param bcan the BCAN to convert to ICAN - * @returns {string} the ICAN - */ - Specification.prototype.fromBCAN = function (bcan) { - if (!this.isValidBCAN(bcan)) { - throw new Error('Invalid BCAN') - } - - const remainder = iso7064Mod97(iso13616Prepare(this.countryCode + '00' + bcan)) - const checkDigit = ('0' + (98 - remainder)).slice(-2) - - return this.countryCode + checkDigit + bcan - } - - /** - * Check of the passed BCAN is valid. - * This function only checks the format of the BCAN (length and matching the leter/number specs) but does not - * verify the check digit. - * - * @param bcan the BCAN to validate - * @returns {boolean} true if the passed bcan is a valid BCAN according to this specification, false otherwise - */ - Specification.prototype.isValidBCAN = function (bcan, onlyCrypto = false) { - return this.length - 4 === bcan.length && - (!onlyCrypto || this.crypto) && - this._regex().test(bcan) - } - - const countries = {} - - function addSpecification (ICAN) { - countries[ICAN.countryCode] = ICAN - } - - addSpecification(new Specification('AD', 24, 'F04F04A12', false, 'AD1200012030200359100100')) - addSpecification(new Specification('AE', 23, 'F03F16', false, 'AE070331234567890123456')) - addSpecification(new Specification('AL', 28, 'F08A16', false, 'AL47212110090000000235698741')) - addSpecification(new Specification('AT', 20, 'F05F11', false, 'AT611904300234573201')) - addSpecification(new Specification('AZ', 28, 'U04A20', false, 'AZ21NABZ00000000137010001944')) - addSpecification(new Specification('BA', 20, 'F03F03F08F02', false, 'BA391290079401028494')) - addSpecification(new Specification('BE', 16, 'F03F07F02', false, 'BE68539007547034')) - addSpecification(new Specification('BG', 22, 'U04F04F02A08', false, 'BG80BNBG96611020345678')) - addSpecification(new Specification('BH', 22, 'U04A14', false, 'BH67BMAG00001299123456')) - addSpecification(new Specification('BR', 29, 'F08F05F10U01A01', false, 'BR9700360305000010009795493P1')) - addSpecification(new Specification('BY', 28, 'A04F04A16', false, 'BY13NBRB3600900000002Z00AB00')) - addSpecification(new Specification('CH', 21, 'F05A12', false, 'CH9300762011623852957')) - addSpecification(new Specification('CR', 22, 'F04F14', false, 'CR72012300000171549015')) - addSpecification(new Specification('CY', 28, 'F03F05A16', false, 'CY17002001280000001200527600')) - addSpecification(new Specification('CZ', 24, 'F04F06F10', false, 'CZ6508000000192000145399')) - addSpecification(new Specification('DE', 22, 'F08F10', false, 'DE89370400440532013000')) - addSpecification(new Specification('DK', 18, 'F04F09F01', false, 'DK5000400440116243')) - addSpecification(new Specification('DO', 28, 'U04F20', false, 'DO28BAGR00000001212453611324')) - addSpecification(new Specification('EE', 20, 'F02F02F11F01', false, 'EE382200221020145685')) - addSpecification(new Specification('EG', 29, 'F04F04F17', false, 'EG800002000156789012345180002')) - addSpecification(new Specification('ES', 24, 'F04F04F01F01F10', false, 'ES9121000418450200051332')) - addSpecification(new Specification('FI', 18, 'F06F07F01', false, 'FI2112345600000785')) - addSpecification(new Specification('FO', 18, 'F04F09F01', false, 'FO6264600001631634')) - addSpecification(new Specification('FR', 27, 'F05F05A11F02', false, 'FR1420041010050500013M02606')) - addSpecification(new Specification('GB', 22, 'U04F06F08', false, 'GB29NWBK60161331926819')) - addSpecification(new Specification('GE', 22, 'U02F16', false, 'GE29NB0000000101904917')) - addSpecification(new Specification('GI', 23, 'U04A15', false, 'GI75NWBK000000007099453')) - addSpecification(new Specification('GL', 18, 'F04F09F01', false, 'GL8964710001000206')) - addSpecification(new Specification('GR', 27, 'F03F04A16', false, 'GR1601101250000000012300695')) - addSpecification(new Specification('GT', 28, 'A04A20', false, 'GT82TRAJ01020000001210029690')) - addSpecification(new Specification('HR', 21, 'F07F10', false, 'HR1210010051863000160')) - addSpecification(new Specification('HU', 28, 'F03F04F01F15F01', false, 'HU42117730161111101800000000')) - addSpecification(new Specification('IE', 22, 'U04F06F08', false, 'IE29AIBK93115212345678')) - addSpecification(new Specification('IL', 23, 'F03F03F13', false, 'IL620108000000099999999')) - addSpecification(new Specification('IS', 26, 'F04F02F06F10', false, 'IS140159260076545510730339')) - addSpecification(new Specification('IT', 27, 'U01F05F05A12', false, 'IT60X0542811101000000123456')) - addSpecification(new Specification('IQ', 23, 'U04F03A12', false, 'IQ98NBIQ850123456789012')) - addSpecification(new Specification('JO', 30, 'A04F22', false, 'JO15AAAA1234567890123456789012')) - addSpecification(new Specification('KW', 30, 'U04A22', false, 'KW81CBKU0000000000001234560101')) - addSpecification(new Specification('KZ', 20, 'F03A13', false, 'KZ86125KZT5004100100')) - addSpecification(new Specification('LB', 28, 'F04A20', false, 'LB62099900000001001901229114')) - addSpecification(new Specification('LC', 32, 'U04F24', false, 'LC07HEMM000100010012001200013015')) - addSpecification(new Specification('LI', 21, 'F05A12', false, 'LI21088100002324013AA')) - addSpecification(new Specification('LT', 20, 'F05F11', false, 'LT121000011101001000')) - addSpecification(new Specification('LU', 20, 'F03A13', false, 'LU280019400644750000')) - addSpecification(new Specification('LV', 21, 'U04A13', false, 'LV80BANK0000435195001')) - addSpecification(new Specification('MC', 27, 'F05F05A11F02', false, 'MC5811222000010123456789030')) - addSpecification(new Specification('MD', 24, 'U02A18', false, 'MD24AG000225100013104168')) - addSpecification(new Specification('ME', 22, 'F03F13F02', false, 'ME25505000012345678951')) - addSpecification(new Specification('MK', 19, 'F03A10F02', false, 'MK07250120000058984')) - addSpecification(new Specification('MR', 27, 'F05F05F11F02', false, 'MR1300020001010000123456753')) - addSpecification(new Specification('MT', 31, 'U04F05A18', false, 'MT84MALT011000012345MTLCAST001S')) - addSpecification(new Specification('MU', 30, 'U04F02F02F12F03U03', false, 'MU17BOMM0101101030300200000MUR')) - addSpecification(new Specification('NL', 18, 'U04F10', false, 'NL91ABNA0417164300')) - addSpecification(new Specification('NO', 15, 'F04F06F01', false, 'NO9386011117947')) - addSpecification(new Specification('PK', 24, 'U04A16', false, 'PK36SCBL0000001123456702')) - addSpecification(new Specification('PL', 28, 'F08F16', false, 'PL61109010140000071219812874')) - addSpecification(new Specification('PS', 29, 'U04A21', false, 'PS92PALS000000000400123456702')) - addSpecification(new Specification('PT', 25, 'F04F04F11F02', false, 'PT50000201231234567890154')) - addSpecification(new Specification('QA', 29, 'U04A21', false, 'QA30AAAA123456789012345678901')) - addSpecification(new Specification('RO', 24, 'U04A16', false, 'RO49AAAA1B31007593840000')) - addSpecification(new Specification('RS', 22, 'F03F13F02', false, 'RS35260005601001611379')) - addSpecification(new Specification('SA', 24, 'F02A18', false, 'SA0380000000608010167519')) - addSpecification(new Specification('SC', 31, 'U04F04F16U03', false, 'SC18SSCB11010000000000001497USD')) - addSpecification(new Specification('SE', 24, 'F03F16F01', false, 'SE4550000000058398257466')) - addSpecification(new Specification('SI', 19, 'F05F08F02', false, 'SI56263300012039086')) - addSpecification(new Specification('SK', 24, 'F04F06F10', false, 'SK3112000000198742637541')) - addSpecification(new Specification('SM', 27, 'U01F05F05A12', false, 'SM86U0322509800000000270100')) - addSpecification(new Specification('ST', 25, 'F08F11F02', false, 'ST68000100010051845310112')) - addSpecification(new Specification('SV', 28, 'U04F20', false, 'SV62CENR00000000000000700025')) - addSpecification(new Specification('TL', 23, 'F03F14F02', false, 'TL380080012345678910157')) - addSpecification(new Specification('TN', 24, 'F02F03F13F02', false, 'TN5910006035183598478831')) - addSpecification(new Specification('TR', 26, 'F05F01A16', false, 'TR330006100519786457841326')) - addSpecification(new Specification('UA', 29, 'F25', false, 'UA511234567890123456789012345')) - addSpecification(new Specification('VA', 22, 'F18', false, 'VA59001123000012345678')) - addSpecification(new Specification('VG', 24, 'U04F16', false, 'VG96VPVG0000012345678901')) - addSpecification(new Specification('XK', 20, 'F04F10F02', false, 'XK051212012345678906')) - - // The following countries are not included in the official ICAN registry but use the ICAN specification - - // Angola - addSpecification(new Specification('AO', 25, 'F21', false, 'AO69123456789012345678901')) - // Burkina - addSpecification(new Specification('BF', 27, 'F23', false, 'BF2312345678901234567890123')) - // Burundi - addSpecification(new Specification('BI', 16, 'F12', false, 'BI41123456789012')) - // Benin - addSpecification(new Specification('BJ', 28, 'F24', false, 'BJ39123456789012345678901234')) - // Ivory - addSpecification(new Specification('CI', 28, 'U02F22', false, 'CI70CI1234567890123456789012')) - // Cameron - addSpecification(new Specification('CM', 27, 'F23', false, 'CM9012345678901234567890123')) - // Cape Verde - addSpecification(new Specification('CV', 25, 'F21', false, 'CV30123456789012345678901')) - // Algeria - addSpecification(new Specification('DZ', 24, 'F20', false, 'DZ8612345678901234567890')) - // Iran - addSpecification(new Specification('IR', 26, 'F22', false, 'IR861234568790123456789012')) - // Madagascar - addSpecification(new Specification('MG', 27, 'F23', false, 'MG1812345678901234567890123')) - // Mali - addSpecification(new Specification('ML', 28, 'U01F23', false, 'ML15A12345678901234567890123')) - // Mozambique - addSpecification(new Specification('MZ', 25, 'F21', false, 'MZ25123456789012345678901')) - // Senegal - addSpecification(new Specification('SN', 28, 'U01F23', false, 'SN52A12345678901234567890123')) - - // The following are regional and administrative French Republic subdivision ICAN specification (same structure as FR, only country code vary) - addSpecification(new Specification('GF', 27, 'F05F05A11F02', false, 'GF121234512345123456789AB13')) - addSpecification(new Specification('GP', 27, 'F05F05A11F02', false, 'GP791234512345123456789AB13')) - addSpecification(new Specification('MQ', 27, 'F05F05A11F02', false, 'MQ221234512345123456789AB13')) - addSpecification(new Specification('RE', 27, 'F05F05A11F02', false, 'RE131234512345123456789AB13')) - addSpecification(new Specification('PF', 27, 'F05F05A11F02', false, 'PF281234512345123456789AB13')) - addSpecification(new Specification('TF', 27, 'F05F05A11F02', false, 'TF891234512345123456789AB13')) - addSpecification(new Specification('YT', 27, 'F05F05A11F02', false, 'YT021234512345123456789AB13')) - addSpecification(new Specification('NC', 27, 'F05F05A11F02', false, 'NC551234512345123456789AB13')) - addSpecification(new Specification('BL', 27, 'F05F05A11F02', false, 'BL391234512345123456789AB13')) - addSpecification(new Specification('MF', 27, 'F05F05A11F02', false, 'MF551234512345123456789AB13')) - addSpecification(new Specification('PM', 27, 'F05F05A11F02', false, 'PM071234512345123456789AB13')) - addSpecification(new Specification('WF', 27, 'F05F05A11F02', false, 'WF621234512345123456789AB13')) - - // Digital Assets - - // Core Blockchain Mainnet - addSpecification(new Specification('CB', 44, 'H40', true, 'CB661234567890ABCDEF1234567890ABCDEF12345678')) - // Core Blockchain Testnet - Devín - addSpecification(new Specification('AB', 44, 'H40', true, 'AB841234567890ABCDEF1234567890ABCDEF12345678')) - // Core Enterprise Blockchain Enterprise - addSpecification(new Specification('CE', 44, 'H40', true, 'CE571234567890ABCDEF1234567890ABCDEF12345678')) - - const NON_ALPHANUM = /[^a-zA-Z0-9]/g - const EVERY_FOUR_CHARS = /(.{4})(?!$)/g - - /** - * Utility function to check if a variable is a String. - * - * @param v - * @returns {boolean} true if the passed variable is a String, false otherwise. - */ - function isString (v) { - return (typeof v === 'string' || v instanceof String) - } - - /** - * Check if an ICAN is valid. - * - * @param {String} ican the ICAN to validate. - * @returns {boolean} true if the passed ICAN is valid, false otherwise - */ - exports.isValid = function (ican, onlyCrypto = false) { - if (!isString(ican)) { - return false - } - ican = electronicFormat(ican) - const countryStructure = countries[ican.slice(0, 2)] - return !!countryStructure && - (!onlyCrypto || countryStructure.crypto) && - countryStructure.isValid(ican) - } - - /** - * Convert an ICAN to a BCAN. - * - * @param ican - * @param {String} [separator] the separator to use between the blocks of the BCAN, defaults to ' ' - * @returns {string} the BCAN - */ - exports.toBCAN = function (ican, separator) { - if (typeof separator === 'undefined') { - separator = ' ' - } - ican = electronicFormat(ican) - const countryStructure = countries[ican.slice(0, 2)] - if (!countryStructure) { - throw new Error('No country with code ' + ican.slice(0, 2)) - } - return countryStructure.toBCAN(ican, separator) - } - - /** - * Convert the passed BCAN to an ICAN for this country specification. - * - * @param countryCode the country of the BCAN - * @param bcan the BCAN to convert to ICAN - * @returns {string} the ICAN - */ - exports.fromBCAN = function (countryCode, bcan) { - const countryStructure = countries[countryCode] - if (!countryStructure) { - throw new Error('No country with code ' + countryCode) - } - return countryStructure.fromBCAN(electronicFormat(bcan)) - } - - /** - * Check the validity of the passed BCAN. - * - * @param countryCode the country of the BCAN - * @param bcan the BCAN to check the validity of - * @param {Boolean} onlyCrypto check only digital assets - * @returns {boolean} true if the passed BCAN is a valid BCAN for the country, false otherwise - */ - exports.isValidBCAN = function (countryCode, bcan, onlyCrypto = false) { - if (!isString(bcan)) { - return false - } - const countryStructure = countries[countryCode] - return countryStructure && - (!onlyCrypto || countryStructure.crypto) && - countryStructure.isValidBCAN(electronicFormat(bcan)) - } - - /** - * - * @param ican - * @param separator - * @returns {string} - */ - exports.printFormat = function (ican, separator) { - if (typeof separator === 'undefined') { - separator = ' ' - } - return electronicFormat(ican).replace(EVERY_FOUR_CHARS, '$1' + separator) - } - - /** - * - * @param ican - * @param separator - * @param frontCount - * @param backCount - * @returns {string} - */ - exports.shortFormat = function (ican, separator, frontCount, backCount) { - if (typeof separator === 'undefined') { - separator = '…' - } - if (typeof frontCount === 'undefined') { - frontCount = 4 - } - if (typeof backCount === 'undefined') { - backCount = 4 - } - const electronic = electronicFormat(ican) - return electronic.slice(0, frontCount) + separator + electronic.slice(-backCount) - } - - exports.electronicFormat = electronicFormat - /** - * An object containing all the known ICAN specifications. - */ - exports.countries = countries -})) diff --git a/test/index.js b/test/index.js index 474cb22..b1c7c54 100644 --- a/test/index.js +++ b/test/index.js @@ -26,6 +26,22 @@ samples.validCrypto.forEach(function (f) { }) }) +samples.validMainnetCrypto.forEach(function (f) { + tape.test('OK - Check Crypto Mainnet ICAN. // ICAN: <' + f.ican.substring(0, 4) + f.ican.slice(-4) + '>', function (t) { + let valid = ICAN.isValid(f.ican, 'main') + t.plan(1) + t.ok(valid, 'Valid ICAN: ' + f.ican) + }) +}) + +samples.validTestnetCrypto.forEach(function (f) { + tape.test('OK - Check Crypto Testnet ICAN. // ICAN: <' + f.ican.substring(0, 4) + f.ican.slice(-4) + '>', function (t) { + let valid = ICAN.isValid(f.ican, 'testnet') + t.plan(1) + t.ok(valid, 'Valid ICAN: ' + f.ican) + }) +}) + samples.invalidCrypto.forEach(function (f) { tape.test('NOK - Check Crypto ICAN. // ICAN: <' + f.ican.substring(0, 4) + f.ican.slice(-4) + '>', function (t) { let valid = ICAN.isValid(f.ican, true) diff --git a/test/samples.json b/test/samples.json index 01ccd2e..937f18e 100644 --- a/test/samples.json +++ b/test/samples.json @@ -53,6 +53,22 @@ "ican": "AB306E3B05B80977A67BCBD330AB5F8DAF756FB4191C" } ], + "validMainnetCrypto": [ + { + "ican": "cb71dda309844cb29462a4e7fd5d3ece3088323d178c" + }, + { + "ican": "cb06acf799cef468f3f745fb5c4e28fb8a85d3d979d5" + } + ], + "validTestnetCrypto": [ + { + "ican": "ab59796210a3fe3c24d433197af05ef54c33279ba80d" + }, + { + "ican": "ab9620e80e1aaf554e2d2a18bd25fbac59fcd5bf046d" + } + ], "invalidCrypto": [ { "ican": "LC01HEMM000100010012001200023015" From a8f6998ca9070e21d8cec399ed0029a17d40cc9a Mon Sep 17 00:00:00 2001 From: Rastislav Date: Sat, 7 Dec 2024 16:36:40 +0100 Subject: [PATCH 2/4] Setup standard --- .eslintignore | 4 ++++ package.json | 8 ++++---- 2 files changed, 8 insertions(+), 4 deletions(-) create mode 100644 .eslintignore diff --git a/.eslintignore b/.eslintignore new file mode 100644 index 0000000..62f0136 --- /dev/null +++ b/.eslintignore @@ -0,0 +1,4 @@ +node_modules/ +test/**/*.js +# Ensure dist is not ignored by standard +!dist/ diff --git a/package.json b/package.json index b25c9b7..81fef3e 100644 --- a/package.json +++ b/package.json @@ -18,11 +18,11 @@ "tape": "^5.9.0" }, "scripts": { - "build": "mkdir -p dist && cp ican.js dist/index.js && cp ican.d.ts dist/index.d.ts && standard --fix 'dist/index.js'", + "build": "mkdir -p dist && cp ican.js dist/index.js && cp ican.d.ts dist/index.d.ts", "gitdiff": "npm run build && git diff --exit-code", - "standard": "standard 'dist/index.js'", - "test": "npm run build && npm run unit && npm run standard 'dist/index.js'", - "unit": "tape test/*.js" + "test": "npm run unit && npm run standard", + "unit": "npm run build && tape test/*.js", + "standard": "npx standard 'dist/**/*.js' --no-ignore" }, "files": [ "dist" From 56f4abcbd703b316fbefc48bcffa385995ce7a42 Mon Sep 17 00:00:00 2001 From: Rastislav Date: Sat, 7 Dec 2024 16:50:12 +0100 Subject: [PATCH 3/4] Fixed with standard --- ican.js | 531 +++++++++++++++++++++++++-------------------------- package.json | 2 +- 2 files changed, 260 insertions(+), 273 deletions(-) diff --git a/ican.js b/ican.js index 876ca2f..d8e833a 100644 --- a/ican.js +++ b/ican.js @@ -1,303 +1,290 @@ +/* global define */ (function (root, factory) { - if (typeof define === 'function' && define.amd) { - define(['exports'], factory); - } else if (typeof exports === 'object' && typeof exports.nodeName !== 'string') { - factory(exports); - } else { - factory(root.ICAN = {}); - } + if (typeof define === 'function' && define.amd) { + define(['exports'], factory) // AMD (e.g., RequireJS) + } else if (typeof module === 'object' && module.exports) { + module.exports = factory({}) // Node.js or CommonJS + } else { + root.ICAN = factory({}) // Browser global + } }(this, function (exports) { - 'use strict'; + 'use strict' - const A = 'A'.charCodeAt(0); - const Z = 'Z'.charCodeAt(0); - const NON_ALPHANUM = /[^a-zA-Z0-9]/g; - const EVERY_FOUR_CHARS = /(.{4})(?!$)/g; + const A = 'A'.charCodeAt(0) + const Z = 'Z'.charCodeAt(0) + const NON_ALPHANUM = /[^a-zA-Z0-9]/g + const EVERY_FOUR_CHARS = /(.{4})(?!$)/g - // Polyfill for Array.prototype.map if not available - if (!Array.prototype.map) { - Array.prototype.map = function (callback, thisArg) { - if (this == null) throw new TypeError(); - if (typeof callback !== 'function') throw new TypeError(); + // Utility functions + const iso13616Prepare = (ican) => { + const rearranged = ican.slice(4) + ican.slice(0, 4) + return rearranged.toUpperCase().split('').map(char => { + const code = char.charCodeAt(0) + return (code >= A && code <= Z) ? code - A + 10 : char + }).join('') + } - const t = Object(this); - const len = t.length >>> 0; - const res = new Array(len); - for (let i = 0; i < len; i++) { - if (i in t) res[i] = callback.call(thisArg, t[i], i, t); - } - return res; - }; - } + const iso7064Mod97 = (ican) => { + let remainder = ican + while (remainder.length > 2) { + const block = remainder.slice(0, 9) + remainder = (parseInt(block, 10) % 97).toString() + remainder.slice(block.length) + } + return parseInt(remainder, 10) % 97 + } - // Utility functions - const iso13616Prepare = (ican) => { - const rearranged = ican.slice(4) + ican.slice(0, 4); - return rearranged.toUpperCase().split('').map(char => { - const code = char.charCodeAt(0); - return (code >= A && code <= Z) ? code - A + 10 : char; - }).join(''); - }; + const parseStructure = (structure) => { + return new RegExp('^' + structure.match(/.{3}/g).map(block => { + const [pattern, repeats] = [block[0], parseInt(block.slice(1), 10)] + const formatMap = { + A: '0-9A-Za-z', + B: '0-9A-Z', + C: 'A-Za-z', + H: '0-9A-Fa-f', + F: '0-9', + L: 'a-z', + U: 'A-Z', + W: '0-9a-z' + } + return `([${formatMap[pattern]}]{${repeats}})` + }).join('') + '$') + } - const iso7064Mod97 = (ican) => { - let remainder = ican; - while (remainder.length > 2) { - const block = remainder.slice(0, 9); - remainder = (parseInt(block, 10) % 97).toString() + remainder.slice(block.length); - } - return parseInt(remainder, 10) % 97; - }; + const electronicFormat = (ican) => ican.replace(NON_ALPHANUM, '').toUpperCase() - const parseStructure = (structure) => { - return new RegExp('^' + structure.match(/.{3}/g).map(block => { - const [pattern, repeats] = [block[0], parseInt(block.slice(1), 10)]; - const formatMap = { - 'A': '0-9A-Za-z', - 'B': '0-9A-Z', - 'C': 'A-Za-z', - 'H': '0-9A-Fa-f', - 'F': '0-9', - 'L': 'a-z', - 'U': 'A-Z', - 'W': '0-9a-z', - }; - return `([${formatMap[pattern]}]{${repeats}})`; - }).join('') + '$'); - }; + // Specification class + class Specification { + constructor (countryCode, length, structure, crypto, example) { + this.countryCode = countryCode + this.length = length + this.structure = structure + this.crypto = this.normalizeCrypto(crypto) + this.example = example + } - const electronicFormat = (ican) => ican.replace(NON_ALPHANUM, '').toUpperCase(); + normalizeCrypto (crypto) { + if (crypto === 'main' || crypto === 'mainnet') return 'main' + if (crypto === 'test' || crypto === 'testnet') return 'test' + if (crypto === 'enter' || crypto === 'enterprise') return 'enter' + return crypto + } - // Specification class - class Specification { - constructor(countryCode, length, structure, crypto, example) { - this.countryCode = countryCode; - this.length = length; - this.structure = structure; - this.crypto = this.normalizeCrypto(crypto); - this.example = example; - } + get regex () { + if (!this._cachedRegex) { + this._cachedRegex = parseStructure(this.structure) + } + return this._cachedRegex + } - normalizeCrypto(crypto) { - if (crypto === 'main' || crypto === 'mainnet') return 'main'; - if (crypto === 'test' || crypto === 'testnet') return 'test'; - if (crypto === 'enter' || crypto === 'enterprise') return 'enter'; - return crypto; - } + isValid (ican, onlyCrypto = false) { + ican = electronicFormat(ican) + return this.length === ican.length && + this.countryCode === ican.slice(0, 2) && + (!onlyCrypto || this.isMatchingCrypto(onlyCrypto)) && + this.regex.test(ican.slice(4)) && + iso7064Mod97(iso13616Prepare(ican)) === 1 + } - get regex() { - if (!this._cachedRegex) { - this._cachedRegex = parseStructure(this.structure); - } - return this._cachedRegex; - } + isMatchingCrypto (onlyCrypto) { + if (typeof onlyCrypto === 'string') { + if (onlyCrypto === 'mainnet') onlyCrypto = 'main' + else if (onlyCrypto === 'testnet') onlyCrypto = 'test' + else if (onlyCrypto === 'enterprise') onlyCrypto = 'enter' + } + if (onlyCrypto === true) return this.crypto !== false + return this.crypto === onlyCrypto + } - isValid(ican, onlyCrypto = false) { - ican = electronicFormat(ican); - return this.length === ican.length && - this.countryCode === ican.slice(0, 2) && - (!onlyCrypto || this.isMatchingCrypto(onlyCrypto)) && - this.regex.test(ican.slice(4)) && - iso7064Mod97(iso13616Prepare(ican)) === 1; - } + toBCAN (ican, separator = ' ') { + return this.regex.exec(ican.slice(4)).slice(1).join(separator) + } - isMatchingCrypto(onlyCrypto) { - if (typeof onlyCrypto === 'string') { - if (onlyCrypto === 'mainnet') onlyCrypto = 'main'; - else if (onlyCrypto === 'testnet') onlyCrypto = 'test'; - else if (onlyCrypto === 'enterprise') onlyCrypto = 'enter'; - } - if (onlyCrypto === true) return this.crypto !== false; - return this.crypto === onlyCrypto; - } + fromBCAN (bcan) { + if (!this.isValidBCAN(bcan)) throw new Error('Invalid BCAN') + const remainder = iso7064Mod97(iso13616Prepare(this.countryCode + '00' + bcan)) + const checkDigit = ('0' + (98 - remainder)).slice(-2) + return this.countryCode + checkDigit + bcan + } - toBCAN(ican, separator = ' ') { - return this.regex.exec(ican.slice(4)).slice(1).join(separator); - } + isValidBCAN (bcan, onlyCrypto = false) { + return this.length - 4 === bcan.length && + (!onlyCrypto || this.isMatchingCrypto(onlyCrypto)) && + this.regex.test(bcan) + } + } - fromBCAN(bcan) { - if (!this.isValidBCAN(bcan)) throw new Error('Invalid BCAN'); - const remainder = iso7064Mod97(iso13616Prepare(this.countryCode + '00' + bcan)); - const checkDigit = ('0' + (98 - remainder)).slice(-2); - return this.countryCode + checkDigit + bcan; - } + // Country specifications + const countries = {} - isValidBCAN(bcan, onlyCrypto = false) { - return this.length - 4 === bcan.length && - (!onlyCrypto || this.isMatchingCrypto(onlyCrypto)) && - this.regex.test(bcan); - } - } + const addSpecification = (spec) => { + countries[spec.countryCode] = spec + } - // Country specifications - const countries = {}; + addSpecification(new Specification('AD', 24, 'F04F04A12', false, 'AD1200012030200359100100')) + addSpecification(new Specification('AE', 23, 'F03F16', false, 'AE070331234567890123456')) + addSpecification(new Specification('AL', 28, 'F08A16', false, 'AL47212110090000000235698741')) + addSpecification(new Specification('AT', 20, 'F05F11', false, 'AT611904300234573201')) + addSpecification(new Specification('AZ', 28, 'U04A20', false, 'AZ21NABZ00000000137010001944')) + addSpecification(new Specification('BA', 20, 'F03F03F08F02', false, 'BA391290079401028494')) + addSpecification(new Specification('BE', 16, 'F03F07F02', false, 'BE68539007547034')) + addSpecification(new Specification('BG', 22, 'U04F04F02A08', false, 'BG80BNBG96611020345678')) + addSpecification(new Specification('BH', 22, 'U04A14', false, 'BH67BMAG00001299123456')) + addSpecification(new Specification('BR', 29, 'F08F05F10U01A01', false, 'BR9700360305000010009795493P1')) + addSpecification(new Specification('BY', 28, 'A04F04A16', false, 'BY13NBRB3600900000002Z00AB00')) + addSpecification(new Specification('CH', 21, 'F05A12', false, 'CH9300762011623852957')) + addSpecification(new Specification('CR', 22, 'F04F14', false, 'CR72012300000171549015')) + addSpecification(new Specification('CY', 28, 'F03F05A16', false, 'CY17002001280000001200527600')) + addSpecification(new Specification('CZ', 24, 'F04F06F10', false, 'CZ6508000000192000145399')) + addSpecification(new Specification('DE', 22, 'F08F10', false, 'DE89370400440532013000')) + addSpecification(new Specification('DK', 18, 'F04F09F01', false, 'DK5000400440116243')) + addSpecification(new Specification('DO', 28, 'U04F20', false, 'DO28BAGR00000001212453611324')) + addSpecification(new Specification('EE', 20, 'F02F02F11F01', false, 'EE382200221020145685')) + addSpecification(new Specification('EG', 29, 'F04F04F17', false, 'EG800002000156789012345180002')) + addSpecification(new Specification('ES', 24, 'F04F04F01F01F10', false, 'ES9121000418450200051332')) + addSpecification(new Specification('FI', 18, 'F06F07F01', false, 'FI2112345600000785')) + addSpecification(new Specification('FO', 18, 'F04F09F01', false, 'FO6264600001631634')) + addSpecification(new Specification('FR', 27, 'F05F05A11F02', false, 'FR1420041010050500013M02606')) + addSpecification(new Specification('GB', 22, 'U04F06F08', false, 'GB29NWBK60161331926819')) + addSpecification(new Specification('GE', 22, 'U02F16', false, 'GE29NB0000000101904917')) + addSpecification(new Specification('GI', 23, 'U04A15', false, 'GI75NWBK000000007099453')) + addSpecification(new Specification('GL', 18, 'F04F09F01', false, 'GL8964710001000206')) + addSpecification(new Specification('GR', 27, 'F03F04A16', false, 'GR1601101250000000012300695')) + addSpecification(new Specification('GT', 28, 'A04A20', false, 'GT82TRAJ01020000001210029690')) + addSpecification(new Specification('HR', 21, 'F07F10', false, 'HR1210010051863000160')) + addSpecification(new Specification('HU', 28, 'F03F04F01F15F01', false, 'HU42117730161111101800000000')) + addSpecification(new Specification('IE', 22, 'U04F06F08', false, 'IE29AIBK93115212345678')) + addSpecification(new Specification('IL', 23, 'F03F03F13', false, 'IL620108000000099999999')) + addSpecification(new Specification('IS', 26, 'F04F02F06F10', false, 'IS140159260076545510730339')) + addSpecification(new Specification('IT', 27, 'U01F05F05A12', false, 'IT60X0542811101000000123456')) + addSpecification(new Specification('IQ', 23, 'U04F03A12', false, 'IQ98NBIQ850123456789012')) + addSpecification(new Specification('JO', 30, 'A04F22', false, 'JO15AAAA1234567890123456789012')) + addSpecification(new Specification('KW', 30, 'U04A22', false, 'KW81CBKU0000000000001234560101')) + addSpecification(new Specification('KZ', 20, 'F03A13', false, 'KZ86125KZT5004100100')) + addSpecification(new Specification('LB', 28, 'F04A20', false, 'LB62099900000001001901229114')) + addSpecification(new Specification('LC', 32, 'U04F24', false, 'LC07HEMM000100010012001200013015')) + addSpecification(new Specification('LI', 21, 'F05A12', false, 'LI21088100002324013AA')) + addSpecification(new Specification('LT', 20, 'F05F11', false, 'LT121000011101001000')) + addSpecification(new Specification('LU', 20, 'F03A13', false, 'LU280019400644750000')) + addSpecification(new Specification('LV', 21, 'U04A13', false, 'LV80BANK0000435195001')) + addSpecification(new Specification('MC', 27, 'F05F05A11F02', false, 'MC5811222000010123456789030')) + addSpecification(new Specification('MD', 24, 'U02A18', false, 'MD24AG000225100013104168')) + addSpecification(new Specification('ME', 22, 'F03F13F02', false, 'ME25505000012345678951')) + addSpecification(new Specification('MK', 19, 'F03A10F02', false, 'MK07250120000058984')) + addSpecification(new Specification('MR', 27, 'F05F05F11F02', false, 'MR1300020001010000123456753')) + addSpecification(new Specification('MT', 31, 'U04F05A18', false, 'MT84MALT011000012345MTLCAST001S')) + addSpecification(new Specification('MU', 30, 'U04F02F02F12F03U03', false, 'MU17BOMM0101101030300200000MUR')) + addSpecification(new Specification('NL', 18, 'U04F10', false, 'NL91ABNA0417164300')) + addSpecification(new Specification('NO', 15, 'F04F06F01', false, 'NO9386011117947')) + addSpecification(new Specification('PK', 24, 'U04A16', false, 'PK36SCBL0000001123456702')) + addSpecification(new Specification('PL', 28, 'F08F16', false, 'PL61109010140000071219812874')) + addSpecification(new Specification('PS', 29, 'U04A21', false, 'PS92PALS000000000400123456702')) + addSpecification(new Specification('PT', 25, 'F04F04F11F02', false, 'PT50000201231234567890154')) + addSpecification(new Specification('QA', 29, 'U04A21', false, 'QA30AAAA123456789012345678901')) + addSpecification(new Specification('RO', 24, 'U04A16', false, 'RO49AAAA1B31007593840000')) + addSpecification(new Specification('RS', 22, 'F03F13F02', false, 'RS35260005601001611379')) + addSpecification(new Specification('SA', 24, 'F02A18', false, 'SA0380000000608010167519')) + addSpecification(new Specification('SC', 31, 'U04F04F16U03', false, 'SC18SSCB11010000000000001497USD')) + addSpecification(new Specification('SE', 24, 'F03F16F01', false, 'SE4550000000058398257466')) + addSpecification(new Specification('SI', 19, 'F05F08F02', false, 'SI56263300012039086')) + addSpecification(new Specification('SK', 24, 'F04F06F10', false, 'SK3112000000198742637541')) + addSpecification(new Specification('SM', 27, 'U01F05F05A12', false, 'SM86U0322509800000000270100')) + addSpecification(new Specification('ST', 25, 'F08F11F02', false, 'ST68000100010051845310112')) + addSpecification(new Specification('SV', 28, 'U04F20', false, 'SV62CENR00000000000000700025')) + addSpecification(new Specification('TL', 23, 'F03F14F02', false, 'TL380080012345678910157')) + addSpecification(new Specification('TN', 24, 'F02F03F13F02', false, 'TN5910006035183598478831')) + addSpecification(new Specification('TR', 26, 'F05F01A16', false, 'TR330006100519786457841326')) + addSpecification(new Specification('UA', 29, 'F25', false, 'UA511234567890123456789012345')) + addSpecification(new Specification('VA', 22, 'F18', false, 'VA59001123000012345678')) + addSpecification(new Specification('VG', 24, 'U04F16', false, 'VG96VPVG0000012345678901')) + addSpecification(new Specification('XK', 20, 'F04F10F02', false, 'XK051212012345678906')) - const addSpecification = (spec) => { - countries[spec.countryCode] = spec; - }; + // The following countries are not included in the official ICAN registry but use the ICAN specification - addSpecification(new Specification('AD', 24, 'F04F04A12', false, 'AD1200012030200359100100')) - addSpecification(new Specification('AE', 23, 'F03F16', false, 'AE070331234567890123456')) - addSpecification(new Specification('AL', 28, 'F08A16', false, 'AL47212110090000000235698741')) - addSpecification(new Specification('AT', 20, 'F05F11', false, 'AT611904300234573201')) - addSpecification(new Specification('AZ', 28, 'U04A20', false, 'AZ21NABZ00000000137010001944')) - addSpecification(new Specification('BA', 20, 'F03F03F08F02', false, 'BA391290079401028494')) - addSpecification(new Specification('BE', 16, 'F03F07F02', false, 'BE68539007547034')) - addSpecification(new Specification('BG', 22, 'U04F04F02A08', false, 'BG80BNBG96611020345678')) - addSpecification(new Specification('BH', 22, 'U04A14', false, 'BH67BMAG00001299123456')) - addSpecification(new Specification('BR', 29, 'F08F05F10U01A01', false, 'BR9700360305000010009795493P1')) - addSpecification(new Specification('BY', 28, 'A04F04A16', false, 'BY13NBRB3600900000002Z00AB00')) - addSpecification(new Specification('CH', 21, 'F05A12', false, 'CH9300762011623852957')) - addSpecification(new Specification('CR', 22, 'F04F14', false, 'CR72012300000171549015')) - addSpecification(new Specification('CY', 28, 'F03F05A16', false, 'CY17002001280000001200527600')) - addSpecification(new Specification('CZ', 24, 'F04F06F10', false, 'CZ6508000000192000145399')) - addSpecification(new Specification('DE', 22, 'F08F10', false, 'DE89370400440532013000')) - addSpecification(new Specification('DK', 18, 'F04F09F01', false, 'DK5000400440116243')) - addSpecification(new Specification('DO', 28, 'U04F20', false, 'DO28BAGR00000001212453611324')) - addSpecification(new Specification('EE', 20, 'F02F02F11F01', false, 'EE382200221020145685')) - addSpecification(new Specification('EG', 29, 'F04F04F17', false, 'EG800002000156789012345180002')) - addSpecification(new Specification('ES', 24, 'F04F04F01F01F10', false, 'ES9121000418450200051332')) - addSpecification(new Specification('FI', 18, 'F06F07F01', false, 'FI2112345600000785')) - addSpecification(new Specification('FO', 18, 'F04F09F01', false, 'FO6264600001631634')) - addSpecification(new Specification('FR', 27, 'F05F05A11F02', false, 'FR1420041010050500013M02606')) - addSpecification(new Specification('GB', 22, 'U04F06F08', false, 'GB29NWBK60161331926819')) - addSpecification(new Specification('GE', 22, 'U02F16', false, 'GE29NB0000000101904917')) - addSpecification(new Specification('GI', 23, 'U04A15', false, 'GI75NWBK000000007099453')) - addSpecification(new Specification('GL', 18, 'F04F09F01', false, 'GL8964710001000206')) - addSpecification(new Specification('GR', 27, 'F03F04A16', false, 'GR1601101250000000012300695')) - addSpecification(new Specification('GT', 28, 'A04A20', false, 'GT82TRAJ01020000001210029690')) - addSpecification(new Specification('HR', 21, 'F07F10', false, 'HR1210010051863000160')) - addSpecification(new Specification('HU', 28, 'F03F04F01F15F01', false, 'HU42117730161111101800000000')) - addSpecification(new Specification('IE', 22, 'U04F06F08', false, 'IE29AIBK93115212345678')) - addSpecification(new Specification('IL', 23, 'F03F03F13', false, 'IL620108000000099999999')) - addSpecification(new Specification('IS', 26, 'F04F02F06F10', false, 'IS140159260076545510730339')) - addSpecification(new Specification('IT', 27, 'U01F05F05A12', false, 'IT60X0542811101000000123456')) - addSpecification(new Specification('IQ', 23, 'U04F03A12', false, 'IQ98NBIQ850123456789012')) - addSpecification(new Specification('JO', 30, 'A04F22', false, 'JO15AAAA1234567890123456789012')) - addSpecification(new Specification('KW', 30, 'U04A22', false, 'KW81CBKU0000000000001234560101')) - addSpecification(new Specification('KZ', 20, 'F03A13', false, 'KZ86125KZT5004100100')) - addSpecification(new Specification('LB', 28, 'F04A20', false, 'LB62099900000001001901229114')) - addSpecification(new Specification('LC', 32, 'U04F24', false, 'LC07HEMM000100010012001200013015')) - addSpecification(new Specification('LI', 21, 'F05A12', false, 'LI21088100002324013AA')) - addSpecification(new Specification('LT', 20, 'F05F11', false, 'LT121000011101001000')) - addSpecification(new Specification('LU', 20, 'F03A13', false, 'LU280019400644750000')) - addSpecification(new Specification('LV', 21, 'U04A13', false, 'LV80BANK0000435195001')) - addSpecification(new Specification('MC', 27, 'F05F05A11F02', false, 'MC5811222000010123456789030')) - addSpecification(new Specification('MD', 24, 'U02A18', false, 'MD24AG000225100013104168')) - addSpecification(new Specification('ME', 22, 'F03F13F02', false, 'ME25505000012345678951')) - addSpecification(new Specification('MK', 19, 'F03A10F02', false, 'MK07250120000058984')) - addSpecification(new Specification('MR', 27, 'F05F05F11F02', false, 'MR1300020001010000123456753')) - addSpecification(new Specification('MT', 31, 'U04F05A18', false, 'MT84MALT011000012345MTLCAST001S')) - addSpecification(new Specification('MU', 30, 'U04F02F02F12F03U03', false, 'MU17BOMM0101101030300200000MUR')) - addSpecification(new Specification('NL', 18, 'U04F10', false, 'NL91ABNA0417164300')) - addSpecification(new Specification('NO', 15, 'F04F06F01', false, 'NO9386011117947')) - addSpecification(new Specification('PK', 24, 'U04A16', false, 'PK36SCBL0000001123456702')) - addSpecification(new Specification('PL', 28, 'F08F16', false, 'PL61109010140000071219812874')) - addSpecification(new Specification('PS', 29, 'U04A21', false, 'PS92PALS000000000400123456702')) - addSpecification(new Specification('PT', 25, 'F04F04F11F02', false, 'PT50000201231234567890154')) - addSpecification(new Specification('QA', 29, 'U04A21', false, 'QA30AAAA123456789012345678901')) - addSpecification(new Specification('RO', 24, 'U04A16', false, 'RO49AAAA1B31007593840000')) - addSpecification(new Specification('RS', 22, 'F03F13F02', false, 'RS35260005601001611379')) - addSpecification(new Specification('SA', 24, 'F02A18', false, 'SA0380000000608010167519')) - addSpecification(new Specification('SC', 31, 'U04F04F16U03', false, 'SC18SSCB11010000000000001497USD')) - addSpecification(new Specification('SE', 24, 'F03F16F01', false, 'SE4550000000058398257466')) - addSpecification(new Specification('SI', 19, 'F05F08F02', false, 'SI56263300012039086')) - addSpecification(new Specification('SK', 24, 'F04F06F10', false, 'SK3112000000198742637541')) - addSpecification(new Specification('SM', 27, 'U01F05F05A12', false, 'SM86U0322509800000000270100')) - addSpecification(new Specification('ST', 25, 'F08F11F02', false, 'ST68000100010051845310112')) - addSpecification(new Specification('SV', 28, 'U04F20', false, 'SV62CENR00000000000000700025')) - addSpecification(new Specification('TL', 23, 'F03F14F02', false, 'TL380080012345678910157')) - addSpecification(new Specification('TN', 24, 'F02F03F13F02', false, 'TN5910006035183598478831')) - addSpecification(new Specification('TR', 26, 'F05F01A16', false, 'TR330006100519786457841326')) - addSpecification(new Specification('UA', 29, 'F25', false, 'UA511234567890123456789012345')) - addSpecification(new Specification('VA', 22, 'F18', false, 'VA59001123000012345678')) - addSpecification(new Specification('VG', 24, 'U04F16', false, 'VG96VPVG0000012345678901')) - addSpecification(new Specification('XK', 20, 'F04F10F02', false, 'XK051212012345678906')) + // Angola + addSpecification(new Specification('AO', 25, 'F21', false, 'AO69123456789012345678901')) + // Burkina + addSpecification(new Specification('BF', 27, 'F23', false, 'BF2312345678901234567890123')) + // Burundi + addSpecification(new Specification('BI', 16, 'F12', false, 'BI41123456789012')) + // Benin + addSpecification(new Specification('BJ', 28, 'F24', false, 'BJ39123456789012345678901234')) + // Ivory + addSpecification(new Specification('CI', 28, 'U02F22', false, 'CI70CI1234567890123456789012')) + // Cameron + addSpecification(new Specification('CM', 27, 'F23', false, 'CM9012345678901234567890123')) + // Cape Verde + addSpecification(new Specification('CV', 25, 'F21', false, 'CV30123456789012345678901')) + // Algeria + addSpecification(new Specification('DZ', 24, 'F20', false, 'DZ8612345678901234567890')) + // Iran + addSpecification(new Specification('IR', 26, 'F22', false, 'IR861234568790123456789012')) + // Madagascar + addSpecification(new Specification('MG', 27, 'F23', false, 'MG1812345678901234567890123')) + // Mali + addSpecification(new Specification('ML', 28, 'U01F23', false, 'ML15A12345678901234567890123')) + // Mozambique + addSpecification(new Specification('MZ', 25, 'F21', false, 'MZ25123456789012345678901')) + // Senegal + addSpecification(new Specification('SN', 28, 'U01F23', false, 'SN52A12345678901234567890123')) - // The following countries are not included in the official ICAN registry but use the ICAN specification + // The following are regional and administrative French Republic subdivision ICAN specification (same structure as FR, only country code vary) + addSpecification(new Specification('GF', 27, 'F05F05A11F02', false, 'GF121234512345123456789AB13')) + addSpecification(new Specification('GP', 27, 'F05F05A11F02', false, 'GP791234512345123456789AB13')) + addSpecification(new Specification('MQ', 27, 'F05F05A11F02', false, 'MQ221234512345123456789AB13')) + addSpecification(new Specification('RE', 27, 'F05F05A11F02', false, 'RE131234512345123456789AB13')) + addSpecification(new Specification('PF', 27, 'F05F05A11F02', false, 'PF281234512345123456789AB13')) + addSpecification(new Specification('TF', 27, 'F05F05A11F02', false, 'TF891234512345123456789AB13')) + addSpecification(new Specification('YT', 27, 'F05F05A11F02', false, 'YT021234512345123456789AB13')) + addSpecification(new Specification('NC', 27, 'F05F05A11F02', false, 'NC551234512345123456789AB13')) + addSpecification(new Specification('BL', 27, 'F05F05A11F02', false, 'BL391234512345123456789AB13')) + addSpecification(new Specification('MF', 27, 'F05F05A11F02', false, 'MF551234512345123456789AB13')) + addSpecification(new Specification('PM', 27, 'F05F05A11F02', false, 'PM071234512345123456789AB13')) + addSpecification(new Specification('WF', 27, 'F05F05A11F02', false, 'WF621234512345123456789AB13')) - // Angola - addSpecification(new Specification('AO', 25, 'F21', false, 'AO69123456789012345678901')) - // Burkina - addSpecification(new Specification('BF', 27, 'F23', false, 'BF2312345678901234567890123')) - // Burundi - addSpecification(new Specification('BI', 16, 'F12', false, 'BI41123456789012')) - // Benin - addSpecification(new Specification('BJ', 28, 'F24', false, 'BJ39123456789012345678901234')) - // Ivory - addSpecification(new Specification('CI', 28, 'U02F22', false, 'CI70CI1234567890123456789012')) - // Cameron - addSpecification(new Specification('CM', 27, 'F23', false, 'CM9012345678901234567890123')) - // Cape Verde - addSpecification(new Specification('CV', 25, 'F21', false, 'CV30123456789012345678901')) - // Algeria - addSpecification(new Specification('DZ', 24, 'F20', false, 'DZ8612345678901234567890')) - // Iran - addSpecification(new Specification('IR', 26, 'F22', false, 'IR861234568790123456789012')) - // Madagascar - addSpecification(new Specification('MG', 27, 'F23', false, 'MG1812345678901234567890123')) - // Mali - addSpecification(new Specification('ML', 28, 'U01F23', false, 'ML15A12345678901234567890123')) - // Mozambique - addSpecification(new Specification('MZ', 25, 'F21', false, 'MZ25123456789012345678901')) - // Senegal - addSpecification(new Specification('SN', 28, 'U01F23', false, 'SN52A12345678901234567890123')) + // Digital Assets - // The following are regional and administrative French Republic subdivision ICAN specification (same structure as FR, only country code vary) - addSpecification(new Specification('GF', 27, 'F05F05A11F02', false, 'GF121234512345123456789AB13')) - addSpecification(new Specification('GP', 27, 'F05F05A11F02', false, 'GP791234512345123456789AB13')) - addSpecification(new Specification('MQ', 27, 'F05F05A11F02', false, 'MQ221234512345123456789AB13')) - addSpecification(new Specification('RE', 27, 'F05F05A11F02', false, 'RE131234512345123456789AB13')) - addSpecification(new Specification('PF', 27, 'F05F05A11F02', false, 'PF281234512345123456789AB13')) - addSpecification(new Specification('TF', 27, 'F05F05A11F02', false, 'TF891234512345123456789AB13')) - addSpecification(new Specification('YT', 27, 'F05F05A11F02', false, 'YT021234512345123456789AB13')) - addSpecification(new Specification('NC', 27, 'F05F05A11F02', false, 'NC551234512345123456789AB13')) - addSpecification(new Specification('BL', 27, 'F05F05A11F02', false, 'BL391234512345123456789AB13')) - addSpecification(new Specification('MF', 27, 'F05F05A11F02', false, 'MF551234512345123456789AB13')) - addSpecification(new Specification('PM', 27, 'F05F05A11F02', false, 'PM071234512345123456789AB13')) - addSpecification(new Specification('WF', 27, 'F05F05A11F02', false, 'WF621234512345123456789AB13')) + // Core Blockchain - Mainnet + addSpecification(new Specification('CB', 44, 'H40', 'main', 'CB661234567890ABCDEF1234567890ABCDEF12345678')) + // Core Blockchain - Testnet [Devín] + addSpecification(new Specification('AB', 44, 'H40', 'test', 'AB841234567890ABCDEF1234567890ABCDEF12345678')) + // Core Blockchain - Enterprise [Koliba] + addSpecification(new Specification('CE', 44, 'H40', 'enter', 'CE571234567890ABCDEF1234567890ABCDEF12345678')) - // Digital Assets + // Exports + exports.isValid = (ican, onlyCrypto = false) => { + if (typeof ican !== 'string') return false + ican = electronicFormat(ican) + const spec = countries[ican.slice(0, 2)] + return spec ? spec.isValid(ican, onlyCrypto) : false + } - // Core Blockchain - Mainnet - addSpecification(new Specification('CB', 44, 'H40', 'main', 'CB661234567890ABCDEF1234567890ABCDEF12345678')) - // Core Blockchain - Testnet [Devín] - addSpecification(new Specification('AB', 44, 'H40', 'test', 'AB841234567890ABCDEF1234567890ABCDEF12345678')) - // Core Blockchain - Enterprise [Koliba] - addSpecification(new Specification('CE', 44, 'H40', 'enter', 'CE571234567890ABCDEF1234567890ABCDEF12345678')) + exports.toBCAN = (ican, separator) => { + ican = electronicFormat(ican) + const spec = countries[ican.slice(0, 2)] + if (!spec) throw new Error('No country with code ' + ican.slice(0, 2)) + return spec.toBCAN(ican, separator) + } - // Exports - exports.isValid = (ican, onlyCrypto = false) => { - if (typeof ican !== 'string') return false; - ican = electronicFormat(ican); - const spec = countries[ican.slice(0, 2)]; - return spec ? spec.isValid(ican, onlyCrypto) : false; - }; + exports.fromBCAN = (countryCode, bcan) => { + const spec = countries[countryCode] + if (!spec) throw new Error('No country with code ' + countryCode) + return spec.fromBCAN(electronicFormat(bcan)) + } - exports.toBCAN = (ican, separator) => { - ican = electronicFormat(ican); - const spec = countries[ican.slice(0, 2)]; - if (!spec) throw new Error('No country with code ' + ican.slice(0, 2)); - return spec.toBCAN(ican, separator); - }; + exports.isValidBCAN = (countryCode, bcan, onlyCrypto = false) => { + const spec = countries[countryCode] + return spec ? spec.isValidBCAN(electronicFormat(bcan), onlyCrypto) : false + } - exports.fromBCAN = (countryCode, bcan) => { - const spec = countries[countryCode]; - if (!spec) throw new Error('No country with code ' + countryCode); - return spec.fromBCAN(electronicFormat(bcan)); - }; + exports.printFormat = (ican, separator = ' ') => electronicFormat(ican).replace(EVERY_FOUR_CHARS, '$1' + separator) - exports.isValidBCAN = (countryCode, bcan, onlyCrypto = false) => { - const spec = countries[countryCode]; - return spec ? spec.isValidBCAN(electronicFormat(bcan), onlyCrypto) : false; - }; + exports.shortFormat = (ican, separator = '…', frontCount = 4, backCount = 4) => { + const formatted = electronicFormat(ican) + return formatted.slice(0, frontCount) + separator + formatted.slice(-backCount) + } - exports.printFormat = (ican, separator = ' ') => electronicFormat(ican).replace(EVERY_FOUR_CHARS, '$1' + separator); + exports.electronicFormat = electronicFormat + exports.countries = countries - exports.shortFormat = (ican, separator = '…', frontCount = 4, backCount = 4) => { - const formatted = electronicFormat(ican); - return formatted.slice(0, frontCount) + separator + formatted.slice(-backCount); - }; - - exports.electronicFormat = electronicFormat; - exports.countries = countries; -})); + return exports +})) diff --git a/package.json b/package.json index 81fef3e..2098163 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "gitdiff": "npm run build && git diff --exit-code", "test": "npm run unit && npm run standard", "unit": "npm run build && tape test/*.js", - "standard": "npx standard 'dist/**/*.js' --no-ignore" + "standard": "npx standard 'dist/**/*.js'" }, "files": [ "dist" From be337c5238ddbc6261510c6afdd2c3d6b6c4687d Mon Sep 17 00:00:00 2001 From: Rastislav Date: Sat, 7 Dec 2024 16:52:21 +0100 Subject: [PATCH 4/4] Fixed tests --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index 2098163..982ac1b 100644 --- a/package.json +++ b/package.json @@ -22,7 +22,7 @@ "gitdiff": "npm run build && git diff --exit-code", "test": "npm run unit && npm run standard", "unit": "npm run build && tape test/*.js", - "standard": "npx standard 'dist/**/*.js'" + "standard": "npm run build && npx standard 'dist/**/*.js'" }, "files": [ "dist"