]> git.codecow.com Git - libnemo.git/commitdiff
Allow block signing to return block immediately instead of a promise if a precalculat...
authorChris Duncan <chris@zoso.dev>
Thu, 7 Aug 2025 19:14:17 +0000 (12:14 -0700)
committerChris Duncan <chris@zoso.dev>
Thu, 7 Aug 2025 19:14:17 +0000 (12:14 -0700)
README.md
src/lib/block.ts
src/lib/ledger.ts
src/lib/tools.ts
src/types.d.ts

index 2e34f7c052794e05cd06bb836a1c10159b5055ae..126fa525f5175f157399807dc7c9928d17c25620 100644 (file)
--- a/README.md
+++ b/README.md
@@ -66,16 +66,15 @@ password. Refer to the documentation on each class factory method for specific
 usage.
 
 ```javascript
-import { Bip44Wallet, Blake2bWallet } from 'libnemo'
+import { Wallet } from 'libnemo'
 
-const wallet = await Bip44Wallet.create(password)
-const wallet = await Bip44Wallet.fromEntropy(password, entropy, salt?)
-const wallet = await Bip44Wallet.fromMnemonic(password, mnemonic, salt?)
-const wallet = await Bip44Wallet.fromSeed(password, seed)
+const wallet = await Wallet.create(password)
+const wallet = await Wallet.import(password, mnemonic, salt?)
+const wallet = await Wallet.import(password, seed)
 
-const wallet = await Bip44Wallet.create(password)
-const wallet = await Bip44Wallet.fromSeed(password, seed)
-const wallet = await Bip44Wallet.fromMnemonic(password, mnemonic)
+const wallet = await Wallet.create(password)
+const wallet = await Wallet.import(password, seed)
+const wallet = await Wallet.import(password, mnemonic)
 ```
 
 ```javascript
index b88d36042aa99d1e15eebc729d55b4648466a7d8..44fed0609f376c4690d6e7c349b471d8cf92c748 100644 (file)
@@ -365,7 +365,7 @@ export class Block {
        *
        * @param {string} [key] - 64-byte hexadecimal signature
        */
-       async sign (signature: string): Promise<Block>
+       sign (signature: string): Block
        /**
        * Signs the block using a private key. If successful, the result is stored in
        * the `signature` property of the block.
@@ -392,53 +392,46 @@ export class Block {
        * @param {object} [frontier] - JSON of frontier block for offline signing
        */
        async sign (index: number, frontier?: Block): Promise<Block>
-       async sign (input: unknown, param?: unknown): Promise<Block> {
-               try {
-                       if (typeof input !== 'string' && typeof input !== 'number' && !(input instanceof Wallet)) {
-                               throw new TypeError('Invalid input')
-
-                       } else if (typeof input === 'string') {
-                               if (/^[A-F0-9]{128}$/.test(input)) {
-                                       this.signature = input
-                               } else if (/^[A-F0-9]{64}$/.test(input)) {
+       sign (input: unknown, param?: unknown): Block | Promise<Block> {
+               if (typeof input === 'string' && /^[A-F0-9]{128}$/.test(input)) {
+                       this.signature = input
+                       return this
+               }
+               return new Promise(async (resolve, reject) => {
+                       try {
+                               if (typeof input !== 'number' && typeof input !== 'string' && !(input instanceof Wallet)) {
+                                       throw new TypeError('Invalid signing input')
+                               } else if (typeof input === 'string' && /^[A-F0-9]{64}$/.test(input)) {
                                        const sig = await NanoNaCl.detached(this.hash, hex.toBytes(input))
                                        this.signature = bytes.toHex(sig)
-                               } else {
-                                       throw new TypeError('Invalid signing input')
-                               }
-                               return this
-
-                       } else if (input instanceof Wallet && typeof param === 'number') {
-                               const wallet = input
-                               const sig = await wallet.sign(param, this, 'hex')
-                               if (this.signature !== sig) {
-                                       throw new Error('Wallet signature does not match block signature')
-                               }
-                               return this
-
-                       } else if (typeof input === 'number') {
-                               const index = input
-                               const { Ledger } = await import('./ledger')
-                               const ledger = await Ledger.create()
-                               await ledger.connect()
-                               if (param && param instanceof Block) {
-                                       try {
-                                               await ledger.updateCache(index, param)
-                                       } catch (err) {
-                                               console.warn('Error updating Ledger cache of previous block, attempting signature anyway', err)
+                               } else if (input instanceof Wallet && typeof param === 'number') {
+                                       const wallet = input
+                                       const sig = await wallet.sign(param, this, 'hex')
+                                       if (this.signature !== sig) {
+                                               throw new Error('Wallet signature does not match block signature')
+                                       }
+                               } else if (typeof input === 'number') {
+                                       const index = input
+                                       const { Ledger } = await import('./ledger')
+                                       const ledger = await Ledger.create()
+                                       await ledger.connect()
+                                       if (param && param instanceof Block) {
+                                               try {
+                                                       await ledger.updateCache(index, param)
+                                               } catch (err) {
+                                                       console.warn('Error updating Ledger cache of previous block, attempting signature anyway', err)
+                                               }
                                        }
+                                       this.signature = await ledger.sign(index, this)
+                               } else {
+                                       throw new TypeError('invalid key for block signature', { cause: typeof input })
                                }
-                               this.signature = await ledger.sign(index, this)
-                               return this
-
-                       } else {
-                               throw new TypeError('invalid key for block signature', { cause: typeof input })
+                               resolve(this)
+                       } catch (err) {
+                               console.error(err)
+                               reject(new Error('Failed to sign block', { cause: err }))
                        }
-               } catch (err) {
-                       console.error(err)
-                       throw new Error('Failed to sign block', { cause: err })
-               }
-
+               })
        }
 
        /**
index 962cfee3310649bb68283a3b89222e28c62c9ec0..0b9a00a786d6dd843cb8704fd78c0140c437f082 100644 (file)
@@ -355,9 +355,9 @@ export class Ledger extends Wallet {
                const testWallet = await Wallet.import('BIP-44', '', secret)\r
                await testWallet.unlock('')\r
                const testAccount = await testWallet.account(0)\r
-               const testOpenBlock = new Block(testAccount.address, '0', testAccount.publicKey, testAccount.address,)\r
+               const testOpenBlock = await new Block(testAccount.address, '0', testAccount.publicKey, testAccount.address)\r
                        .receive(testAccount.publicKey, 0)\r
-               await testWallet.sign(0, testOpenBlock)\r
+                       .sign(testWallet, 0)\r
                const testSendBlock = new Block(testAccount.address, '0', bytes.toHex(testOpenBlock.hash), testAccount.address)\r
                        .send(testAccount.address, 0)\r
                const testSignature = await testWallet.sign(0, testOpenBlock, 'hex')\r
index 94e81193f1cb297dbd5387c45ee07aa7877053f1..90357850f6b25be38a0bfd8e7131f88f6b1eb21a 100644 (file)
@@ -107,7 +107,7 @@ export async function sign (key: Key, ...input: string[]): Promise<string> {
 * 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 {Blake2bWallet|Bip44Wallet|Ledger} wallet - Wallet from which to sweep funds
+* @param {(Wallet|Ledger)} wallet - Wallet from which to sweep funds
 * @param {string} recipient - Destination address for all swept funds
 * @param {number} from - Starting account index to sweep
 * @param {number} to - Ending account index to sweep
@@ -136,10 +136,11 @@ export async function sweep (
        const accounts = await wallet.refresh(rpc, from, to)
        for (const account of accounts) {
                if (account.representative?.address && account.frontier && account.index) {
-                       const block = await (await new Block(account)
-                               .send(recipientAccount, account.balance ?? 0n)
-                               .pow())
-                               .sign(wallet, account.index)
+                       const block = new Block(account).send(recipientAccount, account.balance ?? 0n)
+                       await Promise.all([
+                               block.pow(),
+                               block.sign(wallet, account.index)
+                       ])
                        const blockRequest: Promise<void> = new Promise(async (resolve) => {
                                try {
                                        const hash = await block.process(rpc)
index 571de1f50bbf7e38bc793bd1e75a94868e0ea3d1..21201e158a45f60835d529ca1a05fdea58c0f69a 100644 (file)
@@ -79,49 +79,23 @@ export declare class Account {
        */
        static import (keypairs: KeyPair[]): Account[]
        /**
-       * Instantiates an Account object from its private key which is then encrypted
-       * and stored in IndexedDB. The corresponding public key will automatically be
-       * derived and saved.
+       * Instantiates an Account object from its private key which is used to derive
+       * a public key and then discarded.
        *
-       * @param {KeyPair} keypair - Index and keys of the account
-       * @param {Key} password - Used to encrypt the private key
+       * @param {KeyPair} keypair - Index and key of the account
+       * @param {string} type - Indicates a private key
        * @returns {Promise<Account>} Promise for a new Account object
        */
-       static import (keypair: KeyPair, password: Key): Promise<Account>
+       static import (keypair: KeyPair, type: 'private'): Promise<Account>
        /**
-       * Instantiates Account objects from their private keys which are then
-       * encrypted and stored in IndexedDB. The corresponding public keys will
-       * automatically be derived and saved.
+       * Instantiates Account objects from their private keys which are used to
+       * derive public keys and then discarded.
        *
        * @param {KeyPair[]} keypairs - Indexes and keys of the accounts
-       * @param {Key} password - Used to encrypt the private keys
+       * @param {string} type - Indicates private keys
        * @returns {Promise<Account[]>} Promise for array of new Account objects
        */
-       static import (keypairs: KeyPair[], password: Key): Promise<Account[]>
-       /**
-       * USING THIS METHOD IS DISCOURAGED. This library works in its entirety without
-       * exposing the private keys of accounts.
-       *
-       * Retrieves and decrypts the private key of the Account. The same password
-       * used to lock it must be used to unlock it. If derived from a wallet, the
-       * password for the account is the wallet seed.
-       *
-       * @param {Key} password Used previously to lock the Account
-       * @returns Private key bytes as a Uint8Array
-       */
-       export (password: Key): Promise<Uint8Array<ArrayBuffer>>
-       /**
-       * USING THIS METHOD IS DISCOURAGED. This library works in its entirety without
-       * exposing the private keys of accounts.
-       *
-       * Retrieves and decrypts the private key of the Account. The same password
-       * used to lock it must be used to unlock it. If derived from a wallet, the
-       * password for the account is the wallet seed.
-       *
-       * @param {Key} password Used previously to lock the Account
-       * @returns Private key bytes as a hexadecimal string
-       */
-       export (password: Key, format: 'hex'): Promise<string>
+       static import (keypairs: KeyPair[], type: 'private'): Promise<Account[]>
        /**
        * Refreshes the account from its current state on the network.
        *
@@ -132,15 +106,6 @@ export declare class Account {
        */
        refresh (rpc: Rpc | string | URL): Promise<void>
        /**
-       * Signs a block using the private key of the account. The signature is
-       * appended to the signature field of the block before being returned.
-       *
-       * @param {(Block)} block - The block data to be hashed and signed
-       * @param {Key} password - Required to decrypt the private key for signing
-       * @returns {Promise<string>} Hexadecimal-formatted 64-byte signature
-       */
-       sign (block: Block, password: Key): Promise<string>
-       /**
        * Validates a Nano address with 'nano' and 'xrb' prefixes
        * Derived from https://github.com/alecrios/nano-address-validator
        *
@@ -149,7 +114,6 @@ export declare class Account {
        */
        static validate (address: unknown): asserts address is string
 }
-
 export declare class AccountList extends Object {
        [index: number]: Account
        get length (): number;
@@ -270,7 +234,20 @@ export declare class Block {
        link?: Uint8Array<ArrayBuffer>
        signature?: string
        work?: string
-       constructor (account: string | Account, balance?: bigint | string, previous?: string, representative?: string | Account, signature?: string)
+       /**
+       * Initialize a block with the current state of an account so that it can
+       * subsequently be configured as a change, receive, or send transaction.
+       *
+       * All parameters are eventually required in order to initialize the block, but
+       * but if `account` is an Account class object, its properties can be used for
+       * the other parameters instead of passing them into the constructor.
+       *
+       * @param {(string|Account)} account - Target of the transaction; can include `balance`, `frontier`, `representative`
+       * @param {(bigint|number|string)} [balance] - Current balance of the target account
+       * @param {string} [previous] - Current frontier block hash of the target account
+       * @param {(string|Account)} [representative] - Current representative of the target account
+       */
+       constructor (account: string | Account, balance?: bigint | number | string, previous?: string, representative?: string | Account)
        /**
        * Calculates the block hash using Blake2b.
        *
@@ -286,25 +263,13 @@ export declare class Block {
                [key: string]: string
        }
        /**
-       * Set the subtype and link to configure this as a change representative block.
-       *
-       * @returns {Block} This block so that additional calls can be chained
-       */
-       change (): Block
-       /**
-       * Set a block hash as the source send of a receive block.
+       * Set the subtype, link, and target account to configure this as a change
+       * representative block.
        *
-       * @param {string} hash - Hash of send block to be received
-       * @returns {Block} This block so that additional calls can be chained
+       * @param {(string|Account)} account - Account to choose as representative, or its address or public key
+       * @returns {Block} This block with link, representative, and subtype configured
        */
-       from (hash: string): Block
-       /**
-       * Set a send block as the source of a receive block.
-       *
-       * @param {Block} block - Send block to be received
-       * @returns {Block} This block so that additional calls can be chained
-       */
-       from (block: Block): Block
+       change (representative: string | Account): Block
        /**
        * Calculates proof-of-work using a pool of Web Workers.
        *
@@ -322,57 +287,40 @@ export declare class Block {
        */
        process (rpc: Rpc): Promise<string>
        /**
-       * Set the amount of nano that this block will receive from a paired send.
-       *
-       * @param {bigint} amount - Amount that was sent from sender in raw
-       * @returns {Block} This block so that additional calls can be chained
-       */
-       receive (amount: bigint): Block
-       /**
-       * Set the amount of nano that this block will receive from a paired send.
+       * Set the amount of nano that this block will receive from a corresponding
+       * send block.
        *
-       * @param {number} amount - Amount that was sent from sender in nano (10³⁰ raw)
-       * @returns {Block} This block so that additional calls can be chained
+       * @param {(string|Block)} sendBlock - Corresponding send block or its hash
+       * @param {(bigint|number|string)} amount - Amount to be received from sender
+       * @param {string} [unit] - Unit of measure for amount. Default: "NANO" (10³⁰ RAW)
+       * @returns {Block} This block with balance, link, and subtype configured
        */
-       receive (amount: number): Block
-       /**
-       * Set the amount of nano that this block will receive from a paired send.
-       *
-       * @param {string} amount - Amount that was sent from sender in raw
-       * @returns {Block} This block so that additional calls can be chained
-       */
-       receive (amount: string): Block
+       receive (sendBlock: string | Block, amount: bigint | number | string, unit?: string): Block
        /**
        * Set the amount of nano that this block will send to a recipient account.
        *
-       * @param {bigint} amount - Amount to send to recipient in raw
-       * @returns {Block} This block so that additional calls can be chained
+       * @param {(string|Account)} account - Account to target or its address or public key
+       * @param {(bigint|number|string)} amount - Amount to send to recipient
+       * @param {string} [unit] - Unit of measure for amount. Default: "NANO" (10³⁰ RAW)
+       * @returns {Block} This block with balance, link, and subtype configured
        */
-       send (amount: bigint): Block
+       send (account: string | Account, amount: bigint | number | string, unit?: string): Block
        /**
-       * Set the amount of nano that this block will send to a recipient account.
+       * Sets the `signature` property of the block to a precalculated value.
        *
-       * @param {number} amount - Amount to send to recipient in nano (10³⁰ raw)
-       * @returns {Block} This block so that additional calls can be chained
+       * @param {string} [key] - 64-byte hexadecimal signature
        */
-       send (amount: number): Block
-       /**
-       * Set the amount of nano that this block will send to a recipient account.
-       *
-       * @param {string} amount - Amount to send to recipient in raw
-       * @returns {Block} This block so that additional calls can be chained
-       */
-       send (amount: string): Block
+       sign (signature: string): Block
        /**
        * Signs the block using a private key. If successful, the result is stored in
-       * the object's `signature` property.
+       * the `signature` property of the block.
        *
-       * @param {string} [key] - Hexadecimal-formatted private key to use for signing
+       * @param {string} [key] - 32-byte hexadecimal private key to use for signing
        */
        sign (key: string): Promise<Block>
        /**
        * Signs the block using a Wallet. If successful, the result is stored in
-       * the object's `signature` property. The wallet must be unlocked prior to
+       * the `signature` property of the block. The wallet must be unlocked prior to
        * signing.
        *
        * @param {Wallet} wallet - Wallet to use for signing
@@ -381,32 +329,15 @@ export declare class Block {
        sign (wallet: Wallet, index: number): Promise<Block>
        /**
        * Signs the block using a Ledger hardware wallet. If that fails, an error is
-       * thrown with the status code from the device.
-       *
-       * If successful, the result is stored in the object's `signature`
-       * property.
+       * thrown with the status code from the device. If successful, the result is
+       * stored in the `signature` property of the block. The wallet must be unlocked
+       * prior to signing.
        *
        * @param {number} index - Account index between 0x0 and 0x7fffffff
        * @param {object} [frontier] - JSON of frontier block for offline signing
        */
        sign (index: number, frontier?: Block): Promise<Block>
        /**
-       * Set the recipient of a send block or the target representative of a change
-       * block.
-       *
-       * @param {string} account - Address or public key of Account to target
-       * @returns {Block} This block so that additional calls can be chained
-       */
-       to (account: string): Block
-       /**
-       * Set the recipient of a send block or the target representative of a change
-       * block.
-       *
-       * @param {Account} account - Account to target
-       * @returns {Block} This block so that additional calls can be chained
-       */
-       to (account: Account): Block
-       /**
        * Verifies the signature of the block. If a key is not provided, the public
        * key of the block's account will be used if it exists.
        *