Skip to content
This repository has been archived by the owner on Jan 12, 2022. It is now read-only.

feat: provide watch-only mode for PublicKey and Address based Wallet #290

Merged
merged 8 commits into from
Sep 1, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 20 additions & 2 deletions docs/wallet/Wallet.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,12 +20,14 @@ Parameters:
| **walletOpts.HDPrivateKey** | string/HDPrivateKey| no | If you only have a HDPrivateKey representation, you can pass it instead of mnemonic to init the wallet from it |
| **walletOpts.HDPublicKey** | string/HDPublicKey | no | If you only have a HDPublicKey representation, you can pass it instead of mnemonic to init the wallet from it |
| **walletOpts.privateKey** | string/PrivateKey | no | If you only have a PrivateKey representation, you can pass it instead of mnemonic to init the wallet from it |
| **walletOpts.publicKey** | string/PublicKey | no | If you only have a PublicKey representation, you can pass it instead of mnemonic to init the wallet from it |


N.B 1 : If both mnemonic, seed and privateKey are filled, only mnemonic will be used. If none is entered, the wallet will create a mnemonic.
N.B 2 : When initialized from a `privateKey` or an `HDPublicKey`, comportment of Wallet-lib differs slightly.
N.B 2 : When initialized from a `privateKey`, `publicKey` or an `HDPublicKey`, comportment of Wallet-lib differs slightly.

- PrivateKey : There is no path in this mode. It's a unique public address.
- PrivateKey : There is no path in this mode. It's a unique public address. Watch-only.
- HDPublicKey : There is no signing in this mode. Watch-only.

Returns : Wallet instance.
Expand Down Expand Up @@ -67,7 +69,7 @@ const wallet = new Wallet({
})
```

### Creation from HDPrivateKey
### Creation from HDPublicKey

```js
const wallet = new Wallet({
Expand All @@ -82,3 +84,19 @@ const wallet = new Wallet({
seed: '436905e6756c24551bffaebe97d0ebd51b2fa027e838c18d45767bd833b02a80a1dd55728635b54f2b1dbed5963f4155e160ee1e96e2d67f7e8ac28557d87d96'
})
```

### Creation from privateKey

```js
const wallet = new Wallet({
privateKey: 'cR4t6evwVZoCp1JsLk4wURK4UmBCZzZotNzn9T1mhBT19SH9JtNt'
})
```

### Creation from publicKey

```js
const wallet = new Wallet({
HDPublicKey: 'tpubDEB6BgW9JvZRWVbFmwwGuJ2vifakABuxQWdY9yXbFC2rc3zagie1RkhwUEnahb1dzaapchEVeKqKcx99TzkjNvjXcmoQkLJwsYnA1J5bGNj'
})
```
4 changes: 4 additions & 0 deletions src/CONSTANTS.js
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,10 @@ const CONSTANTS = {
},
UNCONFIRMED_TRANSACTION_STATUS_CODE: -1,
WALLET_TYPES: {
ADDRESS: 'address',
PUBLICKEY: 'publicKey',
PRIVATEKEY: 'privateKey',
// TODO: DEPRECATE.
SINGLE_ADDRESS: 'single_address',
HDWALLET: 'hdwallet',
HDPUBLIC: 'hdpublic',
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -172,9 +172,9 @@ class TransactionSyncStreamWorker extends Worker {
const { walletId } = this;
const accountsStore = this.storage.store.wallets[walletId].accounts;

const accountStore = (this.walletType === WALLET_TYPES.SINGLE_ADDRESS)
? accountsStore[this.index.toString()]
: accountsStore[this.BIP44PATH.toString()];
const accountStore = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType))
? accountsStore[this.BIP44PATH.toString()]
: accountsStore[this.index.toString()];

accountStore.blockHash = hash;

Expand All @@ -185,9 +185,9 @@ class TransactionSyncStreamWorker extends Worker {
const { walletId } = this;
const accountsStore = this.storage.store.wallets[walletId].accounts;

const { blockHash } = (this.walletType === WALLET_TYPES.SINGLE_ADDRESS)
? accountsStore[this.index.toString()]
: accountsStore[this.BIP44PATH.toString()];
const { blockHash } = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType))
? accountsStore[this.BIP44PATH.toString()]
: accountsStore[this.index.toString()];

return blockHash;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ const mockedStore2 = {
const mockSelfPrivateKeyType = {
storage: { getStore:()=>mockedStore1 },
walletId: '123456789',
walletType: 'single_address',
walletType: 'privateKey',
}
const mockSelfIndex0 = {
storage: { getStore:()=>mockedStore2 },
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@ module.exports = function getLastSyncedBlockHeight() {
const { walletId } = this;
const accountsStore = this.storage.store.wallets[walletId].accounts;

let { blockHeight } = (this.walletType === WALLET_TYPES.SINGLE_ADDRESS)
? accountsStore[this.index.toString()]
: accountsStore[this.BIP44PATH.toString()];
let { blockHeight } = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType))
? accountsStore[this.BIP44PATH.toString()]
: accountsStore[this.index.toString()];

// Fix Genesis issue on DCore
if (blockHeight === 0) blockHeight = 1;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ module.exports = function setLastSyncedBlockHeight(blockHeight) {
const { walletId } = this;
const accountsStore = this.storage.store.wallets[walletId].accounts;

const accountStore = (this.walletType === WALLET_TYPES.SINGLE_ADDRESS)
? accountsStore[this.index.toString()]
: accountsStore[this.BIP44PATH.toString()];
const accountStore = ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType))
? accountsStore[this.BIP44PATH.toString()]
: accountsStore[this.index.toString()];

accountStore.blockHeight = blockHeight;

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ module.exports = async function subscribeToTransactionWithProofs(
const { client } = this;
logger.silly(`DAPIClient.subscribeToTransactionWithProofs[${addressList}]`);

if (!addressList.length) throw new Error('Unable to subscribe to transaction without addresses');
const bloomfilter = BloomFilter.create(addressList.length, BLOOM_FALSE_POSITIVE_RATE);

addressList.forEach((address) => {
Expand Down
36 changes: 22 additions & 14 deletions src/types/Account/Account.js
Original file line number Diff line number Diff line change
Expand Up @@ -112,20 +112,28 @@ class Account extends EventEmitter {
super.emit(...args);
};
}
if ([WALLET_TYPES.HDWALLET, WALLET_TYPES.HDPUBLIC].includes(this.walletType)) {
this.storage.createAccount(
this.walletId,
this.BIP44PATH,
this.network,
this.label,
);
}
if (this.walletType === WALLET_TYPES.SINGLE_ADDRESS) {
this.storage.createSingleAddress(
this.walletId,
this.network,
this.label,
);
switch (this.walletType) {
case WALLET_TYPES.HDWALLET:
case WALLET_TYPES.HDPUBLIC:
this.storage.createAccount(
this.walletId,
this.BIP44PATH,
this.network,
this.label,
);
break;
case WALLET_TYPES.PRIVATEKEY:
case WALLET_TYPES.PUBLICKEY:
case WALLET_TYPES.ADDRESS:
case WALLET_TYPES.SINGLE_ADDRESS:
this.storage.createSingleAddress(
this.walletId,
this.network,
this.label,
);
break;
default:
throw new Error(`Invalid wallet type ${this.walletType}`);
}

this.keyChain = wallet.keyChain;
Expand Down
2 changes: 2 additions & 0 deletions src/types/Account/Account.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -33,12 +33,14 @@ describe('Account - class', function suite() {
getStore: () => {},
saveState: () => {},
stopWorker: () => {},
createAccount: () => {},
importBlockHeader: (blockheader)=>{
mockStorage.emit(EVENTS.BLOCKHEADER, {type: EVENTS.BLOCKHEADER, payload:blockheader});
}
};
mocks.wallet = (new (function Wallet() {
this.walletId = '1234567891';
this.walletType = WALLET_TYPES.HDWALLET;
this.accounts = [];
this.network = Dashcore.Networks.testnet;
this.storage = mockStorage;
Expand Down
22 changes: 15 additions & 7 deletions src/types/Account/_initializeAccount.js
Original file line number Diff line number Diff line change
Expand Up @@ -31,11 +31,10 @@ async function _initializeAccount(account, userUnsafePlugins) {
account.index,
account.getAddress.bind(account),
);
}

if (account.walletType === WALLET_TYPES.SINGLE_ADDRESS) {
} else {
await account.getAddress('0'); // We force what is usually done by the BIP44Worker.
}

if (!account.offlineMode) {
await account.injectPlugin(ChainPlugin, true);

Expand Down Expand Up @@ -89,10 +88,19 @@ async function _initializeAccount(account, userUnsafePlugins) {
// while SyncWorker fetch'em on network
clearInterval(self.readinessInterval);

if (account.walletType === WALLET_TYPES.SINGLE_ADDRESS) {
account.generateAddress(0);
sendReady();
return resolve(true);
switch (account.walletType) {
case WALLET_TYPES.PRIVATEKEY:
case WALLET_TYPES.SINGLE_ADDRESS:
account.generateAddress(0);
sendReady();
return resolve(true);
case WALLET_TYPES.PUBLICKEY:
case WALLET_TYPES.ADDRESS:
account.generateAddress(0);
sendReady();
return resolve(true);
default:
break;
}

if (!account.injectDefaultPlugins) {
Expand Down
13 changes: 11 additions & 2 deletions src/types/Account/methods/generateAddress.js
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
const { PublicKey } = require('@dashevo/dashcore-lib');
const EVENTS = require('../../../EVENTS');
const { WALLET_TYPES } = require('../../../CONSTANTS');
const { is } = require('../../../utils');
Expand All @@ -10,24 +11,32 @@ function generateAddress(path) {
if (is.undefOrNull(path)) throw new Error('Expected path to generate an address');
let index = 0;
let privateKey;

let address;
const { network } = this;

switch (this.walletType) {
case WALLET_TYPES.ADDRESS:
address = this.keyChain.address;
break;
case WALLET_TYPES.PUBLICKEY:
address = new PublicKey(this.keyChain.publicKey.toString()).toAddress(network).toString();
break;
case WALLET_TYPES.HDWALLET:
// eslint-disable-next-line prefer-destructuring
index = parseInt(path.toString().split('/')[5], 10);
privateKey = this.keyChain.getKeyForPath(path);
address = privateKey.publicKey.toAddress(network).toString();
break;
case WALLET_TYPES.HDPUBLIC:
index = parseInt(path.toString().split('/')[5], 10);
privateKey = this.keyChain.getKeyForChild(index);
address = privateKey.publicKey.toAddress(network).toString();
break;
case WALLET_TYPES.SINGLE_ADDRESS:
default:
privateKey = this.keyChain.getKeyForPath(path.toString());
address = privateKey.publicKey.toAddress(network).toString();
}
const address = privateKey.publicKey.toAddress(network).toString();

const addressData = {
path: path.toString(),
Expand Down
3 changes: 3 additions & 0 deletions src/types/Account/methods/getAddress.js
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ const getTypePathFromWalletType = (walletType, addressType = 'external', index,
type = 'external';
path = `${BIP44PATH}/${addressTypeIndex}/${index}`;
break;
case WALLET_TYPES.PUBLICKEY:
case WALLET_TYPES.ADDRESS:
case WALLET_TYPES.PRIVATEKEY:
case WALLET_TYPES.SINGLE_ADDRESS:
default:
type = 'misc';
Expand Down
8 changes: 7 additions & 1 deletion src/types/Account/methods/getAddresses.js
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,13 @@ const { WALLET_TYPES } = require('../../../CONSTANTS');
* @return {[AddressObj]} address - All address matching the type
*/
function getAddresses(_type = 'external') {
const walletType = (this.walletType === WALLET_TYPES.SINGLE_ADDRESS)
const miscTypes = [
WALLET_TYPES.SINGLE_ADDRESS,
WALLET_TYPES.PUBLICKEY,
WALLET_TYPES.PRIVATEKEY,
WALLET_TYPES.ADDRESS,
];
const walletType = (miscTypes.includes(this.walletType))
? 'misc'
: ((_type) || 'external');
const store = this.storage.getStore();
Expand Down
8 changes: 7 additions & 1 deletion src/types/KeyChain/KeyChain.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,8 +24,14 @@ class KeyChain {
} else if (has(opts, 'privateKey')) {
this.type = 'privateKey';
this.privateKey = opts.privateKey;
} else if (has(opts, 'publicKey')) {
this.type = 'publicKey';
this.publicKey = opts.publicKey;
} else if (has(opts, 'address')) {
this.type = 'address';
this.address = opts.address.toString();
} else {
throw new Error('Expect privateKey, HDPublicKey or HDPrivateKey');
throw new Error('Expect privateKey, publicKey, HDPublicKey, HDPrivateKey or Address');
}
if (opts.network) this.network = opts.network;
if (opts.keys) this.keys = { ...opts.keys };
Expand Down
2 changes: 1 addition & 1 deletion src/types/KeyChain/KeyChain.spec.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const pk = '4226d5e2fe8cbfe6f5beb7adf5a5b08b310f6c4a67fc27826779073be6f5699e';
describe('Keychain', function suite() {
this.timeout(10000);
it('should create a keychain', () => {
const expectedException1 = 'Expect privateKey, HDPublicKey or HDPrivateKey';
const expectedException1 = 'Expect privateKey, publicKey, HDPublicKey, HDPrivateKey or Address';
expect(() => new KeyChain()).to.throw(expectedException1);
expect(() => new KeyChain(mnemonic)).to.throw(expectedException1);

Expand Down
4 changes: 3 additions & 1 deletion src/types/Wallet/Wallet.d.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
import { Mnemonic, PrivateKey, HDPublicKey, Network, Plugins } from "../types";
import { Mnemonic, PrivateKey, PublicKey, PublicAddress, Address, HDPublicKey, Network, Plugins } from "../types";
import { Account } from "../Account/Account";
import { Storage } from "../Storage/Storage";
import { HDPrivateKey } from "@dashevo/dashcore-lib";
Expand Down Expand Up @@ -59,6 +59,8 @@ export declare namespace Wallet {
privateKey?: PrivateKey | string;
HDPrivateKey?: HDPrivateKey | string;
HDPublicKey?: HDPublicKey | string;
publicKey?: PublicKey | string;
address?: Address | PublicAddress | string;
unsafeOptions?: IWalletUnsafeOptions;
waitForInstantLockTimeout?: number;
}
Expand Down
8 changes: 8 additions & 0 deletions src/types/Wallet/Wallet.js
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,8 @@ const defaultOptions = {

const fromMnemonic = require('./methods/fromMnemonic');
const fromPrivateKey = require('./methods/fromPrivateKey');
const fromPublicKey = require('./methods/fromPublicKey');
const fromAddress = require('./methods/fromAddress');
const fromSeed = require('./methods/fromSeed');
const fromHDPublicKey = require('./methods/fromHDPublicKey');
const fromHDPrivateKey = require('./methods/fromHDPrivateKey');
Expand Down Expand Up @@ -53,6 +55,8 @@ class Wallet extends EventEmitter {
fromSeed,
fromHDPrivateKey,
fromPrivateKey,
fromPublicKey,
fromAddress,
fromHDPublicKey,
generateNewWalletId,
});
Expand Down Expand Up @@ -85,8 +89,12 @@ class Wallet extends EventEmitter {
this.fromPrivateKey((opts.privateKey === null)
? new PrivateKey(network).toString()
: opts.privateKey);
} else if ('publicKey' in opts) {
this.fromPublicKey(opts.publicKey);
} else if ('HDPublicKey' in opts) {
this.fromHDPublicKey(opts.HDPublicKey);
} else if ('address' in opts) {
this.fromAddress(opts.address);
} else {
this.fromMnemonic(generateNewMnemonic());
}
Expand Down
Loading