/* This file is part of web3.js. web3.js is free software: you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation, either version 3 of the License, or (at your option) any later version. web3.js is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ import { HexString } from 'web3-types'; import { toChecksumAddress, leftPad, hexToNumber } from 'web3-utils'; import { isAddress } from 'web3-validator'; import { InvalidAddressError } from 'web3-errors'; import { IbanOptions } from './types.js'; /** * Converts Ethereum addresses to IBAN or BBAN addresses and vice versa. */ export class Iban { private readonly _iban: string; /** * Prepare an IBAN 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. */ private static readonly _iso13616Prepare = (iban: string): string => { const A = 'A'.charCodeAt(0); const Z = 'Z'.charCodeAt(0); const upperIban = iban.toUpperCase(); const modifiedIban = `${upperIban.slice(4)}${upperIban.slice(0, 4)}`; return modifiedIban .split('') .map(n => { const code = n.charCodeAt(0); if (code >= A && code <= Z) { // A = 10, B = 11, ... Z = 35 return code - A + 10; } return n; }) .join(''); }; /** * return the bigint of the given string with the specified base */ private static readonly _parseInt = (str: string, base: number): bigint => [...str].reduce( (acc, curr) => BigInt(parseInt(curr, base)) + BigInt(base) * acc, BigInt(0), ); /** * Calculates the MOD 97 10 of the passed IBAN as specified in ISO7064. */ private static readonly _mod9710 = (iban: string): number => { let remainder = iban; let block; while (remainder.length > 2) { block = remainder.slice(0, 9); remainder = `${(parseInt(block, 10) % 97).toString()}${remainder.slice(block.length)}`; } return parseInt(remainder, 10) % 97; }; /** * A static method that checks if an IBAN is Direct. * It actually check the length of the provided variable and, only if it is 34 or 35, it returns true. * Note: this is also available as a method at an Iban instance. * @param iban - an IBAN to be checked * @returns - `true` if the provided `iban` is a Direct IBAN, and `false` otherwise. * * @example * ```ts * web3.eth.Iban.isDirect("XE81ETHXREGGAVOFYORK"); * > false * ``` */ public static isDirect(iban: string): boolean { return iban.length === 34 || iban.length === 35; } /** * An instance method that checks if iban number is Direct. * It actually check the length of the provided variable and, only if it is 34 or 35, it returns true. * Note: this is also available as a static method. * @param iban - an IBAN to be checked * @returns - `true` if the provided `iban` is a Direct IBAN, and `false` otherwise. * * @example * ```ts * const iban = new web3.eth.Iban("XE81ETHXREGGAVOFYORK"); * iban.isDirect(); * > false * ``` */ public isDirect(): boolean { return Iban.isDirect(this._iban); } /** * A static method that checks if an IBAN is Indirect. * It actually check the length of the provided variable and, only if it is 20, it returns true. * Note: this is also available as a method at an Iban instance. * @param iban - an IBAN to be checked * @returns - `true` if the provided `iban` is an Indirect IBAN, and `false` otherwise. * * @example * ```ts * web3.eth.Iban.isIndirect("XE81ETHXREGGAVOFYORK"); * > true * ``` */ public static isIndirect(iban: string): boolean { return iban.length === 20; } /** * check if iban number if indirect * It actually check the length of the provided variable and, only if it is 20, it returns true. * Note: this is also available as a static method. * @param iban - an IBAN to be checked * @returns - `true` if the provided `iban` is an Indirect IBAN, and `false` otherwise. * * @example * ```ts * const iban = new web3.eth.Iban("XE81ETHXREGGAVOFYORK"); * iban.isIndirect(); * > true * ``` */ public isIndirect(): boolean { return Iban.isIndirect(this._iban); } /** * This method could be used to check if a given string is valid IBAN object. * Note: this is also available as a method at an Iban instance. * * @param iban - a string to be checked if it is in IBAN * @returns - true if it is valid IBAN * * @example * ```ts * web3.eth.Iban.isValid("XE81ETHXREGGAVOFYORK"); * > true * * web3.eth.Iban.isValid("XE82ETHXREGGAVOFYORK"); * > false // because the checksum is incorrect * ``` */ public static isValid(iban: string) { return ( /^XE[0-9]{2}(ETH[0-9A-Z]{13}|[0-9A-Z]{30,31})$/.test(iban) && Iban._mod9710(Iban._iso13616Prepare(iban)) === 1 ); } /** * Should be called to check if the early provided IBAN is correct. * Note: this is also available as a static method. * * @example * ```ts * const iban = new web3.eth.Iban("XE81ETHXREGGAVOFYORK"); * iban.isValid(); * > true * * const iban = new web3.eth.Iban("XE82ETHXREGGAVOFYORK"); * iban.isValid(); * > false // because the checksum is incorrect * ``` */ public isValid(): boolean { return Iban.isValid(this._iban); } /** * Construct a direct or indirect IBAN that has conversion methods and validity checks. * If the provided string was not of either the length of a direct IBAN (34 or 35), * nor the length of an indirect IBAN (20), an Error will be thrown ('Invalid IBAN was provided'). * * @param iban - a Direct or an Indirect IBAN * @returns - Iban instance * * @example * ```ts * const iban = new web3.eth.Iban("XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS"); * > Iban { _iban: 'XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS' } * ``` */ public constructor(iban: string) { if (Iban.isIndirect(iban) || Iban.isDirect(iban)) { this._iban = iban; } else { throw new Error('Invalid IBAN was provided'); } } /** * Convert the passed BBAN to an IBAN for this country specification. * Please note that "generation of the IBAN 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_IBAN_check_digits * * @param bban - the BBAN to convert to IBAN * @returns an Iban class instance that holds the equivalent IBAN * * @example * ```ts * web3.eth.Iban.fromBban('ETHXREGGAVOFYORK'); * > Iban {_iban: "XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS"} * ``` */ public static fromBban(bban: string): Iban { const countryCode = 'XE'; const remainder = this._mod9710(this._iso13616Prepare(`${countryCode}00${bban}`)); const checkDigit = `0${(98 - remainder).toString()}`.slice(-2); return new Iban(`${countryCode}${checkDigit}${bban}`); } /** * Should be used to create IBAN object for given institution and identifier * * @param options - an object holds the `institution` and the `identifier` which will be composed to create an `Iban` object from. * @returns an Iban class instance that holds the equivalent IBAN * * @example * ```ts * web3.eth.Iban.createIndirect({ * institution: "XREG", * identifier: "GAVOFYORK" * }); * > Iban {_iban: "XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS"} * ``` */ public static createIndirect(options: IbanOptions): Iban { return Iban.fromBban(`ETH${options.institution}${options.identifier}`); } /** * This method should be used to create iban object from an Ethereum address. * * @param address - an Ethereum address * @returns an Iban class instance that holds the equivalent IBAN * * @example * ```ts * web3.eth.Iban.fromAddress("0x00c5496aEe77C1bA1f0854206A26DdA82a81D6D8"); * > Iban {_iban: "XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS"} * ``` */ public static fromAddress(address: HexString): Iban { if (!isAddress(address)) { throw new InvalidAddressError(address); } const num = BigInt(hexToNumber(address)); const base36 = num.toString(36); const padded = leftPad(base36, 15); return Iban.fromBban(padded.toUpperCase()); } /** * This method should be used to create an ethereum address from a Direct IBAN address. * If the provided string was not a direct IBAN (has the length of 34 or 35), an Error will be thrown: * ('Iban is indirect and cannot be converted. Must be length of 34 or 35'). * Note: this is also available as a method at an Iban instance. * * @param iban - a Direct IBAN address * @return the equivalent ethereum address * * @example * ```ts * web3.eth.Iban.toAddress("XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS"); * > "0x00c5496aEe77C1bA1f0854206A26DdA82a81D6D8" * ``` */ public static toAddress = (iban: string): HexString => { const ibanObject = new Iban(iban); return ibanObject.toAddress(); }; /** * This method should be used to create the equivalent ethereum address for the early provided Direct IBAN address. * If the provided string was not a direct IBAN (has the length of 34 or 35), an Error will be thrown: * ('Iban is indirect and cannot be converted. Must be length of 34 or 35'). * Note: this is also available as a static method. * * @return the equivalent ethereum address * * @example * ```ts * const iban = new web3.eth.Iban("XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS"); * iban.toAddress(); * > "0x00c5496aEe77C1bA1f0854206A26DdA82a81D6D8" * ``` */ public toAddress = (): HexString => { if (this.isDirect()) { // check if Iban can be converted to an address const base36 = this._iban.slice(4); const parsedBigInt = Iban._parseInt(base36, 36); // convert the base36 string to a bigint const paddedBigInt = leftPad(parsedBigInt, 40); return toChecksumAddress(paddedBigInt); } throw new Error('Iban is indirect and cannot be converted. Must be length of 34 or 35'); }; /** * This method should be used to create IBAN address from an Ethereum address * * @param address - an Ethereum address * @return the equivalent IBAN address * * @example * ```ts * web3.eth.Iban.toIban("0x00c5496aEe77C1bA1f0854206A26DdA82a81D6D8"); * > "XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS" * ``` */ public static toIban(address: HexString): string { return Iban.fromAddress(address).toString(); } /** * Should be called to get client identifier within institution * * @return the client of the IBAN instance. * * @example * ```ts * const iban = new web3.eth.Iban("XE81ETHXREGGAVOFYORK"); * iban.client(); * > 'GAVOFYORK' * ``` */ public client(): string { return this.isIndirect() ? this._iban.slice(11) : ''; } /** * Returns the IBAN checksum of the early provided IBAN * * @example * ```ts * const iban = new web3.eth.Iban("XE81ETHXREGGAVOFYORK"); * iban.checksum(); * > "81" * ``` * */ public checksum(): string { return this._iban.slice(2, 4); } /** * Returns institution identifier from the early provided IBAN * * @example * ```ts * const iban = new web3.eth.Iban("XE81ETHXREGGAVOFYORK"); * iban.institution(); * > 'XREG' * ``` */ public institution(): string { return this.isIndirect() ? this._iban.slice(7, 11) : ''; } /** * Simply returns the early provided IBAN * * @example * ```ts * const iban = new web3.eth.Iban('XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS'); * iban.toString(); * > 'XE7338O073KYGTWWZN0F2WZ0R8PX5ZPPZS' * ``` */ public toString(): string { return this._iban; } }