diff --git a/README.md b/README.md index 62ad9740..57a6ea59 100644 --- a/README.md +++ b/README.md @@ -67,4 +67,31 @@ const tokens = await wallet.mintTokens(64, request.quote); Contributions are very welcome. -If you want to contribute, please open an Issue or a PR. +If you want to contribute, please open an Issue or a PR. +If you open a PR, please do so from the `development` branch as the base branch. + +### Version + +``` +* `main` +|\ +|\ \ +| | * `hotfix` +| | +| * `staging` +| |\ +| |\ \ +| | | * `bugfix` +| | | +| | * `development` +| | |\ +| | | * `feature1` +| | | | +| | |/ +| | * +| | |\ +| | | * `feature2` +| | |/ +| |/ +|/ (create new version) +``` diff --git a/src/utils.ts b/src/utils.ts index 1729b995..ca654c36 100644 --- a/src/utils.ts +++ b/src/utils.ts @@ -4,10 +4,23 @@ import { TOKEN_PREFIX, TOKEN_VERSION } from './utils/Constants.js'; import { bytesToHex, hexToBytes } from '@noble/curves/abstract/utils'; import { sha256 } from '@noble/hashes/sha256'; -function splitAmount(value: number, amountPreference?: Array): Array { +/** + * Splits a number into its constituent powers of 2. + * @param value The number to split + * @param amountPreference An optional array of preferred amounts + * @returns An array containing the constituent powers of 2 + */ +export function splitAmount( + value: number, + amountPreference?: Array +): Array { const chunks: Array = []; if (amountPreference) { - chunks.push(...getPreference(value, amountPreference)); + try { + chunks.push(...getPreference(value, amountPreference)); + } catch (error) { + console.error('Error occurred while getting preferences: ', error); + } value = value - chunks.reduce((curr, acc) => { @@ -23,10 +36,21 @@ function splitAmount(value: number, amountPreference?: Array): return chunks; } -function isPowerOfTwo(number: number) { - return number && !(number & (number - 1)); +/** + * Checks if a number is a power of two. + * @param number The number to check + * @returns True if the number is a power of two, false otherwise + */ +function isPowerOfTwo(number: number): boolean { + return !!number && !(number & (number - 1)); } +/** + * Splits an amount into preferred chunks. + * @param amount The amount to split + * @param preferredAmounts An array of preferred amounts + * @returns An array containing the split amounts + */ function getPreference(amount: number, preferredAmounts: Array): Array { const chunks: Array = []; let accumulator = 0; @@ -47,55 +71,84 @@ function getPreference(amount: number, preferredAmounts: Array return chunks; } -function getDefaultAmountPreference(amount: number): Array { +/** + * Returns the default amount preference for a given amount. + * @param amount The amount to split + * @returns An array of AmountPreference objects + */ +export function getDefaultAmountPreference(amount: number): Array { const amounts = splitAmount(amount); return amounts.map((a) => { return { amount: a, count: 1 }; }); } -function bytesToNumber(bytes: Uint8Array): bigint { +/** + * Converts a byte array to a number. + * @param bytes The byte array to convert + * @returns The converted number + */ +export function bytesToNumber(bytes: Uint8Array): bigint { return hexToNumber(bytesToHex(bytes)); } -function hexToNumber(hex: string): bigint { +/** + * Converts a hexadecimal string to a number. + * @param hex The hexadecimal string to convert + * @returns The converted number + */ +export function hexToNumber(hex: string): bigint { return BigInt(`0x${hex}`); } -//used for json serialization -function bigIntStringify(_key: unknown, value: T) { - return typeof value === 'bigint' ? value.toString() : value; +/** + * Stringifies a BigInt for JSON serialization. + * @param _key The key of the value being stringified + * @param value The value to stringify + * @returns The stringified value + */ +export function bigIntStringify(_key: unknown, value: T): string { + return typeof value === 'bigint' ? value.toString() : JSON.stringify(value); } /** - * Helper function to encode a v3 cashu token - * @param token - * @returns + * Encodes a cashu token. + * @param token The token to encode + * @returns The encoded token */ -function getEncodedToken(token: Token): string { +export function getEncodedToken(token: Token): string { return TOKEN_PREFIX + TOKEN_VERSION + encodeJsonToBase64(token); } /** - * Helper function to decode cashu tokens into object + * Helper function to remove prefixes from a token * @param token an encoded cashu token (cashuAey...) - * @returns cashu token object + * @returns token without prefixes */ -function getDecodedToken(token: string): Token { - // remove prefixes +function removePrefixes(token: string): string { const uriPrefixes = ['web+cashu://', 'cashu://', 'cashu:', 'cashuA']; uriPrefixes.forEach((prefix) => { - if (!token.startsWith(prefix)) { - return; + if (token.startsWith(prefix)) { + token = token.slice(prefix.length); } - token = token.slice(prefix.length); }); + return token; +} + +/** + * Helper function to decode cashu tokens into object + * @param token an encoded cashu token (cashuAey...) + * @returns cashu token object + */ +export function getDecodedToken(token: string): Token { + token = removePrefixes(token); return handleTokens(token); } /** - * @param token - * @returns + * Handles different versions of cashu tokens. + * @param token The token to handle + * @returns The handled token */ function handleTokens(token: string): Token { const obj = encodeBase64ToJson | Token>(token); @@ -113,12 +166,13 @@ function handleTokens(token: string): Token { // if v2 token return v3 format return { token: [{ proofs: obj.proofs, mint: obj?.mints[0]?.url ?? '' }] }; } + /** - * Returns the keyset id of a set of keys - * @param keys keys object to derive keyset id from - * @returns + * Derives the keyset id from a set of keys. + * @param keys The keys to derive the keyset id from + * @returns The derived keyset id */ -export function deriveKeysetId(keys: Keys) { +export function deriveKeysetId(keys: Keys): string { const pubkeysConcat = Object.entries(keys) .sort((a, b) => +a[0] - +b[0]) .map(([, pubKey]) => hexToBytes(pubKey)) @@ -137,13 +191,9 @@ function mergeUInt8Arrays(a1: Uint8Array, a2: Uint8Array): Uint8Array { } /** - * merge proofs from same mint, - * removes TokenEntrys with no proofs or no mint field - * and sorts proofs by id - * - * @export - * @param {Token} token - * @return {*} {Token} + * Cleans a token by merging proofs from the same mint, removing TokenEntrys with no proofs or no mint field, and sorting proofs by id. + * @param token The token to clean + * @returns The cleaned token */ export function cleanToken(token: Token): Token { const tokenEntryMap: { [key: string]: TokenEntry } = {}; @@ -168,34 +218,43 @@ export function cleanToken(token: Token): Token { })) }; } -export function sortProofsById(proofs: Array) { + +/** + * Sorts an array of proofs by id. + * @param proofs The proofs to sort + * @returns The sorted proofs + */ +export function sortProofsById(proofs: Array): Array { return proofs.sort((a, b) => a.id.localeCompare(b.id)); } -export function isObj(v: unknown): v is object { - return typeof v === 'object'; +/** + * Checks if a value is an object. + * @param v The value to check + * @returns True if the value is an object, false otherwise + */ +export function isObj(v: unknown): v is Record { + return typeof v === 'object' && v !== null; } -export function checkResponse(data: { error?: string; detail?: string }) { +/** + * Checks if a response object has any error fields. + * Throws an Error if so. + * @param data The response data to check + */ +export function checkResponse(data: { error?: string; detail?: string }): void { if (!isObj(data)) return; - if ('error' in data && data.error) { - throw new Error(data.error); - } - if ('detail' in data && data.detail) { - throw new Error(data.detail); + const message = data.error ?? data.detail; + if (message) { + throw new Error(message); } } +/** + * Joins multiple URL parts into a single URL. + * @param parts The parts of the URL to join + * @returns The joined URL + */ export function joinUrls(...parts: Array): string { return parts.map((part) => part.replace(/(^\/+|\/+$)/g, '')).join('/'); } - -export { - bigIntStringify, - bytesToNumber, - getDecodedToken, - getEncodedToken, - hexToNumber, - splitAmount, - getDefaultAmountPreference -};