@vechain/sdk-core
Advanced tools
Comparing version 1.0.0-beta.4 to 1.0.0-beta.5
{ | ||
"name": "@vechain/sdk-core", | ||
"version": "1.0.0-beta.4", | ||
"version": "1.0.0-beta.5", | ||
"description": "Includes modules for fundamental operations like hashing and cryptography", | ||
"author": "vechain Foundation", | ||
"license": "MIT", | ||
"homepage": "https://github.com/vechain/vechain-sdk-js", | ||
"repository": { | ||
"type": "git", | ||
"url": "github:vechain/vechain-sdk-js" | ||
}, | ||
"keywords": [ | ||
@@ -34,8 +39,13 @@ "vechain", | ||
"@ethereumjs/rlp": "^5.0.2", | ||
"@scure/bip32": "^1.4.0", | ||
"@scure/bip39": "^1.3.0", | ||
"@types/elliptic": "^6.4.18", | ||
"@vechain/sdk-errors": "1.0.0-beta.4", | ||
"@vechain/sdk-errors": "1.0.0-beta.5", | ||
"@vechain/sdk-logging": "1.0.0-beta.5", | ||
"bignumber.js": "^9.1.2", | ||
"blakejs": "^1.2.1", | ||
"elliptic": "^6.5.5", | ||
"ethers": "6.12.1" | ||
} | ||
} | ||
"ethers": "6.12.1", | ||
"fast-json-stable-stringify": "^2.1.0" | ||
} | ||
} |
@@ -20,3 +20,3 @@ import { ADDRESS, assert } from '@vechain/sdk-errors'; | ||
* @param {Uint8Array} privateKey - The private key used to compute the public key | ||
* from wihich the address is computed. | ||
* from which the address is computed. | ||
* @returns {string} - The string representation of the address, | ||
@@ -23,0 +23,0 @@ * prefixed with `0x` according the |
@@ -0,14 +1,26 @@ | ||
import * as utils from '@noble/curves/abstract/utils'; | ||
import fastJsonStableStringify from 'fast-json-stable-stringify'; | ||
import { Hex, Hex0x } from '../utils'; | ||
import { addressUtils } from '../address'; | ||
import { assert, buildError, CERTIFICATE } from '@vechain/sdk-errors'; | ||
import { blake2b256 } from '../hash'; | ||
import { secp256k1 } from '../secp256k1'; | ||
import fastJsonStableStringify from 'fast-json-stable-stringify'; | ||
import { Buffer } from 'buffer'; | ||
import { Hex0x } from '../utils'; | ||
import { type Certificate } from './types'; | ||
import { assert, CERTIFICATE } from '@vechain/sdk-errors'; | ||
/** | ||
* Deterministically encodes a certificate into a JSON string. | ||
* @param cert - The certificate object to be encoded. | ||
* @returns A JSON string representation of the certificate. | ||
* Encodes a certificate object to a JSON string. | ||
* | ||
* The JSON representation of the signer's address is represented according the | ||
* [EIP/ERC-55: Mixed-case checksum address encoding](https://eips.ethereum.org/EIPS/eip-55). | ||
* | ||
* Secure audit function. | ||
* - {@link addressUtils.toERC55Checksum} | ||
* | ||
* @param {Certificate} cert - The certificate object to encode. | ||
* @return {string} - The encoded JSON string. | ||
* | ||
* @throws {InvalidAddressError} if `address` is not a valid hexadecimal | ||
* representation 40 digits long, prefixed with `0x`. | ||
* | ||
* @see {verify} | ||
*/ | ||
@@ -18,3 +30,3 @@ function encode(cert: Certificate): string { | ||
...cert, | ||
signer: cert.signer, | ||
signer: addressUtils.toERC55Checksum(cert.signer), | ||
signature: cert.signature | ||
@@ -27,40 +39,72 @@ }); | ||
* | ||
* @throws {CertificateNotSignedError, CertificateInvalidSignerError, CertificateInvalidSignatureFormatError} | ||
* @param cert - The certificate object with a digital signature. | ||
* This method is insensitive to the case representation of the signer's address. | ||
* | ||
* [EIP/ERC-55: Mixed-case checksum address encoding](https://eips.ethereum.org/EIPS/eip-55). | ||
* is supported. | ||
* | ||
* Secure audit function. | ||
* - {@link blake2b256}; | ||
* - {@link certificate.encode}; | ||
* - {@link secp256k1.recover}. | ||
* | ||
* @param {Certificate} cert - The certificate to verify. | ||
* | ||
* @returns {void} - No return value. | ||
* | ||
* @throws CertificateInvalidSignatureFormatError - If the certificate signature's is not a valid hexadecimal expression prefixed with `0x`. | ||
* @throws CertificateNotSignedError - If the certificate is not signed. | ||
* @throws CertificateInvalidSignerError - If the certificate's signature's doesn't match with the signer;s public key. | ||
* | ||
* @remark This methods {@link certificate.encode} the `cert` instance | ||
* to extract its signer 's address and compare it with the address derioved from the public key recovered from the | ||
* certificate using the | ||
* [BLAKE2](https://en.wikipedia.org/wiki/BLAKE_(hash_function)#BLAKE2) | ||
* hash of its JSON encoded representation. | ||
* | ||
* @see {encode} | ||
*/ | ||
function verify(cert: Certificate): void { | ||
// No signature | ||
// No signature. | ||
assert( | ||
'verify', | ||
'certificate.verify', | ||
cert.signature !== undefined && cert.signature !== null, | ||
CERTIFICATE.CERTIFICATE_NOT_SIGNED, | ||
"Verification failed: Certificate's signature is missing.", | ||
"Verification failed: certificate's signature is missing.", | ||
{ cert } | ||
); | ||
// Invalid signature | ||
// Invalid hexadecimal as signature. | ||
assert( | ||
'verify', | ||
'certificate.verify', | ||
Hex0x.isValid(cert.signature as string, false, true), | ||
CERTIFICATE.CERTIFICATE_INVALID_SIGNATURE_FORMAT, | ||
'Verification failed: Signature format is invalid.', | ||
'Verification failed: signature format is invalid.', | ||
{ cert } | ||
); | ||
// Encode the certificate without the signature and get signing hash | ||
const encoded = encode({ ...cert, signature: undefined }); | ||
const signingHash = blake2b256(encoded); | ||
const pubKey = secp256k1.recover( | ||
Buffer.from(signingHash), | ||
Buffer.from((cert.signature as string).slice(2), 'hex') | ||
); | ||
// Signature does not match with the signer's public key | ||
assert( | ||
'verify', | ||
addressUtils.fromPublicKey(Buffer.from(pubKey)) === cert.signer, | ||
CERTIFICATE.CERTIFICATE_INVALID_SIGNER, | ||
"Verification failed: Signature does not correspond to the signer's public key.", | ||
{ pubKey, cert } | ||
); | ||
// Encode the certificate without the signature and get signing hash. | ||
try { | ||
// The `encode` method could throw `InvalidAddressError`. | ||
const encoded = encode({ ...cert, signature: undefined }); | ||
const signingHash = blake2b256(encoded); | ||
const pubKey = secp256k1.recover( | ||
signingHash, | ||
utils.hexToBytes(Hex.canon(cert.signature as string)) | ||
); | ||
// Signature does not match with the signer's public key. | ||
assert( | ||
'certificate.verify', | ||
addressUtils.fromPublicKey(pubKey).toLowerCase() === | ||
cert.signer.toLowerCase(), | ||
CERTIFICATE.CERTIFICATE_INVALID_SIGNER, | ||
"Verification failed: signature does not correspond to the signer's public key.", | ||
{ pubKey, cert } | ||
); | ||
} catch (e) { | ||
throw buildError( | ||
'certificate.verify', | ||
CERTIFICATE.CERTIFICATE_INVALID_SIGNER, | ||
(e as Error).message, | ||
{ cert }, | ||
e | ||
); | ||
} | ||
} | ||
@@ -67,0 +111,0 @@ |
@@ -1,2 +0,1 @@ | ||
import { isAddress } from 'ethers'; | ||
import { abi, coder, type FunctionFragment } from '../abi'; | ||
@@ -7,2 +6,3 @@ import { type TransactionClause } from '../transaction'; | ||
import { assert, buildError, DATA } from '@vechain/sdk-errors'; | ||
import { addressUtils } from '../address'; | ||
@@ -161,3 +161,3 @@ /** | ||
'transferNFT', | ||
isAddress(contractAddress), | ||
addressUtils.isAddress(contractAddress), | ||
DATA.INVALID_DATA_TYPE, | ||
@@ -164,0 +164,0 @@ `Invalid 'contractAddress' parameter. Expected a contract address but received ${contractAddress}.` |
@@ -0,12 +1,20 @@ | ||
import { type Keystore, type KeystoreAccount } from './types'; | ||
import { keystoreEthers, keystoreExperimental } from './cryptography'; | ||
import { VechainSDKLogger } from '@vechain/sdk-logging'; | ||
/** | ||
* Implements the JSON Keystore v3 Wallet encryption, decryption, and validation functionality. | ||
* A boolean indicating whether the keystore cryptography is experimental or not. | ||
*/ | ||
import { addressUtils } from '../address'; | ||
import { secp256k1 } from '../secp256k1'; | ||
import { ethers } from 'ethers'; | ||
import { Hex0x, SCRYPT_PARAMS } from '../utils'; | ||
import { type Keystore, type KeystoreAccount } from './types'; | ||
import { assert, buildError, KEYSTORE } from '@vechain/sdk-errors'; | ||
let EXPERIMENTAL_CRYPTOGRAPHY: boolean = false; | ||
/** | ||
* Sets the keystore cryptography to experimental mode. | ||
* | ||
* @param experimentalCryptography - A boolean indicating whether the keystore cryptography is experimental or not. | ||
*/ | ||
function useExperimentalCryptography(experimentalCryptography: boolean): void { | ||
EXPERIMENTAL_CRYPTOGRAPHY = experimentalCryptography; | ||
} | ||
/** | ||
* Encrypts a given private key into a keystore format using the specified password. | ||
@@ -22,31 +30,17 @@ * | ||
): Promise<Keystore> { | ||
// Public and Address are derived from private key | ||
const derivePublicKey = secp256k1.derivePublicKey(privateKey); | ||
const deriveAddress = addressUtils.fromPublicKey( | ||
Buffer.from(derivePublicKey) | ||
); | ||
if (EXPERIMENTAL_CRYPTOGRAPHY) | ||
VechainSDKLogger('warning').log({ | ||
title: `Experimental cryptography`, | ||
messages: [ | ||
`Remember, you are using an experimental cryptography library.`, | ||
'functions: keystore.encrypt' | ||
] | ||
}); | ||
// Create keystore account compatible with ethers | ||
const keystoreAccount: ethers.KeystoreAccount = { | ||
address: deriveAddress, | ||
privateKey: Hex0x.of(privateKey) | ||
}; | ||
// Scrypt options | ||
const encryptOptions: ethers.EncryptOptions = { | ||
scrypt: { | ||
N: SCRYPT_PARAMS.N, | ||
r: SCRYPT_PARAMS.r, | ||
p: SCRYPT_PARAMS.p | ||
} | ||
}; | ||
// Stirng version of keystore | ||
const keystoreJsonString = await ethers.encryptKeystoreJson( | ||
keystoreAccount, | ||
password, | ||
encryptOptions | ||
); | ||
return JSON.parse(keystoreJsonString) as Keystore; | ||
return EXPERIMENTAL_CRYPTOGRAPHY | ||
? keystoreExperimental.encrypt( | ||
privateKey, | ||
new TextEncoder().encode(password.normalize('NFKC')) | ||
) | ||
: await keystoreEthers.encrypt(privateKey, password); | ||
} | ||
@@ -66,30 +60,17 @@ | ||
): Promise<KeystoreAccount> { | ||
// Invalid keystore | ||
assert( | ||
'keystore.decrypt', | ||
isValid(keystore), | ||
KEYSTORE.INVALID_KEYSTORE, | ||
'Invalid keystore. Ensure the keystore is properly formatted and contains the necessary data.', | ||
{ | ||
keystore | ||
} | ||
); | ||
if (EXPERIMENTAL_CRYPTOGRAPHY) | ||
VechainSDKLogger('warning').log({ | ||
title: `Experimental cryptography`, | ||
messages: [ | ||
`Remember, you are using an experimental cryptography library.`, | ||
'functions: keystore.decrypt' | ||
] | ||
}); | ||
try { | ||
return (await ethers.decryptKeystoreJson( | ||
JSON.stringify(keystore), | ||
password | ||
)) as KeystoreAccount; | ||
} catch (e) { | ||
throw buildError( | ||
'keystore.decrypt', | ||
KEYSTORE.INVALID_PASSWORD, | ||
'Decryption failed: Invalid Password for the given keystore.', | ||
{ | ||
keystore, | ||
password | ||
}, | ||
e | ||
); | ||
} | ||
return EXPERIMENTAL_CRYPTOGRAPHY | ||
? keystoreExperimental.decrypt( | ||
keystore, | ||
new TextEncoder().encode(password.normalize('NFKC')) | ||
) | ||
: await keystoreEthers.decrypt(keystore, password); | ||
} | ||
@@ -104,3 +85,14 @@ | ||
function isValid(keystore: Keystore): boolean { | ||
return ethers.isKeystoreJson(JSON.stringify(keystore)); | ||
if (EXPERIMENTAL_CRYPTOGRAPHY) | ||
VechainSDKLogger('warning').log({ | ||
title: `Experimental cryptography`, | ||
messages: [ | ||
`Remember, you are using an experimental cryptography library.`, | ||
'functions: keystore.isValid' | ||
] | ||
}); | ||
return EXPERIMENTAL_CRYPTOGRAPHY | ||
? keystoreExperimental.isValid(keystore) | ||
: keystoreEthers.isValid(keystore); | ||
} | ||
@@ -111,3 +103,3 @@ | ||
*/ | ||
const keystore = { encrypt, decrypt, isValid }; | ||
const keystore = { encrypt, decrypt, isValid, useExperimentalCryptography }; | ||
export { keystore }; |
/** | ||
* Types of ciphers for keystore encryption | ||
* @interface Keystore | ||
* Represents a | ||
* [Web3 Secret Storage](https://ethereum.org/en/developers/docs/data-structures-and-encoding/web3-secret-storage) | ||
* keystore object that holds information about a private cryptographic key. | ||
* and its associated wallet address. | ||
* | ||
* @property {string} address - The wallet address associated with the stored private key. | ||
* @property {Object} crypto - The encryption information for the key. | ||
* @property {string} crypto.cipher - The encryption algorithm used. | ||
* @property {Object} crypto.cipherparams - Additional parameters for the encryption algorithm. | ||
* @property {string} crypto.cipherparams.iv - The initialization vector (IV) used for encryption. | ||
* @property {string} crypto.ciphertext - The encrypted private key. | ||
* @property {string} crypto.kdf - The key derivation function (KDF) used. | ||
* @property {Object} crypto.kdfparams - Additional parameters for the KDF. | ||
* @property {number} crypto.kdfparams.dklen - The derived private key length. | ||
* @property {number} crypto.kdfparams.n - The CPU/memory cost parameter for the key derivation function. | ||
* @property {number} crypto.kdfparams.p - The parallelization factor. | ||
* @property {number} crypto.kdfparams.r - The block size factor. | ||
* @property {string} crypto.kdfparams.salt - The salt value used in the KDF. | ||
* @property {string} crypto.mac - The MAC (Message Authentication Code) | ||
* to match the KDF function with the private key derived by the cyphered text stored. | ||
* @property {string} id - The | ||
* [unique identifier version 4](https://en.wikipedia.org/wiki/Universally_unique_identifier) | ||
* for the key store. | ||
* @property {number} version - The version number of the key store. | ||
*/ | ||
type Cipher = 'aes-128-ctr' | 'aes-128-cbc' | 'aes-256-cbc'; | ||
/** | ||
* Scrypt parameters for keystore encryption | ||
*/ | ||
interface ScryptParams { | ||
dklen: number; | ||
n: number; | ||
p: number; | ||
r: number; | ||
salt: Uint8Array | string; | ||
} | ||
/** | ||
* PBKDF2SHA256 parameters for keystore encryption | ||
*/ | ||
interface PBKDF2SHA256Params { | ||
c: number; | ||
dklen: number; | ||
prf: 'hmac-sha256'; | ||
salt: Uint8Array | string; | ||
} | ||
/** | ||
* Keystore type. | ||
* Output of encryption function. | ||
*/ | ||
interface Keystore { | ||
address: string; | ||
crypto: { | ||
cipher: Cipher; | ||
ciphertext: string; | ||
cipher: string; | ||
cipherparams: { | ||
iv: string; | ||
}; | ||
kdf: 'pbkdf2' | 'scrypt'; | ||
kdfparams: ScryptParams | PBKDF2SHA256Params; | ||
ciphertext: string; | ||
kdf: string; | ||
kdfparams: { | ||
dklen: number; | ||
n: number; | ||
p: number; | ||
r: number; | ||
salt: string; | ||
}; | ||
mac: string; | ||
}; | ||
id: string; | ||
version: 3; | ||
address: string; | ||
version: number; | ||
} | ||
/** | ||
* Keystore account type | ||
* Output of decryption function. | ||
* Interface representing a keystore account. | ||
* | ||
* **WARNING:** call | ||
* ```javascript | ||
* privateKey.fill(0) | ||
* ``` | ||
* after use to avoid to invalidate any security audit and certification granted to this code. | ||
* | ||
* @property {string} address - The address associated with the account. | ||
* @property {Uint8Array} privateKey - The private key associated with the account. | ||
* | ||
* @remark **Differently from | ||
* [ethers KeystoreAccount](https://github.com/ethers-io/ethers.js/blob/main/src.ts/wallet/json-keystore.ts), | ||
* this type represents the private key as a buffer of bytes to avoid | ||
* [Memory Dumping](https://github.com/paulmillr/noble-hashes?tab=readme-ov-file#memory-dumping) | ||
* attack.** | ||
*/ | ||
@@ -54,2 +71,3 @@ interface KeystoreAccount { | ||
privateKey: string; | ||
// @NOTE: Added ONLY for compatibility with ethers KeystoreAccount of ethers. | ||
mnemonic?: { | ||
@@ -62,8 +80,2 @@ path?: string; | ||
export { | ||
type Cipher, | ||
type ScryptParams, | ||
type PBKDF2SHA256Params, | ||
type Keystore, | ||
type KeystoreAccount | ||
}; | ||
export { type Keystore, type KeystoreAccount }; |
export * from './data'; | ||
export * from './hdnode'; | ||
export * from './keystore'; | ||
export * from './mnemonic'; | ||
@@ -5,0 +4,0 @@ export * from './secp256k1'; |
@@ -28,6 +28,6 @@ import { addressUtils } from '../../address'; | ||
if (clause.to !== null) { | ||
// Invalid address | ||
// Invalid address or no vet.domains name | ||
assert( | ||
'intrinsicGas', | ||
addressUtils.isAddress(clause.to), | ||
addressUtils.isAddress(clause.to) || clause.to.includes('.'), | ||
DATA.INVALID_DATA_TYPE, | ||
@@ -34,0 +34,0 @@ `Invalid data type in clause. Each 'to' field must be a valid address.`, |
/** | ||
* BigNumberish type | ||
* @typedef {string | number | bigint} BigNumberish | ||
*/ | ||
type BigNumberish = string | number | bigint; | ||
/** | ||
* The supported units of Ether currency which are supported by VechainThor too. | ||
@@ -27,2 +21,2 @@ * | ||
export type { BigNumberish, WEI_UNITS }; | ||
export type { WEI_UNITS }; |
@@ -1,93 +0,305 @@ | ||
import { ethers } from 'ethers'; | ||
import { type BigNumberish, type WEI_UNITS } from './types'; | ||
import { dataUtils } from '..'; | ||
import { assert, DATA } from '@vechain/sdk-errors'; | ||
import * as utils from '@noble/curves/abstract/utils'; | ||
import { BigNumber } from 'bignumber.js'; | ||
import { Hex, Hex0x } from '../hex'; | ||
import { assert, buildError, DATA } from '@vechain/sdk-errors'; | ||
import { type WEI_UNITS } from './types'; | ||
/** | ||
* Parse a string number to a string with the specified number of decimals | ||
* Precision of big numbers. | ||
*/ | ||
const BIG_NUMBER_PRECISION = 80; | ||
/** | ||
* Set the {@link BIG_NUMBER_PRECISION} for the fixed precision math. | ||
*/ | ||
BigNumber.set({ DECIMAL_PLACES: BIG_NUMBER_PRECISION }); | ||
/** | ||
* One VET is 10^18. | ||
* | ||
* @param value - The value to format. In order to avoid overflow issues with 'number' | ||
* only numeric strings are supported. | ||
* @see {formatUnits} | ||
* @see {parseUnits} | ||
*/ | ||
const VET_DECIMAL_EXPONENT = 18; | ||
/** | ||
* Array of Ethereum wei unit names. | ||
* | ||
* @param decimals - The number of decimals to format the value to. | ||
* It can be a number, bigint or `WEI_UNITS` ('wei', 'kwei', 'mwei', 'gwei', 'szabo', 'finney', 'ether'). | ||
* @see {formatUnits} | ||
* @see {parseUnits} | ||
*/ | ||
const WEI_UNIT_NAMES: WEI_UNITS[] = [ | ||
'wei', | ||
'kwei', | ||
'mwei', | ||
'gwei', | ||
'szabo', | ||
'finney', | ||
'ether' | ||
]; | ||
/** | ||
* Converts the given value to a BigNumber. | ||
* | ||
* @link see [ethers.js parseUnits](https://docs.ethers.org/v6/api/utils/#parseUnits) | ||
* @param {bigint | number | string} value - The value to be converted to BigNumber, | ||
* decimal or hexadecimal prefixed with `0x`. | ||
* | ||
* @returns The formatted value as a bigint | ||
* @returns {BigNumber} - The converted value as a BigNumber. | ||
* | ||
* @throws {InvalidDataTypeError} - If the conversion fails due to an invalid data type. | ||
*/ | ||
const parseUnits = ( | ||
value: string, | ||
decimals: WEI_UNITS | number | bigint | ||
): bigint => { | ||
function bigNumberOf(value: bigint | number | string): BigNumber { | ||
let bn: BigNumber; | ||
switch (typeof value) { | ||
case 'bigint': | ||
bn = BigNumber(value.toString()); | ||
break; | ||
case 'number': | ||
bn = BigNumber(value); | ||
break; | ||
case 'string': { | ||
if (Hex0x.isValid(value)) { | ||
bn = BigNumber(utils.hexToNumber(Hex.canon(value)).toString()); | ||
} else { | ||
bn = BigNumber(value); | ||
} | ||
} | ||
} | ||
assert( | ||
'parseUnits', | ||
typeof value !== 'string' || dataUtils.isNumeric(value), | ||
'unitsUtils.bigNumberOf', | ||
!bn.isNaN(), | ||
DATA.INVALID_DATA_TYPE, | ||
`Invalid value format. The value "${value}" must be a numeric string.`, | ||
{ value } | ||
'Not a number.', | ||
{ value: value.toString() } | ||
); | ||
return bn; | ||
} | ||
return ethers.parseUnits(value, decimals); | ||
}; | ||
/** | ||
* Calculates the number of digits in the fractional part of a given value. | ||
* | ||
* @param {BigNumber} value - The value to calculate the number of digits. | ||
* | ||
* @return {number} - The number of digits in the fractional part. | ||
*/ | ||
function digitsOfFractionalPart(value: BigNumber): number { | ||
let d = 0; // Digits of the fractional part. | ||
const i = value.abs().integerValue(BigNumber.ROUND_FLOOR); // Integer part, no sign. | ||
let f = value.abs().minus(i); // Fractional part, no sign. | ||
while (!f.isInteger()) { | ||
++d; | ||
f = f.times(10); | ||
} | ||
return d; | ||
} | ||
/** | ||
* Converts a value into a decimal string assuming the specified number of decimals. | ||
* Calculates the number of digits in the integer part of a given BigNumber. | ||
* | ||
* @param value - The value to format. It can be a string, number or bigint. If it is a string, it must be a valid number. | ||
* Hex strings are supported. | ||
* @param decimals - The number of decimals to format the value to. | ||
* It can be a number, bigint or `WEI_UNITS` ('wei', 'kwei', 'mwei', 'gwei', 'szabo', 'finney', 'ether'). | ||
* @param {BigNumber} value - The BigNumber to calculate the number of digits for. | ||
* | ||
* @link see [ethers.js formatUnits](https://docs.ethers.org/v6/api/utils/#formatUnits) | ||
* @return {number} - The number of digits in the integer part of the BigNumber. | ||
*/ | ||
function digitsOfIntegerPart(value: BigNumber): number { | ||
let d = 0; // Digits of the integer part. | ||
let i = value.abs().integerValue(BigNumber.ROUND_FLOOR); // Integer part, no sign. | ||
while (i.gte(1)) { | ||
d++; | ||
i = i.div(10); | ||
} | ||
return d; | ||
} | ||
/** | ||
* Returns the number of digits expressed by `digitsOrUnit`: | ||
* - parsing {@link WEI_UNITS}, or | ||
* - interpreting the argument as an integer, truncating any factional part. | ||
* | ||
* @returns The formatted value as a string. | ||
* @param {bigint | number | WEI_UNITS} digitsOrUnit - The meaningful digits, | ||
* to represent the unit if this parameter is a {@link WEI_UNITS} type. | ||
* | ||
* @returns {number} - The number of meaningful digits. | ||
* | ||
* @throws {InvalidDataTypeError} - If the `digitsOrUnit` doesn't express a valid | ||
* {@link WEI_UNITS} type, or it is a negative value. | ||
*/ | ||
const formatUnits = ( | ||
value: BigNumberish, | ||
decimals: WEI_UNITS | number | bigint | ||
): string => { | ||
return ethers.formatUnits(value, decimals); | ||
}; | ||
function digitsOfUnit(digitsOrUnit: bigint | number | WEI_UNITS): number { | ||
let digits: number; | ||
switch (typeof digitsOrUnit) { | ||
case 'bigint': | ||
digits = Number(digitsOrUnit); | ||
break; | ||
case 'number': | ||
digits = Math.floor(digitsOrUnit); | ||
break; | ||
case 'string': { | ||
const index = WEI_UNIT_NAMES.indexOf(digitsOrUnit); | ||
if (index < 0) { | ||
// assert method fails to serialize bigint. | ||
throw buildError( | ||
'unitUtils.digitOfUnit', | ||
DATA.INVALID_DATA_TYPE, | ||
'Invalid unit name.', | ||
{ digitsOrUnit } | ||
); | ||
} | ||
digits = index * 3; | ||
} | ||
} | ||
assert( | ||
'unitsUtils.digitOfUnit', | ||
digits <= BIG_NUMBER_PRECISION, | ||
DATA.INVALID_DATA_TYPE, | ||
'Precision overflow (digits or unit name).', | ||
{ digitsOrUnit: digitsOrUnit.toString() } | ||
); | ||
assert( | ||
'unitsUtils.digitOfUnit', | ||
digits >= 0, | ||
DATA.INVALID_DATA_TYPE, | ||
'Negative precision (digits or unit name).', | ||
{ digitsOrUnit: digitsOrUnit.toString() } | ||
); | ||
return digits; | ||
} | ||
/** | ||
* Parses a string number to a string with 18 decimals. | ||
* Formats the given `value` into a decimal string, | ||
* assuming `decimalsOrUnits` decimal places. | ||
* | ||
* The method returns **value / 10^decimalsOrUnit**. | ||
* | ||
* @param {bigint | number | string} value - The value to be formatted, | ||
* it can be a hexadecimal expression prefixed with `0x`. | ||
* @param {bigint | number | WEI_UNITS} decimalsOrUnit - The number of decimals | ||
* or the name unit of measurement to use for formatting | ||
* (e.g. `gwei` for 9 decimal places). | ||
* Default value is {@link VET_DECIMAL_EXPONENT}. | ||
* | ||
* @return {string} - The formatted value as a string, | ||
* as [ethers.formatUnits](https://docs.ethers.org/v6/api/utils/#formatUnits) | ||
* it returns at least a fractional digit unless the `digitsOrUnits` is `wei`. | ||
* | ||
* @throws {Error} - If an error occurs during the formatting process. | ||
* | ||
* @remark This function is a drop-in replacement for | ||
* [ethers.formatUnits](https://docs.ethers.org/v6/api/utils/#formatUnits). | ||
* | ||
*/ | ||
function formatUnits( | ||
value: bigint | number | string, | ||
decimalsOrUnit: bigint | number | WEI_UNITS = VET_DECIMAL_EXPONENT | ||
): string { | ||
try { | ||
const bn = bigNumberOf(value); | ||
const powerOfTen = digitsOfUnit(decimalsOrUnit); | ||
const divisor = BigNumber(10).pow(powerOfTen); | ||
const result = bn.div(divisor); | ||
let fixedDecimals: number = digitsOfFractionalPart(result); | ||
if (fixedDecimals === 0 && decimalsOrUnit !== WEI_UNIT_NAMES[0]) { | ||
fixedDecimals = 1; | ||
} | ||
return result.toFixed(fixedDecimals); | ||
} catch (e) { | ||
throw buildError( | ||
'unitsUtils.formatUnits', | ||
DATA.INVALID_DATA_TYPE, | ||
(e as Error).message, | ||
{ value, digitsOrUnit: decimalsOrUnit }, | ||
e | ||
); | ||
} | ||
} | ||
/** | ||
* Converts a value to decimal string assuming 18 digits. | ||
* | ||
* VET is the native token of the VechainThor blockchain. | ||
* It has 18 decimals. | ||
* | ||
* This method can parse any numeric string with 18 decimals (e.g., VTHO balance too). | ||
* This method can format any numeric value with 18 decimals, VTHO balance too. | ||
* | ||
* @link see [ethers.js parseEther](https://docs.ethers.org/v6/api/utils/#parseEther) | ||
* @param {bigint | number | string} value - The value to be converted, | ||
* hexadecimal supported prefixed with `0x`. | ||
* | ||
* @param value - The value to parse. It must be a valid number. Hex strings are not supported. | ||
* @returns The parsed value as a bigint | ||
* @throws {Error} - If an error occurs during the formatting process. | ||
* | ||
* @returns {string} The value in Ether format. | ||
* | ||
* @see {formatUnits} | ||
*/ | ||
const parseVET = (value: string): bigint => { | ||
return parseUnits(value, 18); | ||
const formatVET = (value: bigint | number | string): string => { | ||
return formatUnits(value, VET_DECIMAL_EXPONENT); | ||
}; | ||
/** | ||
* Converts a value into a decimal string assuming 18 decimals. | ||
* Parses the given `value` and converts it to a BigInt value, | ||
* assuming `decimalsOrUnits` decimal places. | ||
* | ||
* The method returns **value * 10^digitsOrUnit**. | ||
* | ||
* @param {bigint | number | string} value - The value to parse and convert, | ||
* it can be a hexadecimal expression prefixed with `0x`. | ||
* @param {bigint | number | WEI_UNITS} digitsOrUnit - The number of digits | ||
* or the name of the unit of measurement to use for the conversion | ||
* (e.g. `gwei` for 9 decimal places), | ||
* Default value is VET_DECIMAL_EXPONENT. | ||
* | ||
* @returns {bigint} - The parsed value converted to units. | ||
* | ||
* @throws {Error} - Throws an error if the value cannot be parsed or converted. | ||
* | ||
* @remark This function is a drop-in replacement for | ||
* [ethers.parseUnits](https://docs.ethers.org/v6/api/utils/#parseUnits). | ||
*/ | ||
function parseUnits( | ||
value: bigint | number | string, | ||
digitsOrUnit: bigint | number | WEI_UNITS = VET_DECIMAL_EXPONENT | ||
): bigint { | ||
try { | ||
const bn = bigNumberOf(value); | ||
const powerOfTen = digitsOfUnit(digitsOrUnit); | ||
const multiplier = BigNumber(10).pow(powerOfTen); | ||
const result = bn.times(multiplier); | ||
const precisionDigits = digitsOfIntegerPart(bn) + powerOfTen; | ||
return BigInt(result.toPrecision(precisionDigits)); | ||
} catch (e) { | ||
throw buildError( | ||
'unitsUtils.parseUnits', | ||
DATA.INVALID_DATA_TYPE, | ||
(e as Error).message, | ||
{ value, decimalsOrUnit: digitsOrUnit }, | ||
e | ||
); | ||
} | ||
} | ||
/** | ||
* Parses the given value as a VET (VechainThor) amount and returns it as a bigint. | ||
* | ||
* VET is the native token of the VechainThor blockchain. | ||
* It has 18 decimals. | ||
* | ||
* This method can format any numeric value with 18 decimals (e.g., VTHO balance too). | ||
* This method can parse any numeric string with 18 decimals, VTHO balance too. | ||
* | ||
* @link see [ethers.js formatEther](https://docs.ethers.org/v6/api/utils/#formatEther) | ||
* @param {string} value - The value to parse as a VET amount. | ||
* hexadecimal supported previxed with `0x`. | ||
* | ||
* @param value - The value to format. It can be a string, number or bigint. If it is a string, it must be a valid number. | ||
* Hex strings are supported. | ||
* @returns The formatted value as a string. | ||
* @returns {bigint} The parsed value as a bigint. | ||
* | ||
* @throws {Error} - Throws an error if the value cannot be parsed or converted. | ||
* | ||
* @link See [ethers.js parseEther](https://docs.ethers.org/v6/api/utils/#parseEther). | ||
* | ||
* @see {parseUnits} | ||
*/ | ||
const formatVET = (value: BigNumberish): string => { | ||
return formatUnits(value, 18); | ||
const parseVET = (value: string): bigint => { | ||
return parseUnits(value, VET_DECIMAL_EXPONENT); | ||
}; | ||
export const unitsUtils = { | ||
formatUnits, | ||
formatVET, | ||
parseUnits, | ||
formatUnits, | ||
parseVET, | ||
formatVET | ||
parseVET | ||
}; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is too big to display
Sorry, the diff of this file is not supported yet
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
Dynamic require
Supply chain riskDynamic require can indicate the package is performing dangerous or unsafe dynamic code execution.
Found 1 instance in 1 package
No repository
Supply chain riskPackage does not have a linked source code repository. Without this field, a package will have no reference to the location of the source code use to generate the package.
Found 1 instance in 1 package
No website
QualityPackage does not have a website.
Found 1 instance in 1 package
110
1
0
981144
11
26828
+ Added@scure/bip32@^1.4.0
+ Added@scure/bip39@^1.3.0
+ Addedbignumber.js@^9.1.2
+ Added@noble/curves@1.6.0(transitive)
+ Added@noble/hashes@1.5.0(transitive)
+ Added@scure/base@1.1.9(transitive)
+ Added@scure/bip32@1.5.0(transitive)
+ Added@scure/bip39@1.4.0(transitive)
+ Added@vechain/sdk-errors@1.0.0-beta.5(transitive)
+ Added@vechain/sdk-logging@1.0.0-beta.5(transitive)
+ Addedbignumber.js@9.1.2(transitive)
+ Addedfast-json-stable-stringify@2.1.0(transitive)
- Removed@vechain/sdk-errors@1.0.0-beta.4(transitive)