diff --git a/README.md b/README.md index 769b8950..adc394cb 100644 --- a/README.md +++ b/README.md @@ -38,6 +38,9 @@ Also, make sure that you close Ledger Live or any other apps that might be using Add udev rules `wget -q -O - https://raw.githubusercontent.com/LedgerHQ/udev-rules/master/add_udev_rules.sh | sudo bash` +### Ledger on Latest Chrome browser +If you're experiencing issues connecting your Ledger device open the following url chrome://flags#new-usb-backend in Chrome browser and disable it. Alternatively you can try Chromium or Brave browsers. + ## Build/compile issues If you are experiencing build issues that lead to minify errors add required modules to [config-overrides.js](https://github.com/pbca26/hw-kmd-wallet/blob/master/config-overrides.js#L19) file. diff --git a/electron/main.js b/electron/main.js index b2f80327..3cf5cfc8 100755 --- a/electron/main.js +++ b/electron/main.js @@ -172,32 +172,32 @@ function createWindow() { mainWindow = null; }); - ipcMain.on('getAddress', (e, derivationPath) => { + ipcMain.on('getAddress', (e, {ruid, derivationPath}) => { console.log(derivationPath); if (mainWindow) { getAddress(derivationPath, false).then(result => { - mainWindow.webContents.send('getAddress', result); + mainWindow.webContents.send('getAddress', {ruid, result}); }); } }); - ipcMain.on('createPaymentTransactionNew', (e, txData) => { + ipcMain.on('createPaymentTransactionNew', (e, {ruid, txData}) => { console.log(txData); if (mainWindow) { createPaymentTransactionNew(txData).then(result => { - mainWindow.webContents.send('createPaymentTransactionNew', result); + mainWindow.webContents.send('createPaymentTransactionNew', {ruid, result}); }); } }); - ipcMain.on('splitTransaction', (e, txData) => { + ipcMain.on('splitTransaction', (e, {ruid, txData}) => { console.log(txData); if (mainWindow) { splitTransaction(txData).then(result => { - mainWindow.webContents.send('splitTransaction', result); + mainWindow.webContents.send('splitTransaction', {ruid, result}); }); } }); diff --git a/src/App.js b/src/App.js index fabf407d..8f5c4fea 100755 --- a/src/App.js +++ b/src/App.js @@ -34,7 +34,7 @@ import { } from './lib/blockchain'; import accountDiscovery from './lib/account-discovery'; import blockchain from './lib/blockchain'; -import apiEndpoints from './lib/insight-endpoints'; +import apiEndpoints from './lib/coins'; import getKomodoRewards from './lib/get-komodo-rewards'; import {osName} from 'react-device-detect'; import { @@ -135,34 +135,36 @@ class App extends React.Component { } checkExplorerEndpoints = async () => { - const getInfoRes = await Promise.all(apiEndpoints[this.state.coin].map((value, index) => { + const getInfoRes = await Promise.all(apiEndpoints[this.state.coin].api.map((value, index) => { return getInfo(value); })); let isExplorerEndpointSet = false; console.warn('checkExplorerEndpoints', getInfoRes); - for (let i = 0; i < apiEndpoints[this.state.coin].length; i++) { + for (let i = 0; i < apiEndpoints[this.state.coin].api.length; i++) { if (getInfoRes[i] && getInfoRes[i].hasOwnProperty('info') && getInfoRes[i].info.hasOwnProperty('version')) { - console.warn('set api endpoint to ' + apiEndpoints[this.state.coin][i]); - setExplorerUrl(apiEndpoints[this.state.coin][i]); + console.warn('set api endpoint to ' + apiEndpoints[this.state.coin].api[i]); + setExplorerUrl(apiEndpoints[this.state.coin].api[i]); isExplorerEndpointSet = true; this.setState({ - explorerEndpoint: apiEndpoints[this.state.coin][i], + explorerEndpoint: apiEndpoints[this.state.coin].api[i], }); break; } } - if (!isExplorerEndpointSet) { - this.setState({ - explorerEndpoint: false, - }); - } + setTimeout(() => { + if (!isExplorerEndpointSet) { + this.setState({ + explorerEndpoint: false, + }); + } + }, 50); }; resetState = () => { diff --git a/src/Transactions.js b/src/Transactions.js index 6cd3f46c..e9129f39 100644 --- a/src/Transactions.js +++ b/src/Transactions.js @@ -1,5 +1,9 @@ import React from 'react'; -import explorerLink from './lib/explorer-link'; +import explorerLink from './lib/coins'; +import { + isElectron, + shell, +} from './Electron'; const headings = [ 'Type', @@ -36,9 +40,16 @@ const Transactions = ({transactions, coin}) => { {Number(tx.height) === -1 || Number(tx.height) === 0 ? '' : tx.date} - {tx.txid} + {isElectron && + shell.openExternal(`${explorerLink[coin].explorer}tx/${tx.txid}`)}>{tx.txid} + } + {!isElectron && + {tx.txid} + } ))} diff --git a/src/TxidLink.js b/src/TxidLink.js index 79760c8e..a9d3b537 100644 --- a/src/TxidLink.js +++ b/src/TxidLink.js @@ -1,5 +1,5 @@ import React from 'react'; -import explorerLink from './lib/explorer-link'; +import explorerLink from './lib/coins'; import { isElectron, shell, @@ -9,11 +9,11 @@ const TxidLink = ({txid, coin}) => !isElectron ? ( {txid} + href={`${explorerLink[coin].explorer}tx/${txid}`}>{txid} ) : ( shell.openExternal(`${explorerLink[coin]}tx/${txid}`)}>{txid} + onClick={() => shell.openExternal(`${explorerLink[coin].explorer}tx/${txid}`)}>{txid} ); export default TxidLink; diff --git a/src/lib/coins.js b/src/lib/coins.js index 4284d3e1..f72d4e53 100644 --- a/src/lib/coins.js +++ b/src/lib/coins.js @@ -155,8 +155,9 @@ const coins = { RICK: { explorer: 'https://rick.kmd.dev/', api: [ - 'https://rick.kmd.dev/insight-api-komodo/', + 'https://explorer.komodoplatform.com:10000/rick/api/', 'https://rick.explorer.dexstats.info/insight-api-komodo/', + 'https://rick.kmd.dev/insight-api-komodo/', ], }, MORTY: { @@ -164,6 +165,7 @@ const coins = { api: [ 'https://morty.kmd.dev/insight-api-komodo/', 'https://morty.explorer.dexstats.info/insight-api-komodo/', + 'https://explorer.komodoplatform.com:10000/morty/api/', ], }, VRSC: { @@ -171,6 +173,7 @@ const coins = { api: [ 'https://explorer.komodoplatform.com:10000/vrsc/api/', 'https://vrsc.explorer.dexstats.info/insight-api-komodo/', + 'https://insight.vrsc.0x03.services/insight-api-komodo/', ], }, // DP: '', diff --git a/src/lib/explorer-link.js b/src/lib/explorer-link.js deleted file mode 100644 index 3cf9fb2c..00000000 --- a/src/lib/explorer-link.js +++ /dev/null @@ -1,52 +0,0 @@ -const explorers = { - KMD: 'https://kmd.explorer.dexstats.info/', - AXO: 'https://axo.explorer.dexstats.info/', - ETOMIC: 'https://etomic.explorer.dexstats.info/', - KOIN: 'https://koin.explorer.dexstats.info/', - MESH: 'https://mesh.explorer.dexstats.info/', - DEX: 'https://dex.explorer.dexstats.info/', - SUPERNET: 'https://supernet.explorer.dexstats.info/', - DION: 'https://explorer.dionpay.com/', - CCL: 'https://ccl.explorer.dexstats.info/', - KV: 'https://kv.explorer.dexstats.info/', - CHAIN: 'https://chain.explorer.dexstats.info/', - PGT: 'https://pgt.explorer.dexstats.info/', - MSHARK: 'https://mshark.explorer.dexstats.info/', - REVS: 'https://revs.explorer.dexstats.info/', - PANGEA: 'https://pangea.explorer.dexstats.info/', - JUMBLR: 'https://jumblr.explorer.dexstats.info/', - BET: 'https://bet.explorer.dexstats.info/', - CRYPTO: 'https://crypto.explorer.dexstats.info/', - HODL: 'https://hodl.explorer.dexstats.info/', - ILN: 'https://explorer.ilien.io/', - BOTS: 'https://bots.explorer.dexstats.info/', - MGW: 'https://mgw.explorer.dexstats.info/', - WLC21: 'https://wlc21.explorer.dexstats.info/', - COQUICASH: 'https://coquicash.explorer.dexstats.info/', - BTCH: 'https://btch.explorer.dexstats.info/', - HUSH3: 'https://hush3.explorer.dexstats.info/', - NINJA: 'https://ninja.explorer.dexstats.info/', - SEC: 'https://sec.explorer.dexstats.info/', - THC: 'https://thc.explorer.dexstats.info/', - KMDICE: 'https://kmdice.explorer.dexstats.info/', - ZEXO: 'https://zex.explorer.dexstats.info/', - KSB: 'https://ksb.explorer.dexstats.info/', - OUR: 'https://our.explorer.dexstats.info/', - MCL: 'https://mcl.explorer.dexstats.info/', - RFOX: 'https://rfox.explorer.dexstats.info/', - LABS: 'https://labs.explorer.dexstats.info/', - VOTE2020: 'https://vote2020.explorer.dexstats.info/', - RICK: 'https://rick.kmd.dev/', - MORTY: 'https://morty.kmd.dev/', - VRSC: 'https://vrsc.explorer.dexstats.info/', - ZILLA: 'https://zilla.explorer.dexstats.info/', - OOT: 'https://oot.explorer.dexstats.info/', - // DP: '', - // SHARK: '', - // EQL: '', - // PIZZA: '', - // BEER: '', - // DSEC: '', -}; - -export default explorers; \ No newline at end of file diff --git a/src/lib/insight-endpoints.js b/src/lib/insight-endpoints.js deleted file mode 100644 index 61ff93cc..00000000 --- a/src/lib/insight-endpoints.js +++ /dev/null @@ -1,69 +0,0 @@ -// TODO: refactor as coin object -import {sortObject} from './sort'; - -const apiEndpoints = { - KMD: [ - 'https://explorer.komodoplatform.com:10000/kmd/api/', - 'https://kmd.explorer.dexstats.info/insight-api-komodo/' - ], - AXO: ['https://axo.explorer.dexstats.info/insight-api-komodo/'], - ETOMIC: ['https://etomic.explorer.dexstats.info/insight-api-komodo/'], - KOIN: ['https://koin.explorer.dexstats.info/insight-api-komodo/'], - MESH: ['https://mesh.explorer.dexstats.info/insight-api-komodo/'], - DEX: ['https://dex.explorer.dexstats.info/insight-api-komodo/'], - SUPERNET: ['https://supernet.explorer.dexstats.info/insight-api-komodo/'], - DION: ['https://explorer.dionpay.com/insight-api-komodo/'], - CCL: ['https://ccl.explorer.dexstats.info/insight-api-komodo/'], - KV: ['https://kv.explorer.dexstats.info/insight-api-komodo/'], - CHAIN: ['https://chain.explorer.dexstats.info/insight-api-komodo/'], - PGT: ['https://pgt.explorer.dexstats.info/insight-api-komodo/'], - MSHARK: ['https://mshark.explorer.dexstats.info/insight-api-komodo/'], - REVS: ['https://revs.explorer.dexstats.info/insight-api-komodo/'], - PANGEA: ['https://pangea.explorer.dexstats.info/insight-api-komodo/'], - JUMBLR: ['https://jumblr.explorer.dexstats.info/insight-api-komodo/'], - BET: ['https://bet.explorer.dexstats.info/insight-api-komodo/'], - CRYPTO: ['https://crypto.explorer.dexstats.info/insight-api-komodo/'], - HODL: ['https://hodl.explorer.dexstats.info/insight-api-komodo/'], - ILN: ['https://explorer.ilien.io/insight-api-komodo/'], - BOTS: ['https://bots.explorer.dexstats.info/insight-api-komodo/'], - MGW: ['https://mgw.explorer.dexstats.info/insight-api-komodo/'], - WLC21: ['https://wlc21.explorer.dexstats.info/insight-api-komodo/'], - COQUICASH: ['https://coquicash.explorer.dexstats.info/insight-api-komodo/'], - BTCH: ['https://btch.explorer.dexstats.info/insight-api-komodo/'], - HUSH3: ['https://hush3.explorer.dexstats.info/insight-api-komodo/'], - NINJA: ['https://ninja.explorer.dexstats.info/insight-api-komodo/'], - SEC: ['https://sec.explorer.dexstats.info/insight-api-komodo/'], - THC: ['https://thc.explorer.dexstats.info/insight-api-komodo/'], - KMDICE: ['https://kmdice.explorer.dexstats.info/insight-api-komodo/'], - ZEXO: ['https://zexo.explorer.dexstats.info/insight-api-komodo/'], - KSB: ['https://ksb.explorer.dexstats.info/insight-api-komodo/'], - OUR: ['https://our.explorer.dexstats.info/insight-api-komodo/'], - MCL: ['https://mcl.explorer.dexstats.info/insight-api-komodo/'], - RFOX: ['https://rfox.explorer.dexstats.info/insight-api-komodo/'], - LABS: ['https://labs.explorer.dexstats.info/insight-api-komodo/'], - VOTE2020: ['https://vote2020.explorer.dexstats.info/insight-api-komodo/'], - RICK: [ - 'https://rick.kmd.dev/insight-api-komodo/', - 'https://rick.explorer.dexstats.info/insight-api-komodo/' - ], - MORTY: [ - 'https://morty.kmd.dev/insight-api-komodo/', - 'https://morty.explorer.dexstats.info/insight-api-komodo/' - ], - VRSC: [ - 'https://explorer.komodoplatform.com:10000/vrsc/api/', - 'https://vrsc.explorer.dexstats.info/insight-api-komodo/' - ] - // EQL: [], - // DP: [], - // PIZZA: [], - // BEER: [], - // DSEC: [], - // SHARK: [], - /* coins below need special handling due to no overwinter support - ZILLA: ['https://zilla.explorer.dexstats.info/insight-api-komodo/'], - OOT: ['https://oot.explorer.dexstats.info/insight-api-komodo/'], - */ -}; - -export default sortObject(apiEndpoints); \ No newline at end of file diff --git a/src/lib/ledger-ipc-wrapper.js b/src/lib/ledger-ipc-wrapper.js index 006c6b04..d20fbe72 100644 --- a/src/lib/ledger-ipc-wrapper.js +++ b/src/lib/ledger-ipc-wrapper.js @@ -1,17 +1,70 @@ -import {ipcRenderer} from '../Electron'; +import { + ipcRenderer, + isElectron, +} from '../Electron'; + +let data = {}; +let ruid = 0; +let intervals = {}; +let pendingCalls = {}; + +// return data only when it was sent over from ipc main proc +const getData = (ruid, payload) => { + return new Promise((resolve, reject) => { + if (!data[ruid]) { + console.warn(`ledger data ruid ${ruid} not available yet, set interval`, ruid); + + intervals[ruid] = setInterval((ruid) => { + if (data[ruid]) { + console.warn(`ledger data ruid ${ruid} available, clear interval`, data[ruid]); + clearInterval(intervals[ruid]); + delete pendingCalls[ruid]; + resolve(data[ruid] === 'false' ? false : data[ruid]); + } else { + pendingCalls[ruid] = payload; + } + }, 100, ruid); + } else { + console.warn(`ledger data ruid ${ruid} available`, data[ruid]); + delete pendingCalls[ruid]; + resolve(data[ruid] === 'false' ? false : data[ruid]); + } + }); +}; + +if (isElectron) { + ipcRenderer.on('getAddress', (event, arg) => { + console.warn('getAddress arg', arg); + console.warn('arg.bitcoinAddress', arg); + if (arg === -777) data[arg.ruid] = 'false'; + else data[arg.ruid] = arg.result; + }); + + ipcRenderer.on('createPaymentTransactionNew', (event, arg) => { + console.warn('createPaymentTransactionNew arg', arg); + if (arg === -777) data[arg.ruid] = 'false'; + else data[arg.ruid] = arg.result; + }); + + ipcRenderer.on('splitTransaction', (event, arg) => { + console.warn('splitTransaction arg', arg); + if (arg === -777) data[arg.ruid] = 'false'; + else data[arg.ruid] = arg.result; + }); +} // wrap ledger methods using ipc renderer const getDevice = async () => { return { getWalletPublicKey: (derivationPath) => { - return new Promise((resolve, reject) => { - ipcRenderer.on('getAddress', (event, arg) => { - console.warn('getAddress arg', arg); - console.warn('arg.bitcoinAddress', arg.bitcoinAddress); - if (arg === -777) resolve(false); - resolve(arg); - }); - ipcRenderer.send('getAddress', derivationPath); + console.warn(`ledger getWalletPublicKey`); + ruid++; + ipcRenderer.send('getAddress', {derivationPath, ruid}); + + return new Promise(async(resolve, reject) => { + const _data = await getData(ruid); + console.warn('ledger getAddress ready', _data); + resolve(_data); }); }, createPaymentTransactionNew: ( @@ -26,24 +79,25 @@ const getDevice = async () => { additionals, expiryHeight, ) => { - return new Promise((resolve, reject) => { - ipcRenderer.on('createPaymentTransactionNew', (event, arg) => { - console.warn('createPaymentTransactionNew arg', arg); - if (arg === -777) resolve(false); - resolve(arg); - }); - ipcRenderer.send('createPaymentTransactionNew', { - inputs, - associatedKeysets, - changePath, - outputScript, - lockTime, - sigHashType, - segwit, - initialTimestamp, - additionals, - expiryHeight, - }); + console.warn(`ledger createPaymentTransactionNew`); + ruid++; + ipcRenderer.send('createPaymentTransactionNew', {txData: { + inputs, + associatedKeysets, + changePath, + outputScript, + lockTime, + sigHashType, + segwit, + initialTimestamp, + additionals, + expiryHeight, + }, ruid}); + + return new Promise(async(resolve, reject) => { + const _data = await getData(ruid); + console.warn('ledger createPaymentTransactionNew ready', _data); + resolve(_data); }); }, splitTransaction: ( @@ -53,19 +107,20 @@ const getDevice = async () => { hasExtraData, additionals, ) => { - return new Promise((resolve, reject) => { - ipcRenderer.on('splitTransaction', (event, arg) => { - console.warn('splitTransaction arg', arg); - if (arg === -777) resolve(false); - resolve(arg); - }); - ipcRenderer.send('splitTransaction', { - transactionHex, - isSegwitSupported, - hasTimestamp, - hasExtraData, - additionals, - }); + console.warn(`ledger splitTransaction`); + ruid++; + ipcRenderer.send('splitTransaction', {txData: { + transactionHex, + isSegwitSupported, + hasTimestamp, + hasExtraData, + additionals, + }, ruid}); + + return new Promise(async(resolve, reject) => { + const _data = await getData(ruid); + console.warn('ledger splitTransaction ready', _data); + resolve(_data); }); }, close: () => {},