]> zoso.dev Git - libnemo.git/commitdiff
Account class is pretty simple overall, so having async factory methods is not very...
authorChris Duncan <chris@zoso.dev>
Thu, 28 Nov 2024 10:19:38 +0000 (02:19 -0800)
committerChris Duncan <chris@zoso.dev>
Thu, 28 Nov 2024 10:19:38 +0000 (02:19 -0800)
src/lib/account.ts
src/lib/block.ts
src/lib/tools.ts
src/lib/wallet.ts

index 636a84f8cb6c1fea44832659573ed3c3b8cb3496..953fdef6fa369a9a4b25287481a49fed634613d8 100644 (file)
@@ -15,6 +15,7 @@ import { nacl } from './workers/nano-nacl.js'
 * be fetched from the network.\r
 */\r
 export class Account {\r
+       static #isInternal: boolean = false\r
        #a: string\r
        #pub: string\r
        #prv: string | null\r
@@ -43,53 +44,97 @@ export class Account {
                if (v?.constructor === Account) {\r
                        this.#rep = v\r
                } else if (typeof v === 'string') {\r
-                       this.#rep = new Account(v)\r
+                       this.#rep = Account.fromAddress(v)\r
                } else {\r
                        throw new TypeError(`Invalid argument for account representative: ${v}`)\r
                }\r
        }\r
        set weight (v) { this.#w = v ? BigInt(v) : undefined }\r
 \r
-       constructor (address: string, index?: number) {\r
-               Account.validate(address)\r
+       constructor (address: string, publicKey: string, privateKey?: string, index?: number) {\r
+               if (!Account.#isInternal) {\r
+                       throw new Error(`Account cannot be instantiated directly. Use factory methods instead.`)\r
+               }\r
                if (index !== undefined && typeof index !== 'number') {\r
                        throw new TypeError(`Invalid index ${index} when creating Account ${address}`)\r
                }\r
                this.#a = address\r
-                       .replace(PREFIX, '')\r
-                       .replace(PREFIX_LEGACY, '')\r
-               this.#pub = Account.#addressToKey(this.#a)\r
-               this.#prv = null\r
+               this.#pub = publicKey\r
+               this.#prv = privateKey ?? null\r
                this.#i = index\r
                this.#s = new Safe()\r
+               Account.#isInternal = false\r
+       }\r
+\r
+       /**\r
+       * Instantiates an Account object from its Nano address.\r
+       *\r
+       * @param {string} address - Address of the account\r
+       * @param {number} [index] - Account number used when deriving the address\r
+       * @returns {Promise<Account>} The instantiated Account object\r
+       */\r
+       static fromAddress (address: string, index?: number): Account {\r
+               Account.#isInternal = true\r
+               Account.validate(address)\r
+               address = address\r
+                       .replace(PREFIX, '')\r
+                       .replace(PREFIX_LEGACY, '')\r
+               const publicKey = Account.#addressToKey(address)\r
+               const account = new this(address, publicKey, undefined, index)\r
+               return account\r
        }\r
 \r
        /**\r
-       * Asynchronously instantiates an Account object from its public key.\r
+       * Instantiates an Account object from its public key.\r
        *\r
-       * @param {string} key - Public key of the account\r
+       * @param {string} publicKey - Public key of the account\r
        * @param {number} [index] - Account number used when deriving the key\r
        * @returns {Promise<Account>} The instantiated Account object\r
        */\r
-       static async fromPublicKey (key: string, index?: number): Promise<Account> {\r
-               Account.#validateKey(key)\r
-               const address = await Account.#keyToAddress(key)\r
-               const account = new this(address, index)\r
+       static fromPublicKey (publicKey: string, index?: number): Account {\r
+               Account.#isInternal = true\r
+               Account.#validateKey(publicKey)\r
+               const address = Account.#keyToAddress(publicKey)\r
+               const account = new this(address, publicKey, undefined, index)\r
                return account\r
        }\r
 \r
        /**\r
-       * Asynchronously instantiates an Account object from its private key.\r
+       * Instantiates an Account object from its private key. The\r
+       * corresponding public key will automatically be derived and saved.\r
        *\r
-       * @param {string} key - Private key of the account\r
+       * @param {string} privateKey - Private key of the account\r
        * @param {number} [index] - Account number used when deriving the key\r
        * @returns {Promise<Account>} A new Account object\r
        */\r
-       static async fromPrivateKey (key: string, index?: number): Promise<Account> {\r
-               Account.#validateKey(key)\r
-               const { publicKey } = nacl.keyPair.fromSeed(hex.toBytes(key))\r
-               const account = await Account.fromPublicKey(bytes.toHex(publicKey), index)\r
-               account.#prv = key.toUpperCase()\r
+       static fromPrivateKey (privateKey: string, index?: number): Account {\r
+               Account.#isInternal = true\r
+               Account.#validateKey(privateKey)\r
+               const { publicKey } = nacl.keyPair.fromSeed(hex.toBytes(privateKey))\r
+               const account = Account.fromPublicKey(bytes.toHex(publicKey), index)\r
+               account.#prv = privateKey.toUpperCase()\r
+               return account\r
+       }\r
+\r
+       /**\r
+       * Asynchronously instantiates an Account object from its public and private\r
+       * keys.\r
+       *\r
+       * WARNING: The validity of the keys is checked, but they are assumed to have\r
+       * been precalculated. Whether they are an actual matching pair is NOT checked!\r
+       * If unsure, use `Account.fromPrivateKey(key)` instead.\r
+       *\r
+       * @param {string} publicKey - Public key of the account\r
+       * @param {string} privateKey - Private key of the account\r
+       * @param {number} [index] - Account number used when deriving the key\r
+       * @returns {Promise<Account>} The instantiated Account object\r
+       */\r
+       static fromKnownKeys (publicKey: string, privateKey: string, index?: number): Account {\r
+               Account.#isInternal = true\r
+               Account.#validateKey(publicKey)\r
+               Account.#validateKey(privateKey)\r
+               const account = Account.fromPublicKey(publicKey)\r
+               account.#prv = privateKey.toUpperCase()\r
                return account\r
        }\r
 \r
@@ -180,7 +225,7 @@ export class Account {
                this.#b = BigInt(balance)\r
                this.#f = frontier\r
                this.#r = BigInt(receivable)\r
-               this.#rep = new Account(representative)\r
+               this.#rep = Account.fromAddress(representative)\r
                this.#w = BigInt(weight)\r
        }\r
 \r
@@ -195,7 +240,7 @@ export class Account {
                return bytes.toHex(keyBytes)\r
        }\r
 \r
-       static async #keyToAddress (key: string): Promise<string> {\r
+       static #keyToAddress (key: string): string {\r
                const publicKeyBytes = hex.toBytes(key)\r
                const checksum = new Blake2b(5).update(publicKeyBytes).digest() as Uint8Array\r
                checksum.reverse()\r
index c5d2a88300793ef1a5e9063ef4d504cfe706de89..14a244f1d51398c9db76013dde37c8cae652c7db 100644 (file)
@@ -31,7 +31,7 @@ abstract class Block {
                if (account.constructor === Account) {
                        this.account = account
                } else if (typeof account === 'string') {
-                       this.account = new Account(account)
+                       this.account = Account.fromAddress(account)
                } else {
                        throw new TypeError('Invalid account')
                }
@@ -226,8 +226,8 @@ export class SendBlock extends Block {
        constructor (sender: Account | string, balance: string, recipient: string, amount: string, representative: string, frontier: string, work?: string) {
                super(sender)
                this.previous = frontier
-               this.representative = new Account(representative)
-               this.link = new Account(recipient).publicKey
+               this.representative = Account.fromAddress(representative)
+               this.link = Account.fromAddress(recipient).publicKey
                this.work = work ?? ''
 
                const bigBalance = BigInt(balance)
@@ -254,8 +254,8 @@ export class ReceiveBlock extends Block {
 
        constructor (recipient: string, balance: string, origin: string, amount: string, representative: string, frontier?: string, work?: string) {
                super(recipient)
-               this.previous = frontier ?? new Account(recipient).publicKey
-               this.representative = new Account(representative)
+               this.previous = frontier ?? Account.fromAddress(recipient).publicKey
+               this.representative = Account.fromAddress(representative)
                this.link = origin
                this.work = work ?? ''
 
@@ -278,14 +278,14 @@ export class ChangeBlock extends Block {
        previous: string
        representative: Account
        balance: bigint
-       link: string = new Account(BURN_ADDRESS).publicKey
+       link: string = Account.fromAddress(BURN_ADDRESS).publicKey
        signature?: string
        work?: string
 
        constructor (account: string, balance: string, representative: string, frontier: string, work?: string) {
                super(account)
                this.previous = frontier
-               this.representative = new Account(representative)
+               this.representative = Account.fromAddress(representative)
                this.balance = BigInt(balance)
                this.work = work ?? ''
 
index 2ef54b7d4587fd6338b0a7dd50c61d3d6a5b8319..c4d516691935dd0acf1ff0261e6acfb52f878d1b 100644 (file)
@@ -119,7 +119,7 @@ export async function sweep (rpc: Rpc | string | URL, wallet: Blake2bWallet | Bi
        const blockQueue: Promise<void>[] = []
        const results: { status: 'success' | 'error', address: string, message: string }[] = []
 
-       const recipientAccount = new Account(recipient)
+       const recipientAccount = Account.fromAddress(recipient)
        const accounts = await wallet.refresh(rpc, from, to)
        for (const account of accounts) {
                if (account.representative?.address && account.frontier) {
index 71d393fa71721d5f3b202d1080dc9f79e30e36e2..2f0da5f5054b1879d91f17b9a5d9909548bcded0 100644 (file)
@@ -123,7 +123,7 @@ abstract class Wallet {
                for (const key of Object.keys(errors ?? {})) {\r
                        const value = errors[key]\r
                        if (value === 'Account not found') {\r
-                               return new Account(key)\r
+                               return Account.fromAddress(key)\r
                        }\r
                }\r
                return await this.getNextNewAccount(rpc, batchSize, from + batchSize)\r