diff --git a/packages/web3-common/src/web3_base_wallet.ts b/packages/web3-common/src/web3_base_wallet.ts index 376addf6f9d..9e3d94d2501 100644 --- a/packages/web3-common/src/web3_base_wallet.ts +++ b/packages/web3-common/src/web3_base_wallet.ts @@ -59,7 +59,7 @@ export abstract class Web3BaseWallet extends Ar } public abstract create(numberOfAccounts: number): this; - public abstract add(account: T | string): boolean; + public abstract add(account: T | string): this; public abstract get(addressOrIndex: string | number): T | undefined; public abstract remove(addressOrIndex: string | number): boolean; public abstract clear(): this; diff --git a/packages/web3-eth-accounts/src/account.ts b/packages/web3-eth-accounts/src/account.ts index e6fe85804d8..fe36f4fc510 100644 --- a/packages/web3-eth-accounts/src/account.ts +++ b/packages/web3-eth-accounts/src/account.ts @@ -65,9 +65,17 @@ import { } from './types'; /** + * * Hashes the given message. The data will be UTF-8 HEX decoded and enveloped as follows: "\x19Ethereum Signed Message:\n" + message.length + message and hashed using keccak256. + * @param message A message to hash, if its HEX it will be UTF8 decoded. + * @returns The hashed message + * ```ts + * hashMessage("Hello world") + * > "0x8144a6fa26be252b86456491fbcd43c1de7e022241845ffea1c3df066f7cfede" + * hashMessage(utf8ToHex("Hello world")) // Will be hex decoded in hashMessage + * > "0x8144a6fa26be252b86456491fbcd43c1de7e022241845ffea1c3df066f7cfede" + * ``` */ - export const hashMessage = (message: string): string => { const messageHex = isHexStrict(message) ? message : utf8ToHex(message); @@ -81,10 +89,22 @@ export const hashMessage = (message: string): string => { }; /** - * Signs arbitrary data. The value passed as the data parameter will be UTF-8 HEX decoded and wrapped as follows: "\x19Ethereum Signed Message:\n" + message.length + message - * - * @param data - * @param privateKey + * Signs arbitrary data. + * **_NOTE:_** The value passed as the data parameter will be UTF-8 HEX decoded and wrapped as follows: "\x19Ethereum Signed Message:\n" + message.length + message + * @param data - The data to sign + * @param privateKey - The 32 byte private key to sign with + * @returns The signature Object containing the message, messageHash, signature r, s, v + * ```ts + * web3.eth.accounts.sign('Some data', '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318') + * > { + * message: 'Some data', + * messageHash: '0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655', + * v: '0x1c', + * r: '0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd', + * s: '0x6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a029', + * signature: '0xb91467e570a6466aa9e9876cbcd013baba02900b8979d43fe208a4a4f339f5fd6007e74cd82e037b800186422fc2da167c747ef045e5d18a5f5d4300f8e1a0291c' + * } + * ``` */ export const sign = (data: string, privateKey: HexString): signResult => { const privateKeyParam = privateKey.startsWith('0x') ? privateKey.substring(2) : privateKey; @@ -115,10 +135,82 @@ export const sign = (data: string, privateKey: HexString): signResult => { }; /** - * Signs an Ethereum transaction with a given private key. * - * @param transaction - * @param privateKey + * Signs an Ethereum transaction with a given private key. + * @param transaction - The transaction, must be a legacy, EIP2930 or EIP 1559 transaction type + * @param privateKey - The private key to import. This is 32 bytes of random data. + * @returns A signTransactionResult object that contains message hash, r, s, v, transaction hash and raw transaction. + * + * Signing a legacy transaction + * ```ts + * signTransaction({ + * to: '0x118C2E5F57FD62C2B5b46a5ae9216F4FF4011a07', + * value: '0x186A0', + * gasLimit: '0x520812', + * gasPrice: '0x09184e72a000', + * data: '', + * chainId: 1, + * nonce: 0, + * }, '0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318')) + * } + * > { + * messageHash: '0x28b7b75f7ba48d588a902c1ff4d5d13cc0ca9ac0aaa39562368146923fb853bf', + * v: '0x25', + * r: '0x601b0017b0e20dd0eeda4b895fbc1a9e8968990953482214f880bae593e71b5', + * s: '0x690d984493560552e3ebdcc19a65b9c301ea9ddc82d3ab8cfde60485fd5722ce', + * rawTransaction: '0xf869808609184e72a0008352081294118c2e5f57fd62c2b5b46a5ae9216f4ff4011a07830186a08025a00601b0017b0e20dd0eeda4b895fbc1a9e8968990953482214f880bae593e71b5a0690d984493560552e3ebdcc19a65b9c301ea9ddc82d3ab8cfde60485fd5722ce', + * transactionHash: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' + * ``` + * Signing an eip 1559 transaction + * ```ts + * signTransaction({ + * to: '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', + * maxPriorityFeePerGas: '0x3B9ACA00', + * maxFeePerGas: '0xB2D05E00', + * gasLimit: '0x6A4012', + * value: '0x186A0', + * data: '', + * chainId: 1, + * nonce: 0, + * },"0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318") + * > { + * messageHash: '0x5744f24d5f0aff6c70487c8e85adf07d8564e50b08558788f00479611d7bae5f', + * v: '0x25', + * r: '0x78a5a6b2876c3985f90f82073d18d57ac299b608cc76a4ba697b8bb085048347', + * s: '0x9cfcb40cc7d505ed17ff2d3337b51b066648f10c6b7e746117de69b2eb6358d', + * rawTransaction: '0xf8638080836a401294f0109fc8df283027b6285cc889f5aa624eac1f55830186a08025a078a5a6b2876c3985f90f82073d18d57ac299b608cc76a4ba697b8bb085048347a009cfcb40cc7d505ed17ff2d3337b51b066648f10c6b7e746117de69b2eb6358d', + * transactionHash: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' + * } + * ``` + * Signing an eip 2930 transaction + * ```ts + * signTransaction({ + * chainId: 1, + * nonce: 0, + * gasPrice: '0x09184e72a000', + * gasLimit: '0x2710321', + * to: '0xF0109fC8DF283027b6285cc889F5aA624EaC1F55', + * value: '0x186A0', + * data: '', + * accessList: [ + * { + * address: '0x0000000000000000000000000000000000000101', + * storageKeys: [ + * '0x0000000000000000000000000000000000000000000000000000000000000000', + * '0x00000000000000000000000000000000000000000000000000000000000060a7', + * ], + * }, + * ], + * },"0x4c0883a69102937d6231471b5dbb6204fe5129617082792ae468d01a3f362318") + * > { + * messageHash: '0xc55ea24bdb4c379550a7c9a6818ac39ca33e75bc78ddb862bd82c31cc1c7a073', + * v: '0x26', + * r: '0x27344e77871c8b2068bc998bf28e0b5f9920867a69c455b2ed0c1c150fec098e', + * s: '0x519f0130a1d662841d4a28082e9c9bb0a15e0e59bb46cfc39a52f0e285dec6b9', + * rawTransaction: '0xf86a808609184e72a000840271032194f0109fc8df283027b6285cc889f5aa624eac1f55830186a08026a027344e77871c8b2068bc998bf28e0b5f9920867a69c455b2ed0c1c150fec098ea0519f0130a1d662841d4a28082e9c9bb0a15e0e59bb46cfc39a52f0e285dec6b9', + * transactionHash: '0xc5d2460186f7233c927e7db2dcc703c0e500b653ca82273b7bfad8045d85a470' + * } + * ``` */ export const signTransaction = ( transaction: TxData | AccessListEIP2930TxData | FeeMarketEIP1559TxData, @@ -158,8 +250,12 @@ export const signTransaction = ( /** * Recovers the Ethereum address which was used to sign the given RLP encoded transaction. - * - * @param rawTransaction + * @param rawTransaction - The hex string having RLP encoded transaction + * @returns The Ethereum address used to sign this transaction + * ```ts + * recoverTransaction('0xf869808504e3b29200831e848094f0109fc8df283027b6285cc889f5aa624eac1f55843b9aca008025a0c9cf86333bcb065d140032ecaab5d9281bde80f21b9687b3e94161de42d51895a0727a108a0b8d101465414033c3f705a9c7b826e596766046ee1183dbc8aeaa68'); + * > "0x2c7536E3605D9C16a7a3D7b1898e529396a65c23" + * ``` */ export const recoverTransaction = (rawTransaction: HexString): Address => { if (isNullish(rawTransaction)) throw new UndefinedRawTransactionError(); @@ -171,15 +267,28 @@ export const recoverTransaction = (rawTransaction: HexString): Address => { /** * Recovers the Ethereum address which was used to sign the given data - * - * @param data - * @param signature - * @param hashed + * @param data - Either a signed message, hash, or the {@link signatureObject} + * @param signature - The raw RLP encoded signature + * @param prefixed - (default: false) If the last parameter is true, the given message will NOT automatically be prefixed with "\x19Ethereum Signed Message:\n" + message.length + message, and assumed to be already prefixed. + * @returns The Ethereum address used to sign this data + * ```ts + * sign('Some data', '0xbe6383dad004f233317e46ddb46ad31b16064d14447a95cc1d8c8d4bc61c3728'); + * > { + * message: 'Some data', + * messageHash: '0x1da44b586eb0729ff70a73c326926f6ed5a25f5b056e7f47fbc6e58d86871655', + * v: '0x1b', + * r: '0xa8037a6116c176a25e6fc224947fde9e79a2deaa0dd8b67b366fbdfdbffc01f9', + * s: '0x53e41351267b20d4a89ebfe9c8f03c04de9b345add4a52f15bd026b63c8fb150', + * signature: '0xa8037a6116c176a25e6fc224947fde9e79a2deaa0dd8b67b366fbdfdbffc01f953e41351267b20d4a89ebfe9c8f03c04de9b345add4a52f15bd026b63c8fb1501b' + * } + * recover('0xa8037a6116c176a25e6fc224947fde9e79a2deaa0dd8b67b366fbdfdbffc01f953e41351267b20d4a89ebfe9c8f03c04de9b345add4a52f15bd026b63c8fb1501b'); + * > '0xEB014f8c8B418Db6b45774c326A0E64C78914dC0' + * ``` */ export const recover = ( data: string | signatureObject, signature?: string, - hashed?: boolean, + prefixed?: boolean, ): Address => { if (typeof data === 'object') { const signatureStr = `${data.r}${data.s.slice(2)}${data.v.slice(2)}`; @@ -189,7 +298,7 @@ export const recover = ( if (isNullish(signature)) throw new InvalidSignatureError('signature string undefined'); const V_INDEX = 130; // r = first 32 bytes, s = second 32 bytes, v = last byte of signature - const hashedMessage = hashed ? data : hashMessage(data); + const hashedMessage = prefixed ? data : hashMessage(data); const v = signature.substring(V_INDEX); // 0x + r + s + v @@ -209,12 +318,15 @@ export const recover = ( return address; }; -// Generate a version 4 uuid -// https://github.com/uuidjs/uuid/blob/main/src/v4.js#L5 -// https://github.com/ethers-io/ethers.js/blob/ce8f1e4015c0f27bf178238770b1325136e3351a/packages/json-wallets/src.ts/utils.ts#L54 -const uuidV4 = () => { +/** + * Generate a version 4 (random) uuid + * https://github.com/uuidjs/uuid/blob/main/src/v4.js#L5 + * */ + +const uuidV4 = (): string => { const bytes = randomBytes(16); + // https://github.com/ethers-io/ethers.js/blob/ce8f1e4015c0f27bf178238770b1325136e3351a/packages/json-wallets/src.ts/utils.ts#L54 // Section: 4.1.3: // - time_hi_and_version[12:16] = 0b0100 /* eslint-disable-next-line */ @@ -237,6 +349,16 @@ const uuidV4 = () => { ].join('-'); }; +/** + * Get the ethereum Address from a private key + * @param privateKey String or buffer of 32 bytes + * @returns The Ethereum address + * @example + * ```ts + * privateKeyToAddress("0xbe6383dad004f233317e46ddb46ad31b16064d14447a95cc1d8c8d4bc61c3728") + * > "0xEB014f8c8B418Db6b45774c326A0E64C78914dC0" + * ``` + */ export const privateKeyToAddress = (privateKey: string | Buffer): string => { if (!(isString(privateKey) || isBuffer(privateKey))) { throw new InvalidPrivateKeyError(); @@ -263,12 +385,79 @@ export const privateKeyToAddress = (privateKey: string | Buffer): string => { }; /** - * encrypt a private key given a password, returns a V3 JSON Keystore - * https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition + * encrypt a private key with a password, returns a V3 JSON Keystore + * + * Read more: https://github.com/ethereum/wiki/wiki/Web3-Secret-Storage-Definition + * + * @param privateKey - The private key to encrypt, 32 bytes. + * @param password - The password used for encryption. + * @param options - Options to configure to encrypt the keystore either scrypt or pbkdf2 + * @returns Returns a V3 JSON Keystore + * + * + * Encrypt using scrypt options + * ```ts + * encrypt('0x67f476289210e3bef3c1c75e4de993ff0a00663df00def84e73aa7411eac18a6', + * '123', + * { + * n: 8192, + * iv: Buffer.from('bfb43120ae00e9de110f8325143a2709', 'hex'), + * salt: Buffer.from( + * '210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd', + * 'hex', + * ), + * }).then(console.log) + * > { + * version: 3, + * id: 'c0cb0a94-4702-4492-b6e6-eb2ac404344a', + * address: 'cda9a91875fc35c8ac1320e098e584495d66e47c', + * crypto: { + * ciphertext: 'cb3e13e3281ff3861a3f0257fad4c9a51b0eb046f9c7821825c46b210f040b8f', + * cipherparams: { iv: 'bfb43120ae00e9de110f8325143a2709' }, + * cipher: 'aes-128-ctr', + * kdf: 'scrypt', + * kdfparams: { + * n: 8192, + * r: 8, + * p: 1, + * dklen: 32, + * salt: '210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd' + * }, + * mac: 'efbf6d3409f37c0084a79d5fdf9a6f5d97d11447517ef1ea8374f51e581b7efd' + * } + *} + *``` + * Encrypting using pbkdf2 options + * ```ts + * encrypt('0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709', + *'123', + *{ + * iv: 'bfb43120ae00e9de110f8325143a2709', + * salt: '210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd', + * c: 262144, + * kdf: 'pbkdf2', + *}).then(console.log) + * > + * { + * version: 3, + * id: '77381417-0973-4e4b-b590-8eb3ace0fe2d', + * address: 'b8ce9ab6943e0eced004cde8e3bbed6568b2fa01', + * crypto: { + * ciphertext: '76512156a34105fa6473ad040c666ae7b917d14c06543accc0d2dc28e6073b12', + * cipherparams: { iv: 'bfb43120ae00e9de110f8325143a2709' }, + * cipher: 'aes-128-ctr', + * kdf: 'pbkdf2', + * kdfparams: { + * dklen: 32, + * salt: '210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd', + * c: 262144, + * prf: 'hmac-sha256' + * }, + * mac: '46eb4884e82dc43b5aa415faba53cc653b7038e9d61cc32fd643cf8c396189b7' + * } + * } + *``` * - * @param privateKey - * @param password - * @param options */ export const encrypt = async ( privateKey: HexString, @@ -388,9 +577,20 @@ export const encrypt = async ( }; /** - * Get account from private key + * Get an Account object from the privateKey + * @param privateKey String or buffer of 32 bytes + * @returns A Web3Account object + * ```ts + * privateKeyToAccount("0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709"); + * > { + * address: '0xb8CE9ab6943e0eCED004cDe8e3bBed6568B2Fa01', + * privateKey: '0x348ce564d427a3311b6536bbcff9390d69395b06ed6c486954e971d960fe8709', + * sign, + * signTransaction, + * encrypt, + * } * - * @param privateKey + * ``` */ export const privateKeyToAccount = (privateKey: string | Buffer): Web3Account => { const pKey = Buffer.isBuffer(privateKey) ? Buffer.from(privateKey).toString('hex') : privateKey; @@ -410,7 +610,23 @@ export const privateKeyToAccount = (privateKey: string | Buffer): Web3Account => }; /** - * Returns an acoount + * + * Generates and returns a Web3Account object that includes the private and public key + * For creation of private key, it uses an audited package ethereum-cryptography/secp256k1 + * that is cryptographically secure random number with certain characteristics. + * Read more: https://www.npmjs.com/package/ethereum-cryptography#secp256k1-curve + * @returns A Web3Account object + * ```ts + * web3.eth.accounts.create(); + * { + * address: '0xbD504f977021b5E5DdccD8741A368b147B3B38bB', + * privateKey: '0x964ced1c69ad27a311c432fdc0d8211e987595f7eb34ab405a5f16bdc9563ec5', + * signTransaction: [Function: signTransaction], + * sign: [Function: sign], + * encrypt: [AsyncFunction: encrypt] + * } + * ``` + */ export const create = (): Web3Account => { const privateKey = utils.randomPrivateKey(); @@ -419,11 +635,42 @@ export const create = (): Web3Account => { }; /** - * Decrypts a v3 keystore JSON, and creates the account. + * Decrypts a v3 keystore JSON, and creates the account. + * @param keystore - the encrypted Keystore object or string to decrypt + * @param password - The password that was used for encryption + * @param nonStrict - if true and given a json string, the keystore will be parsed as lowercase. + * @returns Returns the decrypted Web3Account object + * * Decrypting scrypt + * + * ```ts + * decrypt({ + * version: 3, + * id: 'c0cb0a94-4702-4492-b6e6-eb2ac404344a', + * address: 'cda9a91875fc35c8ac1320e098e584495d66e47c', + * crypto: { + * ciphertext: 'cb3e13e3281ff3861a3f0257fad4c9a51b0eb046f9c7821825c46b210f040b8f', + * cipherparams: { iv: 'bfb43120ae00e9de110f8325143a2709' }, + * cipher: 'aes-128-ctr', + * kdf: 'scrypt', + * kdfparams: { + * n: 8192, + * r: 8, + * p: 1, + * dklen: 32, + * salt: '210d0ec956787d865358ac45716e6dd42e68d48e346d795746509523aeb477dd' + * }, + * mac: 'efbf6d3409f37c0084a79d5fdf9a6f5d97d11447517ef1ea8374f51e581b7efd' + * } + * }, '123').then(console.log) + * > { + * address: '0xcdA9A91875fc35c8Ac1320E098e584495d66e47c', + * privateKey: '67f476289210e3bef3c1c75e4de993ff0a00663df00def84e73aa7411eac18a6', + * signTransaction: [Function: signTransaction], + * sign: [Function: sign], + * encrypt: [AsyncFunction: encrypt] + * } + * ``` * - * @param keystore - * @param password - * @param nonStrict */ export const decrypt = async ( keystore: KeyStore | string, diff --git a/packages/web3-eth-accounts/src/index.ts b/packages/web3-eth-accounts/src/index.ts index bc9d2fc7a88..3688b685bcb 100644 --- a/packages/web3-eth-accounts/src/index.ts +++ b/packages/web3-eth-accounts/src/index.ts @@ -15,6 +15,29 @@ You should have received a copy of the GNU Lesser General Public License along with web3.js. If not, see . */ +/** + * The web3.eth.accounts contains functions to generate Ethereum accounts and sign transactions and data. + * + * **_NOTE:_** This package has NOT been audited and might potentially be unsafe. Take precautions to clear memory properly, store the private keys safely, and test transaction receiving and sending functionality properly before using in production! + * + * + * To use this package standalone and use its methods use: + * ```ts + * import { create, decrypt } from 'web3-eth-accounts'; // .... + * ``` + * + * To use this package within the web3 object use: + * ```ts + * import { Web3 } from 'web3'; + * + * const web3 = new Web3(Web3.givenProvider || 'ws://some.local-or-remote.node:8546'); + * // now you have access to the accounts class + * web3.eth.accounts.create(); + */ +/** + * + */ + +export * from './wallet'; export * from './account'; export * from './types'; -export * from './wallet'; diff --git a/packages/web3-eth-accounts/src/wallet.ts b/packages/web3-eth-accounts/src/wallet.ts index f2546decf1c..840969ccbca 100644 --- a/packages/web3-eth-accounts/src/wallet.ts +++ b/packages/web3-eth-accounts/src/wallet.ts @@ -20,12 +20,38 @@ import { isNullish } from 'web3-validator'; type BrowserError = { code: number; name: string }; +/** + * Wallet is an in memory `wallet` that can hold multiple accounts. + * These accounts can be used when using web3.eth.sendTransaction(). + * + * ### Parameters + * Web3AccountProvider - AccountProvider for the wallet + * + * ```ts + * import Web3 from 'web3'; + * const web3 = new Web3("https://localhost:8454") + * web3.eth.accounts.wallet + * > Wallet(0) [ + * _accountProvider: { + * create: [Function: create], + * privateKeyToAccount: [Function: privateKeyToAccount], + * decrypt: [Function: decrypt] + * }, + * _addressMap: Map(0) {}, + * _defaultKeyName: 'web3js_wallet' + * ] + * ``` + */ export class Wallet< T extends Web3BaseWalletAccount = Web3BaseWalletAccount, > extends Web3BaseWallet { private readonly _addressMap = new Map(); private readonly _defaultKeyName = 'web3js_wallet'; + /** + * Get the storage object of the browser + * @returns the storage + */ public static getStorage(): Storage | undefined { let storage: Storage | undefined; @@ -54,6 +80,41 @@ export class Wallet< : undefined; } } + /** + * Generates one or more accounts in the wallet. If wallets already exist they will not be overridden. + * @param numberOfAccounts Number of accounts to create. Leave empty to create an empty wallet. + * @returns The wallet + * ```ts + * web3.eth.accounts.wallet.create(2) + * > Wallet(2) [ + * { + * address: '0xde38310a42B751AE57d30cFFF4a0A3c52A442fCE', + * privateKey: '0x6422c9d28efdcbee93c1d32a5fc6fd6fa081b985487885296cf8c9bbb5872600', + * signTransaction: [Function: signTransaction], + * sign: [Function: sign], + * encrypt: [Function: encrypt] + * }, + * { + * address: '0x766BF755246d924B1d017Fdb5390f38a60166691', + * privateKey: '0x756530f13c0eb636ebdda655335f5dea9921e3362e2e588b0ad59e556f7751f0', + * signTransaction: [Function: signTransaction], + * sign: [Function: sign], + * encrypt: [Function: encrypt] + * }, + * _accountProvider: { + * create: [Function: create], + * privateKeyToAccount: [Function: privateKeyToAccount], + * decrypt: [Function: decrypt] + * }, + * _addressMap: Map(2) { + * '0xde38310a42b751ae57d30cfff4a0a3c52a442fce' => 0, + * '0x766bf755246d924b1d017fdb5390f38a60166691' => 1 + * }, + * _defaultKeyName: 'web3js_wallet' + * ] + * + * ``` + */ public create(numberOfAccounts: number) { for (let i = 0; i < numberOfAccounts; i += 1) { @@ -63,7 +124,33 @@ export class Wallet< return this; } - public add(account: T | string): boolean { + /** + * Adds an account using a private key or account object to the wallet. + * + * @param account A private key or account object + * @returns The wallet + * + * ```ts + * web3.eth.accounts.wallet.add('0xbce9b59981303e76c4878b1a6d7b088ec6b9dd5c966b7d5f54d7a749ff683387'); + * > Wallet(1) [ + * { + * address: '0x85D70633b90e03e0276B98880286D0D055685ed7', + * privateKey: '0xbce9b59981303e76c4878b1a6d7b088ec6b9dd5c966b7d5f54d7a749ff683387', + * signTransaction: [Function: signTransaction], + * sign: [Function: sign], + * encrypt: [Function: encrypt] + * }, + * _accountProvider: { + * create: [Function: create], + * privateKeyToAccount: [Function: privateKeyToAccount], + * decrypt: [Function: decrypt] + * }, + * _addressMap: Map(1) { '0x85d70633b90e03e0276b98880286d0d055685ed7' => 0 }, + * _defaultKeyName: 'web3js_wallet' + * ] + * ``` + */ + public add(account: T | string): this { if (typeof account === 'string') { return this.add(this._accountProvider.privateKeyToAccount(account)); } @@ -73,8 +160,14 @@ export class Wallet< this[index] = account; - return true; + return this; } + /** + * Get the account of the wallet with either the index or public address. + * + * @param addressOrIndex A string of the address or number index within the wallet. + * @returns The account object or undefined if the account doesnt exist + */ public get(addressOrIndex: string | number): T | undefined { if (typeof addressOrIndex === 'string') { @@ -90,6 +183,27 @@ export class Wallet< return this[addressOrIndex]; } + /** + * Removes an account from the wallet. + * @param addressOrIndex - The account address, or index in the wallet. + * @returns true if the wallet was removed. false if it couldn’t be found. + * ```ts + * web3.eth.accounts.wallet.add('0xbce9b59981303e76c4878b1a6d7b088ec6b9dd5c966b7d5f54d7a749ff683387'); + * + * web3.eth.accounts.wallet.remove('0x85D70633b90e03e0276B98880286D0D055685ed7'); + * > true + * web3.eth.accounts.wallet + * > Wallet(0) [ + * _accountProvider: { + * create: [Function: create], + * privateKeyToAccount: [Function: privateKeyToAccount], + * decrypt: [Function: decrypt] + * }, + * _addressMap: Map(0) {}, + * _defaultKeyName: 'web3js_wallet' + * ] + * ``` + */ public remove(addressOrIndex: string | number): boolean { if (typeof addressOrIndex === 'string') { const index = this._addressMap.get(addressOrIndex.toLowerCase()); @@ -110,6 +224,25 @@ export class Wallet< return false; } + /** + * Securely empties the wallet and removes all its accounts. + * Use this with *caution as it will remove all accounts stored in local wallet. + * + * @returns The wallet object + * ```ts + * + * web3.eth.accounts.wallet.clear(); + * > Wallet(0) [ + * _accountProvider: { + * create: [Function: create], + * privateKeyToAccount: [Function: privateKeyToAccount], + * decrypt: [Function: decrypt] + * }, + * _addressMap: Map(0) {}, + * _defaultKeyName: 'web3js_wallet' + * ] + * ``` + */ public clear() { this._addressMap.clear(); @@ -119,10 +252,101 @@ export class Wallet< return this; } + /** + * Encrypts all wallet accounts to an array of encrypted keystore v3 objects. + * + * @param password `string` - The password which will be used for encryption + * @param options - encryption options + * @returns An array of the encrypted keystore v3. + * ```ts + * web3.eth.accounts.wallet.create(1) + * web3.eth.accounts.wallet.encrypt("abc").then(console.log); + * > [ + * '{"version":3,"id":"fa46e213-a7c3-4844-b903-dd14d39cc7db", + * "address":"fa3e41a401609103c241431cbdee8623ae2a321a","crypto": + * {"ciphertext":"8d179a911d6146ad2924e86bf493ed89b8ff3596ffec0816e761c542016ab13c", + * "cipherparams":{"iv":"acc888c6cf4a19b86846cef0185a7164"},"cipher":"aes-128-ctr", + * "kdf":"scrypt","kdfparams":{"n":8192,"r":8,"p":1,"dklen":32,"salt":"6a743c9b367d15f4758e4f3f3378ff0fd443708d1c64854e07588ea5331823ae"}, + * "mac":"410544c8307e3691fda305eb3722d82c3431f212a87daa119a21587d96698b57"}}' + * ] + */ public async encrypt(password: string, options?: Record | undefined) { return Promise.all(this.map(async account => account.encrypt(password, options))); } + /** + * Decrypts keystore v3 objects. + * @param encryptedWallets `string[]` An array of encrypted keystore v3 objects to decrypt + * @param password `String` The password to encrypt with + * @param options decrypt options for the wallets + * @returns The decrypted wallet object + * + * ```ts + * web3.eth.accounts.wallet.decrypt([ + * { version: 3, + * id: '83191a81-aaca-451f-b63d-0c5f3b849289', + * address: '06f702337909c06c82b09b7a22f0a2f0855d1f68', + * crypto: + * { ciphertext: '7d34deae112841fba86e3e6cf08f5398dda323a8e4d29332621534e2c4069e8d', + * cipherparams: { iv: '497f4d26997a84d570778eae874b2333' }, + * cipher: 'aes-128-ctr', + * kdf: 'scrypt', + * kdfparams: + * { dklen: 32, + * salt: '208dd732a27aa4803bb760228dff18515d5313fd085bbce60594a3919ae2d88d', + * n: 262144, + * r: 8, + * p: 1 }, + * mac: '0062a853de302513c57bfe3108ab493733034bf3cb313326f42cf26ea2619cf9' } }, + * { version: 3, + * id: '7d6b91fa-3611-407b-b16b-396efb28f97e', + * address: 'b5d89661b59a9af0b34f58d19138baa2de48baaf', + * crypto: + * { ciphertext: 'cb9712d1982ff89f571fa5dbef447f14b7e5f142232bd2a913aac833730eeb43', + * cipherparams: { iv: '8cccb91cb84e435437f7282ec2ffd2db' }, + * cipher: 'aes-128-ctr', + * kdf: 'scrypt', + * kdfparams: + * { dklen: 32, + * salt: '08ba6736363c5586434cd5b895e6fe41ea7db4785bd9b901dedce77a1514e8b8', + * n: 262144, + * r: 8, + * p: 1 }, + * mac: 'd2eb068b37e2df55f56fa97a2bf4f55e072bef0dd703bfd917717d9dc54510f0' } } + * ], 'test').then(console.log) + * > Wallet { + * _accountProvider: { + * create: [Function: create], + * privateKeyToAccount: [Function: privateKeyToAccount], + * decrypt: [Function: decrypt] + * }, + * _defaultKeyName: 'web3js_wallet', + * _accounts: { + * '0x85d70633b90e03e0276b98880286d0d055685ed7': { + * address: '0x85D70633b90e03e0276B98880286D0D055685ed7', + * privateKey: '0xbce9b59981303e76c4878b1a6d7b088ec6b9dd5c966b7d5f54d7a749ff683387', + * signTransaction: [Function: signTransaction], + * sign: [Function: sign], + * encrypt: [Function: encrypt] + * }, + * '0x06f702337909c06c82b09b7a22f0a2f0855d1f68': { + * address: '0x06F702337909C06C82B09B7A22F0a2f0855d1F68', + * privateKey: '87a51da18900da7398b3bab03996833138f269f8f66dd1237b98df6b9ce14573', + * signTransaction: [Function: signTransaction], + * sign: [Function: sign], + * encrypt: [Function: encrypt] + * }, + * '0xb5d89661b59a9af0b34f58d19138baa2de48baaf': { + * address: '0xB5d89661B59a9aF0b34f58D19138bAa2de48BAaf', + * privateKey: '7ee61c5282979aae9dd795bb6a54e8bdc2bfe009acb64eb9a67322eec3b3da6e', + * signTransaction: [Function: signTransaction], + * sign: [Function: sign], + * encrypt: [Function: encrypt] + * } + * } + * } + * ``` + */ public async decrypt( encryptedWallets: string[], password: string, @@ -137,10 +361,20 @@ export class Wallet< for (const res of results) { this.add(res); } - return this; } + /** + * Stores the wallet encrypted and as string in local storage. + * **__NOTE:__** Browser only + * @param password `String` The password to encrypt the wallet + * @param keyName `String` (optional) The key used for the local storage position, defaults to `"web3js_wallet"`. + * @returns Will return boolean value true if saved properly + * ```ts + * web3.eth.accounts.wallet.save('test#!$'); + * >true + * ``` + */ public async save(password: string, keyName?: string) { const storage = Wallet.getStorage(); @@ -156,6 +390,23 @@ export class Wallet< return true; } + /** + * Loads a wallet from local storage and decrypts it. + * **__NOTE:__** Browser only + * @param password `String` The password to decrypt the wallet. + * @param keyName `String` (optional)The key used for local storage position, defaults to `web3js_wallet"` + * @returns Returns the wallet object + * + * ```ts + * web3.eth.accounts.wallet.save('test#!$'); + * > true + * web3.eth.accounts.wallet.load('test#!$'); + * { defaultKeyName: "web3js_wallet", + * length: 0, + * _accounts: Accounts {_requestManager: RequestManager, givenProvider: Proxy, providers: {…}, _provider: WebsocketProvider, …}, + * [[Prototype]]: Object + * } + */ public async load(password: string, keyName?: string) { const storage = Wallet.getStorage(); diff --git a/packages/web3/src/types.ts b/packages/web3/src/types.ts index 7879231bee1..8cf67423ab3 100644 --- a/packages/web3/src/types.ts +++ b/packages/web3/src/types.ts @@ -28,6 +28,7 @@ import { decodeLog, ContractAbi, } from 'web3-eth-abi'; +import { Wallet } from 'web3-eth-accounts'; import { create, privateKeyToAccount, @@ -88,5 +89,6 @@ export interface Web3EthInterface extends Eth { recover: typeof recover; encrypt: typeof encrypt; decrypt: typeof decrypt; + wallet: Wallet; }; } diff --git a/packages/web3/src/web3.ts b/packages/web3/src/web3.ts index 2f248a17d74..45097297133 100644 --- a/packages/web3/src/web3.ts +++ b/packages/web3/src/web3.ts @@ -48,8 +48,10 @@ import { } from 'web3-eth-accounts'; import * as utils from 'web3-utils'; import { Address } from 'web3-utils'; +import { readFileSync } from 'fs'; import { Web3EthInterface } from './types'; -import packageJson from '../package.json'; + +const packageJson = JSON.parse(readFileSync('./package.json', 'utf8')) as { version: string }; export class Web3 extends Web3Context { public static version = packageJson.version; @@ -76,9 +78,7 @@ export class Web3 extends Web3Context { options?: Record, ) => decrypt(keystore, password, (options?.nonStrict as boolean) ?? true), }; - const wallet = new Wallet(accountProvider); - super({ provider, wallet, accountProvider }); this.utils = utils; @@ -152,5 +152,4 @@ export class Web3 extends Web3Context { }); } } - export default Web3;