message: string
}
-const decoder = Object.freeze(new TextDecoder())
-const encoder = Object.freeze(new TextEncoder())
-
+/**
+ * Decodes bytes to a UTF-8 string.
+ *
+ * @param {AllowSharedBufferSource} [input]
+ * @param {TextDecodeOptions} [options]
+ * @returns {string} Decoded string
+ */
export function decode (input?: AllowSharedBufferSource, options?: TextDecodeOptions): string {
return decoder.decode(input, options)
}
+/**
+ * Encodes a UTF-8 string to bytes.
+ *
+ * @param {string} [input]
+ * @returns {Bytes} Encoded bytes
+ */
export function encode (input?: string): Bytes {
return encoder.encode(input)
}
-export class Tools {
- static #normalize (input: string | ArrayBuffer | Bytes): Bytes {
- return (typeof input === 'string')
- ? hex.toBytes(input)
- : input instanceof ArrayBuffer
- ? new Uint8Array(input.slice())
- : input
- }
- /**
- * Converts a decimal amount of nano from one unit divider to another.
- *
- * @param {(bigint|number|string)} amount - Decimal amount to convert
- * @param {string} inputUnit - Current denomination
- * @param {string} outputUnit - Desired denomination
- * @param {string} [format] - Data type of output
- */
- static convert (amount: bigint | number | string, inputUnit: string, outputUnit: string): string
- static convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'bigint'): bigint
- static convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'number'): number
- static convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'string'): string
- static convert (amount: unknown, inputUnit: unknown, outputUnit: unknown, format?: unknown): bigint | number | string {
- if (typeof amount !== 'bigint' && typeof amount !== 'number' && typeof amount !== 'string') {
- throw new Error('Invalid amount', { cause: typeof amount })
- }
- if (typeof amount === 'string' && !/^[0-9]+\.?[0-9]*$/.test(amount)) {
- throw new Error('Invalid amount', { cause: amount })
- }
- if (typeof inputUnit !== 'string') {
- throw new TypeError('Invalid input unit', { cause: typeof inputUnit })
- }
- (inputUnit as string) = inputUnit.toUpperCase()
- if (typeof outputUnit !== 'string') {
- throw new TypeError('Invalid output unit', { cause: typeof outputUnit })
- }
- (outputUnit as string) = outputUnit.toUpperCase()
- if (UNITS[inputUnit] == null) {
- throw new Error(`Unknown denomination ${inputUnit}, expected one of the following: ${Object.keys(UNITS)}`)
- }
- if (UNITS[outputUnit] == null) {
- throw new Error(`Unknown denomination ${outputUnit}, expected one of the following: ${Object.keys(UNITS)}`)
- }
- if (format !== undefined && format !== 'bigint' && format !== 'number' && format !== 'string') {
- throw new Error('Invalid output format', { cause: format })
- }
+/**
+ * Converts a decimal amount of nano from one unit divider to another.
+ *
+ * @param {(bigint|number|string)} amount - Decimal amount to convert
+ * @param {string} inputUnit - Current denomination
+ * @param {string} outputUnit - Desired denomination
+ * @param {string} [format] - Data type of output
+ */
+export function convert (amount: bigint | number | string, inputUnit: string, outputUnit: string): string
+export function convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'bigint'): bigint
+export function convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'number'): number
+export function convert (amount: bigint | number | string, inputUnit: string, outputUnit: string, format: 'string'): string
+export function convert (amount: unknown, inputUnit: unknown, outputUnit: unknown, format?: unknown): bigint | number | string {
+ if (typeof amount !== 'bigint' && typeof amount !== 'number' && typeof amount !== 'string') {
+ throw new Error('Invalid amount', { cause: typeof amount })
+ }
+ if (typeof amount === 'string' && !/^[0-9]+\.?[0-9]*$/.test(amount)) {
+ throw new Error('Invalid amount', { cause: amount })
+ }
+ if (typeof inputUnit !== 'string') {
+ throw new TypeError('Invalid input unit', { cause: typeof inputUnit })
+ }
+ (inputUnit as string) = inputUnit.toUpperCase()
+ if (typeof outputUnit !== 'string') {
+ throw new TypeError('Invalid output unit', { cause: typeof outputUnit })
+ }
+ (outputUnit as string) = outputUnit.toUpperCase()
+ if (UNITS[inputUnit] == null) {
+ throw new Error(`Unknown denomination ${inputUnit}, expected one of the following: ${Object.keys(UNITS)}`)
+ }
+ if (UNITS[outputUnit] == null) {
+ throw new Error(`Unknown denomination ${outputUnit}, expected one of the following: ${Object.keys(UNITS)}`)
+ }
+ if (format !== undefined && format !== 'bigint' && format !== 'number' && format !== 'string') {
+ throw new Error('Invalid output format', { cause: format })
+ }
- const inUnit = UNITS[inputUnit]
- let [i, f] = typeof amount === 'string'
- ? amount.split('.')
- : dec.toString(amount).split('.')
- i = i.replace(/^0*/g, '')
- f = f?.replace(/0*$/g, '')
- if (f?.length > inUnit) {
- throw new RangeError('Amount contains fractional raw')
- }
+ const inUnit = UNITS[inputUnit]
+ let [i, f] = typeof amount === 'string'
+ ? amount.split('.')
+ : dec.toString(amount).split('.')
+ i = i.replace(/^0*/g, '')
+ f = f?.replace(/0*$/g, '')
+ if (f?.length > inUnit) {
+ throw new RangeError('Amount contains fractional raw')
+ }
- // convert to raw
- let shift = 0
- while (shift++ < inUnit) i += '0'
- let int = BigInt(i || '0')
- if (f != null) {
- shift = f.length
- while (shift++ < inUnit) f += '0'
- int += BigInt(f || '0')
- }
- if (int > MAX_SUPPLY) {
- throw new Error('Amount exceeds available supply')
+ // convert to raw
+ let shift = 0
+ while (shift++ < inUnit) i += '0'
+ let int = BigInt(i || '0')
+ if (f != null) {
+ shift = f.length
+ while (shift++ < inUnit) f += '0'
+ int += BigInt(f || '0')
+ }
+ if (int > MAX_SUPPLY) {
+ throw new Error('Amount exceeds available supply')
+ }
+ if (int < 0n) {
+ throw new Error('Amount must be non-negative')
+ }
+
+ // convert to desired denomination
+ const outUnit = UNITS[outputUnit]
+ i = dec.toString(int, 40)
+ f = i.slice(40 - outUnit).replace(/0*$/g, '')
+ i = i.slice(0, 40 - outUnit).replace(/^0*/g, '')
+ const output = `${i === '' ? '0' : i}${f === '' ? '' : '.'}${f}`
+
+ switch (format) {
+ case 'bigint': {
+ if (!f) return BigInt(output)
+ throw new RangeError('Output fractional amount truncated')
}
- if (int < 0n) {
- throw new Error('Amount must be non-negative')
+ case 'number': {
+ if (Number(i) <= Number.MAX_SAFE_INTEGER) return Number(output)
+ throw new RangeError('Output larger than Number.MAX_SAFE_INTEGER')
}
+ case 'string':
+ default: return output
+ }
+}
- // convert to desired denomination
- const outUnit = UNITS[outputUnit]
- i = dec.toString(int, 40)
- f = i.slice(40 - outUnit).replace(/0*$/g, '')
- i = i.slice(0, 40 - outUnit).replace(/^0*/g, '')
- const output = `${i === '' ? '0' : i}${f === '' ? '' : '.'}${f}`
-
- switch (format) {
- case 'bigint': {
- if (!f) return BigInt(output)
- throw new RangeError('Output fractional amount truncated')
- }
- case 'number': {
- if (Number(i) <= Number.MAX_SAFE_INTEGER) return Number(output)
- throw new RangeError('Output larger than Number.MAX_SAFE_INTEGER')
- }
- case 'string':
- default: return output
- }
+/**
+ * Signs an arbitrary string with a secret key using nano25519. The input data
+ * is encoded as UTF-8 and can be up to 32 KiB in total.
+ *
+ * @param {(string|ArrayBuffer|Bytes)} secretKey - 64-byte secret key
+ * @param {string} input - Data to be signed
+ * @returns {string} 64-byte hexadecimal signature
+ */
+export function sign (secretKey: string | ArrayBuffer | Bytes, input: string): string {
+ if (navigator.userActivation?.isActive === false) {
+ throw new DOMException(
+ 'Signing request was blocked due to lack of user activation',
+ 'NotAllowedError'
+ )
+ }
+ const k = normalize(secretKey)
+ try {
+ const signature = nano25519_sign(utf8.toBytes(input), k)
+ return bytes.toHex(signature)
+ } catch (err) {
+ throw new Error(`Failed to sign message`, { cause: err })
+ } finally {
+ k.fill(0)
}
+}
- /**
- * Signs an arbitrary string with a secret key using nano25519. The input data
- * is encoded as UTF-8 and can be up to 32 KiB in total.
- *
- * @param {(string|ArrayBuffer|Bytes)} secretKey - 64-byte secret key
- * @param {string} input - Data to be signed
- * @returns {string} 64-byte hexadecimal signature
- */
- static sign (secretKey: string | ArrayBuffer | Bytes, input: string): string {
- if (navigator.userActivation?.isActive === false) {
- throw new DOMException(
- 'Signing request was blocked due to lack of user activation',
- 'NotAllowedError'
- )
- }
- const k = this.#normalize(secretKey)
- try {
- const signature = nano25519_sign(utf8.toBytes(input), k)
- return bytes.toHex(signature)
- } catch (err) {
- throw new Error(`Failed to sign message`, { cause: err })
- } finally {
- k.fill(0)
- }
+/**
+ * Collects the funds from a specified range of accounts in a wallet and sends
+ * them all to a single recipient address. Hardware wallets are unsupported.
+ *
+ * @param {(Rpc|string|URL)} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks
+ * @param {Wallet} wallet - Wallet from which to sweep funds
+ * @param {string} recipient - Destination address for all swept funds
+ * @param {number} [from=0] - Starting account index to sweep
+ * @param {number} [to=from] - Ending account index to sweep
+ * @returns An array of results including both successes and failures
+ */
+export async function sweep (
+ rpc: Rpc | string | URL,
+ wallet: Wallet,
+ recipient: string,
+ from: number = 0,
+ to: number = from
+): Promise<SweepResult[]> {
+ if (rpc == null || wallet == null || recipient == null) {
+ throw new ReferenceError('Missing required sweep arguments')
+ }
+ if (typeof rpc === 'string' || rpc instanceof URL) {
+ rpc = new Rpc(rpc)
+ }
+ if (!(rpc instanceof Rpc)) {
+ throw new TypeError('RPC must be a valid node')
}
- /**
- * Collects the funds from a specified range of accounts in a wallet and sends
- * them all to a single recipient address. Hardware wallets are unsupported.
- *
- * @param {(Rpc|string|URL)} rpc - RPC node information required to refresh accounts, calculate PoW, and process blocks
- * @param {Wallet} wallet - Wallet from which to sweep funds
- * @param {string} recipient - Destination address for all swept funds
- * @param {number} [from=0] - Starting account index to sweep
- * @param {number} [to=from] - Ending account index to sweep
- * @returns An array of results including both successes and failures
- */
- static async sweep (
- rpc: Rpc | string | URL,
- wallet: Wallet,
- recipient: string,
- from: number = 0,
- to: number = from
- ): Promise<SweepResult[]> {
- if (rpc == null || wallet == null || recipient == null) {
- throw new ReferenceError('Missing required sweep arguments')
- }
- if (typeof rpc === 'string' || rpc instanceof URL) {
- rpc = new Rpc(rpc)
- }
- if (!(rpc instanceof Rpc)) {
- throw new TypeError('RPC must be a valid node')
- }
+ const blockQueue: Promise<void>[] = []
+ const results: SweepResult[] = []
+ const recipientAccount = new Account(recipient)
+ const accounts = await wallet.refresh(rpc, from, to)
- const blockQueue: Promise<void>[] = []
- const results: SweepResult[] = []
- const recipientAccount = new Account(recipient)
- const accounts = await wallet.refresh(rpc, from, to)
-
- for (const [index, account] of accounts) {
- const blockRequest: Promise<void> = new Promise(async (resolve) => {
- let block
- try {
- if (index == null) {
- throw new TypeError('Account index is required', { cause: account })
- }
- block = await new Block(account)
- .send(recipientAccount, account.balance ?? 0n)
- .sign(wallet, index)
- await block.pow()
- const hash = await block.process(rpc)
- results.push({ status: 'success', address: block.account.address, message: hash })
- } catch (err: any) {
- results.push({ status: 'error', address: account.address, message: err.message })
- } finally {
- resolve()
+ for (const [index, account] of accounts) {
+ const blockRequest: Promise<void> = new Promise(async (resolve) => {
+ let block
+ try {
+ if (index == null) {
+ throw new TypeError('Account index is required', { cause: account })
}
- })
- blockQueue.push(blockRequest)
- }
- await Promise.allSettled(blockQueue)
- return results
- }
-
- /**
- * Verifies the signature of an arbitrary string using a public key.
- *
- * @param {(string|ArrayBuffer|Bytes)} publicKey - 32-byte hexadecimal public key
- * @param {(string|ArrayBuffer|Bytes)} signature - 128-character hexadcimal signature
- * @param {string} input - Data to be verified
- * @returns {boolean} True if the data was signed by the public key's matching private key
- */
- static verify (publicKey: string | ArrayBuffer | Bytes, signature: string | ArrayBuffer | Bytes, input: string): boolean {
- const k = this.#normalize(publicKey)
- const s = this.#normalize(signature)
- try {
- return nano25519_verify(s, utf8.toBytes(input), k)
- } catch (err) {
- throw new Error('Failed to verify signature', { cause: err })
- }
+ block = await new Block(account)
+ .send(recipientAccount, account.balance ?? 0n)
+ .sign(wallet, index)
+ await block.pow()
+ const hash = await block.process(rpc)
+ results.push({ status: 'success', address: block.account.address, message: hash })
+ } catch (err: any) {
+ results.push({ status: 'error', address: account.address, message: err.message })
+ } finally {
+ resolve()
+ }
+ })
+ blockQueue.push(blockRequest)
+ }
+ await Promise.allSettled(blockQueue)
+ return results
+}
+
+/**
+ * Verifies the signature of an arbitrary string using a public key.
+ *
+ * @param {(string|ArrayBuffer|Bytes)} publicKey - 32-byte hexadecimal public key
+ * @param {(string|ArrayBuffer|Bytes)} signature - 128-character hexadcimal signature
+ * @param {string} input - Data to be verified
+ * @returns {boolean} True if the data was signed by the public key's matching private key
+ */
+export function verify (publicKey: string | ArrayBuffer | Bytes, signature: string | ArrayBuffer | Bytes, input: string): boolean {
+ const k = normalize(publicKey)
+ const s = normalize(signature)
+ try {
+ return nano25519_verify(s, utf8.toBytes(input), k)
+ } catch (err) {
+ throw new Error('Failed to verify signature', { cause: err })
}
}
+
+function normalize (input: string | ArrayBuffer | Bytes): Bytes {
+ return (typeof input === 'string')
+ ? hex.toBytes(input)
+ : input instanceof ArrayBuffer
+ ? new Uint8Array(input.slice())
+ : input
+}
+
+const decoder = Object.freeze(new TextDecoder())
+const encoder = Object.freeze(new TextEncoder())